8000 docs(specs): add the enrolled box host egress spec (EHE) by norrietaylor · Pull Request #1418 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

docs(specs): add the enrolled box host egress spec (EHE) - #1418

Closed
norrietaylor wants to merge 3 commits into
mainfrom
docs/spec-enrolled-host-egress
Closed

norrietaylor wants to merge 3 commits into
mainfrom
docs/spec-enrolled-host-egress

Conversation

@norrietaylor
@norrietaylor norrietaylor commented Sep 17, 2026 •
Copy link
Copy Markdown
Member

Summary

  • Adds docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md, prefix EHE, cut from the box networking spec NET (#1380) under the local-first ordering of 2026-09-15. Same epic, gominimal/inbox#646.
  • 26 requirements. Nineteen moved from NET, text and tier unchanged, former IDs recorded beside each: EHE-001, EHE-002 (NET-005, NET-008: local names under enrolment, tenant-zone names listed first); EHE-003 to EHE-009 (NET-086 to NET-092: the gateway association, source preservation, the two-identity split, the escape bound at the gateway, the baseline set, feed consumption with regression rejected and the high-water persisted); EHE-010 to EHE-013 (NET-093 to NET-096: egress_pin assertion and refusal, the ceiling); EHE-014 to EHE-016 (NET-097 to NET-099: outbound-only feed and relay reach, WireGuard over WebSocket); EHE-017 to EHE-019 (NET-100, NET-101, NET-103: control-plane address blocks, heartbeat reporting, renumbering). Seven new on 2026-09-17, EHE-020 to EHE-026, bind the bare-metal fleet story S6c added to the epic the same day (design v0.7 §7.6, §8, §10): one virtual NIC into the switch and the pin programmed before ready and never widened per box, the helper's private keys held host-side, box sources forwarded to the per-host gateway without NAT, the escape bound per VM, SSH through a ticket- or ACL-gated forwarder with no direct WireGuard endpoint advertised, association state reset on restore. The pin on a fleet host is the provider's assertion (Box Provider API §6.1), a non-goal, not a requirement.
  • Stories S6c, S10b, S10c, S11a, S11b, S15, S17. S6c is sequenced after the Linux VM-stack stories and before the remote styles. S10c (pods) carries no requirement; it is a non-goal with its destination (the Box Provider API's operator documentation).
  • Tiers: one T2 Kani harness (EHE-008, feed sequence regression, exhaustive over two 64-bit values); the rest T0.
  • Prerequisite: NET. The per-box rules, classifier, and DNS-pinned admission a host applies locally (NET-060 to NET-085) are what the association carries to the gateway.
  • Siblings: GWI #1419 (gateway ingress rides this association), MRF #1420 (mesh and remote forward), NPOL (gominimal/gatehouse#7) for the feed, allocation, and node attributes on the identity plane.
  • Architecture: design §1, §4, §5.1, §5.2, §5.5, §7, §7.6, §8, §10 (v0.7, gominimal/arch#67), architecture D8, Gatehouse §6.10 and §6.11 (v1.19), Box Provider API §6.1 (v0.10).

Open questions

CRITICAL: the node-to-gateway association contract (registration shape, heartbeat member, the feed's normativ 8000 e fields), moved here from NET with the requirements that bind against it. MEDIUM: whether the laptop hairpin opt-in (roadmap arch#43) would take EHE-003 to EHE-009 unchanged, now that the laptop's default path holds no association (design v0.7 §7.1, §11); the forwarder-ticket shape and the host agent's node class for fleet hosts (design §12 item 14, BPA open question 8).

Lint

spec_lint.py --repo-root: 26 P004 (one per requirement, pre-implementation) and one Q002 for the CRITICAL question. Nothing else.

🤖 Generated with Claude Code

Cut from the box networking spec (NET, #1380) under the local-first
ordering of 2026-09-15: the gateway association, the signed policy feed,
the pin and ceiling, control-plane address blocks, relay reach, WireGuard
over WebSocket, renumbering, and local names under enrolment. Nineteen
requirements, text and tiers unchanged, former NET IDs recorded beside
each.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai
coderabbitai Bot commented Sep 17, 2026
Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

@github-actions
github-actions Bot commented Sep 17, 2026 •
Copy link
Copy Markdown
Contributor

Spec lint

docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md — 27 finding(s)

docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:85: P004 EHE-001 names test 'min_internal_survives_enrolment', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:90: P004 EHE-002 names test 'dns_listing_prefers_tenant_zone_when_enrolled', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:95: P004 EHE-003 names test 'gateway_attached_routes_all_egress', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:100: P004 EHE-004 names test 'own_ip_source_preserved_into_association', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:105: P004 EHE-005 names test 'node_netns_flows_snat_two_addresses', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:110: P004 EHE-006 names test 'escape_bounded_at_gateway', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:115: P004 EHE-007 names test 'baseline_set_from_deployment_config', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:120: P004 EHE-008 names test 'feed_seq_regression_rejected', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:127: P004 EHE-009 names test 'feed_fail_closed_on_start', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:132: P004 EHE-010 names test 'localvm_provider_asserts_pinned', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:137: P004 EHE-011 names test 'shared_linux_asserts_advisory_or_none', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:142: P004 EHE-012 names test 'min_surfaces_pin_refusal', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:147: P004 EHE-013 names test 'ceiling_violation_names_rule', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:152: P004 EHE-014 names test 'outbound_only_host_receives_feed', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:157: P004 EHE-015 names test 'outbound_only_host_reachable_via_relay', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:162: P004 EHE-016 names test 'association_falls_back_to_wss', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:167: P004 EHE-017 names test 'enrolled_host_assigns_within_blocks', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:172: P004 EHE-018 names test 'assignments_reported_on_heartbeat', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:177: P004 EHE-019 names test 'renumber_preserves_identity', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:182: P004 EHE-020 names test 'fleet_vm_single_nic_pinned_before_ready', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:191: P004 EHE-021 names test 'fleet_helper_private_keys_stay_host_side', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:196: P004 EHE-022 names test 'fleet_box_source_forwarded_without_nat', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:201: P004 EHE-023 names test 'fleet_vm_escape_bounded_to_resident_union', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:206: P004 EHE-024 names test 'fleet_ssh_endpoint_via_gated_forwarder', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:211: P004 EHE-025 names test 'fleet_vm_advertises_no_direct_endpoint', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:216: P004 EHE-026 names test 'fleet_restore_resets_association_state', which was not found in the repository
docs/specs/21-spec-enrolled-host-egress/21-spec-enrolled-host-egress.md:393: Q002 unresolved CRITICAL open question. This does not block the merge; it is worth resolving before the work is broken down

Advisory. None of this blocks the merge; the review decides.

Advisory, and never a merge gate. Linter: gominimal/foundry@daafe17c, spec format: How Minimal Writes Specs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
norrietaylor added a commit that referenced this pull request Sep 17, 2026
Local-first ordering, 2026-09-15: NET now binds only what a box host does
with no identity plane, on a laptop with VM-backed boxes or a co-resident
Linux host. Thirty-six requirements whose precondition is enrolment, a
gateway association, a mesh, or a remote session move, unchanged in text
and tier, to three sibling specs from the same epic: EHE (#1418), GWI
(#1419), MRF (#1420). Surviving IDs are unchanged; 83 remain, with all
seven failure sub-bullets, nine Kani harnesses, and the T1 parity
property.

Context, Success, First slice, Non-goals, Design reasoning, Security
considerations, and Open questions are rewritten for the local scope.
The Context names the local Box Egress Proxy's dependencies on this
document (box-zone resolution, DNS-pinned admission, proxy parity, UDP
dropped unless declared). The host-OS resolution question is answered by
design §7.1 v0.5 and folded in; NET-018 and NET-019 take their WHERE from
its supersession condition; the macOS per-box-address mechanism stays
open per design §12 item 13; the association-contract question moves out
with EHE. NET-037 now calls the hyphenated spellings legacy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#67

Epic gominimal/inbox#646 gains S6c, the BareMetalVM story added on
2026-09-17 and sequenced after the Linux VM-stack stories and before the
remote styles. EHE-020 to EHE-026 bind the metal host's obligations from
design v0.7 §7.6, §8, and §10: one virtual NIC into the switch and the
pin programmed before ready and never widened per box, the helper's
private keys held host-side, box sources forwarded to the per-host
gateway without NAT, the escape bound per VM, SSH through a ticket- or
ACL-gated forwarder with no direct WireGuard endpoint advertised, and
association state reset on restore. All T0. The pin on a fleet host is
the provider's assertion, as on a cloud VM (decision 2026-09-17): a
non-goal, not a requirement.

Also: the laptop-association open question narrows to the hairpin
opt-in (design v0.7 §7.1, §11); the BEP-association non-goal is scoped
to the remote-BEP path; the fleet provider's side is a non-goal with its
destinations; citations bumped to design v0.7, Gatehouse v1.19, Box
Provider API v0.10.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
norrietaylor added a commit that referenced this pull request Sep 18, 2026
* docs(specs): add the box networking spec (NET)

Spec 12 binds the box host's side of the networking design from the
epic gominimal/inbox#646: names through the hostname proxy and native
resolution, network posture on the CLI, bounded egress enforced outside
the VM, gateway-attached and enrolled-host obligations, mesh join, and
the reverse-proxy retirement. 119 EARS requirements at T0 with named
tests, eleven T2 Kani cores, and one T1 property. Its identity-plane
sibling is NPOL in gominimal/gatehouse.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): renumber box-networking to 18

12 and 13 are taken by the GitHub sessions and min client core specs; 14–16
by other open spec PRs. Directory, file and in-document references move
together; content is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(specs): re-scope NET to the un-enrolled local host

Local-first ordering, 2026-09-15: NET now binds only what a box host does
with no identity plane, on a laptop with VM-backed boxes or a co-resident
Linux host. Thirty-six requirements whose precondition is enrolment, a
gateway association, a mesh, or a remote session move, unchanged in text
and tier, to three sibling specs from the same epic: EHE (#1418), GWI
(#1419), MRF (#1420). Surviving IDs are unchanged; 83 remain, with all
seven failure sub-bullets, nine Kani harnesses, and the T1 parity
property.

Context, Success, First slice, Non-goals, Design reasoning, Security
considerations, and Open questions are rewritten for the local scope.
The Context names the local Box Egress Proxy's dependencies on this
document (box-zone resolution, DNS-pinned admission, proxy parity, UDP
dropped unless declared). The host-OS resolution question is answered by
design §7.1 v0.5 and folded in; NET-018 and NET-019 take their WHERE from
its supersession condition; the macOS per-box-address mechanism stays
open per design §12 item 13; the association-contract question moves out
with EHE. NET-037 now calls the hyphenated spellings legacy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): correct NET's Linux baseline and NET-037's legacy spellings

Two review findings on the re-scope. The Context claimed Linux installs
lack the VM stack; the release manifests have shipped minvmd, the switch,
and the guest payload for linux/amd64 and linux/arm64 since #1070, so the
gap NET-049 and NET-051 bind is a fresh KVM host activating a VM-backed
box, and NET-048 and NET-050 guard shipped behaviour. NET-037 now
enumerates `no-net` beside `host-net` and `own-ip`: it is the CLI's third
kebab-case spelling and the one a saved config would otherwise fail on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): align NET with arch #67 (design v0.7, Gatehouse v1.19)

The local default path carries no WireGuard (design §7.1, §11). The Box
Egress Proxy NET's Context anticipates is now the node-local BEP of
Gatehouse §6.10 v1.19; its dependency list gains NET-084, the relay's
source-address check that lets the proxy attribute a box by switch
source. Store references and [secret-store-rules] consent are routed to
the local BEP document; the hosted BEP stays with the broker epic.
BareMetalVM, the sixth style, is named as EHE's with NET-081 to NET-085
as what a metal host reuses per VM. No requirement changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): answer the NET review findings and the BEP field asks

- NET-060 accepts `egress.allow_dns_hosts`; NET-072 spells the field the
  same way (one spelling; design §7.1's `egress_allow_dns` is a §5.4
  placeholder).
- NET-003 resolves `host.min.internal` to an address; reach over it is
  local reach under the box's egress rules, so deny-all wins (NET-079).
- NET-010 allocates from the host-global allocation; its property and
  harness range over every daemon on the host (NET-027, NET-059).
- NET-045 gains a failure case: `ask` with no client attached is refused
  with a typed error, recorded by NET-046.
- NET-120 accepts an `egress` section on a host-address box, superseding
  03-spec R2.1's HostNet parse-time error; NET-065 keeps `none`.
- NET-043 notes `dynamic_ingress` supersedes `dynamic_allowed_ports`.
- NET-085 and the escape invariant cite design §5.1 for the baseline set.
- Context limits the switch-source attribution to VM-backed hosts; the
  host-address cohort is attributed as one cohort (NET-078).
- S17 is a split story: its text is transcribed, NET-102 keeps the
  un-enrolled clause, EHE holds the enrolled ones.
- Design reasoning records each choice and the alternative rejected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): follow the arch#69 ruling on un-enrolled GitHub minting in NET's Context

The un-enrolled node-local Box Egress Proxy may redeem GitHub-module
members the min client minted from its own sign-in; only
Gatehouse-brokered grants are absent un-enrolled (owner ruling
2026-09-17, proposed in gominimal/arch#71). One clause in the Context;
no requirement changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): revise NET after review, binding §7.1's answerer and bind rules

Answers the open review threads on #1380 and the arch tip (design v0.7.1,
Gatehouse v1.20.1, command tree after gominimal/arch#72).

- NET-121 to NET-130: declared ports bound before the name; resolver
  advisory at session start; reserved-range bind probe with the 127.0.0.1
  interim; answerer semantics (NODATA, NXDOMAIN, TTL, answer confinement,
  stopped shared-address box); host-address namespace-mirror rule with
  collision reporting; un-enrolled node-plane baseline set.
- NET-060 accepts `egress.deny_subnets`; NET-067 names the denied range.
- NET-074/075 gain the in-force WHERE; NET-077 keeps the shipped allow-all
  default; three shipped rules superseded, precedence stated.
- NET-079 states its enforcement locus; NET-068 conditioned on the hosts it
  needs; NET-003 resolves to the host-reach address; NET-002 names the
  zone's general form; NET-010 scoped to own-address and `none` boxes;
  NET-016 to permit-range ports; NET-109 retires the surface, not the verb.
- NET-108 retired; NET-107 names the lane's per-mode tests.
- Design reasoning: command-tree binding, bind discipline, field-name
  attribution, precision-versus-floor split; harness stated once; links
  reference-style; provenance narration removed; roles deduplicated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): restore NET's lists and close the revision-2 review gaps

Revision 2's reference-link reflow collapsed the Non-goals and Open
questions lists into paragraphs; both are lists again. NET-010 and its
injectivity property are scoped WHERE the reserved local range is
present, so NET-123's 127.0.0.1 interim composes with it. NET-121's
failure cases carry the whole design §7.1 bind discipline: revocation
unbinds and terminates established connections, and a failed forwarder
bind is reported, never substituted. Design reasoning states that
egress.deny_subnets has no crate counterpart, that min ls is the tree's
own alias, and NET-072's comment cites box.toml for the canonical
spelling. Bare issue references are links.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(specs): scope NET-127 to the host answerer and close the owner's nits

NET-127 is the host answerer's confinement rule per design §7.1, so it
now triggers on lookups originating on the host OS; in-guest the node's
DNS layer answers with switch addresses, which NET-003 and NET-072/073
rely on. The sixth invariant's enforced-by says the same. NET-124's
NODATA rule covers held names only, leaving unknown names to NET-125.
NET-130 keeps the daemon's registry and cache endpoints in the baseline
enumeration, which is what NET-080's package fetch rests on. NET-003
says "inside boxes" without qualification, NET-123's comment records
that Linux always carries the range on lo, and the retirement non-goal
names the general zone form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@norrietaylor
norrietaylor deleted the docs/spec-enrolled-host-egress branch October 18, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0