Add declarative WG actor bootstrap plan
Build on PR #511's explicit bindspace and WG lifecycle contracts without moving declaration policy into actor runtime params. Deats, - define exact-name planning and whole-table resource realization; - separate parsed identity from local interface and netns policy; - specify owned vs borrowed lifecycles, recursive canonical declarations and snapshot-before-checkpoint semantics; - require explicit routes and actionable privilege diagnostics; and - order the impl through planning, lifecycle, coordinator, composition and real WG dataplane patches. (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/wg_declarative_bootstrap
parent
93dbbd5a02
commit
d097090f2e
|
|
@ -0,0 +1,640 @@
|
|||
# Declarative WireGuard actor bootstrap
|
||||
|
||||
This plan starts from `d80dcbe6`, after the explicit WireGuard,
|
||||
bindspace, root-netns and Trio-child bootstrap work in PR #511.
|
||||
It proposes the final declaration-driven layer only. Production and
|
||||
test implementation remain outside this plan commit.
|
||||
|
||||
## Goal
|
||||
|
||||
Turn an actor-name endpoint table into explicitly owned network
|
||||
resources that configure one root and its named Trio children without
|
||||
moving declarations, WG secrets or lifecycle policy into actor runtime
|
||||
parameters.
|
||||
|
||||
The completed API should let an application:
|
||||
|
||||
1. parse a flat endpoint table;
|
||||
2. combine each actor's declarations with local realization policy;
|
||||
3. realize every required network resource before root startup;
|
||||
4. pass concrete addresses and live bindspaces through existing root
|
||||
and child APIs; and
|
||||
5. retain every owned resource until the complete actor tree is reaped.
|
||||
|
||||
For attached resources, the coordinator pins the namespace FD only.
|
||||
The external owner must keep each borrowed interface present and
|
||||
unchanged until the endpoint-tree context exits.
|
||||
|
||||
## Existing boundaries
|
||||
|
||||
The implementation composes, rather than replaces, these contracts:
|
||||
|
||||
- `parse_endpoints()` parses a flat
|
||||
`dict[str, list[address declaration]]` and preserves ordered
|
||||
`TunnelledAddress` wrappers. It neither selects an actor nor opens
|
||||
resources (`tractor/discovery/_multiaddr.py:170-227`).
|
||||
- A parsed `WGTunnelSpec` carries public path identity. Local interface
|
||||
selection and `WGInterfaceConfig` remain process-local policy
|
||||
(`tractor/net/_tunnel.py:116-149`,
|
||||
`tractor/net/_tunnel.py:218-368`).
|
||||
- `open_wg_bindspace()` manages one bindspace and an ordered stack of
|
||||
owned WG interfaces (`tractor/net/_tunnel.py:637-687`).
|
||||
- `open_root_actor()` accepts concrete addresses and one already-live
|
||||
`Bindspace`; it enters that namespace before registry or IPC work
|
||||
(`tractor/_root.py:183-311`).
|
||||
- `ActorNursery.start_actor()` accepts concrete child bind tuples and
|
||||
one live `Bindspace`; Trio spawn transports a duplicate FD before
|
||||
child runtime startup (`tractor/runtime/_supervise.py:416-513`).
|
||||
- `SpawnSpec.bind_addrs` remains concrete tuples because msgspec cannot
|
||||
decode the abstract address union
|
||||
(`tractor/msg/types.py:193-220`).
|
||||
|
||||
## Decisions proposed for review
|
||||
|
||||
### Keep declaration and runtime layers separate
|
||||
|
||||
Do not add an endpoint table, WG secrets or lifecycle policy to
|
||||
`open_root_actor()` or `ActorNursery.start_actor()`.
|
||||
|
||||
Add an outer `tractor.net` planner and resource context. The caller
|
||||
continues to pass only:
|
||||
|
||||
- realized declarations and a live bindspace to the root;
|
||||
- concrete unwrapped bind addresses and a live bindspace to Trio
|
||||
children; and
|
||||
- registrar addresses explicitly, because remote registry discovery
|
||||
is separate from local actor binding.
|
||||
|
||||
### Start with a flat, exact-name table
|
||||
|
||||
The first implementation supports exact actor-name keys only.
|
||||
|
||||
- `worker` never matches `worker-2` or a prefixed cluster name.
|
||||
- A missing name means no declarative override; callers may retain
|
||||
existing defaults.
|
||||
- An explicitly empty endpoint list is rejected by planning rather
|
||||
than silently selecting a random listener.
|
||||
- Planning snapshots input lists and never pops or mutates caller
|
||||
configuration.
|
||||
- One table entry describes one actor instance. Reusing its fixed
|
||||
binding for concurrent actors violates the API precondition; no
|
||||
runtime claim registry is added in this phase.
|
||||
|
||||
Recursive tables and instance allocation remain separate API design
|
||||
work. Their matching and ownership semantics are not defined by the
|
||||
original parser.
|
||||
|
||||
### Separate declaration identity from local realization
|
||||
|
||||
An maddr does not choose the local interface or namespace. Pair each
|
||||
declared tunnel with explicit process-local realization policy:
|
||||
|
||||
```python
|
||||
WGInterfaceLifecycle = Literal['attach', 'open']
|
||||
|
||||
|
||||
class EndpointLayerConfig(ProcessLocal):
|
||||
spec: WGTunnelSpec
|
||||
lifecycle: WGInterfaceLifecycle
|
||||
config: WGInterfaceConfig | None = None
|
||||
|
||||
|
||||
class EndpointConfig(ProcessLocal):
|
||||
bindspace_spec: BindspaceSpec
|
||||
layers: tuple[EndpointLayerConfig, ...] = ()
|
||||
role: WGRole = 'listen'
|
||||
```
|
||||
|
||||
`EndpointLayerConfig.spec` is the local realization. Planning matches
|
||||
it to the parsed declaration by tunnel depth and maddr-defined fields:
|
||||
|
||||
- `peer_pubkey` and `bearer` must match the declaration;
|
||||
- local `iface` and `netns` may replace parser defaults;
|
||||
- an explicit local `netns` must agree with
|
||||
`BindspaceSpec.key`; and
|
||||
- layer order remains outermost first.
|
||||
|
||||
Planning retains `peer_pubkey`, `bearer` and wrapper order as the public
|
||||
declaration identity, but does not preserve parsed local `iface` or
|
||||
`netns` values in the realized graph. After setup, recursively rebuild
|
||||
every tunnel wrapper from the concrete overlay outward with its matched
|
||||
local spec and the same realized `BindspaceRef`. This canonical graph
|
||||
cannot carry a stale parsed namespace or leave an inner wrapper
|
||||
unannotated. It also avoids calling `with_bindspace_ref()` on an
|
||||
incompatible retained spec.
|
||||
|
||||
For `lifecycle='open'`, `config` is required and the interface is
|
||||
created and removed. For `lifecycle='attach'`, `config` is absent; the
|
||||
coordinator verifies an existing interface but never creates, mutates
|
||||
or deletes it. Attachment is a checked borrowing contract, not an
|
||||
interface lease: the caller must arrange an external owner that keeps
|
||||
the interface alive and configured until context exit.
|
||||
|
||||
Listen declarations identify the local WG public key. After managed
|
||||
creation, or during attachment, read the actual interface key and
|
||||
compare it with the declaration before publishing a binding. Dial
|
||||
declarations identify a remote peer and use explicit peer verification.
|
||||
No key relationship is inferred from private material alone.
|
||||
|
||||
### Require one network stack per actor process
|
||||
|
||||
A process enters one active network namespace for runtime startup.
|
||||
Planning therefore enforces:
|
||||
|
||||
- all tunnel-bearing endpoints for one actor declare one common
|
||||
ordered tunnel stack;
|
||||
- local layers match that stack by declared identity and depth;
|
||||
- plain addresses may coexist inside the same bindspace;
|
||||
- a bindspace-only profile may use an empty layer tuple; and
|
||||
- unknown config keys, missing layers, extra layers, incompatible
|
||||
stacks or namespace mismatches fail before async setup.
|
||||
|
||||
Planning also checks static collisions across actors that resolve into
|
||||
the same bindspace:
|
||||
|
||||
- duplicate fixed TCP/UDS bind addresses;
|
||||
- duplicate managed interface names; and
|
||||
- duplicate ownership claims for one named bindspace.
|
||||
|
||||
This catches configuration collisions but does not police later reuse
|
||||
of one binding by arbitrary runtime calls.
|
||||
|
||||
### Snapshot before the first checkpoint
|
||||
|
||||
Existing process-local WG config structs are mutable. A frozen outer
|
||||
tuple does not freeze nested key, peer or route values.
|
||||
|
||||
Add a frozen process-local marker beside `ProcessLocal` for endpoint
|
||||
records, and prove default msgspec encoding still fails. More
|
||||
importantly, `open_endpoint_tree()` must synchronously copy every
|
||||
mutable nested field into private coordinator-owned values before its
|
||||
first `await`. Setup and cleanup never reread caller-owned plans.
|
||||
|
||||
The proposed public outputs are:
|
||||
|
||||
```python
|
||||
class EndpointPlan(FrozenProcessLocal):
|
||||
name: str
|
||||
declared_addrs: tuple[Address | TunnelledAddress, ...]
|
||||
bind_addrs: tuple[Address, ...]
|
||||
config: EndpointConfig | None
|
||||
|
||||
|
||||
class EndpointBinding(FrozenProcessLocal):
|
||||
name: str
|
||||
declared_addrs: tuple[Address | TunnelledAddress, ...]
|
||||
bind_addrs: tuple[Address, ...]
|
||||
bindspace: Bindspace | None
|
||||
```
|
||||
|
||||
`EndpointBinding` does not expose WG secrets or mutable setup config.
|
||||
Its tunnel declarations are recursively rebuilt with canonical local
|
||||
specs and the realized `BindspaceRef` so every layer agrees with the
|
||||
live bindspace and root endpoint diagnostics report its namespace
|
||||
inode. The existing single-wrapper `with_bindspace_ref()` helper is
|
||||
insufficient when parsed local fields differ or wrappers are nested
|
||||
(`tractor/net/_tunnel.py:886-909`,
|
||||
`tractor/net/_tunnel.py:946-976`).
|
||||
|
||||
### Realize the complete table before root startup
|
||||
|
||||
Add lazy public APIs from `tractor/net/_bootstrap.py`:
|
||||
|
||||
```python
|
||||
def plan_endpoints(
|
||||
endpoints: ParsedEndpoints,
|
||||
configs: Mapping[str, EndpointConfig],
|
||||
) -> dict[str, EndpointPlan]:
|
||||
...
|
||||
|
||||
|
||||
@acm
|
||||
async def open_endpoint_tree(
|
||||
plans: Mapping[str, EndpointPlan],
|
||||
) -> AsyncIterator[Mapping[str, EndpointBinding]]:
|
||||
...
|
||||
```
|
||||
|
||||
`open_endpoint_tree()` enters plans in table order through one
|
||||
`AsyncExitStack` and publishes a read-only binding mapping only after
|
||||
all resources and identity checks are ready. It opens no resource
|
||||
lazily from actor-spawn tasks.
|
||||
|
||||
The intended call shape is:
|
||||
|
||||
```python
|
||||
parsed = tractor.net.parse_endpoints(endpoint_table)
|
||||
plans = tractor.net.plan_endpoints(parsed, local_configs)
|
||||
|
||||
async with tractor.net.open_endpoint_tree(plans) as endpoints:
|
||||
root_ep = endpoints['pikerd']
|
||||
async with tractor.open_root_actor(
|
||||
name='pikerd',
|
||||
tpt_bind_addrs=list(root_ep.declared_addrs),
|
||||
bindspace=root_ep.bindspace,
|
||||
registry_addrs=registry_addrs,
|
||||
):
|
||||
async with tractor.open_nursery() as an:
|
||||
child_ep = endpoints['brokerd']
|
||||
portal = await an.start_actor(
|
||||
'brokerd',
|
||||
bind_addrs=[
|
||||
addr.unwrap()
|
||||
for addr in child_ep.bind_addrs
|
||||
],
|
||||
bindspace=child_ep.bindspace,
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
The nesting is contractual: the endpoint tree encloses the root and
|
||||
actor nursery so networking cannot tear down around a live actor.
|
||||
|
||||
## Concurrency and cleanup contract
|
||||
|
||||
`open_endpoint_tree()` has no shared mutable cache, runtime claim map
|
||||
or lazy setup path.
|
||||
|
||||
The setup path is:
|
||||
|
||||
1. validate and privately snapshot every plan synchronously;
|
||||
2. enter actor A's bindspace and layers;
|
||||
3. enter actor B's bindspace and layers;
|
||||
4. verify local listen keys or dial peers;
|
||||
5. recursively construct canonical declarations whose every wrapper
|
||||
carries the live bindspace ref;
|
||||
6. publish a read-only binding mapping; and
|
||||
7. let caller tasks start actors from already-live capabilities.
|
||||
|
||||
Every async-context entry is a checkpoint. A failure or cancellation
|
||||
must close the entered prefix before it escapes. No task can observe a
|
||||
partial binding mapping, and caller mutation after the private snapshot
|
||||
cannot affect setup.
|
||||
|
||||
After yield, root and Trio startup duplicate borrowed namespace FDs
|
||||
rather than consuming coordinator handles. Concurrent child starts
|
||||
read capabilities but do not mutate coordinator state.
|
||||
|
||||
The teardown path is:
|
||||
|
||||
1. child actors stop and are reaped;
|
||||
2. root listeners, runtime and thread restoration complete;
|
||||
3. actor WG layers close inside-out; and
|
||||
4. actor bindspaces release last.
|
||||
|
||||
Plain `AsyncExitStack` does not preserve this plan's error policy by
|
||||
itself. Lower WG and netns managers can currently mask a body or setup
|
||||
error when cleanup fails. Before exporting the coordinator:
|
||||
|
||||
- shield every owned cleanup from cancellation;
|
||||
- continue attempting all remaining cleanup;
|
||||
- preserve the active setup/body/cancellation exception;
|
||||
- attach later cleanup failures as notes; and
|
||||
- make externally removed owned netns teardown idempotent.
|
||||
|
||||
Apply that policy inside `open_netns()`, `open_wg_iface()`, interface
|
||||
creation rollback and the endpoint-tree stack. An outer manager cannot
|
||||
repair a primary exception already replaced by an inner context.
|
||||
|
||||
## Route and privilege policy
|
||||
|
||||
### Explicit overlay routes
|
||||
|
||||
`_sync_create_wg_iface()` adds addresses and peer settings but no
|
||||
explicit routes (`tractor/net/_tunnel.py:470-559`). Connected routes
|
||||
cover only peers in the configured interface subnet.
|
||||
|
||||
Extend `WGInterfaceConfig` with an explicit `routes` tuple. Validate
|
||||
each CIDR and add it to the newly owned interface without replacing an
|
||||
existing host route. Interface deletion remains the owned route cleanup
|
||||
boundary.
|
||||
|
||||
Do not infer host routes from peer `allowed_ips`: those values control
|
||||
WireGuard peer selection and may exceed routes this process should own.
|
||||
|
||||
Tests must pre-create a conflicting route and prove provisioning fails
|
||||
without changing it, while still rolling back only the new interface.
|
||||
|
||||
### Actionable privilege checks
|
||||
|
||||
Managed netns and WG creation are Linux-only and require authority in
|
||||
the owning user namespace. Add diagnostics before the first resource
|
||||
checkpoint:
|
||||
|
||||
- reject managed provisioning on non-Linux platforms;
|
||||
- report a missing `tractor[wg]` dependency before partial setup;
|
||||
- name `CAP_NET_ADMIN` for interface, address and route changes;
|
||||
- name `CAP_SYS_ADMIN` where namespace creation or entry needs it; and
|
||||
- wrap authoritative kernel `EPERM` with the failed operation,
|
||||
namespace and required capability.
|
||||
|
||||
An effective-capability mask is diagnostic only. User namespaces mean
|
||||
the kernel operation remains authoritative.
|
||||
|
||||
Pre-provisioned attachment pins the existing namespace capability and
|
||||
performs a point-in-time interface identity/configuration check. It
|
||||
cannot pin a link against external deletion or reconfiguration, and it
|
||||
owns no interface cleanup. Keeping that link stable through context
|
||||
exit is an explicit external-owner precondition. Managed creation owns
|
||||
all mutation and removal.
|
||||
|
||||
Strictly dropping provisioning capability before actor code conflicts
|
||||
with same-process teardown, which needs that capability later. A helper
|
||||
or persistent `wgman` is required to satisfy both drop-before-user-code
|
||||
and managed cleanup. That architecture remains deferred; this phase
|
||||
documents the limitation rather than claiming capability isolation.
|
||||
|
||||
## Test strategy
|
||||
|
||||
### Pure endpoint planning
|
||||
|
||||
Add `tests/net/test_bootstrap.py` with unit coverage for:
|
||||
|
||||
- exact matching beside similar names;
|
||||
- absent versus explicitly empty declarations;
|
||||
- defensive snapshots and preserved declaration order;
|
||||
- mixed plain and tunnel-bearing endpoints;
|
||||
- declaration-versus-realization field matching;
|
||||
- canonical recursive rebuilding of parsed local fields;
|
||||
- nested layer order;
|
||||
- attach versus open interface policy;
|
||||
- missing, extra and reordered layer configuration;
|
||||
- differing tunnel stacks for one actor;
|
||||
- bindspace/interface/address collisions across actors;
|
||||
- plain bindspace-only profiles; and
|
||||
- unknown local configuration keys.
|
||||
|
||||
No scheduler, monkeypatch or kernel resource is needed at this layer.
|
||||
|
||||
### Lower lifecycle regressions
|
||||
|
||||
Extend bindspace and WG lifecycle tests to prove:
|
||||
|
||||
- setup rollback preserves the setup error when removal also fails;
|
||||
- body failure remains primary when WG/netns cleanup fails;
|
||||
- cancellation cannot interrupt owned cleanup;
|
||||
- all remaining cleanup still runs after one failure;
|
||||
- externally removed named netns cleanup is tolerated;
|
||||
- attached interfaces are verified but never mutated or deleted;
|
||||
- attached-interface lifetime is not represented as coordinator
|
||||
ownership;
|
||||
- managed listen identity matches the configured interface key; and
|
||||
- dial attachment verifies the declared peer.
|
||||
|
||||
Use real Trio scheduling. Replace only pyroute2/kernel adapters where
|
||||
the proof concerns exception ordering rather than kernel behavior.
|
||||
|
||||
### Coordinator lifecycle component tests
|
||||
|
||||
Use real Trio and `AsyncExitStack`, replacing bindspace and WG kernel
|
||||
adapters with traceable async contexts.
|
||||
|
||||
Prove:
|
||||
|
||||
- every resource is ready before publication;
|
||||
- plans enter in table order and exit in reverse order;
|
||||
- each setup failure closes the entered prefix;
|
||||
- body failure and cancellation close every resource;
|
||||
- cleanup failures become secondary notes and do not stop cleanup;
|
||||
- caller mutation during an entry checkpoint cannot alter setup; and
|
||||
- every wrapper in published declarations contains the realized
|
||||
bindspace inode and canonical local spec.
|
||||
|
||||
The doubles retain context scheduling and ownership. They do not prove
|
||||
pyroute2, kernel routing or `setns()`.
|
||||
|
||||
### Real root and Trio composition
|
||||
|
||||
Add Linux integration coverage that:
|
||||
|
||||
- plans distinct exact-name root and child entries;
|
||||
- opens all resources before root startup;
|
||||
- gives the root realized declarations and its live bindspace;
|
||||
- gives the child concrete tuples and its alternate bindspace;
|
||||
- observes the expected namespace in both actor bodies; and
|
||||
- proves actors stop before owned endpoint resources unwind.
|
||||
|
||||
Fake only WG provisioning. Real namespace FDs, Trio process spawn,
|
||||
handshake, supervision and cleanup remain material boundaries.
|
||||
|
||||
### Real WireGuard dataplane
|
||||
|
||||
Add a required, separately capable Linux system test that:
|
||||
|
||||
1. enters disposable user, mount and network namespaces;
|
||||
2. establishes UID/GID mappings and private mount propagation;
|
||||
3. provides an isolated writable `/var/run/netns` strategy for the
|
||||
existing named-netns implementation;
|
||||
4. creates two named netns and a veth underlay through pyroute2;
|
||||
5. opens one real WG endpoint profile in each namespace;
|
||||
6. configures overlay addresses, peer policy and explicit routes;
|
||||
7. starts the root in one namespace and a Trio child in the other;
|
||||
8. completes the parent handshake over the WG overlay;
|
||||
9. discovers the actor with `find_actor()` and performs one RPC; and
|
||||
10. proves actor, socket, interface, route, namespace and FD teardown.
|
||||
|
||||
The veth pair is test-environment underlay, not a production bindspace
|
||||
feature. The test replaces no networking or process boundary.
|
||||
|
||||
Skip only after a precise preflight proves an unavailable kernel
|
||||
feature. Do not catch-and-skip after partial mutation. No `sudo`,
|
||||
subprocess `wg` or sleep-based synchronization is allowed.
|
||||
|
||||
This commit is not considered a dataplane proof until the test passes
|
||||
in an approved environment with WireGuard and the required namespace
|
||||
capabilities. If no such CI runner or recorded local environment is
|
||||
available, stop and report the system-test evidence as blocked rather
|
||||
than merging a universally skipped scaffold.
|
||||
|
||||
## Implementation sequence
|
||||
|
||||
### Commit 1: Resolve endpoint realization plans
|
||||
|
||||
Files:
|
||||
|
||||
- add a frozen process-local marker in `tractor/msg/_local.py`;
|
||||
- add `tractor/net/_bootstrap.py`;
|
||||
- update lazy exports in `tractor/net/__init__.py`;
|
||||
- add pure tests in `tests/net/test_bootstrap.py`; and
|
||||
- add Prompt-IO provenance for substantive generated code.
|
||||
|
||||
Behavior:
|
||||
|
||||
- add layer/config/plan models and `plan_endpoints()`;
|
||||
- separate parsed identity from local realization fields;
|
||||
- define canonical recursive declaration reconstruction;
|
||||
- enforce exact-name, one-stack and static-collision rules;
|
||||
- snapshot declarations and expose concrete bind addresses; and
|
||||
- reject ambiguous configuration before async setup.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff on changed Python;
|
||||
- process-local encoding and immutability tests;
|
||||
- bootstrap, multiaddr and lazy-import tests; and
|
||||
- full test collection.
|
||||
|
||||
### Commit 2: Harden owned and borrowed network lifecycles
|
||||
|
||||
Files:
|
||||
|
||||
- update netns cleanup in `tractor/net/_bindspace.py`;
|
||||
- update WG rollback/cleanup and attachment in
|
||||
`tractor/net/_tunnel.py`;
|
||||
- extend bindspace and WG lifecycle tests; and
|
||||
- add Prompt-IO provenance.
|
||||
|
||||
Behavior:
|
||||
|
||||
- preserve primary errors and finish shielded cleanup;
|
||||
- tolerate already-removed owned netns teardown;
|
||||
- add explicit borrowed-interface verification with no mutation;
|
||||
- document and test its external-owner lifetime precondition; and
|
||||
- verify listen keys and dial peers before use.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff;
|
||||
- setup/body/cancellation/cleanup failure schedules;
|
||||
- attached versus managed ownership tests;
|
||||
- existing bindspace and WG lifecycle suites; and
|
||||
- full test collection.
|
||||
|
||||
### Commit 3: Add routes and authority diagnostics
|
||||
|
||||
Files:
|
||||
|
||||
- extend WG config/provisioning in `tractor/net/_tunnel.py`;
|
||||
- add a small Linux authority helper under `tractor/net/`;
|
||||
- extend config, provisioning and privilege tests; and
|
||||
- add Prompt-IO provenance.
|
||||
|
||||
Behavior:
|
||||
|
||||
- validate and own explicit route CIDRs;
|
||||
- reject conflicting routes without replacing host state;
|
||||
- distinguish attached from managed privilege requirements;
|
||||
- fail before partial setup when support is clearly absent; and
|
||||
- retain kernel errors while adding actionable capability context.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff;
|
||||
- fake-backed pyroute2 route and rollback tests;
|
||||
- deterministic platform/dependency/capability cases;
|
||||
- real unprivileged namespace probe where available; and
|
||||
- full test collection.
|
||||
|
||||
### Commit 4: Open complete endpoint trees
|
||||
|
||||
This is the first commit exporting the resource-owning coordinator;
|
||||
the lower safety, route and privilege contracts land first.
|
||||
|
||||
Files:
|
||||
|
||||
- extend `tractor/net/_bootstrap.py` and lazy exports;
|
||||
- extend `tests/net/test_bootstrap.py`; and
|
||||
- add Prompt-IO provenance.
|
||||
|
||||
Behavior:
|
||||
|
||||
- add `EndpointBinding` and `open_endpoint_tree()`;
|
||||
- privately snapshot plans before any checkpoint;
|
||||
- realize managed and attached layers eagerly;
|
||||
- verify identities and recursively rebuild canonical declarations
|
||||
with bindspace refs on every wrapper;
|
||||
- publish only after complete setup; and
|
||||
- preserve primary errors through reverse-order cleanup.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff;
|
||||
- lifecycle and caller-mutation schedules;
|
||||
- lower bindspace/WG suites; and
|
||||
- full test collection.
|
||||
|
||||
### Commit 5: Compose a named actor tree
|
||||
|
||||
Files:
|
||||
|
||||
- add focused root/Trio endpoint-tree integration coverage;
|
||||
- update `docs/api/net.rst`;
|
||||
- update the two-host WG example; and
|
||||
- add Prompt-IO provenance as required by code/test changes.
|
||||
|
||||
Behavior:
|
||||
|
||||
- demonstrate root and child argument projection;
|
||||
- preserve root realized declarations for diagnostics;
|
||||
- send concrete child tuples through `SpawnSpec`;
|
||||
- keep endpoint resources outside root and nursery lifetimes; and
|
||||
- leave low-level root, nursery and `SpawnSpec` signatures unchanged.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff;
|
||||
- real root/Trio netns composition with fake WG provisioning;
|
||||
- docs build; and
|
||||
- the affected root, spawn, IPC and network matrix.
|
||||
|
||||
### Commit 6: Prove the real WG dataplane
|
||||
|
||||
Files:
|
||||
|
||||
- add a Linux system test and isolated namespace fixture;
|
||||
- add or update a capable CI job after explicit approval;
|
||||
- document prerequisites and precise skip reasons; and
|
||||
- add Prompt-IO provenance.
|
||||
|
||||
Behavior:
|
||||
|
||||
- provision the disposable veth/WG topology;
|
||||
- prove handshake, discovery and RPC through WG;
|
||||
- exercise routes and live bindspace FD propagation; and
|
||||
- prove complete teardown without privilege escalation.
|
||||
|
||||
Checks:
|
||||
|
||||
- Ruff;
|
||||
- a successful run in an approved capable Linux environment;
|
||||
- ordinary CI collection and precise unavailable-feature skips;
|
||||
- docs and package builds; and
|
||||
- the full supported CI matrix.
|
||||
|
||||
## Deferred work
|
||||
|
||||
The following items remain outside this patch set:
|
||||
|
||||
- recursive endpoint tables and hierarchical actor-name matching;
|
||||
- multiple concurrent actors claiming one fixed name/profile;
|
||||
- multiprocessing alternate-bindspace FD transport;
|
||||
- serializing tunnel declarations through `SpawnSpec` for child-side
|
||||
diagnostic retention;
|
||||
- precise child bootstrap exceptions before IPC handshake;
|
||||
- strict privilege drop with a dedicated `wgman` or helper process;
|
||||
- shared-resource caching and dynamic lazy profile realization;
|
||||
- production veth, VRF, VXLAN and other bindspace kinds;
|
||||
- native tagged `TunnelledAddress` graph decoding; and
|
||||
- replacing the temporary py-multiaddr VCS pin after a `/wg/` release.
|
||||
|
||||
## Review gates before implementation
|
||||
|
||||
Human review should confirm:
|
||||
|
||||
1. public names for layer config, endpoint config, plan and binding
|
||||
models plus `plan_endpoints()` and `open_endpoint_tree()`;
|
||||
2. flat exact-name semantics and caller-enforced single-instance use;
|
||||
3. one bindspace and one common declared stack per actor process;
|
||||
4. separate parsed identity and local interface/netns realization;
|
||||
5. explicit attach/open interface ownership, including the external
|
||||
lifetime precondition for attached links;
|
||||
6. eager whole-table realization instead of a concurrent lazy cache;
|
||||
7. explicit local WG config and routes, with no inferred secrets or
|
||||
host routing policy;
|
||||
8. unchanged low-level root, nursery and `SpawnSpec` contracts;
|
||||
9. privilege diagnostics with strict capability drop deferred to a
|
||||
helper architecture; and
|
||||
10. the capable Linux environment required to claim dataplane proof.
|
||||
|
||||
Implementation begins only after this plan is reviewed and explicitly
|
||||
approved.
|
||||
Loading…
Reference in New Issue