carlos / platform Public

Let a sidecar's placement narrow but never widen

sidecarPlacement took the record's placement whenever it stated one, so a
record saying "any" under a no-inbound manifest placed that bot on boxes
the operator meant to keep it off — the widening direction the console
door already refuses by never accepting a placement field at all. A direct
bucket write, or an older writer, does not go through that door.

Most restrictive now wins: either voice asking for no-inbound gets it. A
record may still narrow an app that states no placement of its own, which
is the direction that reads as a requirement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WvcQVWggXkE5BAkjRDgWF
Paul Campbell pushed by paul@keymail.dev 7966b7948803402609445dd59859df9b587ae9d8 parent 0b2c709
2 files changed, +50 −4
  • internal/provision/sidecar_reconcile.go +17 −4
  • internal/provision/sidecar_reconcile_test.go +33 −0
diff --git a/internal/provision/sidecar_reconcile.go b/internal/provision/sidecar_reconcile.go
index 5297cddef..d7f86c8f3 100644
--- a/internal/provision/sidecar_reconcile.go
+++ b/internal/provision/sidecar_reconcile.go
@@ -75,11 +75,24 @@ func sidecarArgsJSON(m SidecarManifest) string {
}
// sidecarPlacement resolves the placement policy a record is placed under
-// (design §8). The record wins when it states one — it is the workload's own
-// requirement — and the manifest's PlacementOrDefault answers otherwise, so
-// an app-wide "everything I run must sit somewhere with no inbound path"
-// holds for records that never mention placement.
+// (design §8): MOST RESTRICTIVE WINS. Either voice asking for no-inbound gets
+// no-inbound, and only when both are silent (or both say "any") does the
+// workload place anywhere.
+//
+// The record can therefore still NARROW an app-wide "any" — one bot that must
+// sit somewhere with no inbound path, under an app with no such rule — which
+// is the direction that reads as a requirement. What it can no longer do is
+// WIDEN: a record stating "any" under a no-inbound manifest used to place that
+// bot on inbound boxes, turning app-wide policy into a suggestion. The console
+// door already refuses to write such a record (it never accepts a placement
+// field at all, exactly so the manifest stays authoritative); a direct bucket
+// write, an older writer, or anything else holding the prefix's keys does not
+// go through that door, and both doors hold the invariant rather than trusting
+// each other.
func sidecarPlacement(rec SidecarRecord, m SidecarManifest) string {
+ if rec.Placement == PlacementNoInbound || m.PlacementOrDefault() == PlacementNoInbound {
+ return PlacementNoInbound
+ }
if rec.Placement != "" {
return rec.Placement
}
diff --git a/internal/provision/sidecar_reconcile_test.go b/internal/provision/sidecar_reconcile_test.go
index cd996e5b6..81c1cf25b 100644
--- a/internal/provision/sidecar_reconcile_test.go
+++ b/internal/provision/sidecar_reconcile_test.go
@@ -415,6 +415,39 @@ func TestSidecarReconcilePlacementGate(t *testing.T) {
hasRoute(t, f.reg, f.host("hub", "relay"), "it must land on a box that declares CARLOS_INBOUND=none")
}
+// TestSidecarReconcilePlacementIsMostRestrictive: a record may NARROW the
+// app's placement but never widen it. "any" on a record under a no-inbound
+// manifest is the widening direction — the console door refuses to write it
+// at all, and this pass must refuse to honour one that reached the bucket by
+// any other route, or app-wide policy is only a suggestion.
+func TestSidecarReconcilePlacementIsMostRestrictive(t *testing.T) {
+ f := sidecarSetup(t)
+ // App-wide: everything this app runs sits somewhere with no inbound path.
+ f.putManifest(t, "eleven", SidecarManifest{Schema: 1, App: "eleven",
+ Args: []string{"sidecar", "run"}, Placement: PlacementNoInbound})
+ f.putRecord(t, SidecarRecord{Schema: 1, Name: "bot", App: "eleven", Channel: "stable", Placement: PlacementAny})
+
+ f.reconcile(t, ReconcileConfig{}) // an ordinary box: accepts inbound work
+ noRoute(t, f.reg, f.host("bot", "eleven"),
+ `a record's "any" must not override the app's no-inbound placement — that is the widening direction the console door already refuses`)
+
+ f.reconcile(t, ReconcileConfig{Inbound: InboundNone})
+ hasRoute(t, f.reg, f.host("bot", "eleven"), "it still lands where the app's own policy says it may")
+}
+
+// The narrowing direction is untouched: one bot that must run with no inbound
+// path, under an app that states nothing, is honoured.
+func TestSidecarReconcileRecordMayNarrowAnUnplacedApp(t *testing.T) {
+ f := sidecarSetup(t)
+ f.putManifest(t, "eleven", SidecarManifest{Schema: 1, App: "eleven", Args: []string{"sidecar", "run"}})
+ f.putRecord(t, SidecarRecord{Schema: 1, Name: "bot", App: "eleven", Channel: "stable", Placement: PlacementNoInbound})
+
+ f.reconcile(t, ReconcileConfig{})
+ noRoute(t, f.reg, f.host("bot", "eleven"), "a record asking for no-inbound must not land on an inbound box")
+ f.reconcile(t, ReconcileConfig{Inbound: InboundNone})
+ hasRoute(t, f.reg, f.host("bot", "eleven"), "a record may narrow an app that states no placement of its own")
+}
+
// TestSidecarReconcilePlacementChangeEvacuatesTheRow: placement is a SCOPE
// axis, beside region and pool, so flipping a manifest to no-inbound takes the
// row off a box that accepts inbound work — the same way repointing a