package console import ( "context" "net/http" "reflect" "runtime" "slices" "sync" ) type claimEntry struct{ Kind, Pattern, Handler string } type claimEntryKey struct{} type claimInventory struct { mu sync.Mutex entries map[string]claimEntry } func (i *claimInventory) add(e claimEntry) { i.mu.Lock() defer i.mu.Unlock() if i.entries == nil { i.entries = make(map[string]claimEntry) } key := e.Kind + " " + e.Pattern if _, exists := i.entries[key]; exists { panic("duplicate console entry: " + key) } i.entries[key] = e } func (i *claimInventory) snapshot() []claimEntry { i.mu.Lock() defer i.mu.Unlock() out := make([]claimEntry, 0, len(i.entries)) for _, e := range i.entries { out = append(out, e) } slices.SortFunc(out, func(a, b claimEntry) int { if a.Kind+a.Pattern < b.Kind+b.Pattern { return -1 } if a.Kind+a.Pattern > b.Kind+b.Pattern { return 1 } return 0 }) return out } func claimHandlerName(h any) string { f := runtime.FuncForPC(reflect.ValueOf(h).Pointer()) if f == nil { panic("console entry has no handler") } return f.Name() } // registerRoute preserves each handler's existing authorization order. It adds // provenance for the effect-boundary audit; provenance does not grant a permit. func (s *Server) registerRoute(pattern string, h http.HandlerFunc) { e := claimEntry{Kind: "route", Pattern: pattern, Handler: claimHandlerName(h)} s.claimEntries.add(e) if !s.cfg.HostClaim { s.mux.HandleFunc(pattern, h) return } s.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { h(w, r.WithContext(context.WithValue(r.Context(), claimEntryKey{}, e))) }) } func (s *Server) registerClaimWorker(name string, h func(context.Context) error) func(context.Context) error { e := claimEntry{Kind: "worker", Pattern: name, Handler: claimHandlerName(h)} s.claimEntries.add(e) return func(ctx context.Context) error { return h(context.WithValue(ctx, claimEntryKey{}, e)) } }