| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "net/http" |
| 6 | "reflect" |
| 7 | "runtime" |
| 8 | "slices" |
| 9 | "sync" |
| 10 | ) |
| 11 | |
| 12 | type claimEntry struct{ Kind, Pattern, Handler string } |
| 13 | type claimEntryKey struct{} |
| 14 | type claimInventory struct { |
| 15 | mu sync.Mutex |
| 16 | entries map[string]claimEntry |
| 17 | } |
| 18 | |
| 19 | func (i *claimInventory) add(e claimEntry) { |
| 20 | i.mu.Lock() |
| 21 | defer i.mu.Unlock() |
| 22 | if i.entries == nil { |
| 23 | i.entries = make(map[string]claimEntry) |
| 24 | } |
| 25 | key := e.Kind + " " + e.Pattern |
| 26 | if _, exists := i.entries[key]; exists { |
| 27 | panic("duplicate console entry: " + key) |
| 28 | } |
| 29 | i.entries[key] = e |
| 30 | } |
| 31 | func (i *claimInventory) snapshot() []claimEntry { |
| 32 | i.mu.Lock() |
| 33 | defer i.mu.Unlock() |
| 34 | out := make([]claimEntry, 0, len(i.entries)) |
| 35 | for _, e := range i.entries { |
| 36 | out = append(out, e) |
| 37 | } |
| 38 | slices.SortFunc(out, func(a, b claimEntry) int { |
| 39 | if a.Kind+a.Pattern < b.Kind+b.Pattern { |
| 40 | return -1 |
| 41 | } |
| 42 | if a.Kind+a.Pattern > b.Kind+b.Pattern { |
| 43 | return 1 |
| 44 | } |
| 45 | return 0 |
| 46 | }) |
| 47 | return out |
| 48 | } |
| 49 | func claimHandlerName(h any) string { |
| 50 | f := runtime.FuncForPC(reflect.ValueOf(h).Pointer()) |
| 51 | if f == nil { |
| 52 | panic("console entry has no handler") |
| 53 | } |
| 54 | return f.Name() |
| 55 | } |
| 56 | |
| 57 | // registerRoute preserves each handler's existing authorization order. It adds |
| 58 | // provenance for the effect-boundary audit; provenance does not grant a permit. |
| 59 | func (s *Server) registerRoute(pattern string, h http.HandlerFunc) { |
| 60 | e := claimEntry{Kind: "route", Pattern: pattern, Handler: claimHandlerName(h)} |
| 61 | s.claimEntries.add(e) |
| 62 | if !s.cfg.HostClaim { |
| 63 | s.mux.HandleFunc(pattern, h) |
| 64 | return |
| 65 | } |
| 66 | s.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { |
| 67 | h(w, r.WithContext(context.WithValue(r.Context(), claimEntryKey{}, e))) |
| 68 | }) |
| 69 | } |
| 70 | func (s *Server) registerClaimWorker(name string, h func(context.Context) error) func(context.Context) error { |
| 71 | e := claimEntry{Kind: "worker", Pattern: name, Handler: claimHandlerName(h)} |
| 72 | s.claimEntries.add(e) |
| 73 | return func(ctx context.Context) error { return h(context.WithValue(ctx, claimEntryKey{}, e)) } |
| 74 | } |
| 75 | |