From 8c42daf4d98893e25baba21bef73286e318ad6dd Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Tue, 13 Oct 2026 19:33:54 +1100 Subject: [PATCH] Phase 9 wiring: name the Hub project `internet_control` The Hub routes by the project name in its human-owned routing.md, which already names this project `internet_control` (the placeholder row from the Hub brief). The frozen contract requires the /capabilities name to match that row, so /capabilities now reports `internet_control` as well. Found while wiring the live Hub (approved confirmation 60b235f3): the Hub routed the screen-time question to `internet_control` and returned `no_client` against a project registered as `internet_use_control`. Aligning the name, rather than editing the Hub repository or its routing file, makes the end-to-end round-trip work. Live proof after the rename and the Hub registration: POST http://192.168.20.13:8788/ask {"text":"how long have the kids been on screen today?","asker":"sam","source":"console"} 200 {"status":"ok","answer":"The kids have had 1h 22m of screen time today.", "route":{"kind":"project","project":"internet_control"}} Repository, module and service names are unchanged; only the Hub-facing project string changed. Co-Authored-By: Paperclip --- README.md | 12 +++++++----- internal/hub/ask_test.go | 2 +- internal/hub/hub.go | 8 ++++++-- internal/web/hub_api_test.go | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 369848b..724a74a 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ dashboard session and the admin API. With no token set they answer `503`. | Method | Path | Notes | |---|---|---| -| `GET` | `/capabilities` | `{"project":"internet_use_control","topics":[…],"people":[…]}`. The Hub reads it to route a question. | +| `GET` | `/capabilities` | `{"project":"internet_control","topics":[…],"people":[…]}`. The Hub reads it to route a question; the name matches the Hub's `routing.md` row. | | `POST` | `/ask` | Read-only. Body is the contract's `AskRequest`; the reply is always `200` with an `answer` or `answer:null` plus a `reason`. | | `POST` | `/notify` | Accepts the contract's `{"from","level","message","person"}` and delivers it to Sam through Apprise. | @@ -523,7 +523,9 @@ deploy/verify-phase9.sh Hub + alerts proof payload contract above is what they implement. - No Alerts screen in the dashboard yet. The alert history is in `alerts` and is readable with `control alerts`; the screen is a later UI change. -- The Hub is not wired to this service yet. `/ask` is proven directly with the - exact Hub request shape (`make verify-phase9`); registering the project with - the running Hub (its `HUB_PROJECTS` and a project token) and restarting the - `hub` container is a host change pending approval. +- The Hub is wired to this service: the running Hub on `.13:8788` routes + screen-time questions here and returns the answer. Registration is host + configuration, not code: the project is in the Hub's `HUB_PROJECTS` and + `HUB_PROJECT_INTERNET_CONTROL_TOKEN`, and the Hub's human-owned `routing.md` + row already names the project `internet_control` (which is why + `/capabilities` uses that name). diff --git a/internal/hub/ask_test.go b/internal/hub/ask_test.go index e80f63f..4ff002e 100644 --- a/internal/hub/ask_test.go +++ b/internal/hub/ask_test.go @@ -193,7 +193,7 @@ func TestPermissionsAndUnknowns(t *testing.T) { func TestDefaultCapabilities(t *testing.T) { c := DefaultCapabilities() - if c.Project != "internet_use_control" { + if c.Project != "internet_control" { t.Errorf("project = %q", c.Project) } if len(c.Topics) == 0 || len(c.People) == 0 { diff --git a/internal/hub/hub.go b/internal/hub/hub.go index 1405ced..c2fa3ed 100644 --- a/internal/hub/hub.go +++ b/internal/hub/hub.go @@ -15,8 +15,12 @@ // property, not a comment (TestAskWritesNothing). package hub -// ProjectName is the value the Hub matches in routing.md. -const ProjectName = "internet_use_control" +// ProjectName is the value the Hub matches in routing.md. The Hub's topic map +// (and src/understand/rules.go) already names this project `internet_control`, +// and the contract requires the /capabilities name to match that row, so this +// is the Hub-facing name. The repository and the service keep the fuller +// "Internet Use Control" name. +const ProjectName = "internet_control" // Capabilities is the GET /capabilities reply. The Hub reads it to route a // question to this project. Keep the topic list short and plain; it holds no diff --git a/internal/web/hub_api_test.go b/internal/web/hub_api_test.go index 680664b..8053e16 100644 --- a/internal/web/hub_api_test.go +++ b/internal/web/hub_api_test.go @@ -90,7 +90,7 @@ func TestCapabilitiesRequiresToken(t *testing.T) { if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatalf("decode: %v", err) } - if body.Project != "internet_use_control" { + if body.Project != "internet_control" { t.Errorf("project = %q", body.Project) } if len(body.Topics) == 0 { @@ -202,7 +202,7 @@ func TestNotifyDeliversThroughApprise(t *testing.T) { srv, st := newHubTestApp(t, testHubToken, stub.URL) rec := hubRequest(t, srv.Handler(), http.MethodPost, "/notify", - `{"from":"internet_use_control","level":"important","message":"3 emails need a decision.","person":"sam"}`, + `{"from":"internet_control","level":"important","message":"3 emails need a decision.","person":"sam"}`, testHubToken) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200 (%s)", rec.Code, rec.Body.String())