carlos / platform Public

console: list possible placement discrepancies on start (nearest-region §5.7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev ac1e7d4bde527ff765bd513e7bf9d7f278da32f2 parent 594cf6f
3 files changed, +176 −0
  • cmd/console/main.go +3 −0
  • internal/console/regiondrift.go +52 −0
  • internal/console/regiondrift_test.go +121 −0
diff --git a/cmd/console/main.go b/cmd/console/main.go
index 7ca049f16..68485a4d8 100644
--- a/cmd/console/main.go
+++ b/cmd/console/main.go
@@ -401,6 +401,9 @@ func serve() {
// Once, at boot: log any route already sitting at another app's platform
// apex (internal/console/apex.go). Reports; repairs nothing.
go srv.ReportApexSquats(context.Background())
+ // Spec §5.7: after any roll, list possible placement discrepancies for
+ // operator review. Read-only; never blocks startup.
+ go srv.LogPlacementDiscrepancies(context.Background())
addr := envOr("CONSOLE_ADDR", "127.0.0.1:8090")
ln, err := net.Listen("tcp", addr)
if err != nil {
diff --git a/internal/console/regiondrift.go b/internal/console/regiondrift.go
new file mode 100644
index 000000000..c226272c5
--- /dev/null
+++ b/internal/console/regiondrift.go
@@ -0,0 +1,52 @@
+package console
+
+import (
+ "context"
+ "log"
+
+ "github.com/carlosframework/platform/internal/provision"
+)
+
+// discrepancyScanCap bounds one app's record listing. ListRecords treats
+// max=0 as "list nothing", so the cap must be explicit.
+const discrepancyScanCap = 10000
+
+// LogPlacementDiscrepancies is spec §5.7 step 4, run once at console
+// start. For each app holding a non-home region, it lists records with an
+// empty region created after the app — the shape a console rolled back
+// below the floor would have written. Shapes that never inherit the app
+// default are skipped (static, addr-backed, replica, pooled, fleet). A
+// deliberate home override looks the same and the record keeps no
+// provenance, so each line is a CANDIDATE for review, worded as one (C29).
+// It only logs: an error is reported and skipped, never returned, so the
+// check can neither block nor fail console start.
+func (s *Server) LogPlacementDiscrepancies(ctx context.Context) {
+ s.logPlacementDiscrepancies(ctx, discrepancyScanCap)
+}
+
+func (s *Server) logPlacementDiscrepancies(ctx context.Context, scanCap int) {
+ if s.cfg.Releases == nil {
+ return
+ }
+ apps, err := s.store.NonHomeAppRegions()
+ if err != nil {
+ log.Printf("console: placement discrepancy check: %v", err)
+ return
+ }
+ for _, a := range apps {
+ recs, truncated, err := provision.ListRecords(ctx, s.cfg.Releases, a.Sqid, a.App, scanCap)
+ if err != nil {
+ log.Printf("console: placement discrepancy check %s/%s: %v", a.Sqid, a.App, err)
+ continue
+ }
+ if truncated {
+ log.Printf("console: placement discrepancy check %s/%s: only the first %d records were checked", a.Sqid, a.App, scanCap)
+ }
+ for _, r := range recs {
+ if r.Region != "" || r.Role != "" || r.Pool != "" || !r.DerivesPaths() || r.CreatedAt.Before(a.CreatedAt) {
+ continue
+ }
+ log.Print("console: " + msgPlacementDiscrepancy(r.Host, a.App, a.Region))
+ }
+ }
+}
diff --git a/internal/console/regiondrift_test.go b/internal/console/regiondrift_test.go
new file mode 100644
index 000000000..3ca2ce158
--- /dev/null
+++ b/internal/console/regiondrift_test.go
@@ -0,0 +1,121 @@
+package console
+
+import (
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+// codex R2-6: shapes that never inherit the app default are not listed;
+// a no-region socket-backed instance created after the app is.
+func TestPlacementDiscrepancyLog(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.srv.cfg.HomeRegion = "ie"
+ created := time.Now().Add(-time.Hour)
+ fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", created)
+ ctx := context.Background()
+ put := func(r provision.Record) {
+ r.Schema, r.App, r.Account, r.Channel, r.CreatedAt = 1, "blog", release.OpsAccount, "stable", time.Now()
+ if err := provision.CreateRecord(ctx, fx.rel, r); err != nil {
+ t.Fatal(err)
+ }
+ }
+ put(provision.Record{Host: "blog.go.tito.io", Kind: "static"})
+ put(provision.Record{Host: "svc.go.tito.io", Kind: "service", Addrs: []string{"10.0.0.4:8080"}})
+ put(provision.Record{Host: "r.go.tito.io", Region: "au", Role: "replica"})
+ put(provision.Record{Host: "pooled.go.tito.io", Pool: "tito"})
+ put(provision.Record{Host: "uk.go.tito.io", Region: "uk"})
+ put(provision.Record{Host: "drift.go.tito.io"})
+ var logs strings.Builder
+ restore := captureLog(&logs)
+ fx.srv.LogPlacementDiscrepancies(ctx)
+ restore()
+ out := logs.String()
+ if !strings.Contains(out, msgPlacementDiscrepancy("drift.go.tito.io", "blog", "uk")) {
+ t.Fatalf("drift not listed: %s", out)
+ }
+ for _, quiet := range []string{"blog.go.tito.io", "svc.go.tito.io", "r.go.tito.io", "pooled.go.tito.io", "uk.go.tito.io"} {
+ if strings.Contains(out, quiet+" of") {
+ t.Errorf("%s listed", quiet)
+ }
+ }
+}
+
+// A record older than its app cannot be the roll-forward's work, so it is
+// not listed.
+func TestPlacementDiscrepancyLogSkipsRecordsOlderThanTheApp(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", time.Now().Add(-time.Hour))
+ ctx := context.Background()
+ err := provision.CreateRecord(ctx, fx.rel, provision.Record{
+ Schema: 1, Host: "old.go.tito.io", App: "blog", Account: release.OpsAccount,
+ Channel: "stable", CreatedAt: time.Now().Add(-2 * time.Hour),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ var logs strings.Builder
+ restore := captureLog(&logs)
+ fx.srv.LogPlacementDiscrepancies(ctx)
+ restore()
+ if strings.Contains(logs.String(), "old.go.tito.io") {
+ t.Fatalf("older record listed: %s", logs.String())
+ }
+}
+
+// A bucket that cannot be listed is logged (O18) and never fails or panics:
+// the check is advisory.
+func TestPlacementDiscrepancyLogListError(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", time.Now())
+ fx.srv.cfg.Releases = failingList{fx.rel}
+ var logs strings.Builder
+ restore := captureLog(&logs)
+ fx.srv.LogPlacementDiscrepancies(context.Background())
+ restore()
+ want := "console: placement discrepancy check " + release.OpsAccount + "/blog: bucket unreachable"
+ if !strings.Contains(logs.String(), want) {
+ t.Fatalf("want %q in %s", want, logs.String())
+ }
+}
+
+// Past the cap the check says so (O9) instead of silently stopping.
+func TestPlacementDiscrepancyLogTruncated(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", time.Now().Add(-time.Hour))
+ ctx := context.Background()
+ for _, h := range []string{"a.go.tito.io", "b.go.tito.io"} {
+ err := provision.CreateRecord(ctx, fx.rel, provision.Record{
+ Schema: 1, Host: h, App: "blog", Account: release.OpsAccount,
+ Channel: "stable", CreatedAt: time.Now(),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ var logs strings.Builder
+ restore := captureLog(&logs)
+ fx.srv.logPlacementDiscrepancies(ctx, 1)
+ restore()
+ want := "console: placement discrepancy check " + release.OpsAccount + "/blog: only the first 1 records were checked"
+ if !strings.Contains(logs.String(), want) {
+ t.Fatalf("want %q in %s", want, logs.String())
+ }
+}
+
+// No deployment bucket: nothing to check, nothing logged.
+func TestPlacementDiscrepancyLogNoBucket(t *testing.T) {
+ fx := newConsoleFixture(t)
+ fx.srv.cfg.Releases = nil
+ var logs strings.Builder
+ restore := captureLog(&logs)
+ fx.srv.LogPlacementDiscrepancies(context.Background())
+ restore()
+ if logs.Len() != 0 {
+ t.Fatalf("logged: %s", logs.String())
+ }
+}