From 995e773210672a9a5a9827307d39c93629d9654d Mon Sep 17 00:00:00 2001 From: lr00rl Date: Tue, 11 Aug 2026 20:39:56 -0700 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20design-17=20=E2=80=94=20managed=20l?= =?UTF-8?q?ine=20overlay=20on=20adopted=20nodes=20(fleet=20rollout=20+=20a?= =?UTF-8?q?ccount=20binding)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designs/design-17-managed-line-overlay.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/designs/design-17-managed-line-overlay.md diff --git a/docs/designs/design-17-managed-line-overlay.md b/docs/designs/design-17-managed-line-overlay.md new file mode 100644 index 0000000..9e68116 --- /dev/null +++ b/docs/designs/design-17-managed-line-overlay.md @@ -0,0 +1,70 @@ +# Design 17 - Managed line overlay on adopted nodes (fleet rollout + account binding) + +> Status: proposed 2026-08-11, from the operator directive "每个节点新增一个 +> lattice 维护的 inbound,并支持绑定账号" (option B: build the managed-line +> capability, with extreme performance and efficiency). +> Builds on: design-12 (lines/users read model, plan→approve→apply), +> design-15 (D3 dual-track user writes, D6 deferral), the 2026-08-11 fleet +> inventory (ops-archive, operator-private). + +## 1. Intent + +Give every fleet node one additional inbound whose definition Lattice owns — +rendered by the control plane, applied atomically, reconciled after apply — +and bind the operator's account to it, so a single credential works on every +node. The 140 existing adopted lines must keep working byte-for-byte; nothing +about them is re-rendered, converted, or restarted for its own sake. + +This is the operator's goal restated as architecture: "managed" describes who +owns the line's definition, not how the bytes reach the box. + +## 2. Decision registry + +| # | Decision | Rationale | +|---|----------|-----------| +| D1 | The managed line on an adopted node is an **overlay**, not a conversion. The line's definition is server-rendered and server-owned; application uses the adopted track's proven mechanics (atomic fragment write + `sing-box check` + rollback + restart). Nodes are NOT converted to whole-config managed. | design-15 D3's constraint stands: the fleet runs 233boy file-per-line layouts, and whole-config render of a mixed fleet requires the multi-protocol renderer that D6 defers. An overlay delivers "lattice-maintained inbound on every node" with zero risk to existing lines. Full whole-config management stays D6-deferred and unaffected. | +| D2 | Exactly one managed inbound per node, shape locked to **VLESS+REALITY+TCP**. | The only shape the managed renderer and the trust model prove today (design-15 §9, proxycore/singbox.go contract). REALITY-first is also the fleet's own security posture (2026-07-08 netsec design). Multi-protocol managed shapes are a later slice, one protocol at a time, each with its own review. | +| D3 | Port is **planned, not auto-allocated**: a fleet-consistent candidate (24443) checked per node against the inventory's live port map; conflicts take the next free port. The chosen port is pinned in the approval's typed columns. | Silent allocation is how fleets get colliding ports discovered at 2am. The inventory makes the choice evidence-based; the approval columns make it reviewable before anything mutates. | +| D4 | Account binding reuses the operator's existing `ProxyUser` (cdcd) and its existing VLESS credential; the per-line user name follows design-15 §5 (`u_`). One account at first; the model admits more without a schema change. | A second credential for the same person is operational noise. Rotation stays available through the existing §5 rotate path. | +| D5 | One fleet rollout = **one approval event** covering N per-node plans, using the event-grouping shipped 2026-08-10. Per-node isolation: one node's failure rolls back that node and never blocks the batch. | The operator reviews one card, not 24. Fleet upgrades already proved the batch shape. | +| D6 | REALITY camouflage (dest/SNI) follows each node's existing reality lines — read from the inventory, not invented. | The fleet's camouflage domains are deliberate (operator-maintained); a managed line that suddenly uses a novel domain is a fingerprint anomaly. | +| D7 | Post-apply, the line is rediscovered and carries `line_uuid`; the overlay is flagged `managed` in the read model, and drift between the rendered definition and the on-box fragment is surfaced, never silently overwritten. | design-12/15 reconciliation rules apply unchanged; the overlay is not a second-class line. | + +## 3. Why not the naive D6 reading + +"Build managed lines" reads as "render the whole node config." On this fleet +that means reproducing 140 operator-tuned lines across six protocols through a +renderer that today speaks exactly one — a long project whose only reward is +risk. The overlay gets the operator's actual outcome (a lattice-owned, +account-bound inbound on every node) with the apply path that already exists, +and it does not foreclose anything: a node can be converted to whole-config +managed later, protocol by protocol, and the overlay lines migrate with it. + +## 4. Performance contract ("极致性能") + +- All rendering is pure Go (proxycore). No JavaScript engine is anywhere near + this path; a fleet render is milliseconds. +- Per-node apply tasks run under the approvals batch runner's bounded + concurrency (4); a 24-node rollout is ~6 waves, each wave one + check+reload per node. +- The plan is compiled once per node and pinned by SHA in the approval; + apply replays the pinned bytes, never a re-render. +- Client-facing cost of the new lines flows through the snapshot + render + cache (2026-08-11 serve-path work): subscription polls for the fleet + subscription do not re-run the pipeline per poll. + +## 5. Slices + +| # | Slice | Notes | +|---|-------|-------| +| S1 | Server rollout compiler: `POST /api/network/lines/managed-rollout` builds per-node plans (shape, port from the inventory map, REALITY material, binding) and files them as one approval batch. Validation refuses nodes whose port map is unknown. | The endpoint is a compiler, not a mutator: nothing applies without the operator's approval click. | +| S2 | Apply mechanics for an inbound fragment on an adopted node. Decide at implementation: new node-agent task verb (`inbound.apply`) vs the plugin-ops apply channel. Contract either way: write fragment → `sing-box check` → reload → verify → rollback on any failure, audit each step. | The one genuinely new machinery piece; the atomicity contract is fixed, the channel is the choice. | +| S3 | Read-model + UI: the overlay line appears in Networking → Lines with a `managed` badge and the bound account; the rollout action lives on the Lines view with per-node apply status (pending/applied/failed/rolled-back). Designed per the operator's design-intelligence skill (Product path): one dominant action, honest per-node states, destructive rollback visible. | The UI is where "lattice 维护" becomes legible. | +| S4 | Account binding via the existing users-admin `plan_add` per managed line; cdcd only in v1. | Existing machinery, no new path. | + +## 6. Explicitly not here (still D6-deferred) + +Multi-protocol managed rendering (trojan / hysteria2 / tuic / anytls as managed +shapes), quota/expiry auto-disable enforcement, generic plugin event bus / +cron, the sing-box fork with relaxed unknown-field parsing. Each keeps its own +review when its turn comes. From 01f23a65626bc00e40e69c6696d0555b2feba9d2 Mon Sep 17 00:00:00 2001 From: lr00rl Date: Wed, 12 Aug 2026 00:41:42 -0700 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20design-17=20=E2=80=94=20record=20S2?= =?UTF-8?q?=20channel=20decision=20and=20S1+S2=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The apply channel is the server-rendered sh script over the existing task pipeline (the adopted track's universal channel), not a new agent verb and not the plugin-operation channel. Implemented in lattice-server managedline.go: rollout compiler, definition store, fail-closed validation, fragment apply script with check/restart/verify/rollback, result reconcile with rediscovery and vpn-core re-sync. --- docs/designs/design-17-managed-line-overlay.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/designs/design-17-managed-line-overlay.md b/docs/designs/design-17-managed-line-overlay.md index 9e68116..bc3566f 100644 --- a/docs/designs/design-17-managed-line-overlay.md +++ b/docs/designs/design-17-managed-line-overlay.md @@ -1,8 +1,9 @@ # Design 17 - Managed line overlay on adopted nodes (fleet rollout + account binding) -> Status: proposed 2026-08-11, from the operator directive "每个节点新增一个 -> lattice 维护的 inbound,并支持绑定账号" (option B: build the managed-line -> capability, with extreme performance and efficiency). +> Status: S1+S2 implemented 2026-08-12 (lattice-server `managedline.go`), from +> the operator directive "每个节点新增一个 lattice 维护的 inbound,并支持绑定账号" +> (option B: build the managed-line capability, with extreme performance and +> efficiency). S2 channel decided with evidence below. > Builds on: design-12 (lines/users read model, plan→approve→apply), > design-15 (D3 dual-track user writes, D6 deferral), the 2026-08-11 fleet > inventory (ops-archive, operator-private). @@ -58,7 +59,7 @@ managed later, protocol by protocol, and the overlay lines migrate with it. | # | Slice | Notes | |---|-------|-------| | S1 | Server rollout compiler: `POST /api/network/lines/managed-rollout` builds per-node plans (shape, port from the inventory map, REALITY material, binding) and files them as one approval batch. Validation refuses nodes whose port map is unknown. | The endpoint is a compiler, not a mutator: nothing applies without the operator's approval click. | -| S2 | Apply mechanics for an inbound fragment on an adopted node. Decide at implementation: new node-agent task verb (`inbound.apply`) vs the plugin-ops apply channel. Contract either way: write fragment → `sing-box check` → reload → verify → rollback on any failure, audit each step. | The one genuinely new machinery piece; the atomicity contract is fixed, the channel is the choice. | +| S2 | Apply mechanics for an inbound fragment on an adopted node. **Decided 2026-08-12: server-rendered sh script over the existing task pipeline** — the channel every adopted-track mutation already uses (`server_singbox_manage.go`, `lineusers.go`). New agent verb rejected (a fleet-wide agent rollout would gate the first line); the §9.3 plugin-operation channel rejected (it executes PLUGIN-compiled plans; the rollout is server-compiled from the projection). Contract implemented in-script: write fragment → `sing-box check` → restart → verify active → rollback on any failure, audit each step. | The one genuinely new machinery piece; the atomicity contract is fixed, the channel is the choice. | | S3 | Read-model + UI: the overlay line appears in Networking → Lines with a `managed` badge and the bound account; the rollout action lives on the Lines view with per-node apply status (pending/applied/failed/rolled-back). Designed per the operator's design-intelligence skill (Product path): one dominant action, honest per-node states, destructive rollback visible. | The UI is where "lattice 维护" becomes legible. | | S4 | Account binding via the existing users-admin `plan_add` per managed line; cdcd only in v1. | Existing machinery, no new path. |