From f5382b896387045de9d65b7ecd356fb7d60300c2 Mon Sep 17 00:00:00 2001 From: Hubert Pysklo Date: Sat, 18 Apr 2026 01:15:19 +0530 Subject: [PATCH 1/2] Fix platform startup on Starlette 1.0 and register github service - Convert the shutdown hook to a lifespan context manager so uvicorn boots on Starlette 1.0 (the decorator-based ``on_event`` hook was removed in that release and crashed ``create_app`` at import time). - Add ``github`` to the ``Service`` enum so ``initEnv`` accepts the GitHub templates that were landed alongside the service package. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/src/platform/api/main.py | 16 +++++++++------- backend/src/platform/api/models.py | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/platform/api/main.py b/backend/src/platform/api/main.py index f5d4df5..aa4b7cf 100644 --- a/backend/src/platform/api/main.py +++ b/backend/src/platform/api/main.py @@ -1,3 +1,5 @@ +from contextlib import asynccontextmanager + from sqlalchemy import create_engine from sqlalchemy.pool import NullPool from src.platform.isolationEngine.session import SessionManager @@ -35,7 +37,13 @@ def create_app(): - app = Starlette() + @asynccontextmanager + async def lifespan(app_): + yield + if getattr(app_.state, "replication_service", None): + app_.state.replication_service.stop() + + app = Starlette(lifespan=lifespan) db_url = environ["DATABASE_URL"] # Use NullPool when using Neon's PgBouncer (-pooler) to avoid double pooling @@ -143,12 +151,6 @@ def create_app(): app.mount("/api/env/{env_id}/services/linear", linear_graphql) - @app.on_event("shutdown") - async def shutdown_event(): - # Stop replication service if running (it's on-demand now) - if app.state.replication_service: - app.state.replication_service.stop() - return app diff --git a/backend/src/platform/api/models.py b/backend/src/platform/api/models.py index 6a6df6a..3bdd9d9 100644 --- a/backend/src/platform/api/models.py +++ b/backend/src/platform/api/models.py @@ -13,6 +13,7 @@ class Service(str, Enum): linear = "linear" calendar = "calendar" box = "box" + github = "github" class Visibility(str, Enum): From cadc593b9bffd99d1a793a459dcaf64cada39ad7 Mon Sep 17 00:00:00 2001 From: Hubert Pysklo Date: Sat, 18 Apr 2026 01:17:02 +0530 Subject: [PATCH 2/2] Document the Service enum registration step in AGENTS.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add it as step 5 in the "Adding a New Service" section so future service authors don't forget to widen the Pydantic enum — skipping it leaves initEnv returning a validation error even after the router is mounted and templates are seeded. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3a90a6c..27c61a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -410,7 +410,23 @@ myservice_router = Router(myservice_routes) app.mount("/api/env/{env_id}/services/myservice", myservice_router) ``` -**5. Write a seed script** in `backend/utils/seed_myservice_template.py` that: +**5. Register the service with the platform API** in `src/platform/api/models.py`. +Add it to the `Service` enum so `initEnv` (and other platform endpoints that take +a `templateService`) will accept the new value: + +```python +class Service(str, Enum): + slack = "slack" + linear = "linear" + calendar = "calendar" + box = "box" + myservice = "myservice" # ← add this +``` + +If you skip this step, `POST /api/platform/initEnv` returns a Pydantic enum +validation error even though the template rows exist and the router is mounted. + +**6. Write a seed script** in `backend/utils/seed_myservice_template.py` that: - Creates the PostgreSQL schema (e.g. `myservice_default`) - Uses `Base.metadata.create_all()` to create tables - Inserts seed data from a JSON file @@ -419,10 +435,10 @@ app.mount("/api/env/{env_id}/services/myservice", myservice_router) Follow `seed_slack_template.py` as a reference — it shows the full pattern including schema creation, table ordering, and template registration. -**6. Add seed data** in `examples/myservice/seeds/myservice_default.json` and copy to +**7. Add seed data** in `examples/myservice/seeds/myservice_default.json` and copy to `backend/seeds/myservice/` for Docker builds. -**7. Register the seed script** in the Docker startup command in `ops/docker-compose.yml`: +**8. Register the seed script** in the Docker startup command in `ops/docker-compose.yml`: ```yaml command: >