spec: instance sizes and egress allows, rev 4 (Astra R3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+118
−42
docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md+87 −42docs/superpowers/specs/CODEX-FINDINGS-instance-sizes-egress-R3.md+31 −0
CI passed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok github.com/carlosframework/platform 343.140s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.041s
ok github.com/carlosframework/platform/cmd/moveprobe 93.373s
ok github.com/carlosframework/platform/cmd/zddprobe 0.724s
ok github.com/carlosframework/platform/internal/activator 250.653s
ok github.com/carlosframework/platform/internal/agentsock 0.027s
ok github.com/carlosframework/platform/internal/alerting 0.006s
ok github.com/carlosframework/platform/internal/analytics 0.022s
ok github.com/carlosframework/platform/internal/appconfig 2.549s
ok github.com/carlosframework/platform/internal/apperr 0.096s
ok github.com/carlosframework/platform/internal/applog 0.186s
ok github.com/carlosframework/platform/internal/awsinv 0.004s
ok github.com/carlosframework/platform/internal/boxchoice 0.005s
ok github.com/carlosframework/platform/internal/boxid 0.643s
? github.com/carlosframework/platform/internal/boxid/boxidtest [no test files]
? github.com/carlosframework/platform/internal/boxid/internal/signtest [no test files]
ok github.com/carlosframework/platform/internal/boxvolumes 0.011s
ok github.com/carlosframework/platform/internal/caps 0.012s
ok github.com/carlosframework/platform/internal/certwarm 0.008s
ok github.com/carlosframework/platform/internal/cgroup 0.745s
ok github.com/carlosframework/platform/internal/changelog 0.360s
ok github.com/carlosframework/platform/internal/claimactivation 0.909s
ok github.com/carlosframework/platform/internal/clamd 0.060s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 439.553s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 6.056s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.010s
ok github.com/carlosframework/platform/internal/dbverify 0.005s
ok github.com/carlosframework/platform/internal/dnsrec 0.003s
ok github.com/carlosframework/platform/internal/docsite 0.010s
ok github.com/carlosframework/platform/internal/domains 2.531s
ok github.com/carlosframework/platform/internal/doorkey 0.010s
ok github.com/carlosframework/platform/internal/economics 0.006s
ok github.com/carlosframework/platform/internal/edge 45.012s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.016s
ok github.com/carlosframework/platform/internal/fleetapi 0.138s
ok github.com/carlosframework/platform/internal/gate 1.658s
ok github.com/carlosframework/platform/internal/genselect 0.023s
ok github.com/carlosframework/platform/internal/geo 0.012s
ok github.com/carlosframework/platform/internal/geoip 0.034s
ok github.com/carlosframework/platform/internal/historyreadback 0.171s
ok github.com/carlosframework/platform/internal/host 192.271s
ok github.com/carlosframework/platform/internal/hostactors 5.526s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.005s
ok github.com/carlosframework/platform/internal/hostbox 2.774s
ok github.com/carlosframework/platform/internal/hostclaim 0.623s
ok github.com/carlosframework/platform/internal/hostenv 0.005s
ok github.com/carlosframework/platform/internal/hostevidence 0.005s
ok github.com/carlosframework/platform/internal/hostexec 4.945s
ok github.com/carlosframework/platform/internal/hostfence 0.055s
ok github.com/carlosframework/platform/internal/hostfiles 10.401s
ok github.com/carlosframework/platform/internal/hostfloor 0.017s
ok github.com/carlosframework/platform/internal/hostguard 1.058s
ok github.com/carlosframework/platform/internal/hostops 1.732s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.002s
ok github.com/carlosframework/platform/internal/hostquarantine 0.002s
ok github.com/carlosframework/platform/internal/hostrelocate 0.545s
ok github.com/carlosframework/platform/internal/hostresolve 0.138s
ok github.com/carlosframework/platform/internal/hostwrite 0.002s
ok github.com/carlosframework/platform/internal/infraview 0.004s
ok github.com/carlosframework/platform/internal/instancectl 5.219s
ok github.com/carlosframework/platform/internal/instanceexec 2.737s
ok github.com/carlosframework/platform/internal/instancefs 0.009s
ok github.com/carlosframework/platform/internal/instancekey 0.003s
ok github.com/carlosframework/platform/internal/instanceupdates 0.008s
ok github.com/carlosframework/platform/internal/layout 1.580s
ok github.com/carlosframework/platform/internal/lbhealth 0.006s
ok github.com/carlosframework/platform/internal/lease 9.907s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.422s
ok github.com/carlosframework/platform/internal/ledger 0.013s
ok github.com/carlosframework/platform/internal/mail 0.188s
ok github.com/carlosframework/platform/internal/maintenance 0.082s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.558s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.073s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 21.045s
ok github.com/carlosframework/platform/internal/moves/archive 0.812s
ok github.com/carlosframework/platform/internal/objstore 0.208s
ok github.com/carlosframework/platform/internal/ops 0.191s
ok github.com/carlosframework/platform/internal/park 0.208s
ok github.com/carlosframework/platform/internal/privatefile 0.004s
ok github.com/carlosframework/platform/internal/provision 18.396s
ok github.com/carlosframework/platform/internal/publicrelease 0.010s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.430s
ok github.com/carlosframework/platform/internal/relay 7.236s
ok github.com/carlosframework/platform/internal/release 23.726s
ok github.com/carlosframework/platform/internal/rollout 3.676s
ok github.com/carlosframework/platform/internal/rolloutwords 0.004s
ok github.com/carlosframework/platform/internal/rootaws 0.012s
ok github.com/carlosframework/platform/internal/rootparticipation 0.058s
ok github.com/carlosframework/platform/internal/rootstate 0.027s
ok github.com/carlosframework/platform/internal/runs 0.578s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.582s
ok github.com/carlosframework/platform/internal/seeds 0.012s
ok github.com/carlosframework/platform/internal/sim/hostlife 9.292s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.931s
ok github.com/carlosframework/platform/internal/status 0.065s
ok github.com/carlosframework/platform/internal/steering 0.007s
ok github.com/carlosframework/platform/internal/stopevidence 0.128s
ok github.com/carlosframework/platform/internal/svcrole 0.003s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.005s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.006s
ok github.com/carlosframework/platform/internal/usage 0.005s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.573s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 49873058 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: downloading https://github.com/minio/minio/releases/download/RELEASE.2025-09-07T16-13-09Z/minio.linux-arm64.RELEASE.2025-09-07T16-13-09Z
minio: downloaded, digest verified
mc: downloading https://github.com/minio/mc/releases/download/RELEASE.2025-08-13T08-35-41Z/mc.linux-arm64.RELEASE.2025-08-13T08-35-41Z
mc: downloaded, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.025s
=== RUN TestT4StorageProbeMinio
2026/10/21 13:01:28 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E08D013848DF38, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok github.com/carlosframework/platform/internal/host 0.023s
minio contract: PASS
diff --git a/docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md b/docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md| index bb37d8e2a..528be53ac 100644 |
| --- a/docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md |
| +++ b/docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md |
| @@ -1,9 +1,9 @@ |
| # Instance sizes and egress allows |
| Amadan #328 (an isolated instance reaching a private service its operator |
| -runs) and #329 (operator-set CPU and memory per instance). Revision 3, |
| -2026-10-21. Revisions 2 and 3 answer the builder check and Astra rounds |
| -1 and 2 (`CODEX-FINDINGS-instance-sizes-egress-R<n>.md`); the |
| +runs) and #329 (operator-set CPU and memory per instance). Revision 4, |
| +2026-10-21. Revisions 2 to 4 answer the builder check and Astra rounds |
| +1 to 3 (`CODEX-FINDINGS-instance-sizes-egress-R<n>.md`); the |
| dispositions are §9. |
| Both are the same mechanism: **an operator-set policy for an app and its |
| @@ -175,20 +175,48 @@ IPv6 (`internal/edgesfile/private.go:15`) (Astra R1-5). |
| old: a dead or replaced box's last file keeps its addresses excluded |
| until an operator removes the file. That is the conservative direction; |
| the refusal names the box so the operator knows which file to retire. |
| -- **Console:** refuses a grant naming any address in any box's `addrs`. |
| - It also refuses unless the inventory is complete: the edges snapshot's |
| - `BoxesComplete` (`internal/edgesfile/snapshot.go:198`, every box with a |
| - status snapshot has a readable edges file and the reverse), and every |
| - file carries `addrs` (an older box does not). The refusal names the box |
| - the inventory cannot vouch for. |
| +- **Inventory completeness** is its own check, independent of liveness |
| + and of the status listing's 24-hour horizon (Astra R3-2; |
| + `BoxesComplete` is a liveness join and does not give it): list |
| + `control/edges/` completely, read and parse every file, and every file |
| + carries `addrs`. Any listing error, unreadable file, or file without |
| + `addrs` (an older box) makes the inventory incomplete. A retired box's |
| + retained file is simply one more readable file; it blocks nothing but |
| + its own addresses. |
| +- **Console:** refuses a grant naming any address in the inventory, and |
| + refuses any grant while the inventory is incomplete, naming the file |
| + that made it so. |
| - **Root on each box:** before every write (§4.3), drops any entry that |
| - is one of its own addresses (`isLocal`) or appears in any edges file's |
| - `addrs`, and reports the drop (§4.4). If it cannot read the edges |
| - prefix completely, it **fails closed**: it keeps the previous |
| - exclusions and writes no newly-allowed address until it can (Astra |
| - R2-5). An address already held stays only if it was already checked |
| - against a complete read. This catches a box that joins, or an address |
| - that moves, after the grant. |
| + is one of its own addresses (`isLocal`) or appears in the inventory, |
| + and reports the drop (§4.4). **If the inventory is incomplete, root |
| + clears the app's effective allows** (writes the reset) and reports |
| + `inventory incomplete` until a complete read returns, then writes them |
| + again (Astra R3-1, R1-5). A past complete read is not evidence about |
| + now. Its own-address check needs no inventory and always applies. The |
| + cost is stated: a store outage closes granted addresses for its |
| + duration, within one 30-second loop each way. |
| +- **Transport** (Astra R3-3): a box with direct store access reads the |
| + policy object and `control/edges/` itself. A bucketless fleet box reads |
| + through the console's store door (`openFleetDoorStore`, |
| + `internal/host/agent.go:4366`), whose read ACL |
| + (`internal/edge/storedoor.go:431`) grants neither today, and whose |
| + client reads a refusal as an absent object (`fleetapi.go:127`), which |
| + would silently mean "no policy". So: |
| + - the door's `aclRead` grants `control/app-policy/<sqid>/<app>.json` |
| + for the box's own account scope only, read-only (`aclWrite` never |
| + answers true for it); |
| + - the door serves the inventory as one console-computed object, |
| + `control/fleet-addrs.json` (`{"complete": bool, "addrs": [...], |
| + "generation": ...}`), rewritten by the console whenever an edges |
| + file changes, rather than granting fleet boxes a listing of |
| + `control/edges/`. Root fails closed on `complete: false` or on any |
| + read error; |
| + - a fleet-door box tells "refused" from "absent" for these two keys: a |
| + refusal is reported as `policy unreadable`, and root treats it as |
| + incomplete (allows cleared, sizes at the build's tier), never as |
| + "no policy". |
| + Tests prove delivery through the door and that both keys stay |
| + unwritable from a box. |
| - **Residual, stated:** the inventory is published by each box's agent. |
| Under claims a compromised agent could leave its own box's addresses |
| out, so that other boxes' tenants could reach it if an operator also |
| @@ -212,8 +240,9 @@ may run (`validateStagedExe`): |
| (`instanceRegistryHostApp`), so the agent already decides which build |
| an isolated instance runs. The agent converges the policy into a |
| registry column on each of the app's route rows; root reads that |
| - column and validates it. This adds no trust the agent does not already |
| - hold. **One extra rail:** a box whose layout is not wholly nested (any |
| + column and validates it, and reads the inventory verdict (§4.2) the |
| + agent converges beside it. This adds no trust the agent does not |
| + already hold. **One extra rail:** a box whose layout is not wholly nested (any |
| flat exec child, which runs as the agent's user and may be able to |
| write `registry.db`) gets no allow written at all, and says so. |
| @@ -351,23 +380,24 @@ are: they protect the overlap allowance (Astra R1-1). |
| not, or the reply lost), the host is marked unsettled: no swap starts |
| until a fresh read-back settles it, and the captured tier is then taken |
| from that read-back, never from the request. |
| -- **A lowering restore waits for the incumbent to be gone** (Astra |
| - R2 new 1). Today a swap restores the parent (`dropMember`) before it |
| - replaces a legacy co-spawned sidecar (`replaceSidecar`, `swap.go:678`), |
| - and the old sidecar shares the slice. When the successor's size is |
| - smaller than the incumbent's, the restore to the smaller figure happens |
| - only after the retiring generation and the old sidecar (and their |
| - descendants) are proven stopped: units inactive and cgroups empty. Until |
| - then the parent keeps the overlap figure, and a failed restore retries |
| - with the same proof. An equal-size restore keeps today's order. |
| -- **Shrinking memory never touches a running process** (Astra R1-3: a |
| - usage sample cannot make lowering `memory.max` safe, because the |
| - process can allocate between the sample and the write). The new size |
| - takes effect when a process starts under it: the next generation (a |
| - deploy's swap starts the successor at the effective size, and the |
| - overlap figure is old plus new), or the next cold start (a wake from |
| - hibernation). Until then it reports `pending`. An operator who needs |
| - it now restarts the instance. |
| +- **Memory never shrinks while anything runs in the slice** (Astra |
| + R1-3, R2 new 1, R3 new 1; a gate, not a tolerance: three rounds of |
| + proving each co-tenant of the slice stopped kept finding another). A |
| + usage sample cannot make lowering `memory.max` safe, and a swap's |
| + restore runs while the old sidecar and the retiring generation's `carlos |
| + run` commands may still hold memory (`swap.go:678`, |
| + `activator/cmd.go:336,377`). So: |
| + - a swap's successor starts at **the larger of** the effective size |
| + and the incumbent's size, so the restore never lowers the parent; |
| + - a smaller memory size takes effect only at a **cold start**, when |
| + the slice is empty (a wake from hibernation, or a restart the |
| + operator asks for); CPU and IO shrinks still apply live, as they kill |
| + nothing; |
| + - until then the instance reports `pending` with the size it will take |
| + (§5.6). An operator who needs it now restarts the instance. |
| + |
| + No swap ever lowers memory, so today's restore order, the sidecar |
| + replacement and command admission are untouched. |
| - **Read-back** reports the serving generation unit's `MemoryMax` when |
| there is one, else the slice's, and `swap` while the parent is raised. |
| @@ -432,7 +462,9 @@ default and the overrides. |
| Carloku's plan caps. Self-service can come with plans and caps. |
| - The four sizes and their numbers (§5.1). |
| - Shrinking below the build's tier is allowed with a warning (§5.2). |
| -- Memory shrinks wait for the next generation or start (§5.4). |
| +- Memory shrinks wait for a cold start; a swap never lowers memory (§5.4). |
| +- An incomplete address inventory clears every allow until it is |
| + complete again (§4.2). |
| - The quarter-of-a-box refusal (§5.5). |
| - Public addresses are refused as pointless; every fleet box address is |
| refused as dangerous, and an incomplete inventory refuses the grant |
| @@ -474,6 +506,15 @@ does exist, at `internal/activator/`. |
| | new 2 | Freshness and totals depend on region HA | Accepted: `addrs` and `mem_total_mib` are gated fields, no freshness relied on (§4.2, §5.5) | |
| | new 3 | A reused host name inherits an override | Accepted: overrides bound to the record's `CreatedAt` (§3) | |
| +**Astra round 3** (REJECT; everything else resolved): |
| + |
| +| # | Finding | Disposition | |
| +|---|---|---| |
| +| R1-3 / new 1 | `carlos run` commands share the slice through a lowering restore | Accepted as a gate: no swap lowers memory; shrinks land at a cold start only (§5.4) | |
| +| R1-5 | Root keeps old allows through an incomplete read | Accepted: root clears allows while the inventory is incomplete (§4.2) | |
| +| new 2 | `BoxesComplete` is a liveness join, not inventory completeness | Accepted: its own completeness check (§4.2) | |
| +| new 3 | Fleet-door boxes cannot read the policy or the inventory | Accepted: account-scoped door read, a console-computed inventory object, refusal is not absence (§4.2) | |
| + |
| ## 10. Left to the builder |
| - Column names, the call shapes, and the status snapshot fields. |
| @@ -496,15 +537,19 @@ does exist, at `internal/activator/`. |
| - A grant naming another box's address is refused; one naming an address |
| a box acquires later is dropped by root and reported. |
| - Resize during a swap waits; growth reaches the serving generation's |
| - own cap; a memory shrink leaves the running process's limits alone and |
| - applies to the next generation. |
| + own cap; a memory shrink leaves every running process's limits alone, |
| + a swap's successor starts at the larger size, and the shrink lands at |
| + the next cold start. |
| - `small` and `xlarge` through the complete claim launch. |
| - A live growth updates the captured tier; a partly applied change |
| blocks the next swap until a read-back settles it. |
| -- A shrinking swap keeps the overlap figure until the old generation and |
| - old sidecar are proven stopped. |
| -- Root with an unreadable edges prefix writes no newly-allowed address; |
| - an old edges file still excludes its box's addresses. |
| +- Root with an incomplete inventory (listing error, unreadable file, a |
| + file without `addrs`, `complete: false` through the door) clears the |
| + allows and restores them on the next complete read; an old edges file |
| + still excludes its box's addresses. |
| +- A bucketless fleet box receives the policy and the inventory through |
| + the store door, cannot write either, and treats a refusal as |
| + incomplete, not absent. |
| - A host name deleted and recreated does not inherit its override. |
| - Another app's instance on the same box cannot reach the allowed |
| address; the probe cannot either. |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-instance-sizes-egress-R3.md b/docs/superpowers/specs/CODEX-FINDINGS-instance-sizes-egress-R3.md| new file mode 100644 |
| index 000000000..8e61cad10 |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-instance-sizes-egress-R3.md |
| @@ -0,0 +1,31 @@ |
| +R1-1: **RESOLVED** — Populated-slice skips remain; resizing waits for swap completion. |
| + |
| +R1-2: **RESOLVED** — Captured `Gen.Tier` follows read-back, and partial changes block swaps until settled. |
| + |
| +R1-3: **PARTLY (P1)** — The old-sidecar case is addressed; commands remain exposed to a lowering restore. See new finding 1. |
| + |
| +R1-4: **RESOLVED** — Startup reconciliation and periodic read-back retries cover failed revocations. |
| + |
| +R1-5: **PARTLY (P1)** — Expiry no longer removes exclusions, but [§4.2:184](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/docs/superpowers/specs/2026-10-21-instance-sizes-and-egress-design.md:184) explicitly retains previously checked allows during incomplete reads. If another box subsequently acquires an allowed address and its edges file cannot be read, that address stays open indefinitely. A past complete read cannot establish current safety. **Smallest fix:** clear effective app allows when inventory completeness cannot be established, retain known exclusions, and report the refusal until completeness returns. |
| + |
| +R1-6: **RESOLVED** — All three claim validators and complete-launch tests are explicitly included. |
| + |
| +R1-7: **RESOLVED** — Separate policy storage prevents older manifest writers from erasing it. |
| + |
| +R1-8: **RESOLVED** — The declined finding matches the explicitly retained size trust model. |
| + |
| +R1-9: **RESOLVED** — `HighOf` remains authoritative. |
| + |
| +R2-1: **RESOLVED** — Lowering restores now require proof that the old sidecar and its descendants stopped. |
| + |
| +R2-2: **RESOLVED** — Address changes and memory totals publish independently of region HA. |
| + |
| +R2-3: **RESOLVED** — Overrides are bound to the instance record’s `CreatedAt`. |
| + |
| +1. **P1 — The shrinking-swap proof still omits command units.** Commands share the host slice and have no separate memory cap ([carlos-instance-cmd@.service:30](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/host/units/carlos-instance-cmd@.service:30)). Retiring-generation commands are stopped asynchronously, without joining their termination before parent restoration ([cmd.go:377](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/activator/cmd.go:377)). Furthermore, command admission waits only for a spawning successor, so a new command can start after the switch while retirement still holds the overlap allowance ([cmd.go:336](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/activator/cmd.go:336)). Its start preserves that raised limit ([instancectl/cmd.go:78](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/instancectl/cmd.go:78)). During an `xlarge` → `small` swap, such a command can hold more than 256 MiB when the parent shrinks, allowing the resize to trigger OOM killing. [Kernel documentation](https://docs.kernel.org/admin-guide/cgroup-v2.html). **Smallest fix:** require retiring commands and descendants to be proven stopped, and hold new command starts until the smaller parent limit is successfully restored; preserve both requirements across restore retries. |
| + |
| +2. **P2 — `BoxesComplete` does not provide the inventory guarantee claimed in §4.2.** Unreadable edges files set `Snapshot.Complete=false` separately ([snapshot.go:233](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/edgesfile/snapshot.go:233)); missing counterparts are checked only for **fresh** status snapshots ([snapshot.go:173](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/edgesfile/snapshot.go:173)). Thus `BoxesComplete` can remain true with an unreadable edges file and stale status. Conversely, status objects older than 24 hours disappear from the listing ([fleet.go:268](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/status/fleet.go:268)), making a deliberately retained retired-box edges file fail the reverse join and block unrelated grants. **Smallest fix:** specify an inventory completeness check independent of liveness and the status listing horizon, checking every edges read and preserving retired-box exclusions. |
| + |
| +3. **P2 — Fleet boxes using the store API cannot read the new policy.** Bucketless fleet boxes obtain their deployment store through `openFleetDoorStore` ([agent.go:4366](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/host/agent.go:4366)). Its read ACL excludes `control/app-policy/<sqid>/<app>.json` ([storedoor.go:431](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/edge/storedoor.go:431)), and the client treats the resulting 404 as an absent object ([fleetapi.go:127](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-egress-sizes-328-329/internal/fleetapi/fleetapi.go:127)). These instances therefore silently retain build-default sizes and receive no allow. The required edges-prefix listing is also refused. **Smallest fix:** specify account-scoped policy reads and an authorized inventory transport through the fleet API, with tests proving policy delivery while policy writes remain forbidden. |
| + |
| +REJECT |
| \ No newline at end of file |