Skip to content

Commit f0e9fe0

Browse files
author
codex-release
committed
merge: gateway-route-scopes (PR #1471) into minutes-mcp-viewer
2 parents 40c459f + 791caa4 commit f0e9fe0

16 files changed

Lines changed: 1054 additions & 171 deletions

File tree

.dockerignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# Root .dockerignore — used by the meeting-api build (compose `context: ../..`). The other three
2-
# services build from their own narrow contexts. Keep the context light: exclude vendored/build
3-
# trees + venvs; the Dockerfile COPYs only the meeting-api src/, its pyproject+lock, and the five
4-
# sealed contract files it loads by path.
1+
# Root .dockerignore — used by every build whose context is the repository root: meeting-api,
2+
# agent-api, agent-worker and the gateway (compose `context: ../..`). Those Dockerfiles COPY only
3+
# their own src/ + pyproject/lock plus the specific files they load BY PATH — meeting-api's five
4+
# sealed contract schemas, the gateway's five routes.v1 manifests. Keep the context light: exclude
5+
# vendored/build trees + venvs. The remaining services build from their own narrow contexts.
56
node_modules/
67
**/node_modules/
78
**/.venv/

core/agent/routes.v1.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"contract": "routes.v1",
3+
"domain": "agent",
4+
"owner": "core/agent",
5+
"edge": "gateway",
6+
"note": "The agent control plane. ABSENT in the no-agents profile (PRD decision 40.6) \u2014 these seven rows and the routes that carry them are simply not there, so a request 404s.",
7+
"presence_env": "AGENT_API_URL",
8+
"routes": [
9+
{
10+
"method": "POST",
11+
"path": "/agent/chat",
12+
"scopes": [
13+
"bot",
14+
"tx"
15+
]
16+
},
17+
{
18+
"method": "GET",
19+
"path": "/agent/meeting/stream",
20+
"scopes": [
21+
"bot",
22+
"tx"
23+
]
24+
},
25+
{
26+
"method": "DELETE",
27+
"path": "/agent/{path:path}",
28+
"scopes": [
29+
"bot",
30+
"tx"
31+
]
32+
},
33+
{
34+
"method": "GET",
35+
"path": "/agent/{path:path}",
36+
"scopes": [
37+
"bot",
38+
"tx"
39+
]
40+
},
41+
{
42+
"method": "PATCH",
43+
"path": "/agent/{path:path}",
44+
"scopes": [
45+
"bot",
46+
"tx"
47+
]
48+
},
49+
{
50+
"method": "POST",
51+
"path": "/agent/{path:path}",
52+
"scopes": [
53+
"bot",
54+
"tx"
55+
]
56+
},
57+
{
58+
"method": "PUT",
59+
"path": "/agent/{path:path}",
60+
"scopes": [
61+
"bot",
62+
"tx"
63+
]
64+
}
65+
]
66+
}

core/gateway/services/gateway/Dockerfile

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# syntax=docker/dockerfile:1
22
# Gateway (v0.12 P4) — the production edge: auth · routing · /ws fan-out.
3-
# Build context: gateway/services/gateway (pyproject.toml + uv.lock + src/).
3+
#
4+
# BUILD CONTEXT: THE REPOSITORY ROOT (root .dockerignore keeps it light), like meeting-api and
5+
# agent-api. It was this service's own directory until the edge stopped owning its route table:
6+
# `routes_manifest.py` assembles the table from each domain's `routes.v1.json`, read BY PATH at
7+
# import, so the image must carry the manifests of the domains it fronts — and a narrow context
8+
# cannot reach them. Same reason meeting-api builds from the root and COPYs the five sealed
9+
# contract files it loads. A missing manifest is not a degraded gateway: the process refuses to
10+
# start, which is correct and is exactly how this was found (compose stack, gateway unhealthy).
411
FROM python:3.12-slim
512

613
# uv via PyPI (ghcr.io is unreachable in this build env) — pinned for deterministic resolves.
@@ -15,7 +22,7 @@ ENV UV_LINK_MODE=copy \
1522
WORKDIR /app
1623

1724
# 1) Resolve + install the pinned dependency set from the lockfile (no project — package=false).
18-
COPY pyproject.toml uv.lock ./
25+
COPY core/gateway/services/gateway/pyproject.toml core/gateway/services/gateway/uv.lock ./
1926
RUN --mount=type=cache,target=/root/.cache/uv \
2027
uv sync --frozen --no-install-project
2128

@@ -26,7 +33,18 @@ RUN --mount=type=cache,target=/root/.cache/uv \
2633
uv pip install "uvicorn[standard]==0.34.0" "redis==5.2.1"
2734

2835
# 3) The shipped source (src/ is on PYTHONPATH; the package is not pip-installed).
29-
COPY src ./src
36+
COPY core/gateway/services/gateway/src ./src
37+
38+
# 4) THE ROUTE MANIFESTS OF THE DOMAINS THIS EDGE FRONTS. Kept at their repo-relative paths so
39+
# `routes_manifest._repo_root()` — which walks up for core/gateway + core/meetings rather than
40+
# counting parents — resolves them exactly as it does in a checkout. One file per domain, listed
41+
# rather than globbed, for the same reason `manifest_paths()` is a map: a glob would silently
42+
# miss one whose service moved. A domain that adds a route changes only its own file here.
43+
COPY core/gateway/services/gateway/routes.v1.json ./core/gateway/services/gateway/routes.v1.json
44+
COPY core/meetings/routes.v1.json ./core/meetings/routes.v1.json
45+
COPY core/meetings/services/mcp/routes.v1.json ./core/meetings/services/mcp/routes.v1.json
46+
COPY core/identity/routes.v1.json ./core/identity/routes.v1.json
47+
COPY core/agent/routes.v1.json ./core/agent/routes.v1.json
3048

3149
EXPOSE 8000
3250
# python -m gateway → uvicorn gateway.adapters:app (HOST/PORT from env).
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"contract": "routes.v1",
3+
"domain": "gateway",
4+
"owner": "core/gateway/services/gateway",
5+
"edge": "gateway",
6+
"note": "The edge's OWN two routes, and the only ones it may declare: /health is the LB probe and /auth/me is 'who is this key'. Both are identity-only and forward nothing downstream, which is why they carry no scope \u2014 an empty `scopes` is a DECLARATION of that, never an omission.",
7+
"routes": [
8+
{
9+
"method": "GET",
10+
"path": "/auth/me",
11+
"scopes": []
12+
},
13+
{
14+
"method": "GET",
15+
"path": "/health",
16+
"scopes": []
17+
}
18+
]
19+
}

core/gateway/services/gateway/src/gateway/adapters.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,12 @@ def build_production_app(
201201

202202
admin_api_url = admin_api_url or os.getenv("ADMIN_API_URL", "http://admin-api:8001")
203203
meeting_api_url = meeting_api_url or os.getenv("MEETING_API_URL", "http://meeting-api:8080")
204-
agent_api_url = os.getenv("AGENT_API_URL", "http://agent-api:8100")
204+
# THE AGENT DOMAIN'S PRESENCE SIGNAL (PRD decisions 40.6 + 40.7). Unset means the deployment
205+
# does not run agents — the `no-agents` product — and the edge then registers no `/agent/*`
206+
# route and loads no `core/agent/routes.v1.json`, so a request there is a 404. A URL default
207+
# here would assert the domain exists and make absence unsayable, which is the same defect a
208+
# host-port default has one layer down. Every shipped deployment names it: compose, helm, lite.
209+
agent_api_url = os.getenv("AGENT_API_URL", "")
205210
# #795: the MCP service, fronted at the gateway edge under /mcp (streamable-HTTP transport).
206211
mcp_url = os.getenv("MCP_URL", "http://mcp:8010")
207212
redis_url = redis_url or os.getenv("REDIS_URL", "redis://redis:6379/0")

0 commit comments

Comments
 (0)