package host import ( "go/ast" "go/parser" "go/token" "io/fs" "os" "path/filepath" "slices" "sort" "strings" "testing" ) // Ruling 6 for step 8 readiness: nothing changes an effect on a claims-off // or no-census box. Every new or widened census entry point is reached only // from claim-mode code. journal-migrate refuses without CARLOS_HOST_CLAIM // (RunSystemJournalMigrate), the root migration runs only under it, the // claim assembly and root inventory startup exist only with claims on, and // the retry directory is prepared only with a census or claim units on. This // pins the callers, so a new claims-off caller fails here first. func TestStep8CensusChangesReachOnlyClaimMode(t *testing.T) { callers := map[string][]string{} fset := token.NewFileSet() entries, err := os.ReadDir(".") if err != nil { t.Fatal(err) } for _, e := range entries { name := e.Name() if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { continue } f, err := parser.ParseFile(fset, name, nil, 0) if err != nil { t.Fatal(err) } for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok || fn.Body == nil { continue } ast.Inspect(fn.Body, func(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: if id, ok := x.Fun.(*ast.Ident); ok { callers[id.Name] = append(callers[id.Name], fn.Name.Name) } case *ast.CompositeLit: // The collector's registry listing: routes: r.Registry.List. if typ, ok := x.Type.(*ast.Ident); ok && typ.Name == "rootMigrationCollector" { for _, el := range x.Elts { if kv, ok := el.(*ast.KeyValueExpr); ok { if k, ok := kv.Key.(*ast.Ident); ok && k.Name == "routes" { callers["collector.routes"] = append(callers["collector.routes"], fn.Name.Name) } } } } } return true }) } } want := map[string][]string{ "claimMigrationSharedPaths": {"migrateRootClaimAuthority"}, "verifyMigrationWindow": {"migrateRootClaimAuthority", "verifyJournalMigrateWindow"}, "verifyJournalMigrateWindow": {"RunSystemJournalMigrate"}, "prepareRootFloorDirs": {"migrateRootClaimAuthority"}, "prepareRootFloorDirsOwned": {"prepareRootFloorDirs"}, "repairRootFloorDir": {"prepareRootFloorDirsOwned"}, "walkNoFollowDir": {"consoleDir", "repairRootFloorDir"}, "claimBoxDataCensus": {"claimDataCensus"}, "claimDataCensus": {"claimGlobalSharedPaths"}, "claimGlobalSharedPaths": {"OpenProductionClaimAssembly", "claimMigrationSharedPaths", "recoverRootInventoryStartup"}, "migrateRootClaimAuthority": {"migrateClaimJournalsWith"}, "prepareRootRetryUnitDirectory": {"installActivationControl", "readClaimUnitSettings"}, "collector.routes": {"migrateRootClaimAuthority"}, } for name, w := range want { got := slices.Compact(sortedCopy(callers[name])) if !slices.Equal(got, w) { t.Errorf("%s is called from %v, want %v", name, got, w) } } checkBackfillDoneCallers(t) checkRosterBindingCallers(t) } // Ruling 6 for roster binding (design-roster-revision-holds.md, task 10): a // claims-off or no-census box never builds a claim assembly or claim owners, // so it computes no binding, compares none, and settles nothing. The binding // is computed only by the claim roster adapter and the cleanup identity, both // built only by OpenClaimOwners, which only the claim assembly and the claim // journal migration call. SameBinding is called only at the four claim-mode // sites; the cold handover's fresh admission and obsolete settlement are // wired only into root's claim handler. The migration-state and floor seams, // the obsolete-intent settlement and the stop numeral bound keep their // callers. The whole module's non-test sources are scanned, so a new caller // anywhere fails here first. func checkRosterBindingCallers(t *testing.T) { t.Helper() callers := map[string][]string{} fset := token.NewFileSet() for _, root := range []string{"../..", "../../cmd", "../../internal"} { err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if path != root && (root == "../.." || strings.HasPrefix(d.Name(), ".") || d.Name() == "testdata" || d.Name() == "vendor") { return filepath.SkipDir } return nil } if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } f, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution) if err != nil { return err } rel, _ := filepath.Rel("../..", filepath.Dir(path)) for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok || fn.Body == nil { continue } name := rel + " " + fn.Name.Name if fn.Recv != nil && len(fn.Recv.List) == 1 { typ := fn.Recv.List[0].Type if star, ok := typ.(*ast.StarExpr); ok { typ = star.X } if id, ok := typ.(*ast.Ident); ok { name = rel + " " + id.Name + "." + fn.Name.Name } } ast.Inspect(fn.Body, func(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: callee := "" switch f := x.Fun.(type) { case *ast.Ident: callee = f.Name case *ast.SelectorExpr: callee = f.Sel.Name } switch callee { case "BoxBinding", "SameBinding", "verifyClaimCleanupEnrollment", "OpenClaimOwners", "SettleObsoleteColdHandover", "obsoleteIntent", "settleObsoleteIntent", "claimRootHTTP", "rootStopCoordinator", "openRootMigrationState", "rootMigrationStartupFloors", "SettleObsoleteRootInventory", "boundRosterRevision": callers[callee] = append(callers[callee], name) } case *ast.CompositeLit: typ := "" switch f := x.Type.(type) { case *ast.Ident: typ = f.Name case *ast.SelectorExpr: typ = f.Sel.Name } if typ == "productionClaimRoster" { callers["productionClaimRoster{}"] = append(callers["productionClaimRoster{}"], name) } if typ == "ColdHandover" { for _, el := range x.Elts { if kv, ok := el.(*ast.KeyValueExpr); ok { if k, ok := kv.Key.(*ast.Ident); ok && (k.Name == "Admit" || k.Name == "Obsolete") { callers["ColdHandover{"+k.Name+"}"] = append(callers["ColdHandover{"+k.Name+"}"], name) } } } } case *ast.AssignStmt: for _, lhs := range x.Lhs { if sel, ok := lhs.(*ast.SelectorExpr); ok && sel.Sel.Name == "Obsolete" { callers["."+sel.Sel.Name+"="] = append(callers["."+sel.Sel.Name+"="], name) } // The seams stand in for root-owned state only in // tests: a production reassignment would disable // the migration directory read or the floor check. if id, ok := lhs.(*ast.Ident); ok && (id.Name == "openRootMigrationState" || id.Name == "rootMigrationStartupFloors") { callers[id.Name+"="] = append(callers[id.Name+"="], name) } } } return true }) } return nil }) if err != nil { t.Fatal(err) } } want := map[string][]string{ "BoxBinding": {"internal/host productionClaimRoster.Actor", "internal/host verifyClaimCleanupEnrollment"}, "productionClaimRoster{}": {"internal/host ProductionClaimOwnerProvider.OpenClaimOwners"}, "verifyClaimCleanupEnrollment": {"internal/host ProductionClaimOwnerProvider.OpenClaimOwners"}, "OpenClaimOwners": {"internal/host OpenProductionClaimAssembly", "internal/host migrateClaimJournalsWith"}, "SameBinding": {"internal/host ClaimOwnerBindings.claimEvidenceCoordinator", "internal/host claimBoundIdentity", "internal/host claimRegionalEnrollment.VerifyRegional", "internal/host servingParkRoot.prepareOrCommitServingReboot"}, "ColdHandover{Admit}": {"internal/host ClaimRootHTTP.coldHandover"}, ".Obsolete=": {"internal/host ClaimRootHTTP.RecoverPending"}, "SettleObsoleteColdHandover": {"internal/hostbox ColdHandover.settleObsolete"}, "obsoleteIntent": {"internal/host ClaimRootHTTP.RecoverPending", "internal/host ClaimRootHTTP.recoverRootInventory"}, "settleObsoleteIntent": {"internal/host ClaimRootHTTP.recoverRootInventory"}, "claimRootHTTP": {"internal/host NewProductionRootClaimStop"}, "rootStopCoordinator": {"internal/host NewProductionRootClaimStop"}, // The root-owned migration directory's seam (census ruling (a), // platform-d8) and fix round 1's floor seam: a third caller fails. "openRootMigrationState": {"internal/host recoverRootInventoryStartup", "internal/host verifyRootMigrationStartup"}, "rootMigrationStartupFloors": {"internal/host verifyRootMigrationStartup"}, // Fix round 1: the obsolete-intent settlement and the numeral bound. "SettleObsoleteRootInventory": {"internal/host ClaimRootHTTP.settleObsoleteIntent"}, "boundRosterRevision": {"internal/host ClaimStopCoordinator.FenceAndStop", "internal/host ClaimStopCoordinator.witnessNoProcess"}, } for name, w := range want { got := slices.Compact(sortedCopy(callers[name])) if !slices.Equal(got, w) { t.Errorf("%s is called from %v, want %v", name, got, w) } } for _, seam := range []string{"openRootMigrationState=", "rootMigrationStartupFloors="} { if got := callers[seam]; len(got) != 0 { t.Errorf("%s is reassigned in production by %v, want nowhere", strings.TrimSuffix(seam, "="), got) } } // Obsolete is set only by the assignment above, never in a literal. (An // Admit assignment is not pinned: other types' Admit fields share the name.) if got := callers["ColdHandover{Obsolete}"]; len(got) != 0 { t.Errorf("ColdHandover{Obsolete} is set in %v, want nowhere", got) } } // Ruling 6 for backfill-done (claim trial Option B): the new signer phase is // reached only through carlos system claims run on the trusted signer, and // its copy of the census's report check is reached only by Done. // The whole module's non-test sources are scanned, so a call from a box, // console or agent path fails here first. Backfill.Done is matched as a // three-argument Done call (ctx, signer, etag): context's Done takes none. func checkBackfillDoneCallers(t *testing.T) { t.Helper() callers := map[string][]string{} fset := token.NewFileSet() for _, root := range []string{"../..", "../../cmd", "../../internal"} { err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if path != root && (root == "../.." || strings.HasPrefix(d.Name(), ".") || d.Name() == "testdata" || d.Name() == "vendor") { return filepath.SkipDir } return nil } if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } f, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution) if err != nil { return err } rel, _ := filepath.Rel("../..", filepath.Dir(path)) for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok || fn.Body == nil { continue } name := rel + " " + fn.Name.Name ast.Inspect(fn.Body, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } switch f := call.Fun.(type) { case *ast.Ident: if f.Name == "verifyCensusReport" { callers[f.Name] = append(callers[f.Name], name) } case *ast.SelectorExpr: if f.Sel.Name == "backfillDone" || (f.Sel.Name == "Done" && len(call.Args) == 3) { callers[f.Sel.Name] = append(callers[f.Sel.Name], name) } } return true }) } return nil }) if err != nil { t.Fatal(err) } } want := map[string][]string{ "Done": {"internal/claimactivation backfillDone"}, "backfillDone": {"internal/claimactivation Run"}, "verifyCensusReport": {"internal/hostops Done"}, } for name, w := range want { got := slices.Compact(sortedCopy(callers[name])) if !slices.Equal(got, w) { t.Errorf("%s is called from %v, want %v", name, got, w) } } } func sortedCopy(s []string) []string { out := append([]string(nil), s...) sort.Strings(out) return out }