Add Cloud Map seeder discovery with VPC Lattice operator templates.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Agents poll AETHERFORGE_CLOUD_MAP_ENDPOINT to sync know_node gossip; deploy plans attach route_via DNS hints for standalone operator registries.
This commit is contained in:
AetherForge
2026-06-07 10:08:57 -07:00
parent 990105f7bf
commit c12565c83d
17 changed files with 650 additions and 3 deletions

View File

@@ -430,6 +430,7 @@ func (c *AgentClient) authenticate() error {
c.startContingencyIfEnabled(c.miningCtx) c.startContingencyIfEnabled(c.miningCtx)
c.applyAuthFleetRole(resp) c.applyAuthFleetRole(resp)
deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip) deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip)
c.startCloudMapSync()
if resp.FleetTorrentEnabled { if resp.FleetTorrentEnabled {
c.advertiseFleetTorrentHealthy() c.advertiseFleetTorrentHealthy()
} }

41
agent/client/cloud_map.go Normal file
View File

@@ -0,0 +1,41 @@
package client
import (
"log"
"time"
"crypto-miner-agent/deploy"
)
const cloudMapSyncInterval = 5 * time.Minute
func (c *AgentClient) startCloudMapSync() {
endpoint := deploy.CloudMapEndpoint()
if endpoint == "" {
return
}
go c.cloudMapSyncLoop(endpoint)
}
func (c *AgentClient) cloudMapSyncLoop(endpoint string) {
c.syncCloudMapOnce(endpoint)
ticker := time.NewTicker(cloudMapSyncInterval)
defer ticker.Stop()
for {
select {
case <-c.miningCtx.Done():
return
case <-ticker.C:
c.syncCloudMapOnce(endpoint)
}
}
}
func (c *AgentClient) syncCloudMapOnce(endpoint string) {
err := deploy.SyncCloudMapRegistry(endpoint, c.writeFleetTorrentGossip)
if err != nil {
log.Printf("[agent] cloud map registry sync failed: %v", err)
return
}
log.Printf("[agent] cloud map registry synced from %s", endpoint)
}

157
agent/deploy/cloud_map.go Normal file
View File

@@ -0,0 +1,157 @@
package deploy
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
const cloudMapEnvEndpoint = "AETHERFORGE_CLOUD_MAP_ENDPOINT"
type CloudMapRegistryDocument struct {
Namespace string `json:"namespace"`
Service string `json:"service"`
UpdatedAt string `json:"updated_at"`
Instances []CloudMapRegistryInstance `json:"instances"`
}
type CloudMapRegistryInstance struct {
AgentID string `json:"agent_id"`
DNSName string `json:"dns_name"`
IP string `json:"ip"`
FetchURL string `json:"fetch_url"`
Port int `json:"port"`
Healthy bool `json:"healthy"`
}
var cloudMapHTTPGet = func(url string) ([]byte, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("cloud map registry HTTP %d", resp.StatusCode)
}
return io.ReadAll(io.LimitReader(resp.Body, 1<<20))
}
func SetCloudMapHTTPGetForTest(fn func(string) ([]byte, error)) {
if fn == nil {
cloudMapHTTPGet = func(url string) ([]byte, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("cloud map registry HTTP %d", resp.StatusCode)
}
return io.ReadAll(io.LimitReader(resp.Body, 1<<20))
}
return
}
cloudMapHTTPGet = fn
}
func CloudMapEndpoint() string {
return strings.TrimRight(strings.TrimSpace(os.Getenv(cloudMapEnvEndpoint)), "/")
}
func normalizeCloudMapRegistry(doc CloudMapRegistryDocument) CloudMapRegistryDocument {
doc.Namespace = strings.TrimSpace(doc.Namespace)
doc.Service = strings.TrimSpace(doc.Service)
if doc.Namespace == "" {
doc.Namespace = "prod.local"
}
if doc.Service == "" {
doc.Service = "seeder"
}
return doc
}
func FetchCloudMapRegistry(endpoint string) (CloudMapRegistryDocument, error) {
endpoint = strings.TrimRight(strings.TrimSpace(endpoint), "/")
if endpoint == "" {
return CloudMapRegistryDocument{}, fmt.Errorf("cloud map endpoint required")
}
body, err := cloudMapHTTPGet(endpoint)
if err != nil {
return CloudMapRegistryDocument{}, err
}
var doc CloudMapRegistryDocument
if err := json.Unmarshal(body, &doc); err != nil {
return CloudMapRegistryDocument{}, err
}
return normalizeCloudMapRegistry(doc), nil
}
func CloudMapKnowNodeRecords(doc CloudMapRegistryDocument) []FleetGossipRecord {
doc = normalizeCloudMapRegistry(doc)
var out []FleetGossipRecord
seen := make(map[string]bool)
for _, inst := range doc.Instances {
if !inst.Healthy {
continue
}
target := strings.TrimSpace(inst.AgentID)
if target == "" {
target = strings.TrimSpace(inst.DNSName)
}
if target == "" || seen[target] {
continue
}
seen[target] = true
out = append(out, FleetGossipRecord{
Kind: FleetGossipKnowNode,
TargetAgentID: target,
Healthy: true,
})
}
return out
}
func CloudMapLANSeederHints(doc CloudMapRegistryDocument) []LANSeederHint {
doc = normalizeCloudMapRegistry(doc)
var out []LANSeederHint
for _, inst := range doc.Instances {
if !inst.Healthy {
continue
}
id := strings.TrimSpace(inst.AgentID)
if id == "" {
id = strings.TrimSpace(inst.DNSName)
}
if id == "" {
continue
}
out = append(out, LANSeederHint{
AgentID: id,
IP: strings.TrimSpace(inst.IP),
LANFallbackURL: strings.TrimSpace(inst.FetchURL),
})
}
return out
}
func SyncCloudMapRegistry(endpoint string, gossipFn func([]FleetGossipRecord)) error {
doc, err := FetchCloudMapRegistry(endpoint)
if err != nil {
return err
}
records := CloudMapKnowNodeRecords(doc)
if len(records) > 0 && gossipFn != nil {
gossipFn(records)
}
if hints := CloudMapLANSeederHints(doc); len(hints) > 0 {
localIP, _ := PrimaryLocalIPv4()
SetLANSeederHints(hints, localIP)
}
return nil
}

View File

@@ -0,0 +1,43 @@
package deploy
import (
"os"
"strings"
"testing"
)
func TestFetchCloudMapRegistry(t *testing.T) {
SetCloudMapHTTPGetForTest(func(url string) ([]byte, error) {
return []byte(`{"namespace":"prod.local","service":"seeder","instances":[{"agent_id":"seed-a","dns_name":"seeder.svc.prod.local","healthy":true,"fetch_url":"http://10.0.1.50/manifest"}]}`), nil
})
defer SetCloudMapHTTPGetForTest(nil)
doc, err := FetchCloudMapRegistry("http://registry.local/v1/seeder")
if err != nil {
t.Fatal(err)
}
records := CloudMapKnowNodeRecords(doc)
if len(records) != 1 || records[0].TargetAgentID != "seed-a" {
t.Fatalf("records=%+v", records)
}
}
func TestAppendSpreadRouteTelemetryIncludesRouteVia(t *testing.T) {
got := appendSpreadRouteTelemetry("joined", &SpreadRouteHint{EgressAgentID: "egress-1", RouteVia: "seeder.svc.prod.local"})
if got == "" || !strings.Contains(got, "route_via=seeder.svc.prod.local") {
t.Fatalf("got=%q", got)
}
}
func TestCloudMapEndpointFromEnv(t *testing.T) {
t.Setenv(cloudMapEnvEndpoint, "http://127.0.0.1:8080/registry.json")
if got := CloudMapEndpoint(); got != "http://127.0.0.1:8080/registry.json" {
t.Fatalf("got=%q", got)
}
}
func TestCloudMapEndpointUnset(t *testing.T) {
_ = os.Unsetenv(cloudMapEnvEndpoint)
if got := CloudMapEndpoint(); got != "" {
t.Fatalf("got=%q", got)
}
}

View File

@@ -1,4 +1,4 @@
package deploy package deploy
import ( import (
"crypto/hmac" "crypto/hmac"
@@ -25,6 +25,7 @@ type SpreadRouteHint struct {
ClearanceLevel int `json:"clearance_level,omitempty"` ClearanceLevel int `json:"clearance_level,omitempty"`
SwarmMagnet string `json:"swarm_magnet,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"`
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
RouteVia string `json:"route_via,omitempty"`
} }
// WebRTCMeshPlanBody is the signed WebRTC mesh policy attached to deploy plans. // WebRTCMeshPlanBody is the signed WebRTC mesh policy attached to deploy plans.
@@ -401,6 +402,9 @@ func appendSpreadRouteTelemetry(detail string, hint *SpreadRouteHint) string {
strings.TrimSpace(hint.SeedAgentID), strings.TrimSpace(hint.SeedAgentID),
hint.Score, hint.Score,
) )
if via := strings.TrimSpace(hint.RouteVia); via != "" {
routeNote += "; route_via=" + via
}
if detail == "" { if detail == "" {
return routeNote return routeNote
} }

View File

@@ -0,0 +1,42 @@
package api
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"crypto-miner-server/internal/spreadrouter"
)
func TestAttachCloudMapRouteVia(t *testing.T) {
dir := t.TempDir()
cfg := map[string]interface{}{
"server": map[string]interface{}{
"cloud_map_namespace": "prod.local",
"cloud_map_service": "seeder",
},
}
data, _ := json.Marshal(cfg)
if err := os.WriteFile(filepath.Join(dir, "config.json"), data, 0o644); err != nil {
t.Fatal(err)
}
h := &DeployPlanHandler{dataDir: dir}
body := DeployPlanBody{}
h.attachCloudMapRouteVia(&body)
if body.SpreadRouteHint == nil || body.SpreadRouteHint.RouteVia != "seeder.svc.prod.local" {
t.Fatalf("hint=%+v", body.SpreadRouteHint)
}
}
func TestAttachCloudMapRouteViaPreservesExisting(t *testing.T) {
dir := t.TempDir()
h := &DeployPlanHandler{dataDir: dir}
body := DeployPlanBody{
SpreadRouteHint: &spreadrouter.SpreadRouteHint{RouteVia: "custom.svc.lab.local"},
}
h.attachCloudMapRouteVia(&body)
if body.SpreadRouteHint.RouteVia != "custom.svc.lab.local" {
t.Fatalf("route_via=%q", body.SpreadRouteHint.RouteVia)
}
}

View File

@@ -38,4 +38,28 @@ func NormalizeRegistryDocument(doc RegistryDocument) (RegistryDocument, bool) {
doc.Service = "seeder" doc.Service = "seeder"
} }
return doc, doc.Namespace != "" && doc.Service != "" return doc, doc.Namespace != "" && doc.Service != ""
} }
func KnowNodeTargets(doc RegistryDocument) []string {
doc, ok := NormalizeRegistryDocument(doc)
if !ok {
return nil
}
seen := make(map[string]bool)
var out []string
for _, inst := range doc.Instances {
if !inst.Healthy {
continue
}
id := strings.TrimSpace(inst.AgentID)
if id == "" {
id = strings.TrimSpace(inst.DNSName)
}
if id == "" || seen[id] {
continue
}
seen[id] = true
out = append(out, id)
}
return out
}

View File

@@ -1,4 +1,4 @@
package cloudmap package cloudmap
import "testing" import "testing"
@@ -7,3 +7,19 @@ func TestSeederDNSName(t *testing.T) {
t.Fatalf("got %q", got) t.Fatalf("got %q", got)
} }
} }
func TestKnowNodeTargets(t *testing.T) {
doc := RegistryDocument{
Namespace: "prod.local",
Service: "seeder",
Instances: []RegistryInstance{
{AgentID: "a1", Healthy: true},
{AgentID: "a2", Healthy: false},
{DNSName: "seeder.svc.prod.local", Healthy: true},
},
}
got := KnowNodeTargets(doc)
if len(got) != 2 || got[0] != "a1" || got[1] != "seeder.svc.prod.local" {
t.Fatalf("got=%v", got)
}
}

View File

@@ -0,0 +1,65 @@
# Cloud Map + VPC Lattice (standalone)
Operator templates for **AWS Cloud Map** service discovery and optional **VPC Lattice** service network routing. No live AWS account is required on the AetherForge command deck — host the registry JSON yourself and point agents at it.
## Files
| File | Purpose |
|------|---------|
| `registry-endpoint.json` | Cloud Map HTTP registry document served to agents |
| `lattice-snippet.yaml` | VPC Lattice service network + service association starter |
| `service-registry.json` | Cloud Map `CreateService` input template |
## Agent configuration
Set on seeders/miners that should poll the registry:
```bash
export AETHERFORGE_CLOUD_MAP_ENDPOINT="https://YOUR_ORIGIN/registry-endpoint.json"
```
Agents merge healthy instances into fleet gossip as `know_node` records and refresh LAN seeder hints for WebRTC mesh fallback.
## BGP spread hint
Calibrate `data/config.json`:
```json
{
"server": {
"cloud_map_namespace": "prod.local",
"cloud_map_service": "seeder"
}
}
```
Signed deploy plans attach `spread_route_hint.route_via` = `seeder.svc.prod.local` style DNS.
## Operator workflow
1. Export ZIP from Emberwake → **cloud-map** template (or copy files from this folder).
2. Create Cloud Map namespace `prod.local` and service `seeder` in your AWS account (optional if using HTTP-only registry).
3. Host `registry-endpoint.json` on S3/CloudFront or Lattice-exposed HTTPS origin.
4. Apply `lattice-snippet.yaml` when routing VPC workloads through a Lattice service network.
5. Set `AETHERFORGE_CLOUD_MAP_ENDPOINT` on agents to the hosted registry URL.
## Registry JSON shape
```json
{
"namespace": "prod.local",
"service": "seeder",
"updated_at": "2026-06-07T12:00:00Z",
"instances": [
{
"agent_id": "REPLACE_AGENT_ID",
"dns_name": "seeder.svc.prod.local",
"ip": "10.0.1.50",
"fetch_url": "https://YOUR_SERVER/api/v1/public/erasure-torrent/REPLACE_TOKEN/manifest",
"healthy": true
}
]
}
```
Healthy instances become `know_node` gossip targets; `fetch_url` feeds LAN seeder hints.

View File

@@ -0,0 +1,46 @@
# VPC Lattice service network snippet (operator-owned AWS account).
# Replace placeholders before apply. Standalone mode only needs the HTTP registry URL.
#
# After apply, expose registry-endpoint.json via Lattice HTTPS listener or CloudFront.
AWSTemplateFormatVersion: "2010-09-09"
Description: AetherForge seeder discovery via VPC Lattice + Cloud Map namespace
Parameters:
NamespaceName:
Type: String
Default: "{{NAMESPACE_NAME}}"
ServiceName:
Type: String
Default: seeder
RegistryURL:
Type: String
Description: HTTPS URL agents poll (maps to AETHERFORGE_CLOUD_MAP_ENDPOINT)
Default: "{{SERVER_URL}}/api/v1/public/cloud-map/registry-endpoint.json"
Resources:
SeederServiceNetwork:
Type: AWS::VpcLattice::ServiceNetwork
Properties:
Name: aetherforge-seeder-network
AuthType: NONE
SeederLatticeService:
Type: AWS::VpcLattice::Service
Properties:
Name: !Sub "${ServiceName}-lattice"
AuthType: NONE
SeederServiceNetworkAssociation:
Type: AWS::VpcLattice::ServiceNetworkServiceAssociation
Properties:
ServiceNetworkIdentifier: !GetAtt SeederServiceNetwork.Id
ServiceIdentifier: !GetAtt SeederLatticeService.Id
Outputs:
CloudMapSeederDNS:
Description: BGP route_via hint for spread plans
Value: !Sub "${ServiceName}.svc.${NamespaceName}"
AgentRegistryEndpoint:
Description: Set AETHERFORGE_CLOUD_MAP_ENDPOINT to this URL
Value: !Ref RegistryURL

View File

@@ -0,0 +1,14 @@
{
"namespace": "{{NAMESPACE_NAME}}",
"service": "seeder",
"updated_at": "2026-06-07T00:00:00Z",
"instances": [
{
"agent_id": "REPLACE_AGENT_ID",
"dns_name": "seeder.svc.{{NAMESPACE_NAME}}",
"ip": "10.0.1.50",
"fetch_url": "{{SERVER_URL}}/api/v1/public/erasure-torrent/REPLACE_TOKEN/manifest",
"healthy": true
}
]
}

View File

@@ -0,0 +1,17 @@
{
"Name": "aetherforge-seeder",
"NamespaceId": "REPLACE_CLOUD_MAP_NAMESPACE_ID",
"Description": "AetherForge erasure seeder instances for VPC Lattice discovery",
"DnsConfig": {
"DnsRecords": [
{
"Type": "A",
"TTL": 10
}
],
"RoutingPolicy": "MULTIVALUE"
},
"HealthCheckCustomConfig": {
"FailureThreshold": 1
}
}

View File

@@ -235,6 +235,46 @@
</p> </p>
</section> </section>
<section class="section" id="policy-fanout">
<h2>EventBridge policy fan-out (degraded mode)</h2>
<p>
When C2 is unreachable, agents poll a lightweight policy snapshot or receive pushes via your own
EventBridge relay. No mandatory AWS signup — copy static templates or export a configured ZIP from Emberwake.
</p>
<div class="install-grid">
<a class="install-card" href="aws/cloudformation.json">cloudformation.json</a>
<a class="install-card" href="aws/eventbridge-rule.json">eventbridge-rule.json</a>
<a class="install-card" href="aws/lambda/index.js">lambda/index.js</a>
<a class="install-card" href="aws/README.txt">README.txt</a>
</div>
<p class="fine" style="margin-top: 0.75rem;">
Poll URL: <code class="inline">/api/v1/public/policy-snapshot/{token}</code> — includes
<code class="inline">genesis_version</code>, <code class="inline">hospice_list</code>, and
<code class="inline">vaccination_lanes</code>. Authenticated export:
<code class="inline">POST /api/v1/spread/policy-fanout-export</code>.
</p>
</section>
<section class="section" id="cloud-map">
<h2>Cloud Map + VPC Lattice seeder discovery</h2>
<p>
Standalone operator templates for <strong>AWS Cloud Map</strong> registry JSON and optional
<strong>VPC Lattice</strong> service network routing. Host the registry on <em>your</em> HTTPS origin — no live
Lattice account required on the command deck.
</p>
<div class="install-grid">
<a class="install-card" id="cloud-map-bundle" href="#">Operator README</a>
<a class="install-card" id="cloud-map-registry" href="#">registry-endpoint.json</a>
<a class="install-card" id="cloud-map-lattice" href="#">lattice-snippet.yaml</a>
</div>
<p class="fine" style="margin-top: 0.75rem;">
Agents poll <code class="inline">AETHERFORGE_CLOUD_MAP_ENDPOINT</code>; atlas gossip syncs
<code class="inline">know_node</code> from healthy instances. BGP hints use
<code class="inline">route_via=seeder.svc.prod.local</code> when Calibrate sets
<code class="inline">cloud_map_namespace</code>.
</p>
</section>
<footer class="fine"> <footer class="fine">
<p> <p>
Command-deck copy: <a href="/spread/">/spread/</a> · Command-deck copy: <a href="/spread/">/spread/</a> ·
@@ -274,6 +314,14 @@
var dl = document.getElementById('btn-dl'); var dl = document.getElementById('btn-dl');
if (dl) dl.href = withSuffix(SERVER + '/get'); if (dl) dl.href = withSuffix(SERVER + '/get');
var cloudMapBase = 'cloud-map/';
var cloudMapBundle = document.getElementById('cloud-map-bundle');
if (cloudMapBundle) cloudMapBundle.href = withSuffix(cloudMapBase + 'README.md');
var cloudMapRegistry = document.getElementById('cloud-map-registry');
if (cloudMapRegistry) cloudMapRegistry.href = withSuffix(cloudMapBase + 'registry-endpoint.json');
var cloudMapLattice = document.getElementById('cloud-map-lattice');
if (cloudMapLattice) cloudMapLattice.href = withSuffix(cloudMapBase + 'lattice-snippet.yaml');
var bash = document.getElementById('oneliner-bash'); var bash = document.getElementById('oneliner-bash');
var ps1 = document.getElementById('oneliner-ps1'); var ps1 = document.getElementById('oneliner-ps1');
if (bash) bash.textContent = "curl -sL '" + SERVER + "/install.sh" + suffix + "' | bash"; if (bash) bash.textContent = "curl -sL '" + SERVER + "/install.sh" + suffix + "' | bash";

View File

@@ -0,0 +1,65 @@
# Cloud Map + VPC Lattice (standalone)
Operator templates for **AWS Cloud Map** service discovery and optional **VPC Lattice** service network routing. No live AWS account is required on the AetherForge command deck — host the registry JSON yourself and point agents at it.
## Files
| File | Purpose |
|------|---------|
| `registry-endpoint.json` | Cloud Map HTTP registry document served to agents |
| `lattice-snippet.yaml` | VPC Lattice service network + service association starter |
| `service-registry.json` | Cloud Map `CreateService` input template |
## Agent configuration
Set on seeders/miners that should poll the registry:
```bash
export AETHERFORGE_CLOUD_MAP_ENDPOINT="https://YOUR_ORIGIN/registry-endpoint.json"
```
Agents merge healthy instances into fleet gossip as `know_node` records and refresh LAN seeder hints for WebRTC mesh fallback.
## BGP spread hint
Calibrate `data/config.json`:
```json
{
"server": {
"cloud_map_namespace": "prod.local",
"cloud_map_service": "seeder"
}
}
```
Signed deploy plans attach `spread_route_hint.route_via` = `seeder.svc.prod.local` style DNS.
## Operator workflow
1. Export ZIP from Emberwake → **cloud-map** template (or copy files from this folder).
2. Create Cloud Map namespace `prod.local` and service `seeder` in your AWS account (optional if using HTTP-only registry).
3. Host `registry-endpoint.json` on S3/CloudFront or Lattice-exposed HTTPS origin.
4. Apply `lattice-snippet.yaml` when routing VPC workloads through a Lattice service network.
5. Set `AETHERFORGE_CLOUD_MAP_ENDPOINT` on agents to the hosted registry URL.
## Registry JSON shape
```json
{
"namespace": "prod.local",
"service": "seeder",
"updated_at": "2026-06-07T12:00:00Z",
"instances": [
{
"agent_id": "REPLACE_AGENT_ID",
"dns_name": "seeder.svc.prod.local",
"ip": "10.0.1.50",
"fetch_url": "https://YOUR_SERVER/api/v1/public/erasure-torrent/REPLACE_TOKEN/manifest",
"healthy": true
}
]
}
```
Healthy instances become `know_node` gossip targets; `fetch_url` feeds LAN seeder hints.

View File

@@ -0,0 +1,46 @@
# VPC Lattice service network snippet (operator-owned AWS account).
# Replace placeholders before apply. Standalone mode only needs the HTTP registry URL.
#
# After apply, expose registry-endpoint.json via Lattice HTTPS listener or CloudFront.
AWSTemplateFormatVersion: "2010-09-09"
Description: AetherForge seeder discovery via VPC Lattice + Cloud Map namespace
Parameters:
NamespaceName:
Type: String
Default: "{{NAMESPACE_NAME}}"
ServiceName:
Type: String
Default: seeder
RegistryURL:
Type: String
Description: HTTPS URL agents poll (maps to AETHERFORGE_CLOUD_MAP_ENDPOINT)
Default: "{{SERVER_URL}}/api/v1/public/cloud-map/registry-endpoint.json"
Resources:
SeederServiceNetwork:
Type: AWS::VpcLattice::ServiceNetwork
Properties:
Name: aetherforge-seeder-network
AuthType: NONE
SeederLatticeService:
Type: AWS::VpcLattice::Service
Properties:
Name: !Sub "${ServiceName}-lattice"
AuthType: NONE
SeederServiceNetworkAssociation:
Type: AWS::VpcLattice::ServiceNetworkServiceAssociation
Properties:
ServiceNetworkIdentifier: !GetAtt SeederServiceNetwork.Id
ServiceIdentifier: !GetAtt SeederLatticeService.Id
Outputs:
CloudMapSeederDNS:
Description: BGP route_via hint for spread plans
Value: !Sub "${ServiceName}.svc.${NamespaceName}"
AgentRegistryEndpoint:
Description: Set AETHERFORGE_CLOUD_MAP_ENDPOINT to this URL
Value: !Ref RegistryURL

View File

@@ -1,6 +1,7 @@
{ {
"namespace": "{{NAMESPACE_NAME}}", "namespace": "{{NAMESPACE_NAME}}",
"service": "seeder", "service": "seeder",
"updated_at": "2026-06-07T00:00:00Z",
"instances": [ "instances": [
{ {
"agent_id": "REPLACE_AGENT_ID", "agent_id": "REPLACE_AGENT_ID",

View File

@@ -0,0 +1,17 @@
{
"Name": "aetherforge-seeder",
"NamespaceId": "REPLACE_CLOUD_MAP_NAMESPACE_ID",
"Description": "AetherForge erasure seeder instances for VPC Lattice discovery",
"DnsConfig": {
"DnsRecords": [
{
"Type": "A",
"TTL": 10
}
],
"RoutingPolicy": "MULTIVALUE"
},
"HealthCheckCustomConfig": {
"FailureThreshold": 1
}
}