/** * A scenario declares an UNDERLAY and what to place on it — the facts a machine would * have before any of our software touched it. It declares nothing the mesh is * responsible for: no overlay addresses, no hub, no peering, no names, no certificates. * Those are outcomes to observe, and a scenario that supplied them would be certifying * its own work. * * See novox/hq: 02-DECISIONS/0031-the-lab-provides-the-underlay.md * 03-DESIGN/01-to-be/02-scenario-declaration.md */ /** An IP family. Reachability is a property of (machine, family), never of a machine. */ export type Family = "v4" | "v6"; /** * How a segment reaches its parent. * * `address` is the address the outside world sees the network as — for a household * connection, what the ISP hands out. It is load-bearing rather than decorative: it is * what a peer records as an endpoint when a machine here dials out, and what a public * name for a published machine here resolves to. */ export interface Gateway { /** Parent segment name. */ to: string; /** Addresses the gateway holds on the parent segment, one per family. */ address: string[]; /** * Which families are translated. `["v4"]` is the modern default — v4 translated, v6 * routed. `[]` is a routed range where machines keep their own addresses. */ nat: Family[]; /** * Whether an inbound mapping can be created. Independent of `nat`, and the field that * separates a home gateway from carrier-grade NAT — which is your own connection and * still unforwardable. */ forwardable: boolean; /** * How long an unused inbound mapping survives, e.g. "120s". Absent means mappings never * expire, which no real gateway does — so absence is a simplification, not a default. */ mappingTtl?: string; } /** A broadcast domain. Several public segments are unrelated and routed, never bridged. */ export interface Segment { /** * `public` stands in for a public network — and there is normally more than one, * unrelated to each other. `private` is everything else; a private segment with no * gateway is an island that reaches nothing. */ kind: "public" | "private"; /** Address ranges, one per family. */ cidr: string[]; /** Largest packet the segment carries. Default 1500. Lower reproduces tunnelled paths. */ mtu?: number; gateway?: Gateway; } /** Where a machine sits: a segment and the addresses it holds there. */ export interface Attachment { segment: string; address: string[]; } /** A destination-NAT rule on a named gateway, stated as an outcome rather than a port list. */ export interface Publication { port: number; /** The segment whose gateway forwards. Named, because a machine may sit behind several. */ on: string; } export interface Machine { /** * One attachment, or several for a machine on multiple segments at once. Multi-homing * is not exotic: it is what any node with both a LAN and a WAN interface is. * `"detached"` is a machine on no segment — it exists and reaches nothing. */ at: Attachment[] | "detached"; published?: Publication[]; /** * A host firewall. Distinct from NAT and behaves differently: a machine can be perfectly * routable and still refuse everything unsolicited, which is the normal state of a * v6-addressed machine. Without this, v6 addressing would imply reachability. */ inbound?: "allow" | "deny"; } /** Reachability between segments, as a segmented router enforces it. Asymmetric by design. */ export interface Policy { from: string; to: string; allow: boolean; } /** What goes inside the machines. The ONLY part that differs between scenario classes. */ export interface Placement { /** Applied to every machine. */ all?: string[]; /** Per-machine, overriding `all` for that machine. */ [machine: string]: string[] | undefined; } export interface Scenario { /** The kind. Instances are many; this names the shape, not one of them. */ scenario: string; segments: Record; machines: Record; policy?: Policy[]; place?: Placement; /** Name the state once placement finishes, so a run can return to it. */ snapshot?: string; }