carlos / platform Public

region HA 2a: publish the box's validated private address and only what its source reports

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 995223963de2fd8ee2fa12916a67ce2736f01685 parent 2792f1f
10 files changed, +418 −5
  • internal/edgesfile/private.go +34 −0
  • internal/edgesfile/private_test.go +35 −0
  • internal/host/awsaddr.go +57 −0
  • internal/host/boxaddress.go +76 −0
  • internal/host/boxaddress_test.go +179 −0
  • internal/host/edgesclaims.go +6 −1
  • internal/host/edgesclaims_test.go +5 −0
  • internal/host/edgesfile.go +17 −1
  • internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv +4 −2
  • region_ha_boundary_test.go +5 −1
diff --git a/internal/edgesfile/private.go b/internal/edgesfile/private.go
new file mode 100644
index 000000000..835f0ec45
--- /dev/null
+++ b/internal/edgesfile/private.go
@@ -0,0 +1,34 @@
+package edgesfile
+
+import (
+ "net"
+ "strings"
+)
+
+// PrivateAddress validates a box's private address against its region
+// network's CIDR blocks (region HA spec 2 §6, census ruling region-ha-2a):
+// a well-formed IPv4 address, in a private range, neither loopback nor
+// link-local (169.254.0.0/16, the metadata range, included), and inside
+// one of cidrs. It returns the address and the block that holds it, both
+// canonical, or "", "". The writer publishes only what it passes, and a
+// reader dials only what it passes against its own region's network.
+func PrivateAddress(addr string, cidrs []string) (string, string) {
+ ip := net.ParseIP(strings.TrimSpace(addr))
+ if ip == nil || ip.To4() == nil {
+ return "", ""
+ }
+ ip = ip.To4()
+ if !ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
+ return "", ""
+ }
+ for _, c := range cidrs {
+ _, block, err := net.ParseCIDR(strings.TrimSpace(c))
+ if err != nil || block.IP.To4() == nil {
+ continue
+ }
+ if block.Contains(ip) {
+ return ip.String(), block.String()
+ }
+ }
+ return "", ""
+}
diff --git a/internal/edgesfile/private_test.go b/internal/edgesfile/private_test.go
new file mode 100644
index 000000000..8007e512e
--- /dev/null
+++ b/internal/edgesfile/private_test.go
@@ -0,0 +1,35 @@
+package edgesfile
+
+import "testing"
+
+// Census ruling region-ha-2a: a private address passes only when it is a
+// well-formed private IPv4 address inside the region network's CIDR, and
+// never loopback or link-local (the metadata range included).
+func TestPrivateAddress(t *testing.T) {
+ vpc := []string{"10.0.0.0/16"}
+ cases := []struct {
+ name, addr string
+ cidrs []string
+ want, cidr string
+ }{
+ {"inside the region network", "10.0.1.5", vpc, "10.0.1.5", "10.0.0.0/16"},
+ {"inside the second block", "172.31.4.9", []string{"10.0.0.0/16", " 172.31.0.0/16 "}, "172.31.4.9", "172.31.0.0/16"},
+ {"malformed", "10.0.1", vpc, "", ""},
+ {"a hostname", "box.internal", vpc, "", ""},
+ {"the metadata range", "169.254.169.254", []string{"169.254.0.0/16"}, "", ""},
+ {"loopback", "127.0.0.1", []string{"127.0.0.0/8"}, "", ""},
+ {"outside the region network", "10.1.1.5", vpc, "", ""},
+ {"a public address", "203.0.113.4", []string{"203.0.113.0/24"}, "", ""},
+ {"IPv6", "fd00::5", []string{"fd00::/8"}, "", ""},
+ {"no network to check it against", "10.0.1.5", nil, "", ""},
+ {"a malformed network", "10.0.1.5", []string{"10.0.0.0/33"}, "", ""},
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ got, cidr := PrivateAddress(c.addr, c.cidrs)
+ if got != c.want || cidr != c.cidr {
+ t.Fatalf("PrivateAddress(%q, %v) = %q, %q; want %q, %q", c.addr, c.cidrs, got, cidr, c.want, c.cidr)
+ }
+ })
+ }
+}
diff --git a/internal/host/awsaddr.go b/internal/host/awsaddr.go
new file mode 100644
index 000000000..812f53759
--- /dev/null
+++ b/internal/host/awsaddr.go
@@ -0,0 +1,57 @@
+package host
+
+import (
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/carlosframework/platform/internal/edgesfile"
+)
+
+// imdsAddresses is the AWS adapter of AddressSource (region HA spec 2
+// §25): IMDSv2's local-ipv4, public-ipv4, and the VPC's CIDR blocks for
+// the primary interface (network/interfaces/macs/<mac>/vpc-ipv4-cidr-blocks),
+// read on every call. IMDS reports only addresses this instance holds, so
+// once the region's EIP has moved to the load balancer no box reports it
+// (A1-12). A leaf that does not answer 200 is blank.
+type imdsAddresses struct {
+ token func() string
+ leaf func(token, path string) (string, int)
+}
+
+func newIMDSAddresses() imdsAddresses {
+ client := &http.Client{Timeout: 2 * time.Second}
+ return imdsAddresses{
+ token: func() string { return imdsToken(client) },
+ leaf: func(token, path string) (string, int) { return imdsLeaf(client, token, path) },
+ }
+}
+
+func (s imdsAddresses) BoxAddresses() BoxAddresses {
+ token := s.token()
+ if token == "" {
+ return BoxAddresses{}
+ }
+ read := func(path string) string {
+ v, code := s.leaf(token, path)
+ if code != http.StatusOK {
+ return ""
+ }
+ return v
+ }
+ out := BoxAddresses{Public: read("/latest/meta-data/public-ipv4")}
+ ip := read("/latest/meta-data/local-ipv4")
+ mac := read("/latest/meta-data/mac")
+ if ip == "" || mac == "" || strings.ContainsAny(mac, "/\\ ") {
+ return out
+ }
+ blocks := strings.Fields(read("/latest/meta-data/network/interfaces/macs/" + mac + "/vpc-ipv4-cidr-blocks"))
+ out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(ip, blocks)
+ return out
+}
+
+// boxAddressSource is what the edges file publisher reads with region HA
+// on: host.env's settings, then IMDS on EC2.
+func boxAddressSource(getenv func(string) string) AddressSource {
+ return envAddresses{getenv: getenv, provider: newIMDSAddresses()}
+}
diff --git a/internal/host/boxaddress.go b/internal/host/boxaddress.go
new file mode 100644
index 000000000..94ea6d6f4
--- /dev/null
+++ b/internal/host/boxaddress.go
@@ -0,0 +1,76 @@
+package host
+
+import (
+ "net"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/edgesfile"
+)
+
+// BoxAddresses are the addresses this box publishes in its edges file for
+// other edges to dial (region HA spec 2 §6). Public is for an edge in
+// another region. Private, with PrivateCIDR (the region network it lies
+// in), is for an edge in the same region. They are routing data only: an
+// edge dials them, and nothing grants anything on them. "" is an address
+// the source could not report, or one that failed validation, and nothing
+// is published for it.
+type BoxAddresses struct {
+ Private string
+ PrivateCIDR string
+ Public string
+}
+
+// AddressSource reads this box's addresses afresh on every call. The edges
+// file publisher asks it before every publication and publishes only what
+// it reports, so an address the box no longer holds is never republished.
+// Every source is root-configured: the cloud's metadata service, or
+// host.env. None reads a file the agent can write.
+type AddressSource interface {
+ BoxAddresses() BoxAddresses
+}
+
+// envAddresses is the provider-neutral source (§25), from host.env:
+// CARLOS_PRIVATE_IP with CARLOS_PRIVATE_CIDR, and CARLOS_PUBLIC_IP. A field
+// whose setting is present is the answer for that field, and a value that
+// does not validate publishes nothing: an operator's setting is never
+// silently replaced. CARLOS_PRIVATE_IP without CARLOS_PRIVATE_CIDR is
+// invalid (census ruling region-ha-2a). A field left unset comes from
+// provider, the cloud's adapter, when there is one.
+type envAddresses struct {
+ getenv func(string) string
+ provider AddressSource
+}
+
+func (s envAddresses) BoxAddresses() BoxAddresses {
+ ip := strings.TrimSpace(s.getenv("CARLOS_PRIVATE_IP"))
+ cidr := strings.TrimSpace(s.getenv("CARLOS_PRIVATE_CIDR"))
+ public := strings.TrimSpace(s.getenv("CARLOS_PUBLIC_IP"))
+ privateSet, publicSet := ip != "" || cidr != "", public != ""
+ out := BoxAddresses{}
+ if privateSet {
+ out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(ip, []string{cidr})
+ }
+ if publicSet {
+ out.Public = ipv4Only(public)
+ }
+ if (!privateSet || !publicSet) && s.provider != nil {
+ p := s.provider.BoxAddresses()
+ if !privateSet {
+ out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(p.Private, []string{p.PrivateCIDR})
+ }
+ if !publicSet {
+ out.Public = ipv4Only(p.Public)
+ }
+ }
+ return out
+}
+
+// ipv4Only is s when it is an IPv4 address, in its canonical form, and ""
+// otherwise.
+func ipv4Only(s string) string {
+ ip := net.ParseIP(strings.TrimSpace(s))
+ if ip == nil || ip.To4() == nil {
+ return ""
+ }
+ return ip.To4().String()
+}
diff --git a/internal/host/boxaddress_test.go b/internal/host/boxaddress_test.go
new file mode 100644
index 000000000..8a36599f2
--- /dev/null
+++ b/internal/host/boxaddress_test.go
@@ -0,0 +1,179 @@
+package host
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+// staticAddresses is a fixed AddressSource.
+type staticAddresses BoxAddresses
+
+func (a staticAddresses) BoxAddresses() BoxAddresses { return BoxAddresses(a) }
+
+// countingAddresses counts reads, answering each from its list in turn.
+type countingAddresses struct {
+ answers []BoxAddresses
+ reads int
+}
+
+func (c *countingAddresses) BoxAddresses() BoxAddresses {
+ a := c.answers[min(c.reads, len(c.answers)-1)]
+ c.reads++
+ return a
+}
+
+func envOf(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
+
+// The core source works with no provider adapter at all (§25).
+func TestEnvAddressesWithoutAProvider(t *testing.T) {
+ got := envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": " 10.0.1.5 ", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"})}.BoxAddresses()
+ if got != (BoxAddresses{Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "198.51.100.7"}) {
+ t.Fatalf("got %+v", got)
+ }
+ if got := (envAddresses{getenv: envOf(nil)}).BoxAddresses(); got != (BoxAddresses{}) {
+ t.Fatalf("nothing set: %+v", got)
+ }
+ // CARLOS_PRIVATE_IP needs CARLOS_PRIVATE_CIDR beside it.
+ if got := (envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "10.0.1.5"})}).BoxAddresses(); got.Private != "" {
+ t.Fatalf("a private address with no network: %+v", got)
+ }
+ got = envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "169.254.169.254", "CARLOS_PRIVATE_CIDR": "169.254.0.0/16", "CARLOS_PUBLIC_IP": "box.example.com"})}.BoxAddresses()
+ if got != (BoxAddresses{}) {
+ t.Fatalf("invalid settings: %+v", got)
+ }
+}
+
+func TestEnvAddressesOverrideTheProviderFieldByField(t *testing.T) {
+ provider := &countingAddresses{answers: []BoxAddresses{{Private: "10.0.9.9", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"}}}
+ got := envAddresses{getenv: envOf(map[string]string{"CARLOS_PUBLIC_IP": "198.51.100.7"}), provider: provider}.BoxAddresses()
+ if got != (BoxAddresses{Private: "10.0.9.9", PrivateCIDR: "10.0.0.0/16", Public: "198.51.100.7"}) {
+ t.Fatalf("got %+v", got)
+ }
+ // Both set: the provider is not asked.
+ provider.reads = 0
+ envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "10.0.1.5", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"}), provider: provider}.BoxAddresses()
+ if provider.reads != 0 {
+ t.Fatalf("provider asked %d times with both settings present", provider.reads)
+ }
+ // A bad private setting publishes no private address; the provider's
+ // answer does not replace it.
+ got = envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "not-an-ip", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16"}), provider: provider}.BoxAddresses()
+ if got != (BoxAddresses{Public: "203.0.113.9"}) {
+ t.Fatalf("bad setting: %+v", got)
+ }
+ // The provider's answer is validated too.
+ bad := &countingAddresses{answers: []BoxAddresses{{Private: "10.9.9.9", PrivateCIDR: "10.0.0.0/16"}}}
+ if got := (envAddresses{getenv: envOf(nil), provider: bad}).BoxAddresses(); got.Private != "" {
+ t.Fatalf("an out-of-network provider address: %+v", got)
+ }
+}
+
+func TestIMDSAddressesReadsTheLeavesAndTheVPCNetwork(t *testing.T) {
+ leaves := map[string]string{
+ "/latest/meta-data/local-ipv4": "10.0.1.5",
+ "/latest/meta-data/public-ipv4": "203.0.113.4",
+ "/latest/meta-data/mac": "0e:12:34:56:78:9a",
+ "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks": "172.31.0.0/16\n10.0.0.0/16",
+ }
+ s := imdsAddresses{token: func() string { return "tok" }, leaf: func(token, path string) (string, int) {
+ if token != "tok" {
+ t.Fatalf("token %q", token)
+ }
+ v, ok := leaves[path]
+ if !ok {
+ return "", http.StatusNotFound
+ }
+ return v, http.StatusOK
+ }}
+ if got := s.BoxAddresses(); got != (BoxAddresses{Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.4"}) {
+ t.Fatalf("got %+v", got)
+ }
+ // An address outside the VPC's blocks is not published.
+ leaves["/latest/meta-data/local-ipv4"] = "10.9.1.5"
+ if got := s.BoxAddresses(); got.Private != "" || got.PrivateCIDR != "" {
+ t.Fatalf("out of network: %+v", got)
+ }
+ // No VPC network answer: no private address.
+ leaves["/latest/meta-data/local-ipv4"] = "10.0.1.5"
+ delete(leaves, "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks")
+ if got := s.BoxAddresses(); got.Private != "" {
+ t.Fatalf("no network: %+v", got)
+ }
+ // No public address (a box without one, or the EIP gone): blank.
+ delete(leaves, "/latest/meta-data/public-ipv4")
+ if got := s.BoxAddresses(); got.Public != "" {
+ t.Fatalf("no public leaf: %+v", got)
+ }
+ noToken := imdsAddresses{token: func() string { return "" }, leaf: s.leaf}
+ if got := noToken.BoxAddresses(); got != (BoxAddresses{}) {
+ t.Fatalf("no token: %+v", got)
+ }
+}
+
+func readEdgesFile(t *testing.T, s release.Store, box string) edgesFile {
+ t.Helper()
+ body, _, err := s.Get(context.Background(), edgesFileKey(box))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var f edgesFile
+ if err := json.Unmarshal(body, &f); err != nil {
+ t.Fatal(err)
+ }
+ return f
+}
+
+// Region HA spec 2 §6 (A1-12): with region HA on, every publication reads
+// the address source afresh, and a blank read publishes no address. The
+// last published EIP is never held: it may be the region's, which the
+// load balancer has taken.
+func TestEdgesFilePublishesOnlyWhatTheSourceReports(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "app.example.com")
+ claims := newEdgesClaims("annie", (&claimsBook{}).read)
+ src := &countingAddresses{answers: []BoxAddresses{
+ {Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"},
+ {Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16"},
+ }}
+ claims.addrs = src
+ var last []byte
+ ident := edgesFile{Box: "annie", BoxID: "annie"}
+ // The loop's own reading (the eip argument) is not what is published.
+ if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "198.51.100.1", &last, false, claims); err != nil {
+ t.Fatal(err)
+ }
+ if f := readEdgesFile(t, s, "annie"); f.EIP != "203.0.113.9" || f.PrivateIP != "10.0.1.5" || f.PrivateCIDR != "10.0.0.0/16" {
+ t.Fatalf("first publication: %+v", f)
+ }
+ if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, false, claims); err != nil {
+ t.Fatal(err)
+ }
+ if f := readEdgesFile(t, s, "annie"); f.EIP != "" || f.PrivateIP != "10.0.1.5" {
+ t.Fatalf("after a blank public read: eip %q private %q, want no public address", f.EIP, f.PrivateIP)
+ }
+ if src.reads != 2 {
+ t.Fatalf("source read %d times, want once per publication", src.reads)
+ }
+}
+
+// With region HA off there is no claims cache, and the publisher is
+// main's: no private address, and a blank probe holds the last EIP.
+func TestEdgesFileWithRegionHAOffKeepsTheEIPHold(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "app.example.com")
+ var last []byte
+ ident := edgesFile{Box: "annie"}
+ if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "203.0.113.9", &last, false, nil); err != nil {
+ t.Fatal(err)
+ }
+ if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, true, nil); err != nil {
+ t.Fatal(err)
+ }
+ if f := readEdgesFile(t, s, "annie"); f.EIP != "203.0.113.9" || f.PrivateIP != "" || f.PrivateCIDR != "" {
+ t.Fatalf("%+v", f)
+ }
+}
diff --git a/internal/host/edgesclaims.go b/internal/host/edgesclaims.go
index 35a38250f..96749ffdc 100644
--- a/internal/host/edgesclaims.go
+++ b/internal/host/edgesclaims.go
@@ -4,6 +4,7 @@ import (
"context"
"log"
"math"
+ "os"
"sort"
"time"
@@ -71,11 +72,15 @@ type edgesClaims struct {
passDeadline time.Duration
logf func(format string, a ...any)
failingRun int // consecutive passes with a failed claim read
+ // addrs is the box's own addresses, read before every publication
+ // (region HA spec 2 §6). It rides here because this cache exists
+ // exactly when region HA is on.
+ addrs AddressSource
}
func newEdgesClaims(self string, read func(context.Context, string) (hostclaim.Observed, error)) *edgesClaims {
return &edgesClaims{self: self, read: read, now: time.Now, entries: map[string]edgesClaimEntry{},
- passDeadline: edgesClaimPassDeadline, logf: log.Printf}
+ passDeadline: edgesClaimPassDeadline, logf: log.Printf, addrs: boxAddressSource(os.Getenv)}
}
// relocatableRow is spec 1 §2's relocatable host as a registry row shows
diff --git a/internal/host/edgesclaims_test.go b/internal/host/edgesclaims_test.go
index 5cdbff15d..bf0a75c87 100644
--- a/internal/host/edgesclaims_test.go
+++ b/internal/host/edgesclaims_test.go
@@ -50,6 +50,11 @@ func serving(exec string, stamp uint64) *hostclaim.Claim {
func publishWithClaims(t *testing.T, reg *registry.Registry, claims *edgesClaims) map[string]edgeHost {
t.Helper()
+ // These tests are about the hosts; the box's addresses come from a
+ // fixed source, never from IMDS. nil claims is region HA off.
+ if claims != nil {
+ claims.addrs = staticAddresses{Public: "1.2.3.4"}
+ }
st := release.NewDirStore(t.TempDir())
var last []byte
if err := edgesFileWriteClaims(context.Background(), st, reg, edgesFile{Box: "annie", BoxID: "annie"}, "1.2.3.4", &last, false, claims); err != nil {
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go
index b0112d9f1..c188a054a 100644
--- a/internal/host/edgesfile.go
+++ b/internal/host/edgesfile.go
@@ -205,6 +205,13 @@ type edgesFile struct {
Box string `json:"box"`
EIP string `json:"eip"`
Region string `json:"region,omitempty"`
+ // PrivateIP is the address an edge in this box's region dials (region
+ // HA spec 2 §6), and PrivateCIDR the region network that holds it,
+ // both validated by edgesfile.PrivateAddress before they are written.
+ // Routing data only. Additive and omitempty: a reader that does not know
+ // them, or finds them absent, dials EIP, as before.
+ PrivateIP string `json:"private_ip,omitempty"`
+ PrivateCIDR string `json:"private_cidr,omitempty"`
// Placement is this box's CARLOS placement code — CARLOS_REGION verbatim
// ("ie", "uk"), the OTHER region vocabulary (see Region above, issue
// #329). Additive and omitempty like Region: a pre-placement body parses
@@ -591,7 +598,15 @@ func edgesFileWriteClaims(ctx context.Context, s release.Store, reg *registry.Re
hosts[key] = edgeHost{Role: role, Wildcard: true, Alias: alias, Account: acct, App: app, Stamp: stamp}
}
- if eip == "" && *last != nil {
+ var private BoxAddresses
+ if claims != nil && claims.addrs != nil {
+ // Region HA spec 2 §6 (A1-12): publish only what the box's address
+ // source reports now. A blank read publishes no address, never the
+ // last one: that may be the region's EIP, which the load balancer
+ // has since taken.
+ private = claims.addrs.BoxAddresses()
+ eip = private.Public
+ } else if eip == "" && *last != nil {
var prior edgesFile
if err := json.Unmarshal(*last, &prior); err == nil {
eip = prior.EIP
@@ -600,6 +615,7 @@ func edgesFileWriteClaims(ctx context.Context, s release.Store, reg *registry.Re
f := ident
f.V, f.EIP, f.Hosts, f.Generation = 1, eip, hosts, ""
+ f.PrivateIP, f.PrivateCIDR = private.Private, private.PrivateCIDR
gateCopy := f
gateCopy.Headroom = nil // a changing reading is not, by itself, a reason to write
gate, err := json.Marshal(gateCopy)
diff --git a/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv b/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
index a9f4b4b33..0088c44b0 100644
--- a/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
+++ b/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
@@ -4,9 +4,11 @@
# Task 4: the placement policy (internal/boxchoice policy.go; headroom totals and the edges file's generation carried through Eligible and edgesfile.Snapshot.BoxChoice). Envelope-only: no root-read unit changed or was added, and the legacy seal stands.
# Task 5: the edges file's headroom carries the box's total memory and data volume size (headroomFor and capacityMiB, internal/host/headroom.go; edgeHeadroom's two additive fields). headroomFor calls the existing readMemBytes and diskBytes and adds no read of its own. Envelope-only: readHeadroom and readMemBytes are unchanged, no root-read unit changed or was added, and the legacy seal stands.
# Task 6: hostops.ExecutorChooser applies spec 2's policy in Choose and Permits (creates and relocation targets) and names the placeable set in PlaceableBoxes and Capacity. ExecutorChooser.eligible, the accepted-legacy unit (merge ruling 2), is unchanged: the policy runs over its result. Envelope-only, and the legacy seal stands.
+# Task 7: the box's own addresses (internal/host boxaddress.go, the provider-neutral source over CARLOS_PRIVATE_IP with CARLOS_PRIVATE_CIDR, and CARLOS_PUBLIC_IP, all from root-owned host.env; awsaddr.go, the IMDS adapter, which reads the VPC's CIDR blocks too) and the edges file's private_ip and private_cidr, validated before they are written by edgesfile.PrivateAddress (census ruling region-ha-2a, condition 1). edgesFileWriteClaims (authority, F,J1) is re-digested with its class, task, basis and sites unchanged: with region HA on it reads the address source before every publication and no longer holds a blank EIP. It adds no registry, /data, release or claim read, and nothing runs as root that did not before; the legacy seal stands.
@coverage internal/lbhealth ad1211a01a4eb3d02cd8ed95cbff0f8b17b3976ce9c0279ebd2cae2f20c07124
@coverage internal/hostactors 6736698380a247764ada687f2476f89382fc32a1183a50c06afe75967d716eab
@coverage internal/boxchoice 1bfd561bcb48b51e60e63f99e086fd44d3b1be2160e28b3ed0253a14bbb9ff66
-@coverage internal/edgesfile da42c799d62e065817b5cce4f9a3ead1308dbd379615a85a7b14533c16d7e804
-@coverage internal/host ac55f3fa73d0e0c6d5979e446b2accdd2863b06a93c54d2006f6b547f840abd0
+@coverage internal/edgesfile f62c3973bb48fa36f561d26f496c0ba9a14fe882fe5843867e9f7a9eaf0abbdf
+@coverage internal/host 38011bf89a668507ccd9bfac2430c431dd4b3d9ca6ad4ebfe88efc7be41b453b
@coverage internal/hostops 3dc23aaff7bdae0341a8c9a6fbac076a2f57ca1e4676d83543433ff9ad7d1c79
+internal/host/edgesfile.go edgesFileWriteClaims authority F,J1 root-route;kept=F;scope=protected-decision-only registry:Account@10835,registry:Alias@5297,registry:Alias@5332,registry:Alias@5754,registry:Alias@5911,registry:Alias@5981,registry:Alias@6013,registry:Alias@6482,registry:Alias@8772,registry:App@10847,registry:DNSStatus@5429,registry:DNSStatus@5920,registry:Host@10480,registry:Host@2878,registry:Host@5389,registry:Host@6234,registry:Host@6567,registry:Host@8819,registry:Host@8866,registry:Host@8916,registry:Host@8951,registry:Host@9000,registry:Platform@2921,registry:Provisioned@2935,registry:Replica@2959,registry:Status@877,whole:Route@3212,whole:Route@323,whole:Route@3505,whole:Route@351,whole:RouteAlias@5282,whole:RouteAlias@8725,whole:RoutePath@3520,whole:WildcardDomain@8905 48bad9a5f49c5fca53fcf7bd6a241576fd2cd332974114e463b2cb3832e3598d
diff --git a/region_ha_boundary_test.go b/region_ha_boundary_test.go
index 6f0ad3804..327ebcef6 100644
--- a/region_ha_boundary_test.go
+++ b/region_ha_boundary_test.go
@@ -22,11 +22,15 @@ var regionHACoreFiles = []string{
"internal/lbhealth/lbhealth.go",
"internal/hostactors/roles.go",
"internal/boxchoice/policy.go",
+ "internal/host/boxaddress.go",
+ "internal/edgesfile/private.go",
}
// regionHAAdapterFiles are spec 2's AWS adapters. Each is named for AWS so
// a reader can see where the provider lives.
-var regionHAAdapterFiles = []string{}
+var regionHAAdapterFiles = []string{
+ "internal/host/awsaddr.go",
+}
// regionHAPackages are the packages spec 2 touches. Every S3 client built
// in them goes through s3compat.Options(), so CARLOS_S3_ENDPOINT applies.