carlos / platform Public

edge: store door grants a read of the fleet's layout intent

aclRead had no allowance for control/layout.json (release.LayoutIntentKey),
so a box on the fleet door read a denied 404 for it forever and never
converged its storage layout. Grant a read of exactly that key to every
fleet box, the same way control/serial is granted: it is read-only, and
the object carries only a mode and a box label, never tenant data.
aclWrite still refuses it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 945acac37df3de63c16b1cacd899d221d1851810 parent 7075fe1
2 files changed, +34 −1
  • internal/edge/storedoor.go +9 −1
  • internal/edge/storedoor_test.go +25 −0
diff --git a/internal/edge/storedoor.go b/internal/edge/storedoor.go
index 115d39ed8..ae4e1e9cb 100644
--- a/internal/edge/storedoor.go
+++ b/internal/edge/storedoor.go
@@ -356,6 +356,8 @@ func canonicalPrefix(raw string) (string, bool) {
// bootstrap/carlos the sanctioned self-update path (F7);
// stays until update-platform-binary.sh
// is retired (spec §10)
+// control/layout.json the fleet's layout intent (isolated-exec
+// design §8.2) — see release.LayoutIntentKey()
// control/system/<c>/<ch>.json its platform channel pointer (system
// update spec §3) — see systemReadKey
// bootstrap/router/<v>/<asset> a mirrored platform artifact — see
@@ -377,6 +379,12 @@ func canonicalPrefix(raw string) (string, bool) {
// binding takeover guard and the heartbeat both read their own object first.
// It grants access to nothing beyond what aclWrite already grants.
//
+// control/layout.json is not in F3's enumeration either, and is granted for
+// the same reason control/serial is: every box needs the fleet's layout
+// intent to converge (isolated-exec design §8.2), and the object holds
+// nothing more than a mode and a box label, never tenant data. Write stays
+// console-only: aclWrite never answers true for it.
+//
// domains/ is different in kind, not degree (#151): a claim record
// (domains.Claim) is a hostname→account/app/route binding, and that binding
// is cross-tenant-readable BY DESIGN — it is exactly the information DNS and
@@ -391,7 +399,7 @@ func aclRead(sqid, fleet, key string) bool {
return false
}
switch key {
- case "control/serial", "bootstrap/carlos":
+ case "control/serial", "bootstrap/carlos", release.LayoutIntentKey():
return true
}
if systemReadKey(key) {
diff --git a/internal/edge/storedoor_test.go b/internal/edge/storedoor_test.go
index 8210ea4ef..fab601a69 100644
--- a/internal/edge/storedoor_test.go
+++ b/internal/edge/storedoor_test.go
@@ -118,6 +118,10 @@ func TestStoreDoorACLMatrix(t *testing.T) {
{"bootstrap/router.sig", opRead, false, "nothing else under bootstrap/"},
{"bootstrap/router/../../x", opRead, false, "traversal is refused by canonicalKey before the ACL"},
{"control/system/../serial", opRead, false, "…on both prefixes"},
+ {release.LayoutIntentKey(), opRead, true, "every box needs the fleet's layout intent to converge"},
+ {release.LayoutIntentKey(), opWrite, false, "a box must never author the fleet's layout intent"},
+ {release.LayoutIntentKey(), opDelete, false, "nor delete it"},
+ {"control/layout.json2", opRead, false, "the allowlist is exact, not a prefix"},
// --- own account estate ---
// The account manifest is accounts/<sqid>/account.json
@@ -228,6 +232,27 @@ func TestStoreDoorACLMatrix(t *testing.T) {
}
}
+// TestAclReadLayoutIntentNeedsBothIdentities pins the same empty-sqid,
+// empty-fleet guard aclRead already applies to every other key: an
+// authenticated identity is required before the allowlist is even
+// consulted, so a request that never resolved a fleet row cannot read the
+// layout intent either.
+func TestAclReadLayoutIntentNeedsBothIdentities(t *testing.T) {
+ key := release.LayoutIntentKey()
+ if !aclRead(testSqid, testFleet, key) {
+ t.Errorf("aclRead(%q, %q, %q) = false, want true", testSqid, testFleet, key)
+ }
+ if aclRead("", testFleet, key) {
+ t.Errorf("aclRead(\"\", %q, %q) = true, want false", testFleet, key)
+ }
+ if aclRead(testSqid, "", key) {
+ t.Errorf("aclRead(%q, \"\", %q) = true, want false", testSqid, key)
+ }
+ if aclWrite(testSqid, testFleet, key) {
+ t.Errorf("aclWrite(%q, %q, %q) = true, want false", testSqid, testFleet, key)
+ }
+}
+
// TestCanonicalKeyRefusesShapes pins the pre-ACL gate on its own: the ACL is
// prefix-string exact, which is only safe because nothing that could re-point
// a prefix ever reaches it.