diff --git a/app/gateway/index.ts b/app/gateway/index.ts index 9adf3ed..c306dff 100644 --- a/app/gateway/index.ts +++ b/app/gateway/index.ts @@ -16,6 +16,14 @@ export interface GatewayDeps { export function createGateway(deps: GatewayDeps): Hono { const app = new Hono(); + // The gateway has no UI; people who open its URL in a browser should see that it's running, not a 404. + app.get("/", (c) => + c.text( + "software-factory gateway is running.\n\n" + + "It has no web UI. GitHub sends webhooks to POST /webhooks/github, and the factory replies on issues and pull requests.\n" + + "Health checks: GET /health and GET /ready. Setup and usage: docs/quickstart.md in your software-factory repository.\n", + ), + ); app.get("/health", (c) => c.json({ status: "ok" })); app.get("/ready", (c) => c.json({ status: "ready" })); diff --git a/docs/quickstart.md b/docs/quickstart.md index a0c2da4..7512d49 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -59,6 +59,10 @@ Setup is complete when: - applying `factory:triage` produces an App-authored status comment and a useful outcome. +software-factory has no web UI. You use it through labels and comments in +GitHub. Opening the `factory-gateway` URL in a browser shows only a short +message that the gateway is running. + ## Next steps - [Configuration](configuration.md) — change settings, follow or cancel runs, diff --git a/docs/runbooks/setup.md b/docs/runbooks/setup.md index 5656ee5..577da6a 100644 --- a/docs/runbooks/setup.md +++ b/docs/runbooks/setup.md @@ -244,6 +244,9 @@ and apply it. The form asks for no values. Continue when both services have deployed and the gateway is live. +The gateway has no web UI. Opening its URL in a browser shows a short +message that it is running; that is expected, not a failed deploy. + ## Phase 8: Configure GitHub Ask for the public gateway URL. It is not a secret. Run: diff --git a/tests/gateway.test.ts b/tests/gateway.test.ts index 2ab2390..d91b6f7 100644 --- a/tests/gateway.test.ts +++ b/tests/gateway.test.ts @@ -44,6 +44,16 @@ describe("gateway", () => { expect(await ready.json()).toEqual({ status: "ready" }); }); + it("tells a browser at / that the headless gateway is running, without revealing its secret", async () => { + const res = await app.request("/"); + expect(res.status).toBe(200); + const text = await res.text(); + expect(text).toContain("software-factory gateway is running"); + expect(text).toContain("no web UI"); + expect(text).not.toContain(SECRET); + expect(startTask).not.toHaveBeenCalled(); + }); + it("rejects a bad signature before parsing the body", async () => { const parse = vi.spyOn(JSON, "parse"); const res = await app.fetch(webhook("{not json", { signature: sign("{not json", "wrong") }));