feat(api): damascus-api FastAPI service on :9110 (P2) #18

Closed
kaykayyali wants to merge 0 commits from feat/entry-points-api into main
Owner

Implements the P2 deliverable from the entry-points contract.

Endpoints (10 of 10)

  • GET /healthz, /v1/items, /v1/items/{id}, /v1/issues, /v1/events, /v1/cost, /v1/stats
  • POST /v1/items, /v1/items/bulk, /v1/issues/{id}/answer

Cross-cutting

  • Token check on writes (empty DAMASCUS_API_TOKEN at startup -> exit 1, fail-closed)
  • Token-bucket rate limit per source IP (30/min write, 120/min read) -> 429 + Retry-After
  • Lazy psycopg_pool.ConnectionPool(min=2, max=5) shared across threadpool
  • StaticFiles mount at /opt/damascus/ui (P4 ships this; doesn't crash if empty)

Compose

  • New damascus-api service reuses the existing damascus-orchestrator image
  • Port 127.0.0.1:9110:9110 (LAN-only, no public Traefik)
  • Reads /root/.hermes/.env for DAMASCUS_API_TOKEN
  • Existing db service untouched

Tests: 46 pass against live Postgres

  • tests/api/test_api_auth_and_ratelimit.py (auth, 401, 429, /healthz)
  • tests/api/test_api_endpoints.py (every endpoint, happy + error paths)
  • tests/contract/test_api_schemas_match_db.py (enum parity + 3 POST response round-trips)

Acceptance (live :9110)

  • /healthz -> 200
  • POST no token -> 401
  • POST wrong token -> 401
  • POST correct token -> 200
  • 31st POST in 60s from same IP -> 429 with Retry-After
  • /openapi.json exposes all 10 expected paths

Existing P1 deliverables (api_schemas.py, contract wiki page) untouched.

Implements the P2 deliverable from the entry-points contract. **Endpoints (10 of 10)** - GET /healthz, /v1/items, /v1/items/{id}, /v1/issues, /v1/events, /v1/cost, /v1/stats - POST /v1/items, /v1/items/bulk, /v1/issues/{id}/answer **Cross-cutting** - Token check on writes (empty DAMASCUS_API_TOKEN at startup -> exit 1, fail-closed) - Token-bucket rate limit per source IP (30/min write, 120/min read) -> 429 + Retry-After - Lazy psycopg_pool.ConnectionPool(min=2, max=5) shared across threadpool - StaticFiles mount at /opt/damascus/ui (P4 ships this; doesn't crash if empty) **Compose** - New damascus-api service reuses the existing damascus-orchestrator image - Port 127.0.0.1:9110:9110 (LAN-only, no public Traefik) - Reads /root/.hermes/.env for DAMASCUS_API_TOKEN - Existing db service untouched **Tests: 46 pass against live Postgres** - tests/api/test_api_auth_and_ratelimit.py (auth, 401, 429, /healthz) - tests/api/test_api_endpoints.py (every endpoint, happy + error paths) - tests/contract/test_api_schemas_match_db.py (enum parity + 3 POST response round-trips) **Acceptance (live :9110)** - /healthz -> 200 - POST no token -> 401 - POST wrong token -> 401 - POST correct token -> 200 - 31st POST in 60s from same IP -> 429 with Retry-After - /openapi.json exposes all 10 expected paths Existing P1 deliverables (api_schemas.py, contract wiki page) untouched.
kaykayyali added 1 commit 2026-06-24 14:59:24 +00:00
feat(api): damascus-api FastAPI service on :9110 (P2)
All checks were successful
test / contract-and-unit (pull_request) Successful in 14s
7a562b131c
Implements wiki/concepts/entry-points-contract.md sections 2 + 4:

- All 10 endpoints wired to existing state.* helpers (no new mutations):
    GET  /healthz, /v1/items, /v1/items/{id}, /v1/issues,
         /v1/events, /v1/cost, /v1/stats
    POST /v1/items, /v1/items/bulk, /v1/issues/{id}/answer
- Token check middleware on writes (POST). Empty DAMASCUS_API_TOKEN at
  startup fails closed (serve_cmd exits 1 before importing api).
- Token-bucket rate limit per source IP, default 30/min write +
  120/min read, configurable via env. Returns 429 + Retry-After.
- psycopg_pool.ConnectionPool(min=2, max=5) shared across FastAPI
  threadpool (lazy, env-driven).
- StaticFiles mount for UI bundle at /opt/damascus/ui; does not crash
  if the dir is empty (P4 ships this).
- 'damascus serve' CLI subcommand with --reload for dev.

docker-compose: new damascus-api service reuses the existing
damascus-orchestrator image, mounts /opt/damascus/ui from ./ui-bundle
(empty dir is fine), reads /root/.hermes/.env for the token, depends
on db, healthchecks /healthz.

Tests (46 pass against live Postgres at 127.0.0.1:5432):
- tests/api/test_api_auth_and_ratelimit.py (auth, 401, 429, /healthz)
- tests/api/test_api_endpoints.py (every endpoint, all happy/error paths)
- tests/contract/test_api_schemas_match_db.py (enum parity + 3
  POST response shape round-trips through real upsert_story + read-back)

Acceptance (live compose service at :9110):
- healthz -> 200 '{"status":"ok"}'
- POST /v1/items no token -> 401 unauthorized
- POST /v1/items wrong token -> 401 unauthorized
- POST /v1/items correct token -> 200
- 31st POST in 60s from same IP -> 429 with Retry-After
- /openapi.json exposes all 10 expected paths
kaykayyali added 2 commits 2026-06-24 15:13:07 +00:00
# Conflicts:
#	docker-compose.yml
#	src/damascus/cli.py
fix(compose): dedup pyproject.toml optional-dependencies after P2+P3 merge
Some checks failed
test / contract-and-unit (pull_request) Failing after 14s
8205a7df80
The 'git merge origin/main' auto-merged both P2 and P3 dev-deps blocks
into one pyproject.toml with two [project.optional-dependencies] sections
(tomltools refuses to parse that). Drop the second copy; both blocks
listed the same pytest + pytest-asyncio pair, just in different order.

Caught by 'python -m pytest' exiting 1 with a TOMLDecodeError before any
test ran.
kaykayyali closed this pull request 2026-06-25 04:56:26 +00:00
Some checks failed
test / contract-and-unit (pull_request) Failing after 14s

Pull request closed

Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kaykayyali/damascus-orchestrator#18