spec.md 17 KB

Purpose

Defines bounded, failure-safe application lifecycle behavior for resolving, initializing, joining, creating, serving, recreating, and stopping isolated swarm-managed subnets.

ADDED Requirements

Requirement: Descriptor-based resolution uses untrusted candidates

An application SHALL request a subnet using a valid descriptor, not a raw ID. The swarm SHALL derive the normative ID and request only addresses for it. Overlay results SHALL enter only an ephemeral typed unverified-target set for that subnet, preserving original URL and validated socket until compatibility. They MUST NOT enter URL-only persistent host/refinery state before success. Overlay and subnet connections MUST remain separate. Every subnet peer MUST independently pass magic-byte, application-name, and major/minor checks. An overlay channel MUST NOT be reused, transferred, re-handshaken, or multiplexed for subnet traffic. Default transient policy retains the overlay for the application session; immediate post-lookup/join stop requires explicit reduced-privacy policy and occurs after every caller-visible terminal outcome, not an internal lookup phase within join. Any overlay stop MUST NOT stop the independent subnet.

Resolution and connection phases SHALL have finite deadlines. A complete join deadline MUST be configurable and no greater than 900 seconds. Every untrusted candidate SHALL pass the overlay's resolution-time egress policy before connection and consume the configured resolution/dial concurrency, rate, per-destination, and total-candidate budgets. Rejected destinations SHALL remain fallible candidate failures and MUST NOT trigger a second resolution in the connector. Direct candidates SHALL retain the exact validated destination socket. Tor/I2P candidates SHALL retain the exact trusted locally configured proxy socket plus canonical hidden-service destination, with no local DNS or advertisement-selected proxy. Every accepted transport path SHALL reject malformed/unsupported input without unwind before or during compatibility.

Candidate ordering SHALL use two independently OsRng-shuffled bounded tiers: at most 256 URLs from the bounded previously-compatible retry index and a bounded reservoir sampled across the fresh terminal traversal through completion or its fixed 16-page cap. Persisted peers MUST be re-resolved and revalidated. After both tiers exist, the candidate-preparation subdeadline SHALL use at most half the then-remaining overall time and split resolution/validation time equally between tiers; unused persisted time MAY pass to fresh, not conversely. A two-phase pre-start target plan SHALL then split remaining dial time at a monotonic midpoint: persisted targets stop/cancel by it and consume no more than their configured limit or half the attempt budget; fresh targets activate for the second half. URL, wire, store, hash, DNS-answer, and lexical order MUST NOT choose either attempted prefix. Fresh candidates MUST NOT be persisted before compatibility succeeds.

Scenario: Descriptor-only resolution

  • WHEN lookup returns a compatible reachable peer
  • THEN the swarm attempts it through a distinct subnet connection

Scenario: Wrong-subnet candidate

  • WHEN a candidate fails any compatibility field
  • THEN it remains unverified and failure is not attributed to the relay

Scenario: Unknown subnet

  • WHEN no selected source yields a compatible ordinary peer
  • THEN join fails within its deadline and leaves no attempt running

Scenario: Candidate resolves to prohibited destination

  • WHEN an advertised clearnet candidate resolves to loopback/private/ reserved space outside explicit local-test mode
  • THEN it is rejected before dialing and join continues within its budgets

Scenario: Hidden-service candidate enters subnet connector

  • WHEN a canonical Tor/I2P candidate is selected for connection
  • THEN the connector uses the trusted configured proxy socket, passes the hidden name only in proxy/TLS protocol, and has no direct fallback

Scenario: Stored ordering is adversarial

  • WHEN returned URLs or persisted peers are arranged to control lexical or insertion order
  • THEN bounded reservoir selection/CSPRNG shuffles choose attempted prefixes and the verified tier cannot consume the fresh tier's reserved time or budget

Scenario: Default join completion retains overlay

  • WHEN a default-policy transient completes an ordinary subnet join
  • THEN join completion does not itself stop the overlay

Scenario: Explicit immediate overlay stop after join

  • WHEN reduced-privacy policy observes successful, failed, timed-out, or cancelled join completion
  • THEN subnet network/app state continue and the caller was warned about timing correlation

Requirement: Fallible initialization precedes network activity

Before each subnet attempt starts, the swarm SHALL invoke a fallible app initializer with that attempt's P2p handle. It SHALL construct subnet-scoped app state, register protocols, and return both caller-visible state and a bounded shutdown hook. No listener, connection, or protocol job SHALL start before success.

The registry SHALL retain ownership of the returned app state and shutdown hook for the active attempt's entire lifetime; dropping the caller handle MUST NOT drop required state. If initialization fails or is cancelled, partial state is released, nothing starts, and the original error is returned.

Scenario: App state precedes connection

  • WHEN initialization succeeds
  • THEN protocols and registry-owned app state exist before network start

Scenario: Caller drops handle

  • WHEN a caller drops its returned app handle while the subnet remains active
  • THEN registry ownership keeps required app state alive

Scenario: Initializer fails

  • WHEN initialization returns an error
  • THEN no network activity starts and partial state is released

Requirement: Join completion requires an ordinary persistent channel

P2p::start() alone SHALL NOT complete join. Join succeeds only after an ordinary inbound, outbound, or manual subnet channel passes the compatibility handshake and remains registered as an ordinary peer. Temporary direct, seed, and refinement channels MUST NOT complete join, even when their handshake succeeds.

Until then the registry SHALL expose a distinct joining state. Timeout, cancellation, or candidate exhaustion SHALL stop tasks and connections, run the shutdown hook, remove the attempt, and return a typed error without panicking on untrusted input.

Scenario: Seed channel does not complete join

  • WHEN seed discovery completes but no ordinary channel exists
  • THEN join remains pending

Scenario: Refinement channel does not complete join

  • WHEN a refinement probe succeeds but no ordinary channel exists
  • THEN join remains pending

Scenario: Ordinary channel completes join

  • WHEN a compatible ordinary channel completes before deadline
  • THEN state atomically becomes joined

Scenario: Failed join rolls back

  • WHEN deadline, cancellation, or failure ends an attempt
  • THEN its state is removed without disturbing overlay or other subnets

Requirement: Source policies use explicit bounded attempts

Applications SHALL select overlay-only, static-only, combined, or overlay-then-static behavior. Sources MUST NOT activate implicitly outside the selected policy.

  • Overlay-only SHALL use overlay candidates with static seeds absent.
  • Static-only SHALL use configured subnet seed discovery with overlay lookup absent.
  • Combined MAY configure both sources in one attempt.
  • Overlay-then-static SHALL complete and tear down one bounded overlay-only attempt before creating a fresh static-only P2p attempt. It SHALL rerun the initializer for the fresh attempt and remain inside one overall deadline.

No policy SHALL rely on runtime manual- or seed-session reload. Every source uses the same compatibility and ordinary-channel completion rule.

Scenario: Overlay-first succeeds

  • WHEN an ordinary overlay-discovered peer completes in the first attempt
  • THEN no static attempt starts

Scenario: Overlay-first fails and static succeeds

  • WHEN the first attempt fully rolls back under overlay-then-static
  • THEN a fresh static-configured attempt reruns initialization and may join

Scenario: First-attempt state does not leak

  • WHEN overlay-then-static creates its second attempt
  • THEN no task, channel, app state, or registry ownership from the first attempt remains

Scenario: Every source fails

  • WHEN all policy attempts fail within the overall deadline
  • THEN a typed aggregate failure is returned after complete rollback

Requirement: Per-subnet state remains isolated

Each subnet SHALL have independent hostlists, refinement, app state, tasks, datastore, and hostlist files. Persistent paths SHALL be namespaced by full ID under a configured root. No state, address outcome, dispatch, or shutdown signal may cross subnets. Deleting one subnet MUST NOT alter another.

Overlay role persistence rules apply only to overlay-owned state. Applications MAY separately configure subnet persistence for a transient overlay participant; such paths reveal local subnet history and MUST be documented. Private secrets MUST NOT be logged.

Scenario: Independent refinement

  • WHEN one address has different outcomes in A and B
  • THEN each subnet retains only its own outcome

Scenario: Namespaced persistence

  • WHEN two subnets persist under one root
  • THEN their files occupy distinct full-ID paths

Scenario: Transient persists a subnet explicitly

  • WHEN a transient overlay caller enables subnet persistence
  • THEN only that separately configured subnet state is written and its local-history implication is documented

Requirement: Serving mode is selected before initial start

A subnet SHALL be created either join-only or serving. Serving creation SHALL require a persistent overlay role, at least one local listener bind address, and at least one externally advertised endpoint assigned to that subnet. Bind addresses and advertised endpoints SHALL be separate fields and MUST NOT be assumed identical. All addresses SHALL be validated before network start.

Serving initialization SHALL configure listeners before P2p::start(). It completes when app initialization succeeds and required listeners bind; it does not require an existing peer. This permits the first member of a new subnet to serve. It may attempt peer discovery afterward under an explicit source policy. A transient SHALL reject serving before any network activity.

Before initializer, listener, or author activation, serving SHALL atomically allocate or reuse one 256-slot local-author reserve partition for the subnet. If all configured partitions are occupied by subnets with protected local IDs, serving SHALL fail with a typed capacity error. A newly allocated empty partition SHALL be released on pre-authoring initialization/bind failure. Stopping serving SHALL retain a nonempty partition until all protected local IDs expire, then release it only if the subnet has not resumed serving.

Scenario: First serving member

  • WHEN no subnet peer exists but a persistent caller supplies valid bind and advertised addresses
  • THEN the serving subnet succeeds after listener readiness without a peer handshake

Scenario: Bind and advertised endpoint differ

  • WHEN an externally provisioned endpoint forwards to a distinct local bind address
  • THEN the listener binds locally while ads contain only the external endpoint

Scenario: Transient serving is rejected

  • WHEN a transient requests serving
  • THEN failure occurs before initializer, listener, or author task starts

Scenario: Author reserve is exhausted

  • WHEN a persistent caller requests serving while every reserve partition is retained by protected local IDs for other subnets
  • THEN failure occurs before initializer, listener, or author task starts

Requirement: Serving promotion uses controlled recreation

An already started join-only subnet MUST NOT be promoted by mutating inbound settings and calling session reload. Promotion SHALL require a controlled stop/recreate operation: stop the joined instance completely, then create a new serving-configured instance and rerun initialization. The operation SHALL be serialized with other lifecycle actions and return a typed result if recreation fails. Retained state MAY be reused only from that subnet's namespace.

Listener binds and externally advertised endpoints MUST remain distinct. Swarm SHALL NOT claim automatic Tor or I2P identity provisioning. Reusing one advertised endpoint across local subnets SHALL produce an explicit linkability warning.

Scenario: Promotion does not use reload

  • WHEN a joined subnet is promoted to serving
  • THEN its old instance fully stops before a serving-configured instance starts

Scenario: Recreation fails

  • WHEN the serving listener cannot bind or initialization fails
  • THEN no partial serving instance or author task remains

Scenario: Shared endpoint warning

  • WHEN one external endpoint is assigned to two served subnets
  • THEN an explicit local linkability warning is produced

Requirement: Advertisement authoring is serving-only and cadence-only

Join-only subnets SHALL author no ads. A successfully serving subnet SHALL author bounded ads only on the overlay jittered cadence and only with its advertised endpoints. Initialization, listener readiness, peer connection, recreation, and new overlay channels MUST NOT emit immediately. Stopping serving SHALL cease future authoring without a withdrawal.

Scenario: Join-only remains silent

  • WHEN a subnet is joined without serving mode
  • THEN it authors no ad

Scenario: First server waits

  • WHEN a first serving member becomes listener-ready
  • THEN its first ad waits for the next cadence tick

Scenario: Stop is silent

  • WHEN a serving subnet stops
  • THEN no departure message is sent and existing ads expire locally

Requirement: Leave and recreation are idempotent and failure-safe

Leave SHALL disable authoring, stop network producers/channels/protocol jobs, run app shutdown, and remove registry ownership. It MUST emit no withdrawal. Repeated leave SHALL be idempotent. Same-subnet join, serve, recreate, leave, and delete operations SHALL have one serialized owner.

The caller SHALL choose to retain or delete namespaced state after shutdown. Deletion MUST occur only after stop and affect only that ID.

Scenario: Repeated leave

  • WHEN leave is called after stop
  • THEN it returns the already-stopped result without restarting work

Scenario: Retained rejoin

  • WHEN state is retained
  • THEN a later attempt may reuse only that subnet's files

Scenario: Delete after stop

  • WHEN deletion is selected
  • THEN only that namespace is deleted after shutdown

Requirement: Concurrent lifecycle remains isolated

Different subnets MAY transition concurrently; one ID SHALL have one owner. Duplicate joins MUST NOT create duplicate instances. Swarm shutdown SHALL stop authoring, cancel attempts, stop every subnet despite individual failures, and stop the overlay last within a finite deadline.

Scenario: Duplicate concurrent join

  • WHEN two callers request the same descriptor
  • THEN at most one instance is created and deterministic state is returned

Scenario: Late subnet operation

  • WHEN a new join or serving creation starts while others run
  • THEN existing subnet and overlay operation is not restarted

Scenario: One shutdown hook fails

  • WHEN one app shutdown hook returns an error
  • THEN every other subnet still receives a stop attempt and failures are aggregated without panic

Requirement: Pinned and private descriptors share lifecycle rules

Applications SHALL pin public descriptors with golden IDs and accept valid private descriptors. Private secrets MUST NOT be logged, advertised, sent in lookup, or exposed by public status; overlay messages use only the derived ID. Possession of the descriptor SHALL NOT bypass compatibility or app-level authorization.

Scenario: Pinned interoperability

  • WHEN deployments use one pinned descriptor
  • THEN they derive one ID and apply the same compatibility checks

Scenario: Private descriptor is not transmitted

  • WHEN a private subnet is resolved or created
  • THEN no overlay message contains its secret

Scenario: Private ID grants no access

  • WHEN a peer knows an ID but fails application authorization
  • THEN swarm grants no authorization