603 lines
22 KiB
Markdown
603 lines
22 KiB
Markdown
# E2 API-контракт transport control-plane
|
||
|
||
Дата: 2026-03-05
|
||
Статус: in-progress
|
||
Владелец: Engineering
|
||
|
||
## 1) Цель
|
||
- Зафиксировать стабильный `/api/v1/transport/*` контракт для управления несколькими transport-клиентами через единый Go control-plane.
|
||
- Обеспечить одинаковый API для desktop/web/iOS/android.
|
||
- Встроить anti-conflict workflow: `validate -> confirm -> apply`.
|
||
|
||
## 2) Область и ограничения
|
||
- Контракт описывает API-слой и DTO; низкоуровневый backend-runner (systemd/process supervisor) реализуется отдельно.
|
||
- Все изменения policy должны идти только через `validate` и `apply`.
|
||
- Для совместимости с текущим API проект использует паттерн:
|
||
- HTTP `200` + `"ok": false` для операционных ошибок;
|
||
- HTTP `4xx` для ошибки запроса (bad json, invalid id, missing fields).
|
||
|
||
## 3) Общие правила
|
||
|
||
### 3.1 Базовые поля ответа
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"request_id": "req-01JABC...",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
### 3.2 Ошибка доменной валидации
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"message": "policy has blocking conflicts",
|
||
"code": "POLICY_CONFLICT_BLOCK",
|
||
"issues": []
|
||
}
|
||
```
|
||
|
||
### 3.3 Идемпотентность
|
||
- Для mutating POST/PATCH/DELETE клиент передаёт `Idempotency-Key`.
|
||
- Для `apply` дополнительно используется `policy_revision` (optimistic lock).
|
||
- Для `POST /api/v1/transport/policies/apply` и `POST /api/v1/transport/policies/rollback` backend хранит persisted replay-state:
|
||
- одинаковые `(scope, Idempotency-Key, request payload)` возвращают один и тот же сохранённый response без повторного runtime apply;
|
||
- повторное использование того же `Idempotency-Key` с другим payload возвращает `IDEMPOTENCY_KEY_REUSED`.
|
||
|
||
## 4) Модели данных
|
||
|
||
### 4.1 TransportClient
|
||
```json
|
||
{
|
||
"id": "phoenix-eu",
|
||
"name": "Phoenix EU",
|
||
"kind": "phoenix",
|
||
"enabled": true,
|
||
"status": "up",
|
||
"iface": "phx0",
|
||
"routing_table": "agvpn_phoenix_eu",
|
||
"mark_hex": "0x110",
|
||
"priority_base": 13050,
|
||
"capabilities": ["tcp", "udp", "ssh_tunnel"],
|
||
"health": {
|
||
"last_check": "2026-03-05T10:11:12Z",
|
||
"latency_ms": 83,
|
||
"last_error": ""
|
||
},
|
||
"config": {
|
||
"runtime_mode": "exec",
|
||
"runner": "systemd",
|
||
"endpoint": "eu.example.net:443",
|
||
"profile": "default"
|
||
},
|
||
"updated_at": "2026-03-05T10:11:12Z"
|
||
}
|
||
```
|
||
|
||
### 4.2 RouteIntent
|
||
```json
|
||
{
|
||
"selector_type": "domain",
|
||
"selector_value": "youtube.com",
|
||
"client_id": "phoenix-eu",
|
||
"priority": 100,
|
||
"mode": "strict"
|
||
}
|
||
```
|
||
|
||
### 4.3 ConflictRecord
|
||
```json
|
||
{
|
||
"key": "domain:youtube.com",
|
||
"type": "ownership",
|
||
"severity": "block",
|
||
"owners": ["phoenix-eu", "dnstt-home"],
|
||
"reason": "one selector is assigned to multiple clients",
|
||
"suggested_resolution": "keep only one owner or use force_override"
|
||
}
|
||
```
|
||
|
||
## 5) Endpoints: clients
|
||
|
||
### 5.1 `GET /api/v1/transport/clients`
|
||
- Назначение: список клиентов.
|
||
- Query:
|
||
- `enabled_only=true|false` (optional)
|
||
- `kind=singbox|dnstt|phoenix` (optional)
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"items": [],
|
||
"count": 3
|
||
}
|
||
```
|
||
|
||
### 5.2 `POST /api/v1/transport/clients`
|
||
- Назначение: создать клиента.
|
||
|
||
Request:
|
||
```json
|
||
{
|
||
"id": "dnstt-home",
|
||
"name": "DNSTT Home",
|
||
"kind": "dnstt",
|
||
"enabled": true,
|
||
"config": {
|
||
"runtime_mode": "exec",
|
||
"runner": "systemd",
|
||
"packaging_profile": "bundled",
|
||
"bin_root": "/opt/selective-vpn/bin",
|
||
"server": "1.2.3.4:443",
|
||
"domain": "tunnel.example.org",
|
||
"pubkey": "base64..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "client created",
|
||
"item": {}
|
||
}
|
||
```
|
||
|
||
### 5.3 `GET /api/v1/transport/clients/{id}`
|
||
- Назначение: получить детальную карточку клиента.
|
||
|
||
### 5.4 `PATCH /api/v1/transport/clients/{id}`
|
||
- Назначение: частичное обновление метаданных/конфига.
|
||
- Поддерживаемые поля: `name`, `enabled`, `config`.
|
||
|
||
### 5.5 `DELETE /api/v1/transport/clients/{id}`
|
||
- Назначение: удалить клиента.
|
||
- Правило: удаление запрещено, если есть активные policy-ссылки без `force=true`.
|
||
|
||
### 5.6 `POST /api/v1/transport/clients/{id}/start`
|
||
### 5.7 `POST /api/v1/transport/clients/{id}/stop`
|
||
### 5.8 `POST /api/v1/transport/clients/{id}/restart`
|
||
- Назначение: lifecycle операции backend-клиента.
|
||
- Ответ: унифицированный `cmdResult`-совместимый формат + backend runtime поля (`status_before/status_after`, `runtime.metrics`, `runtime.last_error`).
|
||
|
||
Пример:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "start done",
|
||
"exitCode": 0,
|
||
"client_id": "phoenix-eu",
|
||
"kind": "phoenix",
|
||
"action": "start",
|
||
"status_before": "down",
|
||
"status_after": "up",
|
||
"health": { "last_check": "2026-03-07T10:11:12Z", "latency_ms": 83, "last_error": "" },
|
||
"runtime": {
|
||
"backend": "phoenix",
|
||
"allowed_actions": ["start", "stop", "restart"],
|
||
"metrics": { "restarts": 1, "state_changes": 2, "uptime_sec": 17 }
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.9 `GET /api/v1/transport/clients/{id}/health`
|
||
- Назначение: быстрый probe статуса и деградации.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"code": "TRANSPORT_CLIENT_DEGRADED",
|
||
"client_id": "phoenix-eu",
|
||
"kind": "phoenix",
|
||
"status": "degraded",
|
||
"latency_ms": 480,
|
||
"last_error": "upstream timeout",
|
||
"health": {
|
||
"last_check": "2026-03-07T10:11:12Z",
|
||
"latency_ms": 480,
|
||
"last_error": "upstream timeout"
|
||
},
|
||
"runtime": {
|
||
"backend": "phoenix",
|
||
"metrics": { "restarts": 1, "state_changes": 4, "uptime_sec": 0 },
|
||
"last_error": {
|
||
"code": "BACKEND_RUNTIME_ERROR",
|
||
"message": "upstream timeout",
|
||
"retryable": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.10 `GET /api/v1/transport/clients/{id}/metrics`
|
||
- Назначение: read-only срез lifecycle metrics для UI (desktop/web/iOS/android) без знания backend-внутренностей.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"client_id": "phoenix-eu",
|
||
"kind": "phoenix",
|
||
"status": "up",
|
||
"metrics": {
|
||
"restarts": 2,
|
||
"state_changes": 8,
|
||
"uptime_sec": 341,
|
||
"last_transition_at": "2026-03-07T10:11:12Z"
|
||
},
|
||
"runtime": {
|
||
"backend": "phoenix",
|
||
"last_action": "restart",
|
||
"last_action_at": "2026-03-07T10:11:12Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.11 `POST /api/v1/transport/clients/{id}/provision`
|
||
- Назначение: backend-side provision (создание/обновление unit/runner-конфигурации) перед lifecycle-операциями.
|
||
- Для `runner=systemd` пишет unit-файлы и делает `systemctl daemon-reload`.
|
||
|
||
### 5.12 `GET /api/v1/transport/runtime/observability`
|
||
- Назначение: unified multi-interface runtime snapshot для карточек/дашбордов без ручной склейки `interfaces + clients + egress + policy`.
|
||
- Источники:
|
||
- `transport-interfaces`,
|
||
- `transport-clients`,
|
||
- compile-plan policy,
|
||
- `egress identity` для active client на интерфейсе.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"generated_at": "2026-03-16T12:10:00Z",
|
||
"count": 2,
|
||
"items": [
|
||
{
|
||
"iface_id": "edge-a",
|
||
"name": "Edge A",
|
||
"mode": "dedicated",
|
||
"runtime_iface": "tun-edge",
|
||
"active_iface": "tun-edge0",
|
||
"netns_name": "svpn-edge-a",
|
||
"routing_table": "agvpn_if_edge_a",
|
||
"client_id": "sb-main",
|
||
"client_ids": ["sb-main", "dnstt-fallback"],
|
||
"status": "degraded",
|
||
"latency_ms": 81,
|
||
"last_error": "fallback probe failed",
|
||
"last_check": "2026-03-16T12:09:30Z",
|
||
"egress": {
|
||
"scope": "transport:sb-main",
|
||
"source": "transport",
|
||
"source_id": "sb-main",
|
||
"ip": "203.0.113.10",
|
||
"country_code": "SG",
|
||
"country_name": "Singapore",
|
||
"stale": false
|
||
},
|
||
"counters": {
|
||
"client_count": 2,
|
||
"enabled_count": 2,
|
||
"up_count": 1,
|
||
"degraded_count": 1,
|
||
"rule_count": 4
|
||
},
|
||
"engine_counts": [
|
||
{ "kind": "dnstt", "count": 1, "degraded_count": 1 },
|
||
{ "kind": "singbox", "count": 1, "up_count": 1 }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 6) Endpoints: policies
|
||
|
||
### 6.1 `GET /api/v1/transport/policies`
|
||
- Назначение: получить текущую политику и ревизию.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"policy_revision": 12,
|
||
"intents": []
|
||
}
|
||
```
|
||
|
||
### 6.2 `POST /api/v1/transport/policies/validate`
|
||
- Назначение: dry-run валидация без применения.
|
||
|
||
Request:
|
||
```json
|
||
{
|
||
"base_revision": 12,
|
||
"intents": [],
|
||
"options": {
|
||
"allow_warnings": true,
|
||
"force_override": false
|
||
}
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "validation complete",
|
||
"valid": false,
|
||
"summary": {
|
||
"block_count": 1,
|
||
"warn_count": 2
|
||
},
|
||
"conflicts": [],
|
||
"diff": {
|
||
"added": 10,
|
||
"changed": 3,
|
||
"removed": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 `POST /api/v1/transport/policies/apply`
|
||
- Назначение: атомарное применение новой policy.
|
||
- Обязательные условия:
|
||
- `base_revision` совпадает с текущей ревизией,
|
||
- нет blocking-конфликтов или задан `force_override=true` с подтверждением.
|
||
|
||
Request:
|
||
```json
|
||
{
|
||
"base_revision": 12,
|
||
"intents": [],
|
||
"options": {
|
||
"force_override": true,
|
||
"confirm_token": "cnf-01JABC..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "policy applied",
|
||
"policy_revision": 13,
|
||
"apply_id": "apl-01JABC...",
|
||
"rollback_available": true
|
||
}
|
||
```
|
||
|
||
Ошибка конкурентного изменения:
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"message": "stale policy revision",
|
||
"code": "POLICY_REVISION_MISMATCH",
|
||
"current_revision": 13
|
||
}
|
||
```
|
||
|
||
### 6.4 `POST /api/v1/transport/policies/rollback`
|
||
- Назначение: откатить policy к предыдущему snapshot.
|
||
- Условия:
|
||
- snapshot должен существовать,
|
||
- `base_revision` (если задан) должен совпадать с текущей ревизией,
|
||
- snapshot проходит текущую валидацию конфликтов.
|
||
|
||
Request:
|
||
```json
|
||
{
|
||
"base_revision": 13
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "policy rollback applied",
|
||
"policy_revision": 14,
|
||
"apply_id": "rbk-01JABC...",
|
||
"rollback_available": true
|
||
}
|
||
```
|
||
|
||
### 6.5 `GET /api/v1/transport/conflicts`
|
||
- Назначение: получить актуальные конфликты активной конфигурации.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"items": [],
|
||
"has_blocking": true
|
||
}
|
||
```
|
||
|
||
### 6.6 `GET /api/v1/transport/capabilities`
|
||
- Назначение: матрица возможностей backend-клиентов и текущей платформы.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"message": "ok",
|
||
"clients": {
|
||
"singbox": { "tcp": true, "udp": true, "dns_tunnel": true, "ssh_tunnel": false },
|
||
"dnstt": { "tcp": true, "udp": false, "dns_tunnel": true, "ssh_tunnel": true },
|
||
"phoenix": { "tcp": true, "udp": true, "dns_tunnel": false, "ssh_tunnel": true }
|
||
},
|
||
"runtime_modes": {
|
||
"exec": true,
|
||
"embedded": false,
|
||
"sidecar": false
|
||
},
|
||
"packaging_profiles": {
|
||
"system": true,
|
||
"bundled": true
|
||
},
|
||
"lifecycle": ["provision", "start", "stop", "restart"],
|
||
"health_fields": ["status", "latency_ms", "last_error", "health.last_check"],
|
||
"metrics_fields": ["restarts", "state_changes", "uptime_sec", "last_transition_at"],
|
||
"error_codes": [
|
||
"TRANSPORT_CLIENT_NOT_FOUND",
|
||
"TRANSPORT_CLIENT_SAVE_FAILED",
|
||
"TRANSPORT_CLIENT_DEGRADED",
|
||
"BACKEND_RUNTIME_ERROR",
|
||
"TRANSPORT_BACKEND_UNIT_REQUIRED",
|
||
"TRANSPORT_BACKEND_ACTION_FAILED",
|
||
"TRANSPORT_BACKEND_HEALTH_FAILED",
|
||
"TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED",
|
||
"TRANSPORT_BACKEND_PROVISION_FAILED",
|
||
"TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED"
|
||
]
|
||
}
|
||
```
|
||
|
||
Примечание по `config.runtime_mode`:
|
||
- `exec` — текущий production режим (внешний companion-бинарь под управлением backend-адаптера);
|
||
- `embedded`, `sidecar` — зарезервированы для следующих фаз; при попытке lifecycle/provision сейчас возвращается `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED`;
|
||
- alias `external|companion` нормализуются в `exec`.
|
||
|
||
Примечание по packaging для `runtime_mode=exec`:
|
||
- `packaging_profile=system` (default): поиск бинарей в системных путях (`/usr/bin`, `/usr/local/bin`, `$PATH`);
|
||
- `packaging_profile=bundled`: поиск в `bin_root` (default `/opt/selective-vpn/bin`) с опциональным fallback в system (`packaging_system_fallback=true`);
|
||
- `require_binary=true`: fail-fast на этапе `provision`/template build, если целевой бинарь не найден;
|
||
- для ручного override (`singbox_bin`, `dnstt_bin`, `phoenix_bin`) `require_binary=true` также валидирует существование.
|
||
- manual updater/rollback MVP:
|
||
- `scripts/transport-packaging/update.sh` читает pinned manifest, проверяет `sha256`, устанавливает release и атомарно переключает symlink;
|
||
- `update.sh` поддерживает trusted-source policy (`--source-policy`), optional/required detached signature verify (`signature.type=openssl-sha256`) и staged rollout (`rollout.stage/percent`, `--rollout-stage`, `--cohort-id`);
|
||
- `scripts/transport-packaging/auto_update.sh` — opt-in scheduler-wrapper (`enabled=true`) с interval gate/lock/jitter для безопасного фонового запуска;
|
||
- `scripts/transport-packaging/rollback.sh` откатывает компонент на предыдущую запись в `BIN_ROOT/.packaging/*.history`.
|
||
|
||
Примечание для `dnstt`:
|
||
- При `runner=systemd` допускается единая оркестрация `dnstt + ssh overlay`:
|
||
- `unit`: systemd unit DNSTT-клиента;
|
||
- `exec_start`: явный override команды запуска DNSTT-клиента (опционально);
|
||
- если `exec_start` не задан, Go-ядро строит команду по шаблону из полей:
|
||
- resolver: `resolver_mode=doh|dot|udp` + `doh_url|dot_addr|udp_addr|resolver_addr`,
|
||
- ключ: `pubkey` или `pubkey_file`,
|
||
- endpoint: `domain` + `local_addr` (default `127.0.0.1:7000`);
|
||
- `ssh_tunnel` или `ssh_overlay`: `true`;
|
||
- `ssh_unit`: systemd unit SSH-туннеля;
|
||
- `ssh_exec_start` (или `ssh_host` + `ssh_user` + `ssh_port` + `socks_port`): команда запуска SSH overlay.
|
||
|
||
Примечание для `singbox` и `phoenix`:
|
||
- при `runner=systemd` `exec_start` также опционален;
|
||
- при отсутствии `exec_start` команда строится шаблонами ядра:
|
||
- `singbox`: `<bin> run -c <config_path>`;
|
||
- `phoenix`: `<bin> -config <config_path>`.
|
||
|
||
Примечание для `runner=systemd` (общий tuning):
|
||
- `restart_policy`: `no|on-success|on-failure|on-abnormal|on-watchdog|on-abort|always` (default `always`);
|
||
- `restart_sec`: задержка перезапуска в секундах (default `2`);
|
||
- `start_limit_interval_sec`, `start_limit_burst`: анти-flap лимиты unit (defaults `300`, `30`);
|
||
- `timeout_start_sec`, `timeout_stop_sec`: таймауты старта/остановки (defaults `90`, `20`);
|
||
- `watchdog_sec`: опциональный systemd watchdog (default `0`, отключён);
|
||
- для `dnstt + ssh overlay` поддержаны `ssh_*` overrides тех же ключей (`ssh_restart_sec`, `ssh_watchdog_sec` и т.д.) для отдельного tuning SSH unit.
|
||
|
||
Примечание для `runner=systemd` (unit hardening):
|
||
- `hardening_profile`: `baseline|strict|off` (default `baseline`);
|
||
- `hardening_enabled`: `true|false` (может принудительно включить/выключить hardening);
|
||
- baseline-профиль включает:
|
||
- `NoNewPrivileges=yes`, `PrivateTmp=yes`,
|
||
- `ProtectSystem=full`, `ProtectHome=read-only`,
|
||
- `ProtectControlGroups=yes`, `ProtectKernelModules=yes`, `ProtectKernelTunables=yes`,
|
||
- `RestrictSUIDSGID=yes`, `LockPersonality=yes`, `UMask=0077`;
|
||
- strict-профиль дополнительно включает `ProtectSystem=strict` и `PrivateDevices=yes`;
|
||
- тонкие override-ключи:
|
||
- `no_new_privileges`, `private_tmp`, `protect_system`, `protect_home`,
|
||
- `protect_control_groups`, `protect_kernel_modules`, `protect_kernel_tunables`,
|
||
- `restrict_suid_sgid`, `lock_personality`, `private_devices`, `umask`;
|
||
- для overlay-пары поддержаны `ssh_*` overrides этих же hardening-ключей (например `ssh_hardening_enabled`, `ssh_protect_system`, `ssh_umask`).
|
||
|
||
## 7) События SSE (проект)
|
||
- `transport_client_state_changed`
|
||
- `{"id":"phoenix-eu","from":"starting","to":"up"}`
|
||
- `transport_client_provisioned`
|
||
- `{"id":"dnstt-home","ok":true,"msg":"provision done"}`
|
||
- `transport_policy_validated`
|
||
- `{"valid":false,"block_count":1,"warn_count":2}`
|
||
- `transport_policy_applied`
|
||
- `{"apply_id":"apl-...","policy_revision":13}`
|
||
- `transport_runtime_snapshot_changed`
|
||
- `{"reason":"transport_client_state_changed","generated_at":"2026-03-16T12:10:00Z","client_ids":["sb-main"],"iface_ids":["edge-a"],"items":[...]}`
|
||
- payload переиспользует тот же DTO, что и `GET /api/v1/transport/runtime/observability`, чтобы UI мог либо сделать re-fetch, либо обновиться напрямую без ручной агрегации.
|
||
- `transport_conflict_detected`
|
||
- `{"key":"domain:youtube.com","severity":"block"}`
|
||
|
||
## 8) Правила anti-conflict
|
||
- Ownership lock:
|
||
- один `selector_type + selector_value` принадлежит только одному `client_id`.
|
||
- По умолчанию конфликты `severity=block` блокируют `apply`.
|
||
- `force_override` разрешен только с `confirm_token`, полученным на этапе `validate`.
|
||
- Для UX предупреждений backend возвращает:
|
||
- список конфликтов,
|
||
- потенциальный impact (`flows_rebind_required`, `session_drop_risk`),
|
||
- diff по изменениям политики.
|
||
|
||
## 9) Безопасность и аудит
|
||
- Все mutating endpoints требуют `Authorization` + RBAC scope `transport:write`.
|
||
- Для операций `apply`, `delete`, `force_override` обязателен audit record:
|
||
- user id,
|
||
- request id,
|
||
- previous revision,
|
||
- new revision,
|
||
- short diff summary.
|
||
|
||
## 10) Минимальный план внедрения
|
||
- E2.1: ввести DTO и read-only endpoints (`GET clients`, `GET policies`, `GET capabilities`).
|
||
- E2.2: добавить `validate` с ownership/overlap анализом.
|
||
- E2.3: добавить `apply` с optimistic lock + rollback snapshot.
|
||
- E2.4: подключить SSE события и UI flow подтверждения.
|
||
|
||
## 11) Статус реализации в коде (2026-03-07)
|
||
- Реализовано в `selective-vpn-api/app/transport_handlers.go`:
|
||
- `GET/POST /api/v1/transport/clients`
|
||
- `GET/PATCH/DELETE /api/v1/transport/clients/{id}`
|
||
- `POST /api/v1/transport/clients/{id}/provision`
|
||
- `POST /api/v1/transport/clients/{id}/{start|stop|restart}`
|
||
- `GET /api/v1/transport/clients/{id}/health`
|
||
- `GET /api/v1/transport/clients/{id}/metrics`
|
||
- `GET /api/v1/transport/policies`
|
||
- `POST /api/v1/transport/policies/validate`
|
||
- `POST /api/v1/transport/policies/apply`
|
||
- `POST /api/v1/transport/policies/rollback`
|
||
- `GET /api/v1/transport/conflicts`
|
||
- `GET /api/v1/transport/capabilities`
|
||
- D4.1-контракт в Go:
|
||
- унифицированные DTO для lifecycle/health/metrics/errors,
|
||
- runtime-срез в `TransportClient` (`backend`, `allowed_actions`, counters, `last_error`),
|
||
- method-level ответы с кодами ошибок (`TRANSPORT_CLIENT_*`, `BACKEND_RUNTIME_ERROR`).
|
||
- D4.2 foundation в Go:
|
||
- backend-адаптеры `mock/systemd` с выбором по `client.config.runner`,
|
||
- для `dnstt` поддержан режим dual-unit orchestration (`ssh overlay`) в `provision/lifecycle/health`,
|
||
- шаблонный build `exec_start` в Go для `singbox|dnstt|phoenix` (с manual override через `config.exec_start`),
|
||
- systemd tuning для restart/start-limit/timeout/watchdog с отдельными `ssh_*` override для overlay unit,
|
||
- unit hardening профили (`baseline/strict/off`) и `ssh_*` hardening overrides для overlay unit.
|
||
- Валидация конфликтов:
|
||
- ownership conflict (`selector` на несколько клиентов),
|
||
- overlap CIDR между разными клиентами,
|
||
- unknown client / invalid selector.
|
||
- Apply flow:
|
||
- `base_revision` lock,
|
||
- `confirm_token` при `force_override`,
|
||
- snapshot предыдущей policy (`transport-policies.prev.json`),
|
||
- SSE события `transport_policy_validated`, `transport_policy_applied`, `transport_conflict_detected`.
|
||
- Allocator policy v2:
|
||
- резервные диапазоны для `mark_hex` и `priority_base`,
|
||
- детерминированное восстановление слотов при загрузке state,
|
||
- auto re-balance при коллизиях/битых слотах в `transport-clients.json`,
|
||
- детерминированная генерация уникальных `routing_table` (с защитой от коллизий длинных ID).
|