edge-replica-placements
No handoff yet. An agent working here writes one with amadan branch handoff, so that whoever picks the branch up next — a person, or another agent on another machine — can start without reading the whole history.
Recently
<task-notification> <task-id>bioqmk4ao</task-id> <tool-use-id>toolu_01VxDJSMzVMLFKzZk6cdPNdn</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/bioqmk4ao.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 8" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>buo2940eh</task-id> <tool-use-id>toolu_014k3cyXCfas5PtSP3qKwgUY</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/buo2940eh.output</output-file> <status>completed</status> <summary>Background command "Apply round 6 fix, commit, run round 7" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>ba3lz1oh1</task-id> <tool-use-id>toolu_015PrEiSTr1L9BRkq6s3coR1</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/ba3lz1oh1.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 6" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bs0tfd80x</task-id> <tool-use-id>toolu_01YBjAesdrsPMBLwmhWfiurt</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/bs0tfd80x.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 5" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bwbxe6zx0</task-id> <tool-use-id>toolu_019EAUisBoE71RCte7nUV2bu</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/bwbxe6zx0.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 3" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bw14ru9ke</task-id> <tool-use-id>toolu_019j4MAcjZaDTGbuvijJKgeb</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/bw14ru9ke.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 2" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>b706ucbft</task-id> <tool-use-id>toolu_01F3n5afXWtoQKJr5hCcF18a</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/b706ucbft.output</output-file> <status>completed</status> <summary>Background command "Run Astra plan review round 1" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>brz6wyp3y</task-id> <tool-use-id>toolu_01TX4MdnTFZ1X34am3dSXwqB</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/brz6wyp3y.output</output-file> <status>completed</status> <summary>Background command "Run Astra delta review round 6" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>b0g8dxdd6</task-id> <tool-use-id>toolu_01T7Q4ujMGvrVTFLWfFXJLpX</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/b0g8dxdd6.output</output-file> <status>completed</status> <summary>Background command "Run Astra delta review round 5" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>a707b989ed9b4fb49</task-id> <tool-use-id>toolu_01MJs9ySsfo6TrgQPSYB3tEX</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/a707b989ed9b4fb49.output</output-file> <status>completed</status> <summary>Agent "Signatures: declare and publish" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>Everything below was read on branch `edge-asset-cache` with a clean tree. Paths are relative to `/home/paulca/amadan.net/carlos/platform-edge-asset-cache/`. ## Conflicts with the spec you should fix in the plan first - **`carlos instances update <host>` is already taken.** It is the "Update now" verb: `runInstancesUpdate` (`instances_updates.go:201`), flag `--fast-track`, which posts to `/updates/install`. Spec §3.2's `--assets=off|on` would collide with it. The verbs that PATCH a record today are `carlos steering` (`runSteering`, `instances_cli.go:755`), `carlos gate set|clear` (`gate_cli.go:117`/`:181`), `carlos instances set-upstreams` (`:650`) and `carlos instances set-environment` (`:928`). - **The gate does not converge inside `convergeOwned`.** It converges in Reconcile's owned branch, `reconcile.go:1366-1374` (`reg.SetGate`). - **New refusal sentences cannot go into `internal/console/apicli.go` or `apiinstances.go`.** Both files are listed in `movesLegacyFiles` (`approved_moves_copy_test.go:1492-1493`), and `TestMovesLegacyExemptionsAreLegacy` (`:1888`) fails on any `jsonError` text added since the merge base. The swap feature shows the workaround: its sentences live in `internal/console/apiship_swap.go`. - **The staged manifest copy can be missing new keys.** `ReadStagedManifest` (`internal/release/versions.go:261`) may read a copy that an older binary re-marshalled, which drops unknown keys like `assets`. The authoritative read is the bucket, via `release.GetManifest`/`GetManifestRaw` (`release.go:710`/`:718`). `CanonicalDemand` (`demand.go:48`) already works this way. - **No list of all live generations exists.** `ServingGen` returns only the current one, and `actEntry` (`internal/activator/activator.go:469`) holds a single `gen Gen` (field at roughly `:544`). The old generation exists only as a local variable during a swap (`activator.go:3510`, `oldGen := e.gen`). So the §4.1 "union with every live generation" needs new state in the activator. - **`foldHomes` drops the box's own file** (`homemap.go:722`). A home box's own hosts are never in its home map, so the "home serves its own routes" rule (§4.2) has to come from the registry or the edges writer, not from the home map. - **The record view may not exist.** It is armed only when `CARLOS_CHANGE_INDEX=read`, and never on a fleet box (`internal/host/changeindex.go:57-78`). Otherwise `armedRecordView` is nil and `viewStore` reads the bucket directly. `Reconcile` returns a single error; there is no per-scope "converged" signal today. - **`ReconcileAliases` has more log-and-continue points than the spec lists**, and one existing test pins the current behaviour (details in item 6). - **The config file holds only strings.** Every `cliConfig` field is a string, and both `loadConfig`'s trims and `overlayDestination` (`config.go:299`) handle strings only. A list like `"assets": [...]` needs its own decoding and overlay code. - **Spec line references that are off:** the `edgeHost.Account`/`App` rule is at `edgesfile.go:77-85` (not 73-80), and the `hostOwner` fold is at `:93-116`. ## 1. Release manifest and ship - **`release.Manifest`**, `internal/release/release.go:47-107`: `Capabilities *caps.Set \`json:"capabilities,omitempty"\`` at `:106`. `Swap string \`json:"swap,omitempty"\`` at `:79` is the closest model for a small additive field. - **Validation:** `validateManifest(m *Manifest)` at `release.go:322` calls `caps.ValidCapabilities(m.App, m.Capabilities)` last (`:365`). It runs at ship (`claimRawShip`, `:631`, through `Ship` in `claim_app_writes.go:17`) and on every read (`GetManifestRaw`, `:718`; `ReadStagedManifest`). - **`runShip`**, `cli.go:2158`: flag `--capabilities` (`:2167`). `readCapabilities(path, app string) (*caps.Set, error)` (`cli.go:2388`) runs before the mode split (`:2222-2229`) and again after the app is resolved (`:2253-2259`). Direct mode builds the manifest at `:2335-2349`. - **`runShipAPI(sess *apiSession, acct apiAcct, fs *flag.FlagSet, app, ver, kind, target, label, notes string, capsSet *caps.Set, printBanner, programChange bool, messages ...string) (apiShipResult, error)`**, `cli.go:2444`. It calls `sess.ship(...)` at `:2503`. - **On the wire:** `apiSession.ship` (`apimode.go:4447`) sends a multipart `meta` part shaped as an anonymous struct (`:4467-4476`): `version, kind, label, notes, message, capabilities (RawMessage, omitempty), program_change, swap`. - Swap travels as the `sess.shipSwap` field (`apimode.go:82-85`), not a parameter. That is a way to add a field without changing signatures. - Echo checks: `checkCapabilitiesEcho` (`:4543`) and `checkSwapEcho` (`:4581`). - **Console door:** `(*Server).handleAPIShip`, `internal/console/apicli.go:1812`. - Meta struct at `:1900-1926`, read under `apiShipMetaMaxBytes` (`:1674`). - Capabilities are decoded and validated at `:1998-2011`; the manifest is built at `:2167-2178`. - The re-ship short-circuit compares capabilities at `:2216` (`shipCapabilitiesMatch`, `:1781`) and swap at `:2221`. - Echoes are added at `:2238-2239` (re-ship) and `:2293-2294` (new ship). - Swap helpers in `apiship_swap.go`: `refuseBadSwap` `:19`, `refuseSwapConflict` `:31`, `addSwapEcho` `:44`. - **Tests:** - `cli_test.go`: `TestReadCapabilitiesRefusals` `:39`, `TestReadCapabilitiesHappy` `:90`, `TestShipDirectModeEmbedsTheCapabilityFile` `:121`; helpers `writeCapsFile`, `captureStdout`, `clearDeploymentEnv`. - `apimode_test.go`: `TestAPISessionShipCarriesCapabilitiesInMeta` `:1836`, plus echo tests `:1977-2035`; helper `newTestSession(ts.URL)`. - `ship_swap_test.go`: `TestValidSwapFlag`, `TestCheckSwapEcho`. - `internal/console/apicli_test.go`: `TestAPIShipCarriesCapabilitiesIntoTheManifest` `:1583`, `TestAPIShipRefusesABadCapabilityBlock` `:1624`, `TestAPIShipRefusesARecapabilitiedReship` `:1733`, `TestAPIShipEchoesTheStoredCapabilityBlock` `:1858`. Helpers: `shipPart` `:927`, `metaPart` `:933`, `metaPartCaps` `:1568`, `liftShipBudget` `:983`, `postShip` `:993`; `newConsoleFixture` (`server_test.go:43`), `cliToken` (`authcli_test.go:523`). - `internal/console/apiship_swap_test.go`: the template to copy. `metaPartSwap` `:12`, `TestAPIShipStoresTheSwapDemand` `:21`, `TestAPIShipRefusesAddingTheDemandOnAReship` `:74`. - `internal/release/release_test.go` `:549-600` (capabilities), `golden_test.go` `:157`/`:195` (additive parse). ## 2. `carlos deploy` and project defaults - **`runDeploy(args []string)`**, `deploy.go:63`. Flags at `:64-93`; `--capabilities` at `:72`; `capsSet` is read at `:121-127` and again at `:181-185`. `sess.shipSwap = *swap` at `:130`. The ship call is `runShipAPI(sess, acct, fs, *app, *ver, *kind, *target, *label, *notes, capsSet, false, false, message)` at `:334`. - **Zero-arg defaults:** branch at `:198-259`. `configDeploy()` (`config.go:224`) walks for the nearest file with `Artifact != ""`. Defaults apply only when `dsrc != "" && dcfg.Console == sess.console` (`:206`). `Kind` and `Target` are applied only if their flags were not set (`:217-222`, using `flagWasSet` from `flags.go:98`). The wizard path is `:228-244`; values resolved there go into `newly` and then `offerSaveProjectDefaults` (`:266`, defined at `apimode.go:705`). - **`cliConfig`**, `config.go:53-77`; `App/Kind/Artifact/Target` at `:62-65`. Supporting functions: `loadConfig` `:249` (trims at `:291-295`), `overlayDestination` `:299`, `writeConfigKeys` `:408` (keeps unknown keys). - **Tests:** - `deploy_wizard_test.go`: `TestZeroArgDeployRunsFromSavedDefaults` `:219` (a subprocess run via `buildCarlos(t)` and `carlosTestEnv`), `TestZeroArgDeployNonTTYWithoutDefaults` `:271`. - `config_test.go` (`TestConfigRewritesPreserveUnknownFields` `:415`), `config_cli_test.go` (destinations), `deploy_test.go`, `deploy_e2e_test.go` (`TestE2EDeployThroughConsole` `:266`). ## 3. Instance record and PATCH - **`provision.Record`**, `internal/provision/provision.go:38-224`. - Pointer pattern: `Hibernate *bool \`json:"hibernate,omitempty"\`` `:103` and `StatusNamed *bool \`json:"status_named,omitempty"\`` `:120`, with `StatusNamedOrDefault()` at `:458`. - `Steering string` `:159`, `Gate string` `:178`, `Replicas []string` `:212`. - `ValidSteering` `:484`, `ValidateShape` `:559`, `CreateRecord` `:882`, `GetRecordETag` `:2777`, `UpdateRecord` (`claim_app_writes.go:15`). - **Create door:** `handleAPIInstanceCreate`, `internal/console/apiinstances.go:987`. The record literal is at `:1421-1438`; fields set at mint are stamped after it (for example `rec.Steering = "latency"` at `:1480`). It is written by `provision.CreateRecord` (`:1676`), or on the claim path through `hostops.PrepareInstance` (`internal/hostops/record.go:159`, which marshals the whole record). - **Other record creators that would also need to mint a token:** `apiinstanceseed.go:341`, `apiinstanceimport.go:172`, `domainattach.go:250`, `provision/replica.go:84` (`CreateRecordReplica`). - **PATCH door:** `handleAPIInstanceUpdate`, `apiinstances.go:1939`. - Body struct at `:1992-2002`: `Addrs *[]string, Probe json.RawMessage, Steering *string, Environment *string, Gate *string`. - The "nothing to update" check is at `:2006-2010` and lists the field names; a new field must be added there. - Fields are applied at `:2049-2078`, then `ValidateShape` (`:2086`), then the pool rule for steering (`:2112-2116`). - Write: `UpdateRecord(ctx, store, *rec, etag)` at `:2147`. Audit list at `:2165-2185`, then `s.bumpSerial(ctx)` (`:2190`), and the reply is `recordView(*rec)` (`:926`). - **CLI client:** `apiSession.instanceSetSteering` (`apimode.go:3581`) and `instanceSetGate` (`:3603`), both `patchJSON` to `/instances/<host>`. `apiInstanceRecord` is at `apimode.go:3260`. - **Tests:** - `internal/console/apiinstances_test.go`: `TestAPIInstanceUpdateSteeringRoundTrip` `:1361`, `TestAPIInstanceUpdateAuditNamesChangedFields` `:1460`, `TestAPIInstanceUpdateGateRoundTrip` `:2493`. Helpers: `instanceFixture` `:102`, `enableInstances` `:117`, `instancesURL` `:129`, `updateURL` `:1021`, `sharedPoolManifest` `:1351`, `rawReq` `:43`, `letStateUpstreams` `:2716`, `serial(t, fx)`. - `apiinstancesenvironment_test.go` (`TestAPIInstanceUpdateEmptyBodyNamesEnvironment` `:155`). - CLI: `apimode_test.go` `TestInstanceSetSteeringPatches` `:915`; `gate_cli_test.go` `TestInstanceSetGate` `:114`. - Provision: `provision_test.go` `TestRecordWireShapeIsAdditive` `:23`. ## 4. Registry columns, Fingerprint, RouteFor and convergeOwned - **`registry.Route`**, `internal/registry/registry.go:40-363`: `ImportGate` `:89`, `Version` `:93`, `App` `:111`, `Pool` `:112`, `Hibernate` `:173`, `Gate` `:182`, `Platform` `:236`, `Provisioned` `:252`, `Account` `:288`, `Steering` `:309`, `Replicas` `:344`. - **Adding a column takes six edits** (the comment at `:885-891` says so): 1. the struct field; 2. an `ALTER TABLE routes ADD COLUMN ...` line in `migrate()` (`:571-616`); 3. `routeCols` (`:892`), `routeColCount = 55` (`:901`) and `scanRoute` (`:908`); 4. `routeArgs` (`:1261`); 5. the `ON CONFLICT` arm in `Upsert` (`:1170`); 6. a `remoteRequest` field plus a dispatch case in `remote_dispatch.go:8`/`:242-252`, or the simpler `errRemoteMethod` form `SetSeedSource` uses (`:1277`). - **Narrow setters:** `SetSteering(host, steering string) error` `:1952`, `SetReplicas` `:2029`, `SetGate(host, hash string)` `:2090`. Each is an `UPDATE ... WHERE host=?` that errors when no row matches. - **`Fingerprint()`**, `:3055`. It hashes a hand-picked column list (`:3070-3073`): `rowid, status, socket, addr, version, hibernate, channel, backing, tunnel, db_path, region, fleet_box, addrs, health, gate`. It adds `hold_op`, `import_gate` and the claim fields only when non-empty. Then it hashes the `fleets`, `fleet_boxes`, `route_aliases (alias, host, account, app)`, `wildcard_domains` and `route_paths` sections. **Steering and replicas are not hashed.** `SnapshotFingerprint` (`:3314`) covers every column through `routeCols`. - **`RouteFor(rec Record, dataDir, seed string, nested bool, policy *UpdatePolicy) registry.Route`**, `internal/provision/reconcile.go:436`: it copies `Steering`, `Replicas`, `Gate` and the rest at `:437-451`. - **`convergeOwned(reg *registry.Registry, existing registry.Route, rec Record, policy *UpdatePolicy, policyKnown bool, dryRun bool)`**, `reconcile.go:706`. - `StatusNamed` (a pointer, converged only when set) at `:725-733`. `Steering` at `:752-760`. `Replicas` at `:768-776`. - It is called from `:1329` (owned branch), `:1463` (dry run) and `:1478` (adoption). - `shapeMismatch` (`:614`) must not list the new column. - **Tests:** - `internal/registry/registry_test.go`: `TestUpsertRoundTripsAllColumns` `:64`, `TestSetSteeringRoundTrips` `:244`, `TestFingerprintIgnoresSteering` `:285`, `TestSnapshotFingerprintSeesSteeringChange` `:312`; helper `openTestRegistry` `:502`. `importjournal_test.go` `TestImportGateMovesBothFingerprints` `:286`; `paths_test.go` `:80`. - `internal/provision/reconcile_test.go`: `TestReconcileConvergesSteering` `:523`, `TestRouteForCarriesSteering` `:2852`, `TestShapeMismatchIgnoresSteering` `:2872`. Helpers: `setup` `:127`, `acmeRecord` `:139`, `regWith` `:37`, `mustGet` `:51`, `putReplace` `:83`, `mustJSON` `:115`. ## 5. Record view - **Types:** `RecordView` at `internal/provision/recordview.go:34`, with `applied map[string]uint64` (`:47`), `pending map[string]bool` (`:48`), `rebuildDue` and `epoch`, all guarded by `mu`. `ViewSnapshot{Epoch uint64; Complete bool; ...}` is in `recordview_store.go:24`. - **Functions:** `NewRecordView(live release.Store, watch *release.ChangeWatch) *RecordView` `:61`; `Refresh(ctx) error` `:117`; `Rebuild(ctx) error` `:190`; `rebuildLocked(ctx, poll bool)` `:211`; `publish(sn *ViewSnapshot)` `:254`; `ScopeApp` `:70`. - **Where a per-scope confirmed time would go:** - On a successful re-read: `Refresh` at `:164-166` (`v.applied[scope] = obs[scope]; delete(v.pending, scope)`). Failures are marked at `:167-169`. - After a walk: `rebuildLocked` at `:233-245`. - `publish` only computes `Complete` and the epoch. - **Box callers (`internal/host/changeindex.go`):** `armChangeIndex` `:57`, `viewStore(ctx, live) (s, epoch, ok)` `:130` (refreshes, then needs `sn.Complete`), `recordViewBackstopLoop` `:155`. - Reconcile runs through `reconcileSerializedWith` (`internal/host/agent.go:1604`), which calls `viewStore` (`:1612`) and then `provision.Reconcile` and `ReconcileSidecars` (`:1647-1650`). - The console also builds a view, in `cmd/console/main.go:1497`. - **Tests:** `internal/provision/recordview_test.go` (`TestViewRetriesFailedScopeOn304` `:602`, `TestViewCancelledRefreshFailsClosed` `:462`, `TestViewRefreshRereadsOnlyMovedApp` `:563`) and `recordview_sim_test.go`. Helpers: `newViewFixture(t, bucket, indexed bool)` `:159`, `f.record` / `f.view()` / `viewRecord`, `mustRebuild` `:241`, `mustRefresh` `:253`, `viewCountingStore.setFailGet`/`setFailList` `:58-59`. ## 6. ReconcileAliases - **Signature:** `ReconcileAliases(ctx context.Context, s release.Store, reg *registry.Registry, now func() time.Time, deploymentRoutes map[string]bool, cache *ClaimCache) error`, `internal/domains/aliases.go:232`. - **Callers:** `internal/host/agent.go:3615` (the 5-minute instances loop) and `internal/host/changeindex.go:316` (the fast full pass only). - **Points where a failure is logged and the pass still returns nil:** | Line | What fails | |---|---| | `:162-171` | label count (`labelHosts`); keeps existing names | | `:204` | instance alias upsert | | `:310` | alias upsert | | `:531` | catch-all upsert | | `:541` | alias delete | | `:561` / `:568` | wildcard upsert / delete | | `:584` | path upsert | | `:590` | path delete | Two more silent inputs: `ClaimCache.listWithHosts` (`claimcache.go:107`, `:137-145`) drops claims whose body could not be read, and `edgesfile.DeploymentRoutes` returns nil on failure, which the pass treats as "allow". - **Tests:** `aliases_test.go` (helpers `aliasRegWith` `:19`, `putClaim` `:36`, `addRoute` `:59`, `aliasNow` `:68`), `aliases_paths_test.go` (`pathsByPrefix`), `instancecatchall_test.go` (`listFailStore` `:493`, `reconcile` `:53`, `aliasMap` `:29`). - `TestInstanceCatchallKeepsNamesWhenTheListingFails` (`:507`) **asserts a nil error on a failed label count**, so a completeness return value must keep that assertion meaningful or update it. ## 7. Edges file writer (`internal/host/edgesfile.go`) - **`edgeHost`** `:44-91`: `Role`, `Platform`, `Alias`, `Replica`, `Wildcard`, `Account`, `App` (`:84-85`), `Stamp`. - **`edgesFile`** `:229-359`, ending with `Generation` and `Hosts map[string]edgeHost`. - **Owner fold:** `hostOwner{account, app string; set, mixed bool}` `:95`, with `add` `:100` and `owner` `:111`; `servedOwners(routes, served, paths)` `:125`. - **Building the file:** `edgesFileWriteClaims(ctx, s, reg, ident, eip, last *[]byte, refresh bool, claims *edgesClaims) error` `:420`. - Routes: `reg.List()` `:422`; ready, non-sidecar hosts are added at `:473`. - Owners: `ListPaths` `:481`, `servedOwners` `:485`. - Aliases: `ListAliases` `:513`, published only when `DNSStatus == "pointing"` (`:524`, entry at `:546`). - Wildcards at `:567-633`. - **Dirty check:** the marshalled body with `Generation` and `Headroom` cleared, compared with `*last` (`:657-668`). Any new field inside `edgeHost` dirties the file automatically, but only on passes that actually run. Seeded at start by `seedEdgesFileGate` (`:704`). - **When a pass runs:** `edgesFileLoop(reg)` (`:802`, started at `agent.go:835`) ticks every 2 s (`edgesFileWatchInterval` `:918`) on `reg.Fingerprint()`. The timer is `5*time.Minute + rand.N(time.Minute)` (`:865-867`). Scheduling is `edgesFileSchedule.due`/`done` (`:958`/`:972`) wrapped by `edgesLoopState` (`edgesclaims.go:252`). - The loop only receives `reg`: it opens the store itself (`:808`) and has no activator handle. An `at` advance or a generation change needs either a Fingerprint change or a new trigger. - **Shared reader types:** `internal/edgesfile` (`Host` `edgesfile.go:57`, `ReadSnapshot` `snapshot.go:225`, `PlacementView.Hosts` as raw JSON). - **Tests:** `edgesfile_test.go` (`edgesTestRegistry` `:25`, `seedRoute` `:38`, `putCountingStore` `:54`, `publishHosts` `:1104`, `ownedRoute` `:1122`, `wantOwner` `:1129`, `TestEdgesFileSkipsWriteWhenUnchanged` `:113`, `TestEdgesFilePublishesTheOwnerOfAHomeRow` `:1142`), `edgesfile_schedule_test.go`, `edgesclaims_test.go`. ## 8. Home map (`internal/edge/homemap.go`) - **Types:** `edgesFileView` `:133`; `edgesHostView` `:177-198` (Role, Platform, Alias, Wildcard, Account, App, Stamp); `homeEntry` `:201-220`; `homeSnapshot{files, hosts}` `:224`. - **`foldHomes(files map[string]edgesFileView, self string, privateDial bool) (map[string]homeEntry, []string)`** `:706`. It skips the box's own file (`:722`). The owner is taken whole from the entry that wins the dial (`:770-773`, `dialBeats` `:834`). - **Lookups:** `Home(host) (eip, box string, ok bool)` `:868`, `lookupHome` `:894` (the one-label wildcard rule), `Owner` `:921`, `KnownHost` `:960`. - **Poll:** `run` `:1003`, every `homeMapInterval = 30 * time.Second` (`:122`), calling `poll` `:357`, which uses the change index (`edgesScope = "control/edges"`). - `refreshHosts` `:421`: an unreadable or corrupt file keeps its **previous parse** (`:459-471`), which is relevant to the freshness bound. `lastHosts` is stamped at `:498`. - **Tests:** `homemap_test.go` (`putJSON` `:30`, `refreshHomeMap` `:44`, `TestHomeMapCarriesForwardOneUnreadableFile` `:168`, `TestHomeMapFoldsAndExcludesOwnBox` `:51`), `homemap_alias_stamp_test.go`, `homemap_private_test.go`. ## 9. Generations and manifests on the home box - **Installed build:** `Route.Version` (`registry.go:93`). For a generation-managed route the edge skips the version header (`internal/edge/edge.go:842-845`). That route type is selected when `e.gens != nil && t.Kind == "instance" && t.Hibernate` (`edge.go:747-748`). - **Generations:** `edge.GenSource` and `GenInfo{Socket, N, Build}` (`internal/edge/gensource.go:7-40`). - `(*activator.Service).ServingGen(host) (edge.GenInfo, bool)` (`internal/activator/genservice.go:68`) returns the current generation only; it is false while waking or asleep. - `CacheGen(host) (uint64, bool)` (`:81`) keeps the newest generation number, with no build name. - `OnGenRetired` (`:97`). `activator.Gen` is at `generations.go:54`. - **Getting a manifest for a build:** - From the bucket (authoritative): `release.GetManifest(ctx, s, acct, app, version)` (`release.go:710`). - From disk: `release.ReadStagedManifest(optDir, acct, ver, app)` (`versions.go:261`), at `/opt/carlos/versions/<ver>/[<acct>/]<app>.manifest.json` (`StagedManifestPath` `:178`, `OptDir()` `:37`). It returns nil, nil when the file is missing and may be lossy (see top). - Route account: `release.RouteAccount(rt)` (`adopt.go:1355`). ## 10. Gates, pool, path rows and aliases - **Gate:** `Route.Gate` (bcrypt hash, `""` means open) at `registry.go:182`. - **Import gate:** `Route.ImportGate` at `:89`; `registry.ImportGateClosed = "closed"` (`importjournal.go:25`). The edge enforces it in `importGateAdmit` (`internal/edge/importgate.go:35`). - **Pool:** `Route.Pool` (`:112`, `""` means shared). - **Path rows:** `RoutePath{Host, Prefix, Target, Account, App}` `:2905`. `Prefix` is a bare segment (`"paulca"`, not `"/paulca"`). `ListPaths() ([]RoutePath, error)` `:2914`, `UpsertPath` `:2940`. - **Aliases:** `RouteAlias{Alias, Host, Account, App, DNSStatus, DNSCheckedAt, CertExpiresAt, CreatedAt}` `:2737`; `ListAliases() ([]RouteAlias, error)` `:2749`. Catch-alls are `*.parent` rows in the same table.</result> <usage><subagent_tokens>286810</subagent_tokens><tool_uses>89</tool_uses><duration_ms>498002</duration_ms></usage> </task-notification>
<task-notification> <task-id>b1p6vvbzr</task-id> <tool-use-id>toolu_01CavTa1isRanxcZnzLSp6Kq</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/b1p6vvbzr.output</output-file> <status>completed</status> <summary>Background command "Run Astra adversarial review round 4" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bikjopo27</task-id> <tool-use-id>toolu_012JqXmKGAS66BsNnp5QCJTk</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/bikjopo27.output</output-file> <status>completed</status> <summary>Background command "Run Astra adversarial review round 3" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>baha04i4q</task-id> <tool-use-id>toolu_018nZLAqMX2Y85Sp4MsYTsFM</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/8a524042-c54d-483a-a5a0-e726dd55576f/tasks/baha04i4q.output</output-file> <status>completed</status> <summary>Background command "Run Astra adversarial review round 2" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>a875e00d98d23552d</task-id> <tool-use-id>toolu_01NXRgpp8a7K7fkoLrLexzTi</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/08eaa4e9-db04-408b-9066-d9b47fe013f8/tasks/a875e00d98d23552d.output</output-file> <status>completed</status> <summary>Agent "Implement mail IAM user boundary" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>All three review points are fixed in three commits on top of `7aad0c18`, not pushed. All the tests I ran pass. - **`fedae5a7` Backfill on rotation, a one-shot sweep, a strict ARN**: - `MintKey` now reads the user with `GetUser` and sets the boundary before it creates a key. Rotation is covered this way, and if the backfill fails no key is minted. - The new sweep lists every user under `/carlos-mail/`, reads each with `GetUser` (`ListUsers` doesn't return the boundary), and sets it wherever it is missing or different. A failure on one user is recorded and reported, and the sweep carries on. A failed listing is an error. - An operator runs it with `CARLOS_MAIL_BOUNDARY_SWEEP=1`, following the legacy-sweep pattern. It logs each user it fixes, then either "all N mail users bounded" or the users that are not. - The sweep needs `iam:ListUsers`, which AWS scopes to the account rather than a path, so I documented it as `Resource "*"`. It also uses `iam:GetUser` and `iam:PutUserPermissionsBoundary` on `user/carlos-mail/*`. Check the `ListUsers` scoping against the Service Authorization Reference. - The boundary must now be `arn:<partition>:iam::<12 digits>:policy/<path><name>` in IAM's name characters. This also refuses AWS managed policies. - The sweep also runs while `CARLOS_HOST_CLAIM` is on, unlike the legacy sweep. It only ever sets the configured boundary. - **`fba9c6c0` README**: the six-step rollout order, without em dashes. - **`ddbc5aec` Census**: the root-read overlay is re-pinned (serve digest, `cmd/console` and `internal/mail`) and is still loaded last, after wake-latency-1. The seal is now `bea407c5…` and the proposal note is updated. The claim-authority census needed no change. Tests: - `go test ./internal/mail/`: `ok 13.572s`; the new MintKey, sweep and ARN tests pass. - `go test ./cmd/console/ -run 'TestMailUserBoundary|TestMailBackends|TestSweepMailBoundaries'`: `ok` - `go test ./internal/provision/ -run 'Census|Plan14|RootBinary|Envelope|Write'`: `ok` - `go test ./internal/console/ -run TestClaimAuthoritySourceCensus`: `ok` - `go test ./internal/console/ -run Mail`: `ok 40.420s` - Build, vet, gofmt and the darwin and windows builds are clean.</result> <usage><subagent_tokens>255758</subagent_tokens><tool_uses>26</tool_uses><duration_ms>402569</duration_ms></usage> </task-notification>
This sounds good. It shold work with `carlos deploy` too
<task-notification> <task-id>bpx0gb648</task-id> <tool-use-id>toolu_01VZXcZiAKnZB1QxeEq4192V</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/6387394d-0b76-4de7-bbb4-d725fe24b08c/tasks/bpx0gb648.output</output-file> <status>completed</status> <summary>Background command "Wait for seed build run 21" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bv77lhasm</task-id> <tool-use-id>toolu_019AopJfhmKnxPUFvWm7TtXq</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/6387394d-0b76-4de7-bbb4-d725fe24b08c/tasks/bv77lhasm.output</output-file> <status>completed</status> <summary>Background command "Wait for seed build run 15" completed (exit code 0)</summary> </task-notification>
<task-notification> <task-id>bck1f5i9a</task-id> <tool-use-id>toolu_016JzopoypRQ8r5THvpJD5a1</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-carlos-platform/6387394d-0b76-4de7-bbb4-d725fe24b08c/tasks/bck1f5i9a.output</output-file> <status>completed</status> <summary>Background command "Wait for seed build run 2" completed (exit code 0)</summary> </task-notification>