Skip to content

Commit 1bd042a

Browse files
committed
Record operator IPv6 netpolicy remotes
The public design and roadmap now treat operator-authored IPv6 remotes as shipped in iter-030, while keeping domain-valued remotes and non-systemd refresh as explicit future slices. Constraint: The docs guide future feature ordering for DNS and proxy-core work, so current network-policy residuals must be precise.\nRejected: Keep operator IPv6 grouped with domain remotes | IPv6 remotes are now compiled from canonical addresses, while domain remotes still require DNS set semantics.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Design 05 residuals split by trust model: literal/CIDR IP remotes are not the same as DNS-backed domain remotes.\nTested: git diff --check\nNot-tested: Rendered documentation site; docs are markdown-only in this repo.
1 parent 9fc3b7b commit 1bd042a

8 files changed

Lines changed: 147 additions & 38 deletions

docs/PRODUCT-VISION.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
> plan-hash binding for pending high-risk approvals landed in iter-025; the
2323
> control-plane HTTPS-domain nft named set landed in iter-026; the agent-native
2424
> nft domain-set updater landed in iter-027; systemd periodic refresh landed in
25-
> iter-028; IPv6 control-plane parity landed in iter-029. See
25+
> iter-028; IPv6 control-plane parity landed in iter-029; operator-authored IPv6
26+
> policy remotes landed in iter-030. See
2627
> `designs/README.md` for the remaining cross-cutting dependencies and
2728
> recommended build order.
2829
@@ -44,7 +45,7 @@ The bar is not "works." The bar is **惊艳 — a product that makes a self-host
4445

4546
## 3. Honest current state (2026-06-13)
4647

47-
Delivered and pushed: control plane + node agent + SDK + dashboard across 6 repos; security hardening; DDNS, monitoring, notifications, WireGuard mesh, CF Tunnel; plugin manifest/signing + loader + preflight verify + host-API broker/server adapter + lifecycle registry/API + dashboard panel + runtime manager + runner contract; TOTP 2FA; tamper-evident audit WAL; node-token rotation; bounded task execution; **AES-256-GCM at-rest encryption** (ADR-002); bbolt state import/export foundation with explicit JSON migration/rollback CLI and record-level APIs for current state buckets, including secret-bearing identity/auth/DDNS/notify/OIDC/MachineProfile records and non-secret `NFTInputs`/`NetPolicy`/operator `NodeGeo`; low-trust `HostFacts` inventory telemetry from node-agent through server to dashboard; server-only MachineProfile cost/renewal reminders with encrypted console/detail links and a Machines dashboard panel; shared per-node nft input persistence for DNS/ACL single-table rendering; Design 05 `NetPolicy` state/API/graph/dashboard; the committed egress-only nft policy apply path with plan-hash approval, 60s rollback watchdog, agent `/api/health` selfcheck, result-backed applied/failed status, HTTPS-domain IPv4/IPv6 control-plane named-set support, an agent-native domain-set updater, and systemd periodic refresh for those sets; Network Guard `lattice_guard` apply with rollback and ingress-policy composition; required dashboard-computed plan hashes for pending high-risk approvals; a dependency-free dashboard Fleet Map driven by operator-owned geo facts; and dashboard policy-graph SVG driven by server graph JSON. Minimal external Go deps are currently limited to the OIDC stack approved in ADR-001 (`oauth2`, `go-oidc`, transitive `go-jose`) plus bbolt for Phase C storage; still zero CGo. The 2026-06-13 closeout baseline is recorded in `development-report-2026-06-13.md`, with iter-029 as the current netpolicy domain-set delta.
48+
Delivered and pushed: control plane + node agent + SDK + dashboard across 6 repos; security hardening; DDNS, monitoring, notifications, WireGuard mesh, CF Tunnel; plugin manifest/signing + loader + preflight verify + host-API broker/server adapter + lifecycle registry/API + dashboard panel + runtime manager + runner contract; TOTP 2FA; tamper-evident audit WAL; node-token rotation; bounded task execution; **AES-256-GCM at-rest encryption** (ADR-002); bbolt state import/export foundation with explicit JSON migration/rollback CLI and record-level APIs for current state buckets, including secret-bearing identity/auth/DDNS/notify/OIDC/MachineProfile records and non-secret `NFTInputs`/`NetPolicy`/operator `NodeGeo`; low-trust `HostFacts` inventory telemetry from node-agent through server to dashboard; server-only MachineProfile cost/renewal reminders with encrypted console/detail links and a Machines dashboard panel; shared per-node nft input persistence for DNS/ACL single-table rendering; Design 05 `NetPolicy` state/API/graph/dashboard; the committed egress-only nft policy apply path with plan-hash approval, 60s rollback watchdog, agent `/api/health` selfcheck, result-backed applied/failed status, HTTPS-domain IPv4/IPv6 control-plane named-set support, an agent-native domain-set updater, systemd periodic refresh for those sets, and operator-authored IPv6 CIDR/node remotes; Network Guard `lattice_guard` apply with rollback and ingress-policy composition; required dashboard-computed plan hashes for pending high-risk approvals; a dependency-free dashboard Fleet Map driven by operator-owned geo facts; and dashboard policy-graph SVG driven by server graph JSON. Minimal external Go deps are currently limited to the OIDC stack approved in ADR-001 (`oauth2`, `go-oidc`, transitive `go-jose`) plus bbolt for Phase C storage; still zero CGo. The 2026-06-13 closeout baseline is recorded in `development-report-2026-06-13.md`, with iter-030 as the current netpolicy delta.
4849

4950
The three gaps that most separate us from 惊艳, in dependency order: **identity policy polish**, **the platform actually running plugins**, and **a storage engine that scales** — plus a **dashboard worthy of the backend**.
5051

docs/designs/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ Iter-026 removes the IPv4-literal-only constraint for control-plane
1818
hostnames; iter-027 moves that set update into an agent-native helper instead
1919
of shell DNS parsing; iter-028 installs a systemd timer for periodic refresh of
2020
that control-plane set; iter-029 adds IPv6 control-plane parity with
21-
`lattice_control6` plus IPv6 literal `public_url` support. Remaining Design 05
22-
work is domain remotes, operator-authored IPv6 policy remotes, non-systemd
23-
scheduling, bulk geo import, and map overlays. Designs 01/02/03 remain
21+
`lattice_control6` plus IPv6 literal `public_url` support; iter-030 adds
22+
operator-authored IPv6 CIDR/node remotes for egress and ingress composition.
23+
Remaining Design 05 work is domain remotes, non-systemd scheduling, bulk geo
24+
import, and map overlays. Designs 01/02/03 remain
2425
design-ready but unbuilt. Each
2526
new build slice becomes a numbered `iterations/iter-NNN-*.md` (per
2627
`development-workflow.md`: plan → design → build → verify → review → commit).
@@ -33,7 +34,7 @@ new build slice becomes a numbered `iterations/iter-NNN-*.md` (per
3334
| 02 | [Self-hosted DNS](design-02-self-host-dns.md) | One-click private DNS on a chosen node + CF subdomain (gmami-jp1.dns.roobli.org) auto-updated via DDNS + nft-confined | CORE `internal/selfdns`; **pure-Go CoreDNS** (digest-pinned) via plan→approve→apply; reuses `internal/ddns` (CF) + shared `NFTInputs` |
3435
| 03 | [Log ingestion & query](design-03-log-ingestion.md) | Tail an operator-specified log path on a node → queryable store for debugging | Agent tails + ships deltas; **NOT on the JSON store** — a dedicated bounded append-only per-node log store (relates to the bbolt foundation); query API + dashboard |
3536
| 04 | [Machine inventory & cost](design-04-machine-inventory-and-cost.md) | Auto-detect CPU/mem/uptime/arch; operator-set cloud vendor/links/cost/renewal + renewal reminders | `HostFacts` auto-detect/report/display **landed iter-017**; server-only `MachineProfile` + renewal reminder MVP **landed iter-018** |
36-
| 05 | [Network ACL & geo-map](design-05-network-acl-and-map.md) | Per-node nft access rules (deny node→dmit:1234), policy/reachability viz, nezha-style global map | CORE `internal/netpolicy`; `NetPolicy` validation/store/API/graph/dashboard foundation landed iter-020; egress-only nft compiler + `/api/netpolicy/plan` + **60s agent dead-man rollback** apply path landed iter-021; `NodeGeo` CRUD + inline-SVG fleet map landed iter-022; policy graph SVG landed iter-023; Network Guard rollback apply + ingress guard composition landed iter-024; control-plane HTTPS-domain named set landed iter-026; agent-native domain-set updater landed iter-027; systemd periodic refresh landed iter-028; IPv6 control-plane parity landed iter-029; domain remotes/operator IPv6 remain pending |
37+
| 05 | [Network ACL & geo-map](design-05-network-acl-and-map.md) | Per-node nft access rules (deny node→dmit:1234), policy/reachability viz, nezha-style global map | CORE `internal/netpolicy`; `NetPolicy` validation/store/API/graph/dashboard foundation landed iter-020; egress-only nft compiler + `/api/netpolicy/plan` + **60s agent dead-man rollback** apply path landed iter-021; `NodeGeo` CRUD + inline-SVG fleet map landed iter-022; policy graph SVG landed iter-023; Network Guard rollback apply + ingress guard composition landed iter-024; control-plane HTTPS-domain named set landed iter-026; agent-native domain-set updater landed iter-027; systemd periodic refresh landed iter-028; IPv6 control-plane parity landed iter-029; operator IPv6 remotes landed iter-030; domain remotes remain pending |
3738

3839
## Shared architecture (all five honor)
3940

@@ -52,12 +53,12 @@ new build slice becomes a numbered `iterations/iter-NNN-*.md` (per
5253

5354
## Recommended build order (rationale)
5455

55-
1. **05 network ACL domain remotes/operator IPv6 + map polish** — continue from iter-029:
56+
1. **05 network ACL domain remotes + map polish** — continue from iter-030:
5657
ingress composition is now folded into `lattice_guard`, and control-plane
5758
HTTPS-domain `public_url` now uses agent-filled `lattice_control4` /
58-
`lattice_control6` sets with systemd periodic refresh; next add
59-
domain-valued operator remotes and IPv6 for operator-authored policy targets,
60-
then
59+
`lattice_control6` sets with systemd periodic refresh. Operator-authored
60+
IPv6 CIDR/node remotes now compile through the same reviewed path; next add
61+
domain-valued operator remotes, then
6162
add bulk geo import and map latency/renewal overlays.
6263
2. **02 Self-host DNS** — builds on the same nft work + existing ddns; keep DNS
6364
port opening folded into the single firewall render.

docs/designs/design-05-network-acl-and-map.md

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
> agent-native nft domain-set updater replaced the shell DNS pipeline in
1212
> iter-027; systemd periodic refresh for that control-plane set landed in
1313
> iter-028; IPv6 control-plane parity (`lattice_control6` + IPv6 literal
14-
> `public_url`) landed in iter-029. Remaining: domain-valued operator remotes,
15-
> operator-authored IPv6 policy remotes, non-systemd scheduling, bulk geo
16-
> import, and map overlays.
14+
> `public_url`) landed in iter-029; operator-authored IPv6 CIDR/node remotes
15+
> landed in iter-030. Remaining: domain-valued operator remotes, non-systemd
16+
> scheduling, bulk geo import, and map overlays.
1717
> Author: design pass · Date: 2026-06-13
1818
> Builds on: `architecture.md` (Safety Model, WireGuard Mesh, DDNS), `internal/network/nft.go`,
1919
> `internal/wireguard`, `internal/cftunnel`, `internal/ddns`, the `plan → approve → apply` flow.
@@ -53,9 +53,9 @@ Three capabilities, one cohesive slice:
5353
may *derive* nothing it cannot already see.
5454
- **No new on-node daemon.** Reuse the poll-only agent and the bounded apply task. No inbound ports.
5555
- **No nftables NAT / mangle / routing changes.** Filtering (`filter` hook) only.
56-
- **No operator-authored IPv6 policy in the first MVP slice** (the data model is
57-
v6-ready; control-plane IPv6 landed later in iter-029, policy remote rendering
58-
remains v2).
56+
- **No operator-authored IPv6 policy in the first MVP slice** (the data model was
57+
v6-ready; control-plane IPv6 landed later in iter-029, and operator-authored
58+
IPv6 CIDR/node remotes landed in iter-030).
5959
- **Not a plugin.** This is a CORE server-owned provider (see §2).
6060
- **Map tiles / basemap detail:** a single static low-poly world outline, not a real GIS basemap.
6161

@@ -73,8 +73,9 @@ The first committed apply path is intentionally narrower than the full design:
7373
`lattice-agent --update-nft-domain-set` to resolve/filter/fill that set, then
7474
runs the same control-plane selfcheck. On systemd hosts the same approved
7575
apply installs `lattice-nftpolicy-domain-refresh.timer` so DNS churn is
76-
refreshed every minute. Domain-valued operator remotes, operator-authored IPv6
77-
policy remotes, and non-systemd scheduling are still later slices.
76+
refreshed every minute. Operator-authored IPv6 CIDR/node remotes compile to
77+
reviewed `ip6` statements as of iter-030. Domain-valued operator remotes and
78+
non-systemd scheduling are still later slices.
7879
- The node apply task validates with `nft -c`, snapshots rollback state, arms a
7980
60s watchdog, applies the nft batch, then runs
8081
`lattice-agent --selfcheck-controlplane -server <public-url>`. The task shell
@@ -375,8 +376,9 @@ Current output is split by hook ownership:
375376
`set lattice_control4 { type ipv4_addr; flags interval; }` and
376377
`set lattice_control6 { type ipv6_addr; flags interval; }`, then reference
377378
both sets. The apply script delegates set mutation to the agent-native
378-
domain-set updater before selfcheck. Operator-authored IPv6 and domain remotes
379-
remain later slices.
379+
domain-set updater before selfcheck. Operator-authored IPv6 CIDR/node remotes
380+
render as explicit `ip6 daddr` / `ip6 saddr` statements. Domain remotes remain
381+
a later slice.
380382
- **Ingress:** `CompileIngressInputRules` converts enabled ingress `NetPolicy`
381383
rules into typed `network.NFTInputRule` values. `GenerateNFTPlan` folds those
382384
into the single `table inet lattice_guard` input chain rendered by Network
@@ -521,10 +523,11 @@ Smallest end-to-end slice that delivers the operator's exact ask.
521523
apply time; iter-027 moved that mutation into an agent-native helper so DNS
522524
answers no longer flow through shell pipelines; iter-028 installs a systemd
523525
timer to keep the set fresh after apply; iter-029 adds `lattice_control6`
524-
and IPv6 literal control-plane support. Remaining: non-systemd scheduling and
525-
policy remotes that intentionally reference domains or IPv6 targets. DNS is
526-
not treated as authentication: HTTPS verification and Lattice credentials
527-
still decide whether the endpoint is trusted.
526+
and IPv6 literal control-plane support; iter-030 adds operator-authored IPv6
527+
CIDR/node remotes. Remaining: non-systemd scheduling and policy remotes that
528+
intentionally reference domains. DNS is not treated as authentication: HTTPS
529+
verification and Lattice credentials still decide whether the endpoint is
530+
trusted.
528531
2. **Node IP churn.** Node refs resolve at compile time; if a peer's IP changes after apply, the rule
529532
is stale until re-planned. *Decision:* surface "policy stale — peer IP changed" in the graph/list
530533
(the server already detects IP changes for DDNS) and let the operator re-plan; do **not** auto-apply
@@ -629,8 +632,17 @@ Control-plane IPv6 parity was delivered in iter-029.
629632
Domain-backed plans now render both `lattice_control4` and `lattice_control6`;
630633
`lattice-agent --update-nft-domain-set` updates both from one Go resolver result
631634
without shell DNS parsing; IPv6 literal `public_url` values render direct
632-
`ip6 daddr` control-plane allows. Continue with domain-valued operator remotes,
633-
operator-authored IPv6 policy remotes, non-systemd scheduling, and map overlays.
635+
`ip6 daddr` control-plane allows. At that point the remaining work was
636+
domain-valued operator remotes, operator-authored IPv6 policy remotes,
637+
non-systemd scheduling, and map overlays. The operator-authored IPv6 policy
638+
remote residual was delivered in iter-030.
639+
640+
**Progress note (2026-06-14 / iter-030):** operator-authored IPv6 CIDR/node
641+
remotes are landed for the current reviewed policy path. `NetPolicy` CIDR
642+
normalization now accepts IPv4 and IPv6; egress compilation emits `ip daddr` and
643+
`ip6 daddr` statements; ingress composition folds IPv6 sources into
644+
`lattice_guard` as `ip6 saddr` statements. Continue with domain-valued operator
645+
remotes, non-systemd scheduling, bulk geo import, and map overlays.
634646
635647
**Phase MVP**
636648
1. **Plan** — write `lattice/docs/iterations/iter-0NN-netpolicy-mvp.md` (goal, scope, design ref to this
@@ -691,6 +703,6 @@ operator-authored IPv6 policy remotes, non-systemd scheduling, and map overlays.
691703
`styles.css`, inside `script-src 'self'` / `style-src 'self'`.
692704
18. Verify (add a dashboard render smoke check), review, commit per slice.
693705
694-
**Phase Later** — operator-authored IPv6 policy remotes, boot-persistence unit,
706+
**Phase Later** — domain-valued operator remotes, boot-persistence unit,
695707
optional CF map-pin DNS (ADR if default-on), latency/renewal overlay, bbolt
696708
cutover — each its own iter with the same gate.

docs/development-report-2026-06-13.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ The current pushed baseline includes:
8585
`nftpolicy` plans now render `lattice_control4` and `lattice_control6`; the
8686
agent domain-set helper updates both from one Go resolver result; IPv6 literal
8787
`public_url` values render direct `ip6 daddr` allows.
88+
- Operator IPv6 NetPolicy remotes (iter-030, 2026-06-14): `NetPolicy` CIDR and
89+
node remotes now compile IPv6 values into explicit `ip6 daddr` / `ip6 saddr`
90+
nft statements for egress and Network Guard ingress composition.
8891
- Signed plugin manifest verification, fail-closed trust policy, startup loader,
8992
`/api/plugins/verify`, lifecycle registry/API/UI, host-API broker, server host
9093
services adapter, runtime manager, and a no-op runner contract.
@@ -163,10 +166,9 @@ ingress guard composition), and iter-025 (required plan hashes for high-risk
163166
apply approvals). The next work should now be:
164167

165168
1. **Design 05 - domain-set composition + visualization polish.**
166-
Continue from iter-029: add domain-valued operator remotes and
167-
operator-authored IPv6 policy remotes, then add bulk geo import and map
168-
latency/renewal overlays. Also add compiler-vs-graph parity tests now that
169-
ingress has a committed render path.
169+
Continue from iter-030: add domain-valued operator remotes, then add bulk
170+
geo import and map latency/renewal overlays. Also add compiler-vs-graph
171+
parity tests now that ingress has a committed render path.
170172
2. **Design 02 - Self-host DNS.** Add `DNSDeployment`, CoreDNS rendering,
171173
Cloudflare publish via existing DDNS provider, and composition of DNS ports
172174
into the stored `NFTInputs`.

0 commit comments

Comments
 (0)