Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b012a64
Plan REST surface hardening for 4.1.2
leynos May 24, 2026
a59c343
Ignore `.claude/` Claude Code harness state
leynos May 24, 2026
d6fac54
Refresh ExecPlan references for Hecate migration
leynos May 25, 2026
d03a7f5
Add REST error envelope handling
leynos May 25, 2026
b08fc84
Add reference list totals
leynos May 25, 2026
7eb41bf
Add pagination to profile lists
leynos May 25, 2026
2556462
Add pagination to history endpoints
leynos May 25, 2026
40b685e
Normalize REST filter parsing
leynos May 25, 2026
3c872b7
Add REST authorization scaffold
leynos May 25, 2026
f20b664
Align REST surface documentation
leynos May 25, 2026
8bc6fad
Align lifespan test with pagination envelope
leynos May 25, 2026
674ddbc
Mark REST surface roadmap item complete
leynos May 25, 2026
a416e97
Normalize ExecPlan Oxford spellings
leynos May 25, 2026
c184676
Normalize remaining ExecPlan spellings to Oxford -ize
leynos May 30, 2026
140ed87
Dedupe `.claude/` gitignore entry after rebase
leynos May 30, 2026
6837aac
Remove stray blank line in users guide
leynos May 30, 2026
73bda1c
Address reviewer findings on REST surface hardening
leynos May 30, 2026
afc89e2
Revert unrelated mdformat reflow in docs
leynos May 30, 2026
552e97b
Resolve CodeScene findings on REST surface refactor
leynos May 30, 2026
da81d5f
Address second reviewer pass on REST surface refactor
leynos May 31, 2026
d09235a
Extract page-total assertions from round-trip test
leynos May 31, 2026
fc6d934
Extract helpers from resolved-bindings round-trip test
leynos May 31, 2026
edf0964
Parametrise canonical error-envelope tests
leynos May 31, 2026
655c0ac
Inline kind dispatch builders into `_get_repos_for_kind`
leynos May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ __pycache__/
.agents/mcp/context_pack/packs/.repo.lock
**/*.rs.bk
*.swp
.agents/mcp/context_pack/packs/.repo.lock
54 changes: 51 additions & 3 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ Versioned API routing:
including series-profile, episode-template, reusable-reference, and
binding-resolution routes.
- Existing unversioned canonical routes are pre-v0.1.0 implementation details.
They are not compatibility aliases and should return Falcon's normal
`404 Not Found` response.
They are not compatibility aliases and should return the shared
`404 not_found` error envelope.
- Health checks remain root-level operator endpoints at `/health/live` and
`/health/ready`.
- New terminal user interface (TUI)-facing and vertical-slice REST endpoints
Expand All @@ -104,6 +104,53 @@ Versioned API routing:
[`adr-009-source-to-script-rest-vertical-slice.md`](adr/adr-009-source-to-script-rest-vertical-slice.md)
and [`episodic-tui-api-design.md`](episodic-tui-api-design.md).

REST error contract:

- Every Falcon `HTTPError` raised by the canonical API is serialized as
`{"code": "<machine-readable>", "message": "<human>", "details": {...}}` by
`episodic/api/errors.py`.
- Validation helpers attach field-level details where the request parser knows
the field and constraint, for example
`{"field": "limit", "constraint": "range"}`.
- Profile/template domain errors are mapped by `map_profile_template_error`.
Stale optimistic-lock updates use `revision_conflict` and include `entity_id`
plus `expected_revision` when the adapter has that context.
- Reusable-reference domain errors are mapped by `map_reference_error`.
Validation failures use `validation_error`, missing entities use `not_found`,
stale revisions use `revision_conflict`, and remaining persistence conflicts
use `conflict`.

REST pagination and filter contract:

- List resources parse pagination through `parse_pagination`. The shared
contract is `limit=20`, `offset=0`, `1 <= limit <= 100`, and `offset >= 0`.
- List responses return `{"items": […], "limit": <int>, "offset": <int>,
"total": <int>}`.
- Optional UUID query filters use `parse_optional_uuid_param`; invalid values
raise `validation_error` with `{"field": "<name>", "constraint": "uuid"}`.
- Optional enum filters use `parse_enum_param`; invalid values raise
`validation_error` with `{"field": "<name>", "constraint": "enum"}`.
- Resource adapters should validate filters before opening a unit of work, so
malformed filters are not hidden behind later `404` or domain errors.

Authorization scaffold:

- Every `/v1` request passes through `AuthorizationMiddleware` before resource
dispatch. Health checks remain operator endpoints and are not authorized by
this scaffold.
- `ApiDependencies.authorization` accepts an `AuthorizationPort`; production
wiring currently defaults to `PermitAll`, so existing clients do not need an
`Authorization` header yet.
- Authorization adapters receive an `AuthorizationContext` containing the HTTP
method, request path, and raw `Authorization` header. The port is async, so
future policy adapters can call external identity or permission services.
- Non-permit decisions short-circuit with the canonical error envelope:
`unauthorized` returns `401`, and `forbidden` returns `403`.
- Authorization adapter failures short-circuit with `service_unavailable` and
`503`, so policy-backend outages are not reported as resource failures.
- Roadmap item `5.1` is expected to replace the default permit-all adapter with
policy-backed role or scope checks.

Testing guidance:

- Use `tests/test_http_service_scaffold.py` for in-memory ASGI coverage of the
Expand Down Expand Up @@ -578,7 +625,8 @@ Implementation notes:
guest documents. Cross-series access is treated as `404 Not Found` for
profile-scoped routes.
- Inbound adapters map typed reusable-reference service errors to Falcon HTTP
errors through local `_map_reference_error(...)` helpers.
errors through `episodic/api/errors.py`, so callers receive the shared REST
error envelope instead of Falcon's default `{title, description}` body.

### Reusable reference-document repositories

Expand Down
31 changes: 19 additions & 12 deletions docs/episodic-podcast-generation-system-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1841,28 +1841,29 @@ The reusable reference API (Application Programming Interface) is exposed
through Falcon ASGI (Asynchronous Server Gateway Interface) adapters in
`episodic/api/app.py` and `episodic/api/resources/`.

- `POST /series-profiles/{profile_id}/reference-documents` creates one reusable
- `POST /v1/series-profiles/{profile_id}/reference-documents` creates one
reusable
`ReferenceDocument` for the owning series profile.
- `GET /series-profiles/{profile_id}/reference-documents` lists reusable
- `GET /v1/series-profiles/{profile_id}/reference-documents` lists reusable
documents for that series profile. Optional `kind` filter supports
`style_guide`, `host_profile`, `guest_profile`, and `research_brief`.
- `GET /series-profiles/{profile_id}/reference-documents/{document_id}` fetches
one reusable reference document when ownership is series-aligned.
- `PATCH /series-profiles/{profile_id}/reference-documents/{document_id}`
- `GET /v1/series-profiles/{profile_id}/reference-documents/{document_id}`
fetches one reusable reference document when ownership is series-aligned.
- `PATCH /v1/series-profiles/{profile_id}/reference-documents/{document_id}`
updates lifecycle state and metadata with optimistic locking; requires
`expected_lock_version` and returns `409 Conflict` on stale versions.
- `POST /series-profiles/{profile_id}/reference-documents/{document_id}/revisions`
- `POST /v1/series-profiles/{profile_id}/reference-documents/{document_id}/revisions`
creates one immutable `ReferenceDocumentRevision`.
- `GET /series-profiles/{profile_id}/reference-documents/{document_id}/revisions`
- `GET /v1/series-profiles/{profile_id}/reference-documents/{document_id}/revisions`
returns immutable change history for one document.
- `GET /reference-document-revisions/{revision_id}` fetches one immutable
- `GET /v1/reference-document-revisions/{revision_id}` fetches one immutable
revision. Optional query parameter `owner_series_profile_id` enforces
series-scoped access for clients that need explicit owner checks.
- `POST /reference-bindings` creates one `ReferenceBinding` for a target
- `POST /v1/reference-bindings` creates one `ReferenceBinding` for a target
context (`series_profile`, `episode_template`, or `ingestion_job`).
- `GET /reference-bindings` lists bindings for one target context using
- `GET /v1/reference-bindings` lists bindings for one target context using
required query parameters `target_kind` and `target_id`.
- `GET /reference-bindings/{binding_id}` fetches one binding by identifier.
- `GET /v1/reference-bindings/{binding_id}` fetches one binding by identifier.
- Ingestion workflows snapshot the resolved binding set into
`source_documents`, linking each provenance record to the consumed
`reference_document_revision_id`.
Expand All @@ -1872,17 +1873,23 @@ Pagination contract for list endpoints:
- Query parameters: `limit` and `offset`.
- Defaults: `limit=20`, `offset=0`.
- Bounds: `1 <= limit <= 100` and `offset >= 0`.
- Response envelope: `{ "items": [...], "limit": <int>, "offset": <int> }`.
- Response envelope: `{ "items": […], "limit": <int>, "offset": <int>,
"total": <int> }`.

Error contract:

- Error envelope: `{ "code": "<machine-readable>", "message": "<human>",
"details": {…} }`.
- `400 Bad Request` for malformed payloads, invalid UUID/query formats, or
invalid pagination bounds.
- `404 Not Found` for unknown entities and cross-series access paths that fail
series-aligned ownership checks.
- `409 Conflict` for optimistic-lock mismatches and duplicate revision/binding
writes that violate persistence constraints.

ADR 009 and `docs/episodic-tui-api-design.md` remain the source of truth for
tokens, idempotency keys, retry headers, and full role-based access control.

Integration tests run against an in-process PostgreSQL instance provided by
py-pglite, with Alembic migrations applied before each test function. The test
suite validates round-trip persistence for every repository, unit-of-work
Expand Down
Loading
Loading