feat: establish standalone Device Core repository
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
# ADR 0001: Core-initiated Device Gateway Edge channel
|
||||
|
||||
Status: accepted for implementation on 2026-08-10.
|
||||
|
||||
Scope: NODE.DC Device Core / Device Gateway / public Device Edge VPS.
|
||||
|
||||
Authority: NDC PLATFORM `DCPLATFORM-76`, with deploy implementation governed by
|
||||
`DCPLATFORM-21`.
|
||||
|
||||
## Decision
|
||||
|
||||
The NODE.DC Core side initiates and owns the only private control/data channel
|
||||
to a public Device Gateway Edge. The Edge never initiates a network connection
|
||||
to Synology, DSM, Docker, Hub, Engine, Foundry, PostgreSQL, a Mac workstation or
|
||||
another LAN service.
|
||||
|
||||
The target transport is TLS 1.3 mutual authentication over one long-lived
|
||||
HTTP/2 bidirectional session:
|
||||
|
||||
```text
|
||||
ARUSNAVI B2
|
||||
-> raw TCP/9921
|
||||
-> Device Gateway Edge on the VPS
|
||||
-> accepted Core-owned mTLS session on standard HTTPS TCP/443
|
||||
-> Device Gateway Core on Synology
|
||||
-> Device Control Core
|
||||
```
|
||||
|
||||
The Core dials an endpoint selected from an approved Edge registration. The
|
||||
endpoint is not hardcoded into an adapter, device record, Foundry application,
|
||||
Engine workflow or deployment artifact.
|
||||
|
||||
`device.nodedc.ru` remains the HTTPS user surface for Device Core. It is not the
|
||||
raw B2 endpoint and is not moved to the VPS for this transport.
|
||||
|
||||
## Why this direction is mandatory
|
||||
|
||||
The VPS is a public, replaceable and partially untrusted edge host. A VPS owner
|
||||
or a compromised root account must not gain a route into the private NODE.DC
|
||||
network. An outbound Core connection works through NAT without a Synology port
|
||||
forward and gives the Core a single explicit peer and protocol to validate.
|
||||
|
||||
The previous design used a tagged userspace Tailscale process on the VPS,
|
||||
SOCKS5, SSH local forwarding and a Synology backhaul target. Although that
|
||||
design had narrow ACLs, the trust direction was still VPS-initiated and it kept
|
||||
an avoidable private-network membership on the public host. It is superseded.
|
||||
|
||||
## Component ownership
|
||||
|
||||
Device Gateway Edge owns only:
|
||||
|
||||
- public tracker TCP sessions;
|
||||
- allowlisted adapter framing and protocol timing;
|
||||
- the minimum HEADER/PACKAGE acknowledgement state;
|
||||
- bounded in-memory buffers and flow-control counters;
|
||||
- the server side of the mutually authenticated Core channel;
|
||||
- typed command delivery to an already connected tracker session.
|
||||
|
||||
Device Gateway Core and Device Control Core own:
|
||||
|
||||
- Edge registration and certificate identity;
|
||||
- owner scopes, Device Projects and project access;
|
||||
- enrollment, quarantine, claim and transfer policy;
|
||||
- restricted identifiers and credential references;
|
||||
- complete telemetry decoding/normalization and data classification;
|
||||
- command policy, confirmation, ledger and audit;
|
||||
- Engine/Data Product and Foundry bindings.
|
||||
|
||||
The Edge does not contain a business database, durable telemetry store, Hub or
|
||||
Authentik credentials, Engine/Foundry tokens, PostgreSQL credentials or the
|
||||
Core client private key.
|
||||
|
||||
## Adapter split
|
||||
|
||||
One versioned adapter package exposes explicit role-scoped interfaces:
|
||||
|
||||
- `edge-session`: framing, bounded validation, HEADER/PACKAGE ACK and typed
|
||||
command wire encoding required by the active socket;
|
||||
- `core-decoder`: complete tag decoding, safe observation normalization and
|
||||
model/firmware capability mapping;
|
||||
- `command-contract`: typed commands and acknowledgement semantics shared by
|
||||
policy and delivery code.
|
||||
|
||||
The Edge artifact contains only the allowlisted `edge-session` and required
|
||||
wire command implementation. It does not contain owner, workflow or Foundry
|
||||
logic. The Core artifact may contain the full adapter package. A new device
|
||||
model is added through the adapter registry; it does not create another Device
|
||||
Manager application or a model-specific Synology service.
|
||||
|
||||
## Authentication and key ownership
|
||||
|
||||
- The Core has a unique client certificate and private key stored only in the
|
||||
canonical Synology secret/trust boundary.
|
||||
- The Edge has a unique server certificate and private key stored only in the
|
||||
Edge runtime trust boundary.
|
||||
- Both certificates chain to the dedicated Device Edge private CA or an
|
||||
equivalent separately approved workload-identity issuer.
|
||||
- The Core verifies the Edge registration id, certificate identity, CA,
|
||||
validity and configured endpoint.
|
||||
- The Edge accepts only an approved Core workload identity and never accepts a
|
||||
browser, bearer-token-only or anonymous channel.
|
||||
- Certificates have bounded lifetime, explicit generation and audited rotation.
|
||||
- No private key, enrollment token or certificate bundle is carried in Git,
|
||||
Ops, MCP payloads or ordinary deploy artifacts.
|
||||
|
||||
TLS terminates on the Edge process. Root compromise of the VPS can therefore
|
||||
read tracker traffic and impersonate that Edge identity until it is revoked.
|
||||
It cannot obtain the Core private key or open a new connection into Synology.
|
||||
|
||||
## Session protocol
|
||||
|
||||
The application protocol is versioned and fail-closed. Every envelope has:
|
||||
|
||||
- schema version;
|
||||
- Edge registration id;
|
||||
- channel generation;
|
||||
- tracker session id;
|
||||
- adapter/profile reference and version;
|
||||
- monotonically increasing direction-local sequence;
|
||||
- event timestamp and receive timestamp;
|
||||
- bounded payload length;
|
||||
- message kind and correlation id.
|
||||
|
||||
Allowed Edge-to-Core messages are limited to channel hello/health, tracker
|
||||
session opened/closed, discovery evidence, bounded verified frame, delivery
|
||||
acknowledgement and bounded counters. Allowed Core-to-Edge messages are limited
|
||||
to channel acceptance, flow-control window, session disposition and typed
|
||||
command delivery.
|
||||
|
||||
Unknown schema versions, message kinds, adapters, Edge ids or oversized frames
|
||||
close the logical session and create a safe audit event. They never fall back
|
||||
to arbitrary TCP forwarding.
|
||||
|
||||
## Tracker acknowledgement rule
|
||||
|
||||
The VPS has no durable business store. It must not acknowledge a valid tracker
|
||||
PACKAGE merely because bytes reached the VPS.
|
||||
|
||||
The Edge sends the verified frame to Core and waits for a bounded Core
|
||||
acceptance acknowledgement. Only then may the Edge send the protocol PACKAGE
|
||||
acknowledgement to the tracker. If the Core channel is unavailable or the
|
||||
acceptance deadline expires, the Edge does not acknowledge the PACKAGE; the
|
||||
tracker remains responsible for its documented retry behavior.
|
||||
|
||||
HEADER acknowledgement follows the same ownership boundary: discovery must be
|
||||
accepted into Core quarantine or matched to an admitted device session before
|
||||
the Edge completes the handshake. An unavailable Core means no admitted
|
||||
tracker session.
|
||||
|
||||
This gives at-least-once delivery without a VPS database. Core deduplicates by
|
||||
Edge generation, tracker session, package number and content digest.
|
||||
|
||||
## Realtime and flow control
|
||||
|
||||
The Core does not poll the VPS for batches. Telemetry returns immediately over
|
||||
the established channel. The initial implementation contract is:
|
||||
|
||||
- one Core channel per Edge generation;
|
||||
- maximum 128 concurrent tracker sessions on the pilot VPS;
|
||||
- maximum 16 sessions per observed source address;
|
||||
- maximum 60 new tracker connections per minute per observed source;
|
||||
- maximum 256 KiB buffered per tracker session;
|
||||
- maximum 32 MiB aggregate tracker/channel buffering;
|
||||
- maximum 1 MiB for one protocol frame before adapter-specific lower limits;
|
||||
- 15-second keepalive and 45-second dead-peer deadline;
|
||||
- reconnect with jittered exponential delay from 1 to 30 seconds;
|
||||
- no unbounded disk spool;
|
||||
- explicit accepted, duplicate, late, dropped, rejected and throttled counters.
|
||||
|
||||
These are pilot ceilings, not a claim that one 961 MiB VPS supports production
|
||||
scale. Load gates at 1, 100 and 1000 synthetic sessions determine the production
|
||||
Edge topology.
|
||||
|
||||
## Pilot service objectives
|
||||
|
||||
These are acceptance objectives for the controlled pilot, not a published
|
||||
production SLA:
|
||||
|
||||
- zero tracker PACKAGE acknowledgements before durable Core acceptance in all
|
||||
normal, timeout, disconnect and Core-restart tests;
|
||||
- zero loss of Core-accepted packages in the acceptance run; duplicate delivery
|
||||
is permitted on reconnect but must collapse to one normalized observation;
|
||||
- Edge receive to Core acceptance latency at 128 concurrent synthetic sessions:
|
||||
p95 at or below 2 seconds and p99 at or below 5 seconds;
|
||||
- after both peers and the network are healthy, channel re-establishment: p95 at
|
||||
or below 60 seconds and hard acceptance ceiling of 120 seconds;
|
||||
- dead Core detection at the Edge no later than 45 seconds after the last valid
|
||||
channel activity;
|
||||
- memory stays inside the configured 32 MiB aggregate application buffer plus
|
||||
the separately measured fixed runtime baseline; exceeding a bound throttles
|
||||
or closes the offender and never expands the limit;
|
||||
- malformed, unknown, unauthenticated and revoked-identity inputs produce zero
|
||||
accepted telemetry records and zero command deliveries;
|
||||
- a typed command for a currently connected test tracker reaches Edge wire
|
||||
delivery or a conclusive rejection in p95 at or below 2 seconds; verified
|
||||
device execution is a separate adapter-defined objective.
|
||||
|
||||
Availability percentage and the 1000-session production capacity are explicitly
|
||||
uncommitted until measured on the target host and recorded by a later transition.
|
||||
|
||||
## Commands
|
||||
|
||||
Commands use the same Core-owned channel and the existing tracker TCP session.
|
||||
The VPS never exposes a command HTTP API and never accepts an arbitrary raw
|
||||
payload.
|
||||
|
||||
Core sends a typed command containing exact device/session, adapter/profile,
|
||||
command catalog version, parameters, idempotency key, expiry and correlation
|
||||
id. Edge either rejects it before wire delivery or returns delivery evidence.
|
||||
Protocol acknowledgement is `acknowledged`, not `verified`. Verification needs
|
||||
a subsequent device observation or explicit readback defined by the adapter.
|
||||
|
||||
No command is queued durably on the VPS. Channel loss before a conclusive
|
||||
outcome yields `unknown` or `failed` according to the command contract. Unsafe
|
||||
automatic retry is forbidden.
|
||||
|
||||
## Network boundary
|
||||
|
||||
The target Edge exposes only:
|
||||
|
||||
- management SSH according to the separately accepted management policy;
|
||||
- raw tracker ingress TCP/9921;
|
||||
- Core channel TCP/443 with mandatory mTLS; this is not a browser or bearer-token endpoint.
|
||||
|
||||
Synology exposes no public device or backhaul port. The Edge receives no subnet
|
||||
route, exit-node capability, Tailscale SSH, Docker socket or generic proxy. The
|
||||
Core channel cannot request an arbitrary destination or port.
|
||||
|
||||
Firewall policy is default deny. Raw ingress and Core channel have independent
|
||||
connection/rate/resource limits. DDoS and malformed traffic terminate at the
|
||||
provider/VPS and cannot be forwarded as a generic byte stream to Synology.
|
||||
|
||||
## Threat model
|
||||
|
||||
Protected assets are the Synology/LAN network, Device Core data and credentials,
|
||||
Hub/Authentik identities, Engine and Foundry capabilities, command authority and
|
||||
the integrity of admitted telemetry.
|
||||
|
||||
The design assumes any of the following can happen independently: VPS root is
|
||||
compromised; the VPS owner makes an unsafe change; an Internet client floods or
|
||||
sends malformed B2 traffic; an Edge certificate is copied; a tracker identifier
|
||||
is spoofed; the Core channel is interrupted; a valid package is replayed; or a
|
||||
command outcome becomes unknown during disconnect.
|
||||
|
||||
The boundary mitigates lateral entry into the private platform, arbitrary TCP
|
||||
proxying, anonymous/bearer-only channel access, unbounded memory growth, replay
|
||||
as a second normalized observation, command injection through a raw API, secret
|
||||
distribution to the VPS and acknowledgement of telemetry that only reached
|
||||
volatile Edge memory.
|
||||
|
||||
Residual risks are explicit: compromised VPS root can read, drop, delay or forge
|
||||
traffic attributed to that Edge until revocation, attack trackers connected to
|
||||
it and exhaust the VPS or its uplink. Provider-scale DDoS protection, tracker
|
||||
firmware trust and physical tracker compromise are outside this component. None
|
||||
of those residual risks grants an inbound route or credential to Synology.
|
||||
|
||||
## Tailscale decision
|
||||
|
||||
Direct mTLS is the accepted target. Tailscale is not required for the product
|
||||
channel.
|
||||
|
||||
The current VPS userspace Tailscale foundation is a live predecessor and is not
|
||||
removed in Phase 0. A later canonical transition removes it after the mTLS
|
||||
channel is accepted. If direct mTLS proves impossible for an externally
|
||||
evidenced reason, a new ADR may admit Tailscale only with all of the following:
|
||||
|
||||
- Core still initiates the application session;
|
||||
- tagged service identity;
|
||||
- no user ownership, subnet routes, DNS, exit node or Tailscale SSH;
|
||||
- an ACL to one exact Edge application endpoint only;
|
||||
- negative tests for DSM 22/5001, Docker, MacBook and LAN;
|
||||
- no SSH LocalForward or generic SOCKS backhaul.
|
||||
|
||||
The old VPS-to-Synology SSH local-forward is not an allowed fallback.
|
||||
|
||||
## Superseded source
|
||||
|
||||
The following source is historical/recovery evidence and must not be used for a
|
||||
new plan or apply:
|
||||
|
||||
- `deployment/device-edge-vps-backhaul-v1.json`;
|
||||
- `deployment/device-edge-vps-relay-v1.json` when it forwards to port 19921;
|
||||
- `deployment/device-plane-backhaul-vps-enrollment-v1.json`;
|
||||
- `deployment/tailscale-device-edge-policy.hujson` for the old SSH target;
|
||||
- `vps/config/backhaul_ssh_config`;
|
||||
- `vps/systemd/nodedc-b2-backhaul.service`;
|
||||
- `vps/systemd/nodedc-b2-relay.service` with the old upstream;
|
||||
- `infra/deploy-runner/build-device-plane-backhaul-vps-enrollment-artifact.mjs`;
|
||||
- `infra/deploy-runner/nodedc-b2-vps-deploy` backhaul/relay phases.
|
||||
|
||||
Builders fail closed by default. A test-only environment switch may reconstruct
|
||||
historical artifacts for deterministic regression tests, but artifacts built in
|
||||
that mode are not deploy candidates. Both reviewed runner sources reject the
|
||||
superseded VPS phases and Synology enrollment even if such an archive exists.
|
||||
|
||||
## Deployment boundaries
|
||||
|
||||
The successor is a new additive Edge transport generation, not a weakened edit
|
||||
of the old phase. Before an application artifact exists, DCPLATFORM-21 must
|
||||
define:
|
||||
|
||||
- component and transition identity;
|
||||
- exact payload allowlist/denylist;
|
||||
- fixed Edge roots, systemd units and trust roots;
|
||||
- runtime-secret ownership and rotation;
|
||||
- predecessor checks for the current Tailscale foundation;
|
||||
- services stopped/started and preserved state;
|
||||
- health and negative network acceptance inside apply;
|
||||
- automatic rollback to the current closed-port predecessor.
|
||||
|
||||
The first successor apply must leave TCP/9921 closed. It accepts only the mTLS
|
||||
Core channel in a no-device/synthetic mode. Public tracker ingress is a separate
|
||||
later transition after Core-channel acceptance.
|
||||
|
||||
## Acceptance gates
|
||||
|
||||
Phase 0 is accepted when:
|
||||
|
||||
- this ADR and a machine-readable contract are present;
|
||||
- the old builders fail closed by default;
|
||||
- the reviewed VPS runner source rejects old backhaul/relay phases;
|
||||
- regression tests prove the freeze and the new direction;
|
||||
- Ops records exact SLO, threat and rollback boundaries;
|
||||
- no runtime, DNS, route, port or tracker setting changed.
|
||||
|
||||
The future Core-channel slice is accepted only when:
|
||||
|
||||
- Synology has no new public listener or router mapping;
|
||||
- a Core client without the exact identity cannot connect;
|
||||
- an Edge with an unknown/revoked identity is rejected by Core;
|
||||
- the Edge cannot reach DSM, Docker, Hub, Engine, Foundry, PostgreSQL, MacBook
|
||||
or LAN targets;
|
||||
- unknown/oversized/replayed envelopes fail closed;
|
||||
- Core loss causes no tracker PACKAGE acknowledgement after the deadline;
|
||||
- reconnect and deduplication preserve at-least-once behavior;
|
||||
- secrets and unrestricted identifiers are absent from artifacts, logs, Ops,
|
||||
metrics and MCP;
|
||||
- rollback stops the successor channel, restores the closed-port predecessor
|
||||
and preserves Device Plane DB, Gelios, Foundry and Engine.
|
||||
|
||||
## Rollback
|
||||
|
||||
Phase 0 changes only source and Ops; rollback is a source revert with no runtime
|
||||
effect.
|
||||
|
||||
The future transport apply owns an automatic rollback partition containing the
|
||||
new Edge/Core channel units, configs, certificate references, firewall entries
|
||||
and source publication. Rollback must:
|
||||
|
||||
- stop and disable only the candidate channel units;
|
||||
- remove only candidate firewall rules/listeners;
|
||||
- restore the exact accepted predecessor files and unit states;
|
||||
- keep public B2/9921 closed unless it was already an accepted predecessor;
|
||||
- preserve Device Core/PostgreSQL, Gateway Core, Hub, Engine, Foundry and
|
||||
Gelios;
|
||||
- preserve audit evidence and record the failed generation;
|
||||
- never restore or invent the superseded VPS-initiated SSH backhaul.
|
||||
|
||||
## Consequences
|
||||
|
||||
The Edge contains protocol-session code and can be replaced independently. A
|
||||
VPS compromise can disrupt or falsify its tracker observations and can attack
|
||||
connected trackers, but it does not become a path into the private platform.
|
||||
|
||||
The design deliberately accepts temporary telemetry unavailability when Core
|
||||
is unreachable instead of acknowledging data into an untrusted, non-durable
|
||||
VPS buffer. This is the correct failure mode for the stated trust boundary.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Device Edge B2 public pilot runbook
|
||||
|
||||
> Frozen historical runbook — 2026-08-10
|
||||
>
|
||||
> Do not configure these NAT rules, Mini relay, DNS changes or tracker route.
|
||||
> This path is superseded by
|
||||
> `docs/ADR_0001_CORE_INITIATED_EDGE_CHANNEL.md`. The public pilot will receive
|
||||
> a new runbook only after the Core-initiated mTLS channel and VPS Edge pass
|
||||
> their separate security/deploy acceptance.
|
||||
|
||||
Status: router/NAT is **not configured** by this document. It is a one-pilot,
|
||||
human-operated exposure gate for the already accepted Mini relay. It never
|
||||
changes Synology, Gelios, VPN, Device Plane command transport or device
|
||||
ownership.
|
||||
|
||||
## Exact traffic path
|
||||
|
||||
```text
|
||||
ARUSNAVI B2 pilot
|
||||
-> public IPv4 : TCP/9921
|
||||
-> provider router : TCP/9921 -> 192.168.1.151:9921
|
||||
-> Deco X55 : TCP/9921 -> 192.168.71.253:9921
|
||||
-> Mini IPvlan relay
|
||||
-> restricted private backhaul
|
||||
-> Synology Gateway 127.0.0.1:9921
|
||||
```
|
||||
|
||||
The observed Deco WAN is `192.168.1.151` with gateway `192.168.0.1`; it is an
|
||||
RFC1918 address. Therefore this is a double-NAT topology. A Deco rule alone
|
||||
cannot make the tracker reachable from the internet.
|
||||
|
||||
## Immutable safety boundary
|
||||
|
||||
- Forward **TCP only**, external and internal port `9921`.
|
||||
- Deco target is exactly `192.168.71.253`, never the Mini host
|
||||
`192.168.68.54` and never Synology.
|
||||
- Do not enable DMZ, UPnP, port ranges, UDP, 443 forwarding or any catch-all
|
||||
rule.
|
||||
- Do not remove, replace or edit the Gelios monitoring route.
|
||||
- The relay accepts connections only from a public IPv4 source, keeps
|
||||
quarantine-only discovery and has no command transport. A LAN client will
|
||||
intentionally be rejected; that is not a test failure.
|
||||
- The rule must be deleted again if the single pilot is abandoned or the
|
||||
expected quarantine evidence is not obtained.
|
||||
|
||||
## Gate 1 — DNS and public address
|
||||
|
||||
1. Determine the actual public IPv4 on the provider-facing edge. Do not use
|
||||
`192.168.0.1`, `192.168.1.151`, `192.168.68.1` or `192.168.71.253` as a
|
||||
DNS answer: all are private addresses.
|
||||
2. Point `device.nodedc.ru` to that public IPv4 only if the tracker UI accepts
|
||||
a hostname. Otherwise configure the public IPv4 directly.
|
||||
3. If the provider router has no public WAN IPv4, or an internet check still
|
||||
cannot reach it after both rules below, stop. This is provider CGNAT/bridge
|
||||
territory: request a public IPv4 or a bridge/forwarding option from the
|
||||
provider. Do not introduce a VPS as an unreviewed workaround.
|
||||
|
||||
## Gate 2 — provider router
|
||||
|
||||
On the upstream/provider router, create one port-forward rule:
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Name | `NDC B2 pilot to Deco` |
|
||||
| Protocol | `TCP` |
|
||||
| External port | `9921` |
|
||||
| Target address | `192.168.1.151` |
|
||||
| Target port | `9921` |
|
||||
|
||||
Save only that rule. It targets the Deco WAN address, not a service host.
|
||||
|
||||
## Gate 3 — Deco X55
|
||||
|
||||
In the Deco mobile app: **More → Advanced → NAT Forwarding → Port Forwarding →
|
||||
Add**. Create exactly:
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Name | `NDC B2 pilot` |
|
||||
| Protocol | `TCP` |
|
||||
| External port | `9921` |
|
||||
| Internal IP | `192.168.71.253` |
|
||||
| Internal port | `9921` |
|
||||
|
||||
If the app forces a client selection, select the IPvlan relay only if its
|
||||
address is shown as `192.168.71.253`. Do not select the Mini host or any NAS.
|
||||
If the UI will not accept the fixed IPvlan address, stop and record that fact;
|
||||
do not substitute a DHCP address.
|
||||
|
||||
## Gate 4 — ARUSNAVI B2 route
|
||||
|
||||
Use one known pilot B2 and one unused monitoring-server slot. Configure an
|
||||
additional route with the documented **INTERNAL** protocol and the public
|
||||
hostname/IP from Gate 1, TCP port `9921`. Preserve the existing Gelios route in
|
||||
its current slot. The ARUSNAVI account password stays in the operator surface;
|
||||
it is not entered into Foundry, Device Plane or the relay.
|
||||
|
||||
The device IMEI observed in `HEADER2` is a claimed identifier only. It becomes
|
||||
a quarantine discovery, not an owned device and never a command target.
|
||||
|
||||
## Acceptance and failure handling
|
||||
|
||||
The first valid HEADER/PACKAGE through the pilot route must produce a masked,
|
||||
quarantine-only discovery in Device Control Core and the existing Gelios map
|
||||
path must continue independently. Do not issue a device command.
|
||||
|
||||
On any unexpected behavior, remove the two NAT rules in reverse order:
|
||||
|
||||
1. remove the Deco `NDC B2 pilot` rule;
|
||||
2. remove the provider-router `NDC B2 pilot to Deco` rule.
|
||||
|
||||
This ends external reachability while leaving the Mini, VPN, backhaul,
|
||||
Synology and Gelios unchanged. Do not use a LAN port probe as acceptance: the
|
||||
relay correctly rejects private source addresses.
|
||||
@@ -0,0 +1,898 @@
|
||||
# NDC Device Manager / Direct ARUSNAVI B2 / VPS
|
||||
|
||||
> Historical audit notice — 2026-08-10
|
||||
>
|
||||
> Live evidence in this document remains useful, but the product and transport
|
||||
> decisions are superseded by `DCPLATFORM-76` and
|
||||
> `docs/ADR_0001_CORE_INITIATED_EDGE_CHANNEL.md`. Device Core is a standalone
|
||||
> Hub application, not a Foundry Page; the Edge contains a bounded adapter
|
||||
> session role; Synology/Core initiates the private channel to the VPS; the old
|
||||
> VPS-to-Synology relay/backhaul must not be deployed.
|
||||
|
||||
Актуализированный архитектурный аудит и план перехода на Direct-primary с Gelios read fallback.
|
||||
|
||||
Дата фиксации: 6 августа 2026 года.
|
||||
|
||||
## 1. Итоговое решение
|
||||
|
||||
Целевая read-архитектура зафиксирована так:
|
||||
|
||||
- ARUSNAVI B2 отправляет данные параллельно в два monitoring server slot;
|
||||
- прямой route B2 → NODE.DC становится основным источником позиции и текущей телеметрии;
|
||||
- Gelios остаётся постоянно работающим legacy/read-only кандидатом для fallback;
|
||||
- при недоступности прямого потока переключение выполняется отдельно для каждого устройства, а не глобально по одному health endpoint;
|
||||
- наружу публикуется один канонический факт на один трайк; два источника не пишут одновременно в одну current/history projection;
|
||||
- identity трайка, существующие `sourceId`, joins Foundry и Timescale-инфраструктура сохраняются;
|
||||
- команды через Gelios не используются;
|
||||
- команды через Direct B2 не входят в текущий этап и остаются выключенными;
|
||||
- Mac Mini окончательно исключён из production ingress; его артефакты остаются историческим прототипом;
|
||||
- VPS выполняет только публичный L4 ingress и зашифрованный backhaul. На нём нет БД, Engine, EDP, Foundry, Device Control Core, decoder, ACK-логики или command transport.
|
||||
|
||||
Важная терминологическая поправка: B2 приходит на VPS не HTTP-пакетами, а сырым TCP-потоком `INTERNAL`. HTTP/HTTPS может использоваться только внутри закрытого контура после Gateway/decoder. Шифрование начинается на плече VPS → private NODE.DC contour; первый hop B2 → VPS остаётся обычным raw TCP, если сам B2 не поддерживает иной транспорт.
|
||||
|
||||
## 2. Что проверено live
|
||||
|
||||
Этот документ опирается не только на Ops, но и на live MCP и текущий source:
|
||||
|
||||
- Ops: проекты `NDC PLATFORM` и `ROBOT2B`, полные карточки и комментарии;
|
||||
- Engine: granted L2 targets, графы, runtime, executions, credential binding status, output profiles и telemetry catalog;
|
||||
- Foundry: application instance, Map page, bindings, profiles и server-owned consumer progress;
|
||||
- Ontology: live catalog, entities, relations и guardrails;
|
||||
- source: Device Plane, ARUSNAVI adapter, Gateway, Control Core, EDP writer/reader scope и deployment artifacts;
|
||||
- DNS: текущий A-record;
|
||||
- SSH: доступные локальные aliases и наличие однозначной VPS identity.
|
||||
|
||||
Ops в этом аудите является журналом решений и статусов. Реализация считается подтверждённой только там, где она совпадает с live MCP, runtime или source.
|
||||
|
||||
## 3. Что в исходном аудите устарело
|
||||
|
||||
### 3.1 MCP уже работает
|
||||
|
||||
Исходный текст говорил, что Ops, Engine, Ontology и Foundry MCP недоступны. На момент этой актуализации все четыре live boundary доступны и прочитаны.
|
||||
|
||||
### 3.2 Канонический продукт — v5, не v3
|
||||
|
||||
Production Map получает:
|
||||
|
||||
```text
|
||||
fleet.positions.current.v5@5.0.0
|
||||
ontology.map.moving_object.v3
|
||||
delivery = snapshot+patch
|
||||
history = latest observation / 60 s bucket
|
||||
retention = 90 days
|
||||
```
|
||||
|
||||
Определение продукта находится в `platform/services/external-data-plane/definitions/fleet.positions.current.v5.json`.
|
||||
|
||||
### 3.3 Device Manager Page ещё не существует
|
||||
|
||||
Live Page Library Foundry содержит только `map@0.1.0`. Канонического шаблона `Device Manager`, server-owned `device-plane-control` provider и соответствующего UI сейчас нет.
|
||||
|
||||
Device Manager не блокирует Direct ingestion: текущий этап можно завершить через Device Plane, Engine/EDP и существующую Map. UI управления устройствами остаётся отдельной более поздней фазой.
|
||||
|
||||
### 3.4 VPS проверен live по SSH
|
||||
|
||||
После получения точной SSH identity выполнен read-only аудит `root@155.212.211.15`. Изменений на host не выполнялось.
|
||||
|
||||
Проверенная identity:
|
||||
|
||||
- hostname `koffyvngij`;
|
||||
- KVM/QEMU;
|
||||
- Ubuntu 24.04.4 LTS, kernel `6.8.0-137-generic`;
|
||||
- SSH host key уже был pinned локально; ED25519 fingerprint `SHA256:mhqNn2S6zstkYL7VFdvt3SYHv1nLjB4J7/s57RrKG6w`.
|
||||
- используемый client key имеет mode `0600` и fingerprint `SHA256:DYYy1E3DaxIQGC0jnsW6SP7gXdBHUy3A1zn4pvgVUEw`;
|
||||
- в `/root/.ssh/authorized_keys` находятся два unrestricted key lines: этот Mac key и отдельный provider `beget-access-key`; их дальнейшая судьба должна быть explicit management-access policy, а не ручное удаление во время аудита.
|
||||
|
||||
Проверенная ёмкость:
|
||||
|
||||
- 1 vCPU;
|
||||
- 961 MiB RAM, около 621 MiB available во время аудита;
|
||||
- swap отсутствует;
|
||||
- root filesystem 8.7 GiB, 2.3 GiB used, 6.4 GiB available;
|
||||
- inode usage 11%;
|
||||
- system clock synchronized, NTP active, timezone UTC;
|
||||
- failed systemd units отсутствуют;
|
||||
- journal занимает 9.9 MiB.
|
||||
|
||||
Проверенная сеть/runtime:
|
||||
|
||||
- `eth0` имеет public `155.212.211.15/32`, default route через `100.100.1.1` on-link;
|
||||
- наружу слушает только SSH на `0.0.0.0:22` и `[::]:22`; `9921/TCP` не слушает;
|
||||
- Docker, Podman, containerd, Tailscale, WireGuard и relay отсутствуют;
|
||||
- Fail2Ban активен для `sshd`; unattended upgrades и time sync активны.
|
||||
|
||||
Текущий security baseline не принят для production:
|
||||
|
||||
- UFW inactive;
|
||||
- nftables/iptables INPUT policy `ACCEPT`; единственное правило — Fail2Ban reject set для SSH;
|
||||
- `PermitRootLogin yes`;
|
||||
- `PasswordAuthentication yes`, root password установлен;
|
||||
- `X11Forwarding yes`;
|
||||
- `AllowTcpForwarding yes`, `PermitOpen any`, `DisableForwarding no`.
|
||||
|
||||
Следовательно, VPS подходит по мощности для одного bounded relay и encrypted backhaul, но public `9921` нельзя включать до canonical firewall/SSH/runtime bootstrap и rollback acceptance.
|
||||
|
||||
### 3.5 Домен сейчас указывает не на VPS
|
||||
|
||||
`device.nodedc.ru` на 6 августа 2026 года резолвится в `95.165.91.235`. По `DCPLATFORM-34` это внешний адрес Synology/DSM, а не подтверждённый адрес нового VPS.
|
||||
|
||||
`device.dc.ru`, упомянутый устно, A-record не вернул. Каноническое имя в архитектуре — `device.nodedc.ru`.
|
||||
|
||||
До VPS acceptance и DNS cutover нельзя настраивать B2 на текущий `device.nodedc.ru:9921`: raw ingress на Synology запрещён.
|
||||
|
||||
### 3.6 Полный IMEI уже существует в restricted Foundry contour
|
||||
|
||||
Live Foundry получает `device_imei` в двух restricted bindings:
|
||||
|
||||
- `fleet.units.contacts.current.v1`;
|
||||
- `fleet.units.identity.current.v1`.
|
||||
|
||||
Карточка объекта показывает полный IMEI как `restricted` text field из aspect `unit_identity`. Это не public/unrestricted поле, но это и не маскированное значение.
|
||||
|
||||
Новая принятая политика:
|
||||
|
||||
- IMEI не является паролем или secret;
|
||||
- он может проходить внутри NODE.DC contour и использоваться для сопоставления устройства;
|
||||
- он остаётся classified `restricted` identifier;
|
||||
- его нельзя без необходимости писать в Ops, обычные логи, публичные payload, metrics labels или unrestricted MCP output;
|
||||
- VPS видит IMEI только как часть непрозрачного TCP stream и не должен его извлекать или логировать;
|
||||
- основной lookup в Device Plane может оставаться HMAC digest; полный IMEI разрешён в restricted projection там, где он действительно нужен.
|
||||
|
||||
IMEI по-прежнему не является доказательством ownership. Он является идентификатором для lookup/crosswalk, а claim/tenant assignment остаются административным действием.
|
||||
|
||||
## 4. Фактическая production read-цепочка
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B2["ARUSNAVI B2"] --> GELIOS["Gelios monitoring route"]
|
||||
GELIOS --> L2["Engine alxvw4fn\nGELIOS — REALTIME POSITIONS"]
|
||||
L2 --> EDP["EDP / Timescale\nfleet.positions.current.v5"]
|
||||
EDP --> FOUNDRY["Foundry\nRobot2B Trike Positions"]
|
||||
|
||||
B2 -. "Direct route пока не подключён" .-> VPS["VPS Device Edge\nfoundation accepted"]
|
||||
VPS -. "backhaul/relay gated" .-> GW["Synology Device Gateway"]
|
||||
GW -. "PACKAGE сейчас ACK + discard" .-> STOP["Нет decoder / EDP publish"]
|
||||
```
|
||||
|
||||
### 4.1 Live Engine
|
||||
|
||||
Production owner позиции и телеметрии:
|
||||
|
||||
- L1 workflow: `WCb62yGL8v`;
|
||||
- target: `alxvw4fn`;
|
||||
- name: `GELIOS — REALTIME POSITIONS`;
|
||||
- revision: `68f4c852b8bc25b99c40b22fb4ca724fa6a0a34adbff74d1224b12ca7087bad8`;
|
||||
- runtime workflow: `RupsyGGlawBzmUFm`;
|
||||
- runtime active;
|
||||
- schedule: каждые 10 секунд;
|
||||
- рабочий request: `GET /api/v1/units` с `incltrip=true`, `inclcntrs=true`, `inclsnsrs=true`, `incllsv=true`;
|
||||
- writer bindings и Gelios rotating credential зарегистрированы со status `ok`.
|
||||
|
||||
Во время проверки executions завершались успешно примерно за 5–10 секунд. Execution `1297364` был `success` и дал структурный профиль 107 subjects.
|
||||
|
||||
Normalized fact содержит:
|
||||
|
||||
- `sourceId = gelios-unit-<provider unit id>`;
|
||||
- `semanticType = map.moving_object`;
|
||||
- geometry Point;
|
||||
- `display_name`;
|
||||
- `position_source = gelios`;
|
||||
- `signal_state`;
|
||||
- `movement_state`;
|
||||
- speed/course/elevation;
|
||||
- satellite count, HDOP/accuracy при наличии;
|
||||
- mileage, engine hours;
|
||||
- `sensor_readings`.
|
||||
|
||||
Live telemetry catalog execution `1297364` подтвердил 17 безопасных reading IDs без rejected/unsupported values:
|
||||
|
||||
```text
|
||||
sensor.param.call_btn
|
||||
sensor.param.gps_mod
|
||||
sensor.param.gsm
|
||||
sensor.param.gsm_level
|
||||
sensor.param.gsm_st
|
||||
sensor.param.gyro
|
||||
sensor.param.ign_virt
|
||||
sensor.param.in_0
|
||||
sensor.param.in_1
|
||||
sensor.param.in0
|
||||
sensor.param.in1
|
||||
sensor.param.nav_st
|
||||
sensor.param.pwr_ext
|
||||
sensor.param.pwr_int
|
||||
sensor.param.sim1_st
|
||||
sensor.param.sim2_st
|
||||
sensor.param.v_in
|
||||
```
|
||||
|
||||
`in_0` и `in_1` имеют provider-configured label conflict; это уже отражено в telemetry catalog и не должно скрываться при Direct comparison.
|
||||
|
||||
### 4.2 Инертный duplicate target
|
||||
|
||||
Target `g7q86421` (`gelios.positions.current.realtime.v7`) не является вторым production writer.
|
||||
|
||||
В его live graph соединены только:
|
||||
|
||||
```text
|
||||
manual trigger
|
||||
→ monitoring config
|
||||
→ units request
|
||||
→ extraction
|
||||
→ ontology map
|
||||
```
|
||||
|
||||
Scheduled trigger и `NDC Data Product Publish • fleet.positions.current.v5` физически отсоединены. Schedule всё ещё создаёт короткие успешные пустые executions примерно раз в две минуты, но публикации не выполняются.
|
||||
|
||||
Это подтверждает текущий правильный инвариант: у `fleet.positions.current.v5` один активный producer path.
|
||||
|
||||
### 4.3 Engine platform debt
|
||||
|
||||
При будущей переработке L2 нельзя игнорировать открытые карточки:
|
||||
|
||||
- `DCPLATFORM-72`: UI `node.parameters` и executable `data.n8n.parameters` могут расходиться;
|
||||
- `DCPLATFORM-73`: managed writer не всегда корректно перепривязывается к новой graph revision без revoke/recreate.
|
||||
|
||||
Любое изменение arbiter graph требует post-write graph equality, deep validation, свежего execution и exact writer acceptance. Успешный patch preview сам по себе недостаточен.
|
||||
|
||||
## 5. Live Foundry
|
||||
|
||||
Application:
|
||||
|
||||
- ID: `1c7dcdbb-6e50-4272-b1bc-aa5ece77ae99`;
|
||||
- name: `Robot2B Trike Positions`;
|
||||
- slug: `robot2b-trike-positions`;
|
||||
- status: `draft`;
|
||||
- version: `0.1.0`;
|
||||
- одна page `map` на `/`;
|
||||
- Map template `map@0.1.0`;
|
||||
- updated: `2026-08-05T17:29:28.077Z`.
|
||||
|
||||
Live server-owned consumers:
|
||||
|
||||
| Binding | Product | Subjects | Cursor | Last error |
|
||||
|---|---|---:|---:|---|
|
||||
| `trike-current-positions` | `fleet.positions.current.v5` | 107 | 124720 | null |
|
||||
| `trike-unit-profile` | `fleet.units.profile.current.v1` | 107 | 74 | null |
|
||||
| `trike-unit-contacts` | `fleet.units.contacts.current.v1` | 107 | 55 | null |
|
||||
| `trike-unit-identity` | `fleet.units.identity.current.v1` | 107 | 3651 | null |
|
||||
| `depttrans-pmd-slow-zones` | `map.zones.current.v2` | 903 | 10 | null |
|
||||
|
||||
Все consumers включены и используют `target-scoped-server-only` reader grant. Positions consumer получил свежий patch во время аудита.
|
||||
|
||||
Существующее представление уже provider-neutral по renderer и composition:
|
||||
|
||||
- один primary moving-object binding;
|
||||
- profile/identity joins выполняются по стабильному `sourceId`;
|
||||
- в Data tab уже показываются `sourceId`, `semanticType`, `position_source`, `dataProductId`, `receivedAt`;
|
||||
- отдельный Direct pin или отдельная Direct Map не нужны.
|
||||
|
||||
Чего нет:
|
||||
|
||||
- source-selection mode `primary|fallback|shadow|stale`;
|
||||
- причина выбора источника;
|
||||
- последнее Direct и последнее Gelios observation одновременно;
|
||||
- source health/freshness;
|
||||
- source badge/facet/counter;
|
||||
- Arnavi/Direct contract;
|
||||
- Device Manager Page.
|
||||
|
||||
Consumer policy v5 имеет `freshness=none`, `staleAfterMs=null` и `staleTransitions=0`. Следовательно, Foundry сам не определит падение VPS: selection и freshness должны приходить из server-owned upstream contract.
|
||||
|
||||
Отдельная визуальная деталь: сохранённый state `trike-current-positions` имеет `visible=true`, но filters `movement_state=[]` и `signal_state=[]`. По контракту Foundry пустой массив matches nothing. Это может объяснять пустую Map при наличии 107 subjects. Состояние не изменялось в ходе аудита; перед visual acceptance надо отдельно подтвердить, намеренно ли сохранён explicit empty view.
|
||||
|
||||
`trike-unit-contacts` выглядит избыточным: subject detail profile его не использует, а нужные IMEI/contacts уже присутствуют в `unit_identity`. Удалять binding до отдельного UI review не следует.
|
||||
|
||||
## 6. Live Ontology
|
||||
|
||||
Live catalog:
|
||||
|
||||
- hash: `cdf3f5310359cb36`;
|
||||
- 204 entities;
|
||||
- 183 relations;
|
||||
- 143 aliases;
|
||||
- 46 guardrails;
|
||||
- 41 blocked conflations.
|
||||
|
||||
Provider-neutral каркас уже существует:
|
||||
|
||||
```text
|
||||
integration.provider
|
||||
integration.connection
|
||||
integration.collection_profile
|
||||
integration.collection_run
|
||||
integration.raw_envelope
|
||||
integration.canonical_subject
|
||||
integration.read_model
|
||||
integration.realtime_channel
|
||||
map.moving_object
|
||||
map.state_facet
|
||||
```
|
||||
|
||||
Поэтому не нужен новый renderer, новая Map, новый сайт или отдельная пользовательская сущность `Arnavi trike`.
|
||||
|
||||
Но семантический разрыв реальный:
|
||||
|
||||
- tracker entity есть только как `gelios.tracker_device`;
|
||||
- IMEI/device identifier не определён provider-neutral;
|
||||
- Arnavi/ARUSNAVI provider package отсутствует;
|
||||
- source selection/failover semantics отсутствуют;
|
||||
- `gelios.telemetry_snapshot`, `gelios.signal_state` и `gelios.movement_state` имеют Gelios-specific authority.
|
||||
|
||||
Нельзя публиковать Direct B2 под видом Gelios mapping. Особенно нельзя молча объявить Direct `signal_state` результатом Gelios monitoring-config или добавить `fallback/stale` в закрытые `active|inactive` / `moving|stopped` enum.
|
||||
|
||||
Минимальное canonical изменение — не новая бизнес-сущность, а:
|
||||
|
||||
1. source-evidenced ARUSNAVI B2 provider/mapping package;
|
||||
2. provider-neutral policy для выбранного источника и freshness;
|
||||
3. нейтральное определение `signal_state`/`movement_state` для successor product;
|
||||
4. restricted device identifier/crosswalk semantics по открытой `DCPLATFORM-70`.
|
||||
|
||||
## 7. Текущий Direct B2 код
|
||||
|
||||
### 7.1 Что реализовано
|
||||
|
||||
ARUSNAVI adapter реализует:
|
||||
|
||||
- HEADER2 `FF 23`;
|
||||
- 8-byte little-endian IMEI;
|
||||
- 15-digit validation;
|
||||
- PACKAGE boundaries;
|
||||
- packet length и checksum;
|
||||
- HEADER acknowledgement с Unix time;
|
||||
- package-number acknowledgement;
|
||||
- bounded frame/buffer limits;
|
||||
- commands disabled.
|
||||
|
||||
Основные source anchors:
|
||||
|
||||
- `platform/device-plane/packages/arusnavi-b2-adapter/src/index.mjs:56` — HEADER2;
|
||||
- `.../index.mjs:93` — HEADER ACK;
|
||||
- `.../index.mjs:110` — PACKAGE framing/checksum;
|
||||
- `.../index.mjs:187` — PACKAGE ACK;
|
||||
- `platform/device-plane/services/device-gateway/src/runtime.mjs:144` — session state machine.
|
||||
|
||||
Device Plane test suite: 41 passed, 0 failed.
|
||||
|
||||
### 7.2 Критический ACK/data-loss gap
|
||||
|
||||
`tryParseB2Package()` возвращает только:
|
||||
|
||||
```text
|
||||
bytesConsumed
|
||||
packageNumber
|
||||
packetCount
|
||||
```
|
||||
|
||||
Он не возвращает packet data или decoded tags.
|
||||
|
||||
Gateway после успешного parse:
|
||||
|
||||
```text
|
||||
buffer = buffer after package
|
||||
ACK counter++
|
||||
send PACKAGE ACK
|
||||
```
|
||||
|
||||
Package sink, durable queue, EDP publish и decoder отсутствуют. Иными словами, текущий Gateway сообщает B2 «пакет принят», после чего payload теряется.
|
||||
|
||||
Этот режим допустим только как discovery/framing pilot. Он непригоден для Direct-primary telemetry.
|
||||
|
||||
### 7.3 Реализован только quarantine observe
|
||||
|
||||
Core имеет только:
|
||||
|
||||
```text
|
||||
POST /internal/v1/device-discoveries:observe
|
||||
```
|
||||
|
||||
Он HMAC-хэширует IMEI, создаёт/обновляет quarantine discovery и отдаёт masked projection. Claim endpoint, inventory import и device CRUD отсутствуют.
|
||||
|
||||
Дополнительный blocker: Gateway Core client принимает только response `lifecycleState=quarantine`. Если discovery будет переведён в `claimed`, текущий client отклонит response, а HEADER ACK не будет отправлен.
|
||||
|
||||
### 7.4 Нет связи device → canonical trike
|
||||
|
||||
`device_bindings` сейчас содержит только:
|
||||
|
||||
```text
|
||||
contour_id
|
||||
target_kind
|
||||
target_ref
|
||||
capabilities
|
||||
```
|
||||
|
||||
В нём нет `device_id`. В `device_instances` нет `canonical_subject_ref`.
|
||||
|
||||
Следовательно, из одного contour binding нельзя доказать, какой IMEI соответствует какому `gelios-unit-*`. Без explicit crosswalk Direct publisher либо создаст дубликаты, либо присвоит наблюдение неправильному трайку.
|
||||
|
||||
Минимальное исправление без новой доменной сущности:
|
||||
|
||||
- добавить `canonical_subject_ref` к существующему `device_instance`/claim contract;
|
||||
- сопоставлять IMEI digest с restricted Gelios identity inventory;
|
||||
- сохранять существующий `gelios-unit-*` как canonical `sourceId` на переходном этапе;
|
||||
- неизвестный/duplicate IMEI оставлять unmatched quarantine;
|
||||
- автоматический matcher может только предложить mapping; принятие принадлежности остаётся explicit admin action.
|
||||
|
||||
## 8. Почему Mini artifacts нельзя применить к VPS
|
||||
|
||||
Mini deployment жёстко фиксирует:
|
||||
|
||||
- runtime host `ndcmini12`;
|
||||
- NIC `enp1s0f0`;
|
||||
- IPvlan `192.168.71.253`;
|
||||
- LAN `192.168.68.0/22`;
|
||||
- gateway `192.168.68.1`;
|
||||
- Mini host `192.168.68.54/22`;
|
||||
- Amnezia routes;
|
||||
- userspace Tailnet SOCKS container;
|
||||
- Deco/double-NAT topology.
|
||||
|
||||
Эти assumptions находятся в:
|
||||
|
||||
- `docker-compose.device-edge.ingress.yml`;
|
||||
- `deployment/device-edge-ingress-ipvlan-v1.json`;
|
||||
- `deployment/device-edge-admission-gate-v1.json`;
|
||||
- `deployment/device-edge-backhaul-v1.json`;
|
||||
- `infra/deploy-runner/build-device-edge-ingress-artifact.mjs`;
|
||||
- `infra/deploy-runner/nodedc-edge-deploy`;
|
||||
- `docs/DEVICE_EDGE_B2_PUBLIC_PILOT_RUNBOOK.md`.
|
||||
|
||||
Переиспользовать можно relay implementation и fail-closed limits. Переиспользовать Mini Compose overlay, descriptor, builder или runner нельзя.
|
||||
|
||||
VPS требует отдельный versioned placement/transition того же логического component `device-edge`, с собственными:
|
||||
|
||||
- exact host identity и predecessor;
|
||||
- public interface/bind;
|
||||
- firewall contract;
|
||||
- Tailscale/WireGuard route;
|
||||
- SSH backhaul trust;
|
||||
- resource limits;
|
||||
- acceptance;
|
||||
- backup/journal/rollback.
|
||||
|
||||
Это additive extension `DCPLATFORM-21`, а не ручной `docker compose up` и не ослабление канона.
|
||||
|
||||
## 9. Целевая архитектура
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
B2["ARUSNAVI B2\nдва server slot"]
|
||||
|
||||
B2 -->|"INTERNAL raw TCP"| GELIOS["Gelios legacy read route"]
|
||||
B2 -->|"INTERNAL raw TCP :9921"| VPS["VPS Device Edge\nopaque relay only"]
|
||||
|
||||
VPS -->|"encrypted private backhaul"| GW["Synology Device Gateway\nframing + decoder + ACK owner"]
|
||||
GW --> CORE["Device Control Core\nclaim + canonical subject crosswalk"]
|
||||
GW -->|"after decode and durable acceptance"| DIRECT["Direct candidate Data Product\nworking contract"]
|
||||
|
||||
GELIOS --> GL2["Existing Gelios L2 alxvw4fn"]
|
||||
GL2 --> V5["fleet.positions.current.v5\nGelios candidate + legacy history"]
|
||||
|
||||
DIRECT --> ARB["Server-owned per-device arbiter"]
|
||||
V5 --> ARB
|
||||
ARB --> V6["fleet.positions.current.v6\none canonical writer"]
|
||||
V6 --> MAP["Existing Foundry Map binding\nsame page/profile/sourceId"]
|
||||
|
||||
CORE -. "future, separate red domain" .-> CMD["Commands disabled"]
|
||||
```
|
||||
|
||||
### 9.1 Что остаётся существующим
|
||||
|
||||
- Device Plane PostgreSQL/Core/Gateway на Synology;
|
||||
- EDP/Timescale;
|
||||
- Robot2B L1;
|
||||
- Gelios collector `alxvw4fn`;
|
||||
- `fleet.positions.current.v5` как legacy candidate и immutable pre-cutover history;
|
||||
- Foundry application, page, presentation profile и detail profile;
|
||||
- `sourceId = gelios-unit-*` как переходная canonical identity;
|
||||
- profile/identity products и joins;
|
||||
- Gelios route на B2.
|
||||
|
||||
### 9.2 Какие технические контракты неизбежно добавляются
|
||||
|
||||
«Без новых сущностей» достижимо на уровне продукта/UI: не нужны новый сайт, новый backend, новая DB, новая Map или второй трайк.
|
||||
|
||||
Но полностью без новых технических контрактов нельзя:
|
||||
|
||||
- нужен Direct candidate product или другой durable server-owned candidate store;
|
||||
- нужен ARUSNAVI provider/mapping package;
|
||||
- нужен один canonical arbiter writer scope;
|
||||
- нужен successor `fleet.positions.current.v6`, потому что v5 не разрешает поля selection/fallback provenance и имеет старую семантическую authority;
|
||||
- нужен exact device → canonical subject field/claim contract.
|
||||
|
||||
Это versioning и integration configuration, а не новые пользовательские domain objects.
|
||||
|
||||
### 9.3 Где должен жить arbiter
|
||||
|
||||
Не на VPS и не в Foundry.
|
||||
|
||||
Engine подходит для чтения candidate products, нормализации и orchestration. EDP должен оставаться durable state и единственным publish authority. Практический вариант:
|
||||
|
||||
1. Gelios продолжает писать v5.
|
||||
2. Gateway/Direct adapter пишет отдельный candidate product после durable acceptance.
|
||||
3. Arbiter L2 читает оба products отдельными managed reader grants.
|
||||
4. Arbiter читает предыдущий canonical state для hysteresis или хранит selection state в каноническом fact.
|
||||
5. Только arbiter имеет writer grant на v6.
|
||||
|
||||
Существующий `g7q86421` можно рассмотреть как runtime slot для arbiter только после neutral rematerialization. Сейчас он Gelios-scoped; публиковать Direct под его Gelios connection нельзя. Если Engine не позволяет безопасно сменить connection authority, нужен новый служебный L2 target внутри существующего Robot2B L1. Это не новая бизнес-сущность.
|
||||
|
||||
### 9.4 Правило выбора источника
|
||||
|
||||
Выбор выполняется по каждому `canonical_subject_ref`.
|
||||
|
||||
Direct eligible, только если одновременно выполнены:
|
||||
|
||||
- device claimed и crosswalk однозначен;
|
||||
- HEADER/PACKAGE framing и checksum валидны;
|
||||
- telemetry packet decoded по versioned ARUSNAVI contract;
|
||||
- observed time валиден и не уходит недопустимо в будущее;
|
||||
- observation монотонно либо явно допустимо out-of-order;
|
||||
- EDP candidate receipt durable;
|
||||
- direct observation свежее per-device threshold;
|
||||
- Gateway/backhaul не сообщает terminal fault.
|
||||
|
||||
Fallback:
|
||||
|
||||
```text
|
||||
direct stale or invalid
|
||||
→ grace period
|
||||
→ select latest valid Gelios observation
|
||||
→ selection_mode=fallback
|
||||
```
|
||||
|
||||
Возврат:
|
||||
|
||||
```text
|
||||
direct returns
|
||||
→ shadow only
|
||||
→ N consecutive valid observations / accepted time window
|
||||
→ identity and timestamp checks pass
|
||||
→ selection_mode=primary
|
||||
```
|
||||
|
||||
Точные grace/N/timeout не следует угадывать. Их надо вывести из pilot packet cadence и shadow statistics.
|
||||
|
||||
Первый arbiter должен выбирать цельный position+telemetry observation атомарно. Не следует молча смешивать координаты Direct с sensor readings Gelios в одном fact. Cold profile и restricted identity могут временно продолжать приходить из Gelios отдельными joined aspects.
|
||||
|
||||
### 9.5 Provenance successor product
|
||||
|
||||
Рабочий набор полей v6:
|
||||
|
||||
```text
|
||||
position_source
|
||||
telemetry_source
|
||||
source_selection_mode
|
||||
source_selection_reason
|
||||
selected_observed_at
|
||||
direct_last_observed_at
|
||||
gelios_last_observed_at
|
||||
selection_changed_at
|
||||
```
|
||||
|
||||
Точные field IDs и enum должны сначала пройти Ontology/Data Product authority. `fallback` нельзя перегружать в `signal_state`.
|
||||
|
||||
Для визуальной совместимости можно сохранить enum:
|
||||
|
||||
```text
|
||||
signal_state = active | inactive
|
||||
movement_state = moving | stopped
|
||||
```
|
||||
|
||||
Но v6 должен определить их как neutral Robot2B policy. Direct `signal_state` выводится из свежести direct observation, а не из Gelios monitoring-config. Movement threshold, если сохраняется `speed > 2`, должен быть явно принят как platform policy, а не назван «нативным B2 статусом».
|
||||
|
||||
### 9.6 История
|
||||
|
||||
Timescale/Postgres остаётся тем же сервисом и volume, но история EDP scoped по:
|
||||
|
||||
```text
|
||||
tenant + connection + provider + dataProductId + sourceId
|
||||
```
|
||||
|
||||
Поэтому смена writer connection/product не создаёт автоматическую бесшовную history query через старые v5 rows.
|
||||
|
||||
Правильный cutover:
|
||||
|
||||
- v5 history остаётся immutable legacy history до даты переключения;
|
||||
- v6 пишет новую canonical history с тем же `sourceId`;
|
||||
- `NDC Robot2B History and Reports` получает version-aware read: v5 до cutover, v6 после cutover;
|
||||
- никакой второй Timescale DB и никакая destructive migration не создаются.
|
||||
|
||||
## 10. Durable ACK contract
|
||||
|
||||
Production Direct path должен иметь такой порядок:
|
||||
|
||||
```text
|
||||
HEADER2
|
||||
→ resolve/create discovery
|
||||
→ resolve claimed device and canonical subject when available
|
||||
→ HEADER ACK
|
||||
|
||||
PACKAGE
|
||||
→ validate length/checksum
|
||||
→ decode packet tags
|
||||
→ normalize candidate observation
|
||||
→ idempotent durable EDP acceptance
|
||||
→ PACKAGE ACK
|
||||
```
|
||||
|
||||
Если durable acceptance недоступен, PACKAGE ACK не отправляется, и B2 получает возможность повторить пакет.
|
||||
|
||||
Idempotency нельзя строить только по package number: диапазон ограничен и номер переиспользуется. Candidate key должен включать device identity, session/package context и digest подтверждённых bytes/packet timestamp. Raw payload не обязан сохраняться; можно сохранять digest и normalized facts.
|
||||
|
||||
Для quarantine pilot допустим отдельный режим framing proof: validated PACKAGE ACK после безопасного quarantine evidence без заявления, что телеметрия сохранена. Этот режим должен быть явно отличим от production telemetry acceptance.
|
||||
|
||||
Gateway, а не VPS, остаётся ACK owner.
|
||||
|
||||
## 11. VPS contract
|
||||
|
||||
### 11.1 Runtime role
|
||||
|
||||
VPS:
|
||||
|
||||
- слушает public `TCP/9921`;
|
||||
- применяет bounded session/rate/buffer policy;
|
||||
- непрозрачно передаёт stream на private backhaul;
|
||||
- держит минимальный loopback/internal health;
|
||||
- не читает IMEI и telemetry;
|
||||
- не ACK’ает B2;
|
||||
- не хранит raw packet или DB;
|
||||
- не имеет Core/EDP/Engine/Foundry credentials;
|
||||
- не выполняет commands.
|
||||
|
||||
### 11.2 Предлагаемый минимальный placement
|
||||
|
||||
На слабом VPS достаточно:
|
||||
|
||||
- host Tailscale или WireGuard;
|
||||
- `device-edge-relay` container;
|
||||
- `device-edge-backhaul` container с key-only SSH local forward в Synology target;
|
||||
- Docker Compose plugin;
|
||||
- system firewall default-deny;
|
||||
- time sync;
|
||||
- bounded Docker logs;
|
||||
- root-owned deploy runner и state outside artifact.
|
||||
|
||||
Live baseline подтверждает, что host нельзя использовать для production image builds или тяжёлого runtime. Начальные resource limits для reviewed candidate:
|
||||
|
||||
- relay memory limit около 192 MiB;
|
||||
- backhaul около 64 MiB;
|
||||
- PID limits;
|
||||
- logs `10 MiB × 3`;
|
||||
- image build не выполнять на production host, если 1 GiB RAM не выдерживает; использовать reviewed prebuilt/digest-pinned image или swap policy, утверждённую отдельно.
|
||||
|
||||
До application artifact нужен отдельный bootstrap transition, который fail-closed переводит host из текущего baseline:
|
||||
|
||||
- firewall INPUT `ACCEPT` → default-deny с сохранением проверенного SSH access;
|
||||
- root/password/X11/unrestricted forwarding → отдельный key-only management boundary;
|
||||
- установить только утверждённый container/runtime и encrypted backhaul prerequisites;
|
||||
- создать versioned service identities, fixed roots, bounded logs/resources и rollback;
|
||||
- не открывать `9921` в bootstrap transition.
|
||||
|
||||
Текущие Mini defaults `10 s session timeout` и `256 KiB per direction` являются pilot constraints. Их нельзя автоматически переносить в production B2: packet cadence и session lifetime должны быть измерены на одном реальном B2.
|
||||
|
||||
### 11.3 Public surface
|
||||
|
||||
Наружу:
|
||||
|
||||
- `9921/TCP` для B2;
|
||||
- management SSH только key-only и максимально ограниченно, предпочтительно через Tailnet/allowlist.
|
||||
|
||||
Не публикуются:
|
||||
|
||||
- health endpoint;
|
||||
- Docker API;
|
||||
- UDP range;
|
||||
- Core/Gateway/EDP ports;
|
||||
- database;
|
||||
- DSM;
|
||||
- reverse proxy `443 → 9921`.
|
||||
|
||||
### 11.4 DNS
|
||||
|
||||
DNS меняется только после:
|
||||
|
||||
1. exact VPS identity;
|
||||
2. canonical deploy-ok;
|
||||
3. public listener/firewall acceptance;
|
||||
4. encrypted backhaul acceptance;
|
||||
5. external synthetic TCP proof;
|
||||
6. rollback proof.
|
||||
|
||||
Только затем `device.nodedc.ru` переводится с `95.165.91.235` на VPS. Изменение B2 server slot выполняется после DNS convergence и только для одного pilot; Gelios slot не трогается.
|
||||
|
||||
## 12. Фазовый план
|
||||
|
||||
### Phase 0 — authority и точный VPS target
|
||||
|
||||
- точный target `root@155.212.211.15` получен;
|
||||
- pinned ED25519 fingerprint проверен;
|
||||
- live read-only inventory завершён;
|
||||
- VPS placement зафиксирован в `DCPLATFORM-21` и `DCPLATFORM-74`;
|
||||
- не менять DNS, B2 и Synology.
|
||||
|
||||
### Phase 1 — canonical VPS bootstrap
|
||||
|
||||
- отдельный VPS `device-edge` descriptor/runner;
|
||||
- install Docker/Compose, runtime user, firewall, time sync, log policy, private network client;
|
||||
- deploy relay/backhaul exact artifact;
|
||||
- prove resource limits, ports, health, backup/journal и automatic rollback;
|
||||
- terminal state только `deploy-ok`.
|
||||
|
||||
### Phase 2 — transport acceptance без трекера
|
||||
|
||||
- synthetic TCP from external host → VPS `9921` → encrypted backhaul → loopback Gateway;
|
||||
- prove VPS does not parse/ACK;
|
||||
- prove Synology remains non-public;
|
||||
- prove closing/rollback removes only VPS `9921` and preserves Gelios;
|
||||
- no DNS/B2 change yet.
|
||||
|
||||
### Phase 3 — один B2, quarantine shadow
|
||||
|
||||
- вручную добавить свободный B2 INTERNAL slot на accepted VPS address/hostname;
|
||||
- Gelios slot сохранить;
|
||||
- доказать HEADER2 → masked quarantine;
|
||||
- доказать PACKAGE framing/checksum/ACK;
|
||||
- commands remain disabled;
|
||||
- не объявлять telemetry production, пока PACKAGE всё ещё discard.
|
||||
|
||||
### Phase 4 — decoder, claim и durable candidate
|
||||
|
||||
- принять точную официальную tag/framing specification для firmware pilot B2;
|
||||
- parser возвращает packet data/typed records;
|
||||
- реализовать explicit claim/crosswalk в существующем Device Instance contract;
|
||||
- исправить Gateway/Core lifecycle для claimed devices;
|
||||
- добавить Direct candidate product и managed writer;
|
||||
- ACK только после durable candidate receipt;
|
||||
- unknown tags fail closed или попадают в bounded classification evidence, но не в unrestricted Data Product.
|
||||
|
||||
### Phase 5 — shadow comparison
|
||||
|
||||
Для одного pilot сравнивать Direct и Gelios без canonical switch:
|
||||
|
||||
- identity match;
|
||||
- observation timestamps и clock skew;
|
||||
- coordinate distance;
|
||||
- speed/course/elevation;
|
||||
- satellites/HDOP/accuracy;
|
||||
- mileage/engine hours;
|
||||
- все 17 текущих telemetry reading IDs;
|
||||
- missing/extra tags;
|
||||
- duplicate/retry/out-of-order behavior;
|
||||
- reconnect cadence и session lifetime.
|
||||
|
||||
До этой фазы утверждение «Gelios отдаёт ровно те же B2 пакеты» не считается доказанным. Сейчас доказано лишь, что Gelios предоставляет данные, относящиеся к B2 units, и текущий normalized surface совпадает с ожидаемым B2 профилем. Exact packet equivalence требует Direct shadow.
|
||||
|
||||
### Phase 6 — arbiter и v6
|
||||
|
||||
- neutral Ontology/source-selection contract;
|
||||
- per-device eligibility, grace и hysteresis;
|
||||
- один arbiter writer;
|
||||
- successor v6;
|
||||
- history cutover policy;
|
||||
- no field-level silent mixing;
|
||||
- simulate Direct failure and recovery;
|
||||
- Gelios continues collecting at all times.
|
||||
|
||||
### Phase 7 — Foundry cutover
|
||||
|
||||
- существующий `trike-current-positions` binding переводится на v6;
|
||||
- page, binding ID, presentation/detail profiles и sourceId сохраняются;
|
||||
- Data tab получает selection provenance;
|
||||
- optional source badge/facet добавляется только по versioned field contract;
|
||||
- решить explicit empty filters;
|
||||
- проверить все joins profile/identity и 107 subjects;
|
||||
- rollback возвращает binding на v5.
|
||||
|
||||
### Phase 8 — эксплуатационная приёмка
|
||||
|
||||
- sustained Direct-primary window;
|
||||
- controlled VPS/backhaul/Gateway failure → per-device Gelios fallback;
|
||||
- controlled recovery → shadow → Direct promotion;
|
||||
- no duplicate current facts/history;
|
||||
- no timestamp regression;
|
||||
- bounded logs/resources;
|
||||
- alerts/metrics без full IMEI;
|
||||
- documented rollback.
|
||||
|
||||
### Phase 9 — команды, отдельная работа
|
||||
|
||||
Только после принятого Direct read path:
|
||||
|
||||
- official command contract;
|
||||
- draft/plan/confirm/queue/dispatch/ACK/reconciliation;
|
||||
- immutable audit и idempotency;
|
||||
- no automatic Gelios command fallback;
|
||||
- no blind retry after unknown outcome.
|
||||
|
||||
## 13. Ops reconciliation
|
||||
|
||||
Фактическое значение карточек после аудита:
|
||||
|
||||
- `DCPLATFORM-74` — master architecture; Mini placement устарел, VPS должен стать новым accepted Device Edge placement;
|
||||
- `ROBOT2B-5` — старый pause superseded: Direct track возобновлён, SSH access подтверждён, но работа пока blocked на canonical VPS bootstrap/backhaul и Direct data path gaps;
|
||||
- `ROBOT2B-6` — current production Gelios baseline; теперь это сохраняемый legacy candidate/fallback, а не конечная архитектура;
|
||||
- `DCPLATFORM-21` — authority для отдельного VPS runner/artifact/rollback;
|
||||
- `DCPLATFORM-70` — provider-neutral device/identifier/crosswalk debt;
|
||||
- `DCPLATFORM-72` и `DCPLATFORM-73` — обязательные Engine change acceptance risks.
|
||||
|
||||
Актуализация записана в Ops 6 августа 2026 года:
|
||||
|
||||
- `DCPLATFORM-74`: comment `fcdb49c3-8cfc-4c94-8e67-137de14ec9f2`;
|
||||
- `ROBOT2B-5`: comment `eb6284b5-4d0e-4064-8eda-659485b94115`, карточка переведена из Backlog в In Progress;
|
||||
- `ROBOT2B-6`: comment `5fecce6e-fec0-4119-814e-0e2bb7a6a41e`;
|
||||
- `DCPLATFORM-21`: comment `c9bbe692-d301-4127-8734-9c1e91bb5083`.
|
||||
|
||||
После live SSH-аудита VPS добавлены уточнения:
|
||||
|
||||
- `DCPLATFORM-74`: comment `fa26ac68-17c2-4d56-8b6b-f483b1275739`;
|
||||
- `DCPLATFORM-21`: comment `8db0d7e2-1bb4-4390-93e3-aebb68429a1b`;
|
||||
- `ROBOT2B-5`: comment `750aeab7-b2eb-4ea1-bb4a-e6e098c5a02c`.
|
||||
|
||||
Ops не должен утверждать, что:
|
||||
|
||||
- Direct telemetry уже принимается;
|
||||
- VPS relay/bootstrap уже развёрнут и принят;
|
||||
- `device.nodedc.ru` уже указывает на VPS;
|
||||
- Device Manager Page существует;
|
||||
- IMEI полностью отсутствует в Foundry;
|
||||
- текущий Gateway выполняет durable PACKAGE acceptance;
|
||||
- Gelios и Direct уже переключаются автоматически.
|
||||
|
||||
## 14. Текущие блокеры
|
||||
|
||||
1. VPS firewall/SSH baseline не hardened и не принят canonical runner-ом.
|
||||
2. Не выбран и не enrolled exact encrypted VPS → private NODE.DC backhaul identity/target.
|
||||
3. Current DNS указывает на Synology.
|
||||
4. Mini deploy artifacts не переносимы на VPS.
|
||||
5. Gateway ACK’ает и отбрасывает PACKAGE.
|
||||
6. Нет telemetry tag decoder.
|
||||
7. Нет claim endpoint и claimed-device handshake.
|
||||
8. Нет device → canonical trike crosswalk.
|
||||
9. Нет Direct candidate product/writer.
|
||||
10. Нет neutral source-selection ontology/product contract.
|
||||
11. Нет arbiter и successor product/history cutover.
|
||||
12. Foundry source provenance минимальна, а saved filters могут скрывать все subjects.
|
||||
13. Команды намеренно выключены.
|
||||
|
||||
## 15. Следующее разрешённое действие
|
||||
|
||||
Следующий шаг — зафиксировать exact encrypted backhaul choice/identity и подготовить additive VPS deployment domain: root-owned runner, deterministic bootstrap artifact, exact predecessor, firewall/SSH acceptance и rollback. Первый bootstrap не открывает `9921`. Никакой B2, DNS или production Map mutation до отдельного transport acceptance не требуется.
|
||||
|
||||
## 16. Implementation update — 2026-08-06
|
||||
|
||||
Этот раздел заменяет устаревшие operational assertions в разделах 3.4, 11.2,
|
||||
12 Phase 0–1, 13–15. Архитектурные выводы остальных разделов сохраняются.
|
||||
|
||||
Реализовано:
|
||||
|
||||
- созданы отдельные Ops cards `DCPLATFORM-75` и `ROBOT2B-7`;
|
||||
- создан отдельный root-owned VPS deploy domain
|
||||
`/usr/local/sbin/nodedc-b2-vps-deploy`;
|
||||
- Docker на 961 MiB VPS сознательно не устанавливался;
|
||||
- foundation `device-edge-vps-foundation-20260806-003` принят с `deploy-ok`;
|
||||
- SSH переведён в key-only, nftables — в default-deny, публично только TCP/22;
|
||||
- Node.js 22.23.2 и Tailscale 1.102.2 установлены из pinned static archives;
|
||||
- Tailscale runtime user `nodedc-edge` и отдельный ED25519 backhaul key созданы
|
||||
runner-ом; isolation-aware backhaul/relay transitions используют отдельные
|
||||
`nodedc-backhaul` и `nodedc-relay`;
|
||||
- default wildcard tailnet grant удалён; VPS переведён из user ownership в
|
||||
`tag:device-edge-vps` с единственным egress `100.109.216.21:2222/tcp`;
|
||||
- отрицательные проверки с VPS подтвердили запрет Synology SSH/DSM, MacBook и
|
||||
второго edge-узла;
|
||||
- TCP/9921 остаётся закрыт;
|
||||
- backhaul и relay artifacts собраны и стадированы, но не применены до
|
||||
predecessor acceptance;
|
||||
- public VPS key стадирован в Synology enrollment;
|
||||
- marker-only Synology key-rotation artifact и runner candidate стадированы.
|
||||
|
||||
Текущие blockers/gates:
|
||||
|
||||
1. Root на Synology должен promote/verify exact runner, review plan и выполнить
|
||||
exact enrollment apply.
|
||||
2. После этого VPS runner может принять backhaul, затем relay.
|
||||
3. DNS и B2 routes остаются неизменными до отдельного transport pilot.
|
||||
4. Provider recovery key `beget-access-key` — RSA-1024; его отзыв требует
|
||||
отдельного owner decision.
|
||||
|
||||
Полный повторяемый manual и release evidence находятся в
|
||||
`device-plane/docs/ROBOT2B_B2_VPS_CONFIGURATION_BIBLE_2026-08-06.md`.
|
||||
@@ -0,0 +1,287 @@
|
||||
# Device Plane Implementation Baseline
|
||||
|
||||
> Superseded topology notice — 2026-08-10
|
||||
>
|
||||
> The historical Foundry-Page product boundary, Mini ingress, VPS-initiated
|
||||
> Tailscale/SSH backhaul and `device.nodedc.ru` raw-TCP assumptions below are
|
||||
> retained only as implementation history. They must not be used for a new
|
||||
> plan/apply. The accepted successor is
|
||||
> `docs/ADR_0001_CORE_INITIATED_EDGE_CHANNEL.md`: Device Core is a standalone
|
||||
> Hub application, Synology/Core initiates a mutually authenticated full-duplex
|
||||
> channel to the VPS, and `device.nodedc.ru` remains the HTTPS UI surface.
|
||||
|
||||
Status: PostgreSQL, Control Core and Gateway foundation are running healthy on
|
||||
Synology. The accepted foundation has public ingress and discovery ingest
|
||||
disabled. The next additive transition enables only an authenticated,
|
||||
quarantine-only ARUSNAVI B2 discovery path on raw TCP 9921. Command transport
|
||||
remains disabled.
|
||||
|
||||
## Product boundary
|
||||
|
||||
The Device Manager user interface is a canonical Foundry Page Library
|
||||
template. Foundry owns page instances, layout, presentation and an opaque
|
||||
`device-plane-control` binding. It does not own device records, credentials,
|
||||
raw protocol or command delivery.
|
||||
|
||||
The independent NDC Device Plane owns physical-device state and direct
|
||||
connections:
|
||||
|
||||
```text
|
||||
Foundry Device Manager Page
|
||||
|
|
||||
| device-plane-control (typed server boundary)
|
||||
v
|
||||
Device Control Core <-> Device PostgreSQL
|
||||
|
|
||||
v
|
||||
Device Gateway <-> physical devices
|
||||
```
|
||||
|
||||
The isolated ingress placement replaces the direct physical-device arrow when
|
||||
the raw route must not terminate on the multi-service Synology:
|
||||
|
||||
```text
|
||||
ARUSNAVI B2 device
|
||||
|
|
||||
| raw TCP 9921 (future, separately approved)
|
||||
v
|
||||
Device Edge Relay on dedicated mini
|
||||
|
|
||||
| outbound restricted SSH local-forward; opaque byte stream only
|
||||
v
|
||||
Synology loopback 127.0.0.1:9921 -> Device Gateway -> Device Control Core
|
||||
```
|
||||
|
||||
The Edge Relay owns neither protocol acknowledgement nor device identity. It
|
||||
does not receive the Gateway/Core token, PostgreSQL credentials, Foundry
|
||||
bindings or any command capability. The Synology Gateway remains the sole B2
|
||||
codec and acknowledgement owner.
|
||||
|
||||
Engine L2 may consume safe decoded observations and build workflows/Data
|
||||
Products. It does not own TCP sessions, secrets or the command transport.
|
||||
|
||||
## Preserved production path
|
||||
|
||||
The existing Gelios -> Engine L2 -> External Data Plane -> Foundry Map path is
|
||||
outside this implementation slice. Its credentials, workflows, Data Products,
|
||||
bindings and map presentation must not be changed or restarted by a Device
|
||||
Plane artifact.
|
||||
|
||||
The first B2 pilot adds an NDC server route in parallel and keeps the existing
|
||||
Gelios route unchanged.
|
||||
|
||||
## Source and runtime placement
|
||||
|
||||
Source:
|
||||
|
||||
```text
|
||||
platform/device-plane/
|
||||
packages/device-protocol-contract/
|
||||
packages/arusnavi-b2-adapter/
|
||||
services/device-control-core/
|
||||
services/device-gateway/
|
||||
services/device-edge-relay/
|
||||
docker-compose.device-plane.yml
|
||||
docker-compose.device-edge.yml
|
||||
```
|
||||
|
||||
Planned Synology runtime:
|
||||
|
||||
```text
|
||||
/volume1/docker/nodedc-device-plane
|
||||
```
|
||||
|
||||
Planned Compose project and services:
|
||||
|
||||
```text
|
||||
nodedc-device-plane
|
||||
device-control-core
|
||||
device-gateway
|
||||
device-postgres
|
||||
```
|
||||
|
||||
`device-postgres` is a private persistent prerequisite. Application overlays
|
||||
must never force-recreate it or its volume.
|
||||
|
||||
The canonical runner selects only `device-control-core` and `device-gateway`
|
||||
with `--no-deps`. Its health acceptance is scoped to the selected services and
|
||||
requires the fail-closed fields to remain disabled. A failed first activation
|
||||
removes only candidate stateless services and never requests volume removal.
|
||||
Rollback now records an explicit pre-apply service inventory in the backup;
|
||||
the existence of the shared Compose file does not imply that Core or Gateway
|
||||
existed before apply.
|
||||
|
||||
The exact foundation recovery validates the failed archive, journal, backup,
|
||||
partial live source and observed healthy image/container generations. It then
|
||||
publishes the matching source and performs read-only runtime acceptance. It
|
||||
does not build, restart, recreate or remove any service.
|
||||
|
||||
## Network boundary
|
||||
|
||||
The accepted Synology foundation publishes no device port. Device Gateway's
|
||||
raw B2 listener is reachable only through `127.0.0.1:9921`; its health
|
||||
endpoints are loopback-only. The only planned external raw-TCP termination is
|
||||
the dedicated Mini Edge Relay described below.
|
||||
|
||||
`device.nodedc.ru` is a DNS name, not an HTTP/TCP mode. The same name may later
|
||||
serve an HTTPS Control API on 443 and the B2 raw TCP protocol on 9921.
|
||||
|
||||
DSM HTTP/HTTPS Reverse Proxy is not a raw TCP ingress and must not be configured
|
||||
as `443 -> 9921`.
|
||||
|
||||
The artifact never changes DSM firewall, DSM Router Configuration, DNS or a
|
||||
physical router.
|
||||
|
||||
### Dedicated mini Device Edge
|
||||
|
||||
The Debian mini is the isolated raw-TCP edge. Its accepted predecessor keeps the
|
||||
relay disabled and publishes health only on `127.0.0.1:18221`. The reviewed
|
||||
target removes even that host publication: health remains container-internal,
|
||||
the relay stays on the `internal: true` private bridge for backhaul, and a
|
||||
second IPvlan L2 attachment gives only the relay a LAN-routable address for
|
||||
`9921/TCP`. The relay has bounded global/per-address sessions and connection
|
||||
rate, a bounded source-rate table and a per-direction byte budget. It emits no
|
||||
bytes of its own and does not inspect device payloads.
|
||||
|
||||
The admission-gate transition is deliberately fail-closed at the relay: an
|
||||
ingress instance accepts only a syntactically public IPv4 source, limits its
|
||||
in-memory source table to 2,048 addresses and closes either direction after
|
||||
262,144 bytes. Private, loopback, link-local, carrier-grade NAT, multicast,
|
||||
reserved and documentation addresses are rejected before an upstream connection
|
||||
is made. This is a connection-admission and resource-boundary control, not a
|
||||
claim that Docker IPvlan traffic is filtered by a host firewall. A raw B2
|
||||
protocol has no TLS client identity and cellular devices do not offer a stable
|
||||
source-IP allowlist, so a router/NAT mapping remains prohibited until its
|
||||
separate exposure and abuse controls are reviewed.
|
||||
|
||||
IPvlan deliberately reuses the Mini's one physical parent `enp1s0f0`; a second
|
||||
Ethernet adapter is not required. The host keeps `192.168.68.54/22` and the
|
||||
Amnezia `0.0.0.0/1` plus `128.0.0.0/1` routes. The relay has its own fixed LAN
|
||||
IPv4 and default route through `192.168.68.1`, while its private connected route
|
||||
continues to reach `device-edge-backhaul:19921`. No Docker host `ports:` entry,
|
||||
host-network mode, privileged container or VPN teardown is allowed.
|
||||
|
||||
Enabling public ingress is a separate reviewed operation and requires all of
|
||||
the following evidence:
|
||||
|
||||
1. A distinct, no-shell Synology SSH account and key whose sole permitted open
|
||||
target is `127.0.0.1:9921`; host-key pinning and a persistent, monitored
|
||||
tunnel are required.
|
||||
2. A private backhaul sidecar/network; the raw listener may forward only to
|
||||
that tunnel. The Core token and all Core/Database secrets remain on
|
||||
Synology.
|
||||
3. Router evidence proving the fixed relay IPv4 is outside DHCP. The artifact
|
||||
cannot choose an address and never changes router, firewall or DHCP state.
|
||||
A manual router/NAT rule is a later independent approval, after the relay's
|
||||
admission gate and external-exposure runbook have been accepted.
|
||||
4. The host full-tunnel VPN remains active. Before production activation, the
|
||||
exact single-NIC IPvlan design must pass duplicate-address detection,
|
||||
gateway reachability, external return-path and private-backhaul checks.
|
||||
5. One pre-authorized B2 pilot route, quarantine-only Gateway/Core ingest and
|
||||
disabled command transport.
|
||||
|
||||
## Identity and onboarding
|
||||
|
||||
An IMEI is a claimed protocol identifier, not proof of tenant ownership.
|
||||
|
||||
- An unknown connection produces a quarantine-only discovery.
|
||||
- A discovery never receives commands.
|
||||
- Pilot claim requires an explicit platform-admin action.
|
||||
- Production assignment requires authoritative pre-enrollment or an audited
|
||||
inventory import.
|
||||
- First-claim-wins by IMEI is forbidden.
|
||||
|
||||
The ARUSNAVI Web account login/password is used only by the human operator to
|
||||
configure the additional device route. It is not a Device Plane credential.
|
||||
|
||||
## Protocol evidence
|
||||
|
||||
The official B2 material proves:
|
||||
|
||||
- four simultaneous monitoring server routes;
|
||||
- `INTERNAL`, `EXTERNAL`, `USER_AG` and EGTS variants;
|
||||
- INTERNAL server-side identification by modem IMEI;
|
||||
- server route fields for DNS/IP, TCP port, protocol and optional ID;
|
||||
- SMS/TCP command families and a six-digit device access password.
|
||||
|
||||
The official ARUSNAVI INTERNAL protocol sheet now provides the first read-path
|
||||
framing contract:
|
||||
|
||||
- HEADER2 for GPRS is `FF 23` followed by an eight-byte little-endian IMEI;
|
||||
- the server confirms HEADER2 with a bounded `SERVER_COM` carrying Unix time;
|
||||
- a PACKAGE begins with `5B`, carries a package number in `01..FB`, contains
|
||||
one or more length-framed PACKET records and ends with `5D`;
|
||||
- every PACKET checksum is verified before acknowledgement;
|
||||
- every valid PACKAGE is acknowledged by package number;
|
||||
- without acknowledgement the tracker repeats the transmission.
|
||||
|
||||
The pilot codec implements only that verified read/acknowledgement subset. It
|
||||
does not decode telemetry tags, export command builders or accept arbitrary
|
||||
server commands. An IMEI parsed from a valid HEADER2 remains a claimed
|
||||
identifier and never proves tenant ownership.
|
||||
|
||||
## Command boundary
|
||||
|
||||
Outbound command transport is disabled in this baseline. No command builder is
|
||||
exported.
|
||||
|
||||
Later lifecycle:
|
||||
|
||||
```text
|
||||
draft -> planned -> awaiting_confirmation -> queued -> dispatched
|
||||
-> acknowledged | failed | expired | unknown
|
||||
```
|
||||
|
||||
`send` is not success. An `unknown` result forbids automatic retry.
|
||||
|
||||
Erase, factory reset, firmware/custom firmware, physical outputs and arbitrary
|
||||
raw TCP remain forbidden until separate reviewed acceptance slices.
|
||||
|
||||
## Implemented local foundation
|
||||
|
||||
- Provider-neutral discovery, contour and opaque Foundry-binding contracts.
|
||||
- B2 model profile with four parallel routes and INTERNAL/IMEI evidence.
|
||||
- PostgreSQL migration for model profiles, contours, quarantine discoveries,
|
||||
claimed devices, Foundry bindings and append-only audit events.
|
||||
- Core health endpoint and an authenticated quarantine-ingest boundary that is
|
||||
disabled unless explicitly enabled with file-backed secrets.
|
||||
- Gateway discovery-only HEADER2/PACKAGE state machine with bounded buffers,
|
||||
handshake timeout, concurrent/per-source session limits and per-source
|
||||
connection rate limits.
|
||||
- Authenticated Gateway-to-Core discovery ingest. Core HMAC-hashes the full IMEI
|
||||
and persists only its digest, masked view and verified framing evidence.
|
||||
- Only HEADER2 and valid PACKAGE acknowledgements are emitted; no command
|
||||
builder or command transport is present.
|
||||
- Recursive rejection of secret-like fields, raw payloads and command-shaped
|
||||
input in presentation contracts.
|
||||
- Automated contract, adapter, migration, Core and Gateway tests.
|
||||
- Additive `component=device-plane` runner registry with exact roots, builds,
|
||||
services, allowlist/denylist, runner-owned secrets, health contracts and
|
||||
automatic source/runtime rollback.
|
||||
- Deterministic data-only artifact builder and positive/negative regression
|
||||
tests.
|
||||
- Compose foundation with a private internal network, preserved PostgreSQL
|
||||
volume, file-backed database password and loopback-only health publishing.
|
||||
- Exact one-time PostgreSQL bootstrap descriptor, deterministic builder and
|
||||
absence preflight: an existing database container or volume fails closed,
|
||||
and rollback never removes the volume.
|
||||
|
||||
## Next activation slice
|
||||
|
||||
1. The Deco DHCP range has been recorded as `192.168.68.50` through
|
||||
`192.168.71.250`; the fixed Relay IPv4 is `192.168.71.253`, outside that
|
||||
pool and independently DAD-tested. It is pinned in Compose, descriptor,
|
||||
builder and the separate Edge runner.
|
||||
2. Build the deterministic `component=device-edge` artifact, promote the
|
||||
root-owned Edge runner and review its `plan`. The Synology runner and inbox
|
||||
are not used for this host.
|
||||
3. Apply the admission-gate update only to `device-edge-relay`; prove exact
|
||||
IPvlan runtime, no host ports, `public-ipv4-only` admission, byte/source
|
||||
limits, internal health, private backhaul reachability, unchanged
|
||||
backhaul/tailnet identities and preserved Amnezia routes. Automatic rollback
|
||||
restores the reviewed IPvlan predecessor and leaves router state unchanged.
|
||||
4. Independently review and add the single router/NAT rule for TCP `9921` only,
|
||||
then verify that Synology still exposes no public device port.
|
||||
5. Add the NDC route to one approved B2 free server slot while preserving
|
||||
Gelios, then prove HEADER/discovery/PACKAGE acknowledgement. Claim and tenant
|
||||
assignment remain a later explicit platform-admin operation.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Device Core repository boundary
|
||||
|
||||
## Device Core owns
|
||||
|
||||
- Device Manager UI and BFF;
|
||||
- Device Control Core, gateways and edge runtimes;
|
||||
- provider/model adapters and protocol contracts;
|
||||
- Device Plane and Device Edge VPS Compose/configuration sources;
|
||||
- Device Core deployment descriptor templates;
|
||||
- deterministic builders and tests for Device Core artifacts.
|
||||
|
||||
## NODEDC Platform owns
|
||||
|
||||
- the root-owned `nodedc-deploy` executable and component registry;
|
||||
- Hub and Authentik identities, grants and signed runtime claims;
|
||||
- Launcher integration and service catalogue entry;
|
||||
- the `device.nodedc.ru` reverse-proxy route;
|
||||
- shared platform secrets handed to Device Core at runtime;
|
||||
- platform deployment builders and acceptance checks for those integration
|
||||
seams.
|
||||
|
||||
## NODEDC Design Guideline owns
|
||||
|
||||
- `@nodedc/tokens`, `@nodedc/ui-core` and `@nodedc/ui-react`;
|
||||
- canonical controls, icons, tokens and interaction patterns;
|
||||
- design-system validation and catalogue documentation.
|
||||
|
||||
Device Core consumes these packages from the sibling canonical repository. It
|
||||
must not fork or hard-code their source.
|
||||
|
||||
## Migration invariants
|
||||
|
||||
Repository extraction is a source-ownership change only. It must not change:
|
||||
|
||||
- deployed component names;
|
||||
- live filesystem roots;
|
||||
- Compose project or service names;
|
||||
- persistent volume names;
|
||||
- runtime secret paths;
|
||||
- mTLS keys/certificates or edge registrations;
|
||||
- public ingress policy;
|
||||
- release predecessor hashes already recorded by the deploy ledger.
|
||||
|
||||
No runtime state, media upload, credential, private key, device password or
|
||||
real restricted identifier belongs in this repository.
|
||||
@@ -0,0 +1,539 @@
|
||||
# Robot2B B2 VPS — конфигурационная Библия
|
||||
|
||||
> Замороженная историческая конфигурация — 10.08.2026
|
||||
>
|
||||
> Не применять описанные ниже `backhaul`, `relay`, Tailscale/SSH LocalForward,
|
||||
> Synology enrollment или открытие `9921`. Целевой транспорт заменён на
|
||||
> Core-initiated mTLS full-duplex channel по
|
||||
> `docs/ADR_0001_CORE_INITIATED_EDGE_CHANNEL.md`. Документ сохраняется как
|
||||
> evidence уже выполненных экспериментов и текущего predecessor VPS.
|
||||
|
||||
Статус документа: живой manual реализации от 2026-08-06.
|
||||
|
||||
Контур: Robot2B / NODE.DC Device Plane.
|
||||
|
||||
VPS: `155.212.211.15`, hostname `koffyvngij`.
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
VPS является минимальным публичным Device Edge для трекеров Arnavi B2. Он не
|
||||
владеет бизнес-логикой, протоколом, БД, карточками устройств, визуализацией или
|
||||
командами. Его единственная целевая функция — принять ограниченный поток raw TCP
|
||||
на `155.212.211.15:9921`, не интерпретируя пакет, и передать его по шифрованному
|
||||
private backhaul в уже существующий `device-gateway` NODE.DC.
|
||||
|
||||
Gelios остаётся отдельным legacy-источником. Эта конфигурация не меняет Gelios,
|
||||
DNS `device.nodedc.ru`, маршруты B2 или command transport.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B2["Arnavi B2 trackers"]
|
||||
VPS["Device Edge VPS\n155.212.211.15:9921"]
|
||||
TS["Tailscale userspace\nSOCKS5 127.0.0.1:1055"]
|
||||
SSH["SSH local forward\n127.0.0.1:19921"]
|
||||
TARGET["Synology device-backhaul-target\n100.109.216.21:2222"]
|
||||
GW["device-gateway\n127.0.0.1:9921"]
|
||||
CORE["device-control-core + PostgreSQL"]
|
||||
FOUNDRY["Foundry / visualization"]
|
||||
GELIOS["Gelios legacy source"]
|
||||
|
||||
B2 -->|"raw TCP, planned route"| VPS
|
||||
VPS --> TS -->|"grant: tag:device-edge-vps → TCP/2222 only"| SSH --> TARGET --> GW --> CORE --> FOUNDRY
|
||||
GELIOS -->|"independent legacy ingest"| CORE
|
||||
```
|
||||
|
||||
## 2. Красные границы
|
||||
|
||||
- На VPS нет базы данных и durable telemetry storage.
|
||||
- VPS не разбирает Arnavi/B2 protocol и не принимает решения по IMEI.
|
||||
- IMEI остаётся claimed identifier, а не доказательством владения устройством.
|
||||
- Неизвестное устройство должно попадать в quarantine/discovery lifecycle.
|
||||
- Command transport отключён. Команды через VPS или Gelios не вводятся.
|
||||
- Gelios не выключается и не перетирается новым потоком.
|
||||
- На Synology не публикуется Docker-порт `2222`; доступ только через private
|
||||
Tailscale Serve.
|
||||
- Public VPS не является пользовательским tailnet-узлом. Его service identity
|
||||
`tag:device-edge-vps` может инициировать только TCP к `100.109.216.21:2222`.
|
||||
- VPS не принимает subnet routes, DNS tailnet, exit-node capability или
|
||||
Tailscale SSH. Доступ к DSM, MacBook и другим tailnet-узлам запрещён.
|
||||
- Приватные ключи, auth keys, токены и пароли не входят в Git, deploy artifacts,
|
||||
Ops или этот документ.
|
||||
- До отдельного pilot/cutover не меняются `device.nodedc.ru` и настройки B2.
|
||||
|
||||
## 3. Фактический predecessor VPS
|
||||
|
||||
Снято до первого apply:
|
||||
|
||||
- Ubuntu `24.04.4 LTS`, kernel `6.8.0-137-generic`, KVM/QEMU.
|
||||
- 1 vCPU, 961 MiB RAM, swap отсутствует.
|
||||
- Root filesystem около 8.7 GiB; свободно около 6.4 GiB.
|
||||
- `eth0`: `155.212.211.15/32`, default gateway `100.100.1.1`.
|
||||
- Публично слушал только TCP/22; TCP/9921 был закрыт.
|
||||
- Docker, Node.js, Tailscale и WireGuard отсутствовали.
|
||||
- `fail2ban`, NTP и unattended upgrades были активны.
|
||||
- UFW был inactive, nftables/iptables использовали INPUT ACCEPT.
|
||||
- SSH допускал root/password и forwarding; это устранено foundation-переходом.
|
||||
|
||||
## 4. Канонические переходы
|
||||
|
||||
Конфигурация разделена на независимые data-only артефакты. Порядок нельзя
|
||||
переставлять.
|
||||
|
||||
1. `foundation`:
|
||||
- pin статических Node.js и Tailscale runtimes;
|
||||
- service account `nodedc-edge`;
|
||||
- key-only SSH;
|
||||
- default-deny nftables, публично только TCP/22;
|
||||
- отдельный ED25519 backhaul key, сгенерированный на VPS;
|
||||
- userspace `tailscaled`, но без скрытого auth key;
|
||||
- TCP/9921 закрыт.
|
||||
2. Внешняя регистрация и сегментация `nodedc-b2-vps` в существующем tailnet:
|
||||
- заменить default wildcard policy на проверяемую deny-by-default policy;
|
||||
- назначить `tag:device-edge-vps`, тем самым удалить user ownership;
|
||||
- разрешить тегу только `100.109.216.21:2222/tcp`;
|
||||
- отрицательно проверить Synology `22/5001`, MacBook `22` и другие узлы;
|
||||
- только после этого вернуть Tailscale service в состояние Running.
|
||||
3. Synology `backhaul-vps-enrollment`:
|
||||
- принять только публичный VPS key через enrollment;
|
||||
- заменить прежний Mini key;
|
||||
- пересоздать только `device-backhaul-target`;
|
||||
- сохранить Device Plane, PostgreSQL, Tailscale Serve и Gelios;
|
||||
- при ошибке автоматически вернуть прежний key и target generation.
|
||||
4. VPS `backhaul`:
|
||||
- pinned Synology host key;
|
||||
- key-only SSH через Tailscale userspace SOCKS5;
|
||||
- `127.0.0.1:19921 -> 127.0.0.1:9921`;
|
||||
- публичный TCP/9921 всё ещё закрыт.
|
||||
5. VPS `relay`:
|
||||
- открыть публичный TCP/9921;
|
||||
- bounded opaque relay на `127.0.0.1:19921`;
|
||||
- loopback health на `127.0.0.1:18221`.
|
||||
6. Отдельный pilot: изменить адрес сервера у ограниченной группы B2. Это не
|
||||
часть конфигурации VPS.
|
||||
|
||||
## 5. Идентичности и ключи
|
||||
|
||||
### 5.1 Management SSH
|
||||
|
||||
Команда с MacBook:
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/nodedc_b2_vps \
|
||||
-o IdentitiesOnly=yes \
|
||||
-o StrictHostKeyChecking=yes \
|
||||
root@155.212.211.15
|
||||
```
|
||||
|
||||
В Ops и manual фиксируются только путь и fingerprints:
|
||||
|
||||
- локальный private key path: `~/.ssh/nodedc_b2_vps`;
|
||||
- MacBook management public key fingerprint:
|
||||
`SHA256:DYYy1E3DaxIQGC0jnsW6SP7gXdBHUy3A1zn4pvgVUEw` (ED25519);
|
||||
- VPS SSH server host key fingerprint:
|
||||
`SHA256:mhqNn2S6zstkYL7VFdvt3SYHv1nLjB4J7/s57RrKG6w` (ED25519).
|
||||
|
||||
Foundation принудительно задаёт:
|
||||
|
||||
- `PermitRootLogin prohibit-password`;
|
||||
- `AuthenticationMethods publickey`;
|
||||
- `PasswordAuthentication no`;
|
||||
- `KbdInteractiveAuthentication no`;
|
||||
- forwarding, agent forwarding, X11 и tunnels запрещены;
|
||||
- `MaxAuthTries 3`, `LoginGraceTime 20`.
|
||||
|
||||
В `/root/.ssh/authorized_keys` остаются две management identity:
|
||||
|
||||
- MacBook ED25519 — fingerprint выше;
|
||||
- provider recovery key `beget-access-key`, RSA 1024,
|
||||
fingerprint `SHA256:9W1cgovqOlegteEWV0r5j4OjJgG0PGr97eVmpUBjg54`.
|
||||
|
||||
RSA-1024 provider key — остаточный риск. Он не удалён автоматически, потому
|
||||
что отзыв внешнего recovery-доступа является отдельным необратимым решением.
|
||||
После подтверждения владельца нужно либо удалить его отдельным каноническим
|
||||
переходом, либо документировать как принятый break-glass access.
|
||||
|
||||
### 5.2 VPS backhaul client identity
|
||||
|
||||
Private key генерирует root-owned runner непосредственно на VPS:
|
||||
|
||||
- private: `/var/lib/nodedc-b2-vps/trust/backhaul_ed25519`, owner
|
||||
`nodedc-edge` на foundation и `nodedc-backhaul` после backhaul apply,
|
||||
mode `0400`;
|
||||
- public: `/var/lib/nodedc-b2-vps/trust/backhaul_ed25519.pub`, mode `0444`;
|
||||
- fingerprint:
|
||||
`SHA256:HHTiDYiCRxSiKjBLCip6JMSzGfLGrDz5g8SIkosJcVw`;
|
||||
- public key:
|
||||
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGUlvJ8a83qov9DHO2S/BHbVAEH4Chyc4v/DKwOIWeES nodedc-device-edge-vps-backhaul`.
|
||||
|
||||
В Synology staging этот public key хранится по пути:
|
||||
|
||||
`/volume1/docker/nodedc-device-plane/enrollment/device-edge-vps-backhaul.pub`
|
||||
|
||||
Private key никогда не покидает VPS.
|
||||
|
||||
Foundation генерирует ключ до появления публичного relay, поэтому его
|
||||
временным владельцем является `nodedc-edge`. Backhaul transition атомарно
|
||||
создаёт `nodedc-backhaul`, передаёт ему каталог trust и ключ и валидирует mode.
|
||||
Relay запускается как третий пользователь `nodedc-relay`; он не может читать ни
|
||||
private key, ни Tailscale state. Даже локальный доступ relay к SOCKS5 не расширяет
|
||||
полномочия: tailnet grant разрешает только target `2222`, где аутентификация
|
||||
дополнительно требует private key пользователя `nodedc-backhaul`.
|
||||
|
||||
### 5.3 Synology backhaul target identity
|
||||
|
||||
- Tailnet IP: `100.109.216.21`.
|
||||
- Private SSH endpoint: `100.109.216.21:2222` через Tailscale Serve.
|
||||
- User: `device-backhaul`.
|
||||
- PermitOpen: только `127.0.0.1:9921`.
|
||||
- Host key fingerprint:
|
||||
`SHA256:QERJ5CIUXRj0nLChGT6HMtoX+WTaeaEY5ZgaWqT8d30`.
|
||||
- Public host key:
|
||||
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJsmoyS+0Tbhz9VXxrSxXwNMFpfbdTckCilObOnKdlEc nodedc-device-plane-backhaul-target`.
|
||||
- VPS pinned known_hosts:
|
||||
`/var/lib/nodedc-b2-vps/trust/backhaul_known_hosts`.
|
||||
|
||||
### 5.4 Tailscale service identity и grant
|
||||
|
||||
Живая policy сохранена 2026-08-06. Каноническая копия:
|
||||
`device-plane/deployment/tailscale-device-edge-policy.hujson`.
|
||||
|
||||
- `autogroup:member -> autogroup:self`, все протоколы: сохраняет привычный
|
||||
доступ пользователя только между собственными user-owned устройствами;
|
||||
- `tag:device-edge-vps -> device-plane-backhaul`, только `tcp:2222`;
|
||||
- `device-plane-backhaul = 100.109.216.21`;
|
||||
- tag owner: только `autogroup:admin`;
|
||||
- policy test требует accept `100.109.216.21:2222` и deny для Synology
|
||||
`22/5001`, MacBook `22`, `nodedc-device-edge:22`.
|
||||
|
||||
Назначение тега удалило `dcctouch@gmail.com` из поля `Managed by`; живой VPS
|
||||
управляется `tag:device-edge-vps`. После включения проверено с самого VPS:
|
||||
|
||||
- `100.109.216.21:2222` — reachable;
|
||||
- `100.109.216.21:22` — blocked;
|
||||
- `100.109.216.21:5001` — blocked;
|
||||
- `100.114.248.4:22` — blocked;
|
||||
- `100.64.19.31:22` — blocked.
|
||||
|
||||
Enrollment запускается с `--accept-dns=false --accept-routes=false --ssh=false`.
|
||||
Runner для backhaul/relay дополнительно требует `BackendState=Running`, online
|
||||
hostname `nodedc-b2-vps` и ровно один tag `tag:device-edge-vps`.
|
||||
|
||||
## 6. Runtime и файловая система VPS
|
||||
|
||||
### 6.1 Root-owned source/runtime
|
||||
|
||||
- runner: `/usr/local/sbin/nodedc-b2-vps-deploy`;
|
||||
- live root: `/opt/nodedc-b2-vps`;
|
||||
- deploy state: `/var/lib/nodedc-b2-vps-deploy`;
|
||||
- inbox: `/var/lib/nodedc-b2-vps-deploy/inbox`;
|
||||
- applied/failed artifacts: `applied/`, `failed/`;
|
||||
- backups: `backups/`;
|
||||
- journals: `state/applied.jsonl`, `state/failed.jsonl`;
|
||||
- lock: `state/deploy.lock`;
|
||||
- service state/trust: `/var/lib/nodedc-b2-vps`.
|
||||
|
||||
### 6.2 Pinned runtimes
|
||||
|
||||
- Node.js `22.23.2`;
|
||||
archive SHA-256
|
||||
`d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307`.
|
||||
- Tailscale `1.102.2`;
|
||||
archive SHA-256
|
||||
`ad2cde12f8de95f7b93a1e0401e652291c603d42b9d60a33fb1741eb38ab04d8`.
|
||||
|
||||
Docker не устанавливается: для 961 MiB RAM он не нужен и добавляет лишний
|
||||
daemon/state surface.
|
||||
|
||||
### 6.3 systemd units
|
||||
|
||||
- `nodedc-b2-tailscaled.service`:
|
||||
- user `nodedc-edge`;
|
||||
- userspace networking, без TUN;
|
||||
- socket `/run/nodedc-b2-vps/tailscaled.sock`;
|
||||
- SOCKS5 `127.0.0.1:1055`;
|
||||
- `MemoryMax=160M`;
|
||||
- разрешены `AF_UNIX AF_INET AF_INET6 AF_NETLINK`.
|
||||
- `nodedc-b2-backhaul.service`:
|
||||
- user `nodedc-backhaul`, единственный читатель backhaul private key;
|
||||
- strict host key pinning;
|
||||
- local forward `127.0.0.1:19921`;
|
||||
- `MemoryMax=64M`.
|
||||
- `nodedc-b2-relay.service`:
|
||||
- user `nodedc-relay`, без credential access;
|
||||
- public `0.0.0.0:9921`;
|
||||
- health `127.0.0.1:18221`;
|
||||
- upstream `127.0.0.1:19921`;
|
||||
- `MemoryMax=192M`.
|
||||
|
||||
Все units используют `NoNewPrivileges`, `ProtectSystem=strict`,
|
||||
`ProtectHome=yes`, `PrivateTmp`, `PrivateDevices`, ограничение address families,
|
||||
tasks и file descriptors.
|
||||
|
||||
## 7. Firewall и порты
|
||||
|
||||
Foundation nftables policy:
|
||||
|
||||
- input: default drop;
|
||||
- loopback, established/related и ICMP разрешены;
|
||||
- новый TCP/22 ограничен `30/minute`, burst `60`;
|
||||
- TCP/9921 отсутствует;
|
||||
- forward: default drop;
|
||||
- output: accept.
|
||||
|
||||
Relay policy добавляет:
|
||||
|
||||
- drop новых соединений на TCP/9921 сверх `300/second`;
|
||||
- accept TCP/9921 после rate guard.
|
||||
|
||||
Портовая матрица:
|
||||
|
||||
| Endpoint | Видимость | Владелец | Стадия |
|
||||
|---|---|---|---|
|
||||
| `155.212.211.15:22` | public | OpenSSH | foundation |
|
||||
| `127.0.0.1:1055` | loopback | tailscaled SOCKS5 | foundation |
|
||||
| `127.0.0.1:19921` | loopback | SSH local forward | backhaul |
|
||||
| `127.0.0.1:18221` | loopback | relay health | relay |
|
||||
| `155.212.211.15:9921` | public | bounded relay | relay |
|
||||
| `100.109.216.21:2222` | tailnet grant только для VPS tag | Synology target | existing |
|
||||
| `127.0.0.1:9921` на Synology | loopback | device-gateway | existing |
|
||||
|
||||
## 8. Relay limits
|
||||
|
||||
- max concurrent sessions: `128`;
|
||||
- max sessions per source IP: `16`;
|
||||
- max new connections/minute/source IP: `60`;
|
||||
- max tracked source IPs: `4096`;
|
||||
- max bytes per direction/session: `64 MiB`;
|
||||
- session timeout: `300000 ms`;
|
||||
- source policy: `public-ipv4-only`;
|
||||
- protocol inspection: disabled на VPS;
|
||||
- command transport: disabled.
|
||||
|
||||
## 9. Установленный foundation release
|
||||
|
||||
Принят 2026-08-06:
|
||||
|
||||
- patch: `device-edge-vps-foundation-20260806-003`;
|
||||
- artifact SHA-256:
|
||||
`1be852f144e9f0fea32af70bebd07a2607b6a1818825094bd4c1b4062064716a`;
|
||||
- foundation-time runner SHA-256:
|
||||
`3f42d23431937e70c16ce1fd346fb84a706e506ae99d89eaf11780ff1ad56c03`;
|
||||
- current promoted runner SHA-256:
|
||||
`5ccdc1b53ce0688e7c120976e82937842bc8491a2e05eb5f280165accfd40b6c`;
|
||||
- backup:
|
||||
`device-edge-vps-foundation-20260806-003-20260806-151415`;
|
||||
- terminal result: `deploy-ok`;
|
||||
- fresh MacBook key-only SSH acceptance: success.
|
||||
|
||||
Два предыдущих ID терминальны и никогда не должны применяться повторно:
|
||||
|
||||
- `...-001`: `tailscaled` заблокирован отсутствием `AF_NETLINK`; automatic
|
||||
rollback `ok`;
|
||||
- `...-002`: cloud-init `50-cloud-init.conf` опередил `90-*` и сохранил
|
||||
`PasswordAuthentication yes`; automatic rollback `ok`;
|
||||
- `...-003`: исправлены `AF_NETLINK` и ранний `00-nodedc-b2-vps.conf`;
|
||||
acceptance успешна.
|
||||
|
||||
Следующие exact isolation-aware artifacts стадированы в VPS inbox и не
|
||||
применяются до своих predecessor barriers:
|
||||
|
||||
- backhaul `device-edge-vps-backhaul-20260806-002`, SHA-256
|
||||
`830750da8f9590ca4db458ec9e90f4d48ad8d1403160d3878a968b54e9eb6913`;
|
||||
- relay `device-edge-vps-relay-20260806-002`, SHA-256
|
||||
`305a6de769f24b2c6cee801426ec43b98a44d10e08cad75a96fd65d20b16b697`.
|
||||
|
||||
Версии `...-001` не применялись и recoverably перемещены из inbox в
|
||||
`/var/lib/nodedc-b2-vps-deploy/withdrawn/*.superseded-by-002`, потому что в них
|
||||
все три процесса использовали один Unix account `nodedc-edge`.
|
||||
|
||||
## 10. Synology VPS enrollment release
|
||||
|
||||
Стадировано, но до авторизации Tailscale и root plan/apply не считается
|
||||
применённым:
|
||||
|
||||
- runner candidate:
|
||||
`/volume1/docker/nodedc-deploy/runner-install/candidates/nodedc-deploy.device-plane-backhaul-vps-enrollment-20260806-011`;
|
||||
- runner SHA-256:
|
||||
`453228c41b411d9c925091c77dc94e501f2eb3534fc241db0d2e58f0a28e12e2`;
|
||||
- artifact:
|
||||
`/volume1/docker/nodedc-deploy/inbox/nodedc-device-plane-device-plane-backhaul-vps-enrollment-20260806-001.tgz`;
|
||||
- artifact SHA-256:
|
||||
`576dabdafde5e3b2de09c7265127928c4b463e7b40dd554fd71babc16cc70e08`;
|
||||
- VPS enrollment public-key file SHA-256:
|
||||
`c2718c117fd09965386524d32fa9816d9d2d9cf00b59010dcf8bcf78f29bea8c`.
|
||||
|
||||
Root-переход на Synology:
|
||||
|
||||
```bash
|
||||
sudo sha256sum \
|
||||
/volume1/docker/nodedc-deploy/runner-install/candidates/nodedc-deploy.device-plane-backhaul-vps-enrollment-20260806-011
|
||||
|
||||
sudo install -o root -g root -m 0755 \
|
||||
/volume1/docker/nodedc-deploy/runner-install/candidates/nodedc-deploy.device-plane-backhaul-vps-enrollment-20260806-011 \
|
||||
/usr/local/sbin/nodedc-deploy
|
||||
|
||||
sudo /usr/local/sbin/nodedc-deploy verify-install
|
||||
|
||||
sudo /usr/local/sbin/nodedc-deploy plan \
|
||||
/volume1/docker/nodedc-deploy/inbox/nodedc-device-plane-device-plane-backhaul-vps-enrollment-20260806-001.tgz
|
||||
|
||||
# Apply только после review exact plan.
|
||||
sudo /usr/local/sbin/nodedc-deploy apply \
|
||||
/volume1/docker/nodedc-deploy/inbox/nodedc-device-plane-device-plane-backhaul-vps-enrollment-20260806-001.tgz
|
||||
```
|
||||
|
||||
Ожидаемый plan обязан показать:
|
||||
|
||||
- predecessor patch `device-plane-backhaul-target-tailnet-serve-20260804-002`;
|
||||
- predecessor artifact SHA
|
||||
`219408705dd4d80a962ed00eeb53a69df0b9ab6458443734d5c9cd1d1f795eba`;
|
||||
- build `none`;
|
||||
- recreate только `device-backhaul-target`;
|
||||
- next fingerprint `SHA256:HHTi…osJcVw`;
|
||||
- public ingress disabled;
|
||||
- Docker port publication disabled;
|
||||
- Tailscale Serve, router/NAT/firewall, PostgreSQL и Gelios unchanged;
|
||||
- automatic rollback на previous key + target recreate.
|
||||
|
||||
## 11. Внешняя регистрация Tailscale
|
||||
|
||||
На VPS auth key не хранится. Для первой регистрации root запускает:
|
||||
|
||||
```bash
|
||||
/opt/nodedc-b2-vps/runtime/tailscale/tailscale \
|
||||
--socket=/run/nodedc-b2-vps/tailscaled.sock \
|
||||
up \
|
||||
--hostname=nodedc-b2-vps \
|
||||
--accept-dns=false \
|
||||
--accept-routes=false \
|
||||
--ssh=false
|
||||
```
|
||||
|
||||
Одноразовый login URL не копируется в Ops. До включения service владелец tailnet
|
||||
обязан сохранить policy и назначить tag из раздела 5.4. Backhaul plan требует
|
||||
`BackendState=Running`, online hostname `nodedc-b2-vps` и exact service tag.
|
||||
|
||||
## 12. Сборка VPS artifacts
|
||||
|
||||
Из корня repository `platform`:
|
||||
|
||||
```bash
|
||||
NODEDC_DEVICE_EDGE_VPS_RUNTIME_DIR=/tmp \
|
||||
node infra/deploy-runner/build-device-edge-vps-artifact.mjs \
|
||||
foundation <unique-patch-id>
|
||||
|
||||
node infra/deploy-runner/build-device-edge-vps-artifact.mjs \
|
||||
backhaul <unique-patch-id>
|
||||
|
||||
node infra/deploy-runner/build-device-edge-vps-artifact.mjs \
|
||||
relay <unique-patch-id>
|
||||
```
|
||||
|
||||
Builder создаёт deterministic archive с `manifest.env`, `files.txt` и
|
||||
`payload/`. В artifact запрещены `.env`, keys, trust, runtime, logs, uploads,
|
||||
node_modules и symlinks. Foundation дополнительно проверяет pinned runtime
|
||||
digests.
|
||||
|
||||
Каждый release:
|
||||
|
||||
1. собрать дважды и сравнить SHA-256;
|
||||
2. проверить file list и отсутствие секретов;
|
||||
3. скопировать exact artifact в VPS inbox;
|
||||
4. выполнить свежий `plan`;
|
||||
5. review границ;
|
||||
6. выполнить один exact `apply`;
|
||||
7. считать `deploy-ok` терминальным результатом и не запускать apply повторно.
|
||||
|
||||
## 13. Rollback model
|
||||
|
||||
VPS runner перед mutation создаёт backup exact partition и сохраняет текущий
|
||||
nft ruleset и service enablement. При ошибке:
|
||||
|
||||
- candidate services останавливаются;
|
||||
- source/config возвращаются;
|
||||
- nftables и SSH config восстанавливаются;
|
||||
- service enablement возвращается;
|
||||
- при failed foundation удаляются созданные live/runtime roots и service user;
|
||||
- при failed backhaul ключ возвращается `nodedc-edge`, а созданный
|
||||
`nodedc-backhaul` удаляется;
|
||||
- при failed relay созданный `nodedc-relay` удаляется;
|
||||
- artifact переносится в `failed/`;
|
||||
- failed ID и digest становятся терминальными.
|
||||
|
||||
Synology VPS enrollment отдельно сохраняет previous `authorized_keys`. При любой
|
||||
ошибке он:
|
||||
|
||||
- удаляет candidate marker;
|
||||
- атомарно возвращает прежний restricted key;
|
||||
- пересоздаёт только `device-backhaul-target`;
|
||||
- проверяет target, три preserved Device Plane services, Tailscale Serve и
|
||||
loopback `9921`.
|
||||
|
||||
## 14. Развёртывание аналогичного VPS
|
||||
|
||||
Перед клонированием нельзя просто переиспользовать текущий artifact. Нужно
|
||||
создать новый descriptor/release с новыми параметрами:
|
||||
|
||||
1. Получить чистый Ubuntu 24.04 LTS VPS и зафиксировать hostname, public `/32`,
|
||||
gateway, CPU/RAM/disk.
|
||||
2. Добавить отдельный ED25519 management key и проверить fresh connection.
|
||||
3. Зафиксировать server host key fingerprint с доверенного канала.
|
||||
4. Проверить active fail2ban/NTP/unattended upgrades и отсутствие listeners,
|
||||
кроме TCP/22.
|
||||
5. Изменить pin в runner/descriptor:
|
||||
`RUNTIME_HOST`, `PUBLIC_IPV4`, management/server fingerprints и Tailscale
|
||||
node name.
|
||||
6. Не переносить private backhaul key: новый VPS должен сгенерировать новую
|
||||
пару самостоятельно.
|
||||
7. Собрать deterministic foundation с новым terminal patch ID.
|
||||
8. Promoted runner проверить отдельным `verify-install`.
|
||||
9. Сделать plan → review → apply.
|
||||
10. Зарегистрировать новый tailnet node без сохранения auth key.
|
||||
11. Скопировать только public backhaul key в новый enrollment path.
|
||||
12. Выполнить отдельный Synology key rotation/grant transition.
|
||||
13. Только после принятого private backhaul применять relay.
|
||||
14. Только после relay acceptance перенаправлять ограниченный pilot B2.
|
||||
|
||||
## 15. Acceptance checklist
|
||||
|
||||
- [x] Foundation artifact deterministic и secret-free.
|
||||
- [x] Foundation accepted с automatic rollback coverage.
|
||||
- [x] Fresh key-only SSH с MacBook работает.
|
||||
- [x] Public TCP/9921 не открыт на foundation.
|
||||
- [x] Отдельный VPS backhaul public key создан и pinned.
|
||||
- [x] Synology enrollment key, runner candidate и artifact стадированы.
|
||||
- [ ] Tailnet node `nodedc-b2-vps` авторизован и `Running`.
|
||||
- [x] Tailnet wildcard grant удалён; VPS tagged и negative-route tests пройдены.
|
||||
- [x] Isolation-aware VPS runner promoted и `verify-install-ok`.
|
||||
- [ ] Synology runner candidate promoted и `verify-install-ok`.
|
||||
- [ ] Synology enrollment `plan` reviewed и `deploy-ok`.
|
||||
- [ ] VPS backhaul artifact `deploy-ok`.
|
||||
- [ ] VPS relay artifact `deploy-ok`.
|
||||
- [ ] Public TCP/9921 принят внешним probe.
|
||||
- [ ] Pilot B2 route согласован отдельно.
|
||||
- [ ] Provider RSA-1024 recovery key удалён или принят как documented risk.
|
||||
|
||||
## 16. Исходники и проверки
|
||||
|
||||
Канонические файлы:
|
||||
|
||||
- `infra/deploy-runner/nodedc-b2-vps-deploy`;
|
||||
- `infra/deploy-runner/build-device-edge-vps-artifact.mjs`;
|
||||
- `infra/deploy-runner/test_device_edge_vps_artifact.py`;
|
||||
- `device-plane/vps/config/`;
|
||||
- `device-plane/vps/systemd/`;
|
||||
- `device-plane/deployment/device-edge-vps-*-v1.json`;
|
||||
- `infra/deploy-runner/nodedc-deploy`;
|
||||
- `infra/deploy-runner/build-device-plane-backhaul-vps-enrollment-artifact.mjs`;
|
||||
- `infra/deploy-runner/test_device_plane_backhaul_vps_enrollment_artifact.py`.
|
||||
|
||||
Проверки на момент документа:
|
||||
|
||||
- VPS artifact tests: 9/9 OK;
|
||||
- Device Plane targeted runner/artifact tests: 48/48 OK;
|
||||
- полный platform `npm test`: 41/41 OK во внешнем сетевом sandbox;
|
||||
- Python compilation: OK;
|
||||
- systemd unit syntax: OK; отсутствие binaries до foundation было ожидаемым;
|
||||
- `nft -c` foundation/relay: OK;
|
||||
- effective candidate OpenSSH policy: key-only;
|
||||
- `git diff --check`: OK для целевых файлов.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Device Plane baseline test matrix
|
||||
|
||||
| Boundary | Required proof |
|
||||
| --- | --- |
|
||||
| Restricted identity | IMEI accepts exactly 15 decimal digits internally |
|
||||
| Browser projection | Safe discovery view contains only a masked identifier |
|
||||
| Identifier hashing | HMAC digest is deterministic and does not reveal input |
|
||||
| Secret boundary | Secret-like or raw-payload keys are rejected recursively |
|
||||
| Command boundary | Discovery contract rejects command-shaped input |
|
||||
| Framing bound | B2 evidence inspection rejects empty and oversized buffers |
|
||||
| Framing honesty | Unverified B2 bytes return `official_framing_required` |
|
||||
| No identifier guessing | Embedded digit sequences are never returned as IMEI |
|
||||
| Model profile | Four server routes and INTERNAL identification are recorded |
|
||||
| Gelios preservation | Gelios is a parallel route, not a dependency or failover |
|
||||
| Core database secret | Production Compose uses a file-backed password, not a plaintext environment value |
|
||||
| Core health | Database is ready while discovery ingest and command transport remain disabled |
|
||||
| Gateway health | Public ingress, TCP listener and command transport remain disabled |
|
||||
| Compose exposure | Only loopback health ports `18120/18121` are published; raw `9921` is not |
|
||||
| Application service scope | `files.txt` selects only affected Core/Gateway services with `--no-deps` |
|
||||
| Database preservation | Ordinary application artifacts never select `device-postgres` |
|
||||
| Database bootstrap | Exact descriptor selects PostgreSQL only when both container and volume are absent |
|
||||
| Bootstrap rollback | Candidate container may be removed; named volume is never removed |
|
||||
| Rollback predecessor | Backup records actual pre-apply services; Compose presence cannot invent Core/Gateway |
|
||||
| Failed-001 evidence | Recovery requires exact failed archive, journal and backup digests |
|
||||
| Partial source | Recovery accepts only DB-bootstrap source plus the observed healthy foundation runtime |
|
||||
| Recovery mutation | Source is published without build, restart, recreate or service removal |
|
||||
| Recovery rollback | Failed acceptance restores source only and leaves runtime unchanged |
|
||||
| Artifact policy | `.env`, secrets, runtime state, tests, logs and `node_modules` are excluded |
|
||||
| Artifact reproducibility | Repeated builds for the same patch id are byte-identical |
|
||||
| Runner compatibility | Existing canonical Platform registry tests remain green |
|
||||
| Core/Edge trust direction | Core initiates the only private channel; VPS-initiated Synology connections are forbidden |
|
||||
| Core channel authentication | TLS 1.3 mutual authentication; unknown/revoked Edge identity fails closed |
|
||||
| Core channel bounds | Versioned envelopes, per-session and aggregate memory limits, keepalive and reconnect bounds |
|
||||
| Pilot SLO | Zero pre-Core ACK/loss, p95/p99 acceptance latency, reconnect/dead-peer ceilings and no premature availability claim |
|
||||
| Tracker acknowledgement | PACKAGE ACK is emitted only after bounded Core acceptance |
|
||||
| Superseded transport freeze | Old VPS backhaul/relay/enrollment builders fail closed outside explicit test-only reconstruction |
|
||||
| Superseded runner freeze | VPS runner rejects old phases and Synology runner rejects a prebuilt old enrollment artifact |
|
||||
| Runtime preservation in Phase 0 | VPS, Synology, DNS, B2 routes, Gelios, Engine and Foundry remain unchanged |
|
||||
Reference in New Issue
Block a user