feat(trigger-service): store adapters + provider wiring (TS-IMPL-008)#661
Merged
Conversation
…oviders wiring Add the persistence seam for Trigger Service (TS-IMPL-008): - `stores/` package: the narrow `TriggerMetadataStore` Protocol (the eight Subscription/Selector/Resume/Dedup/Schedule write methods Trigger Service owns) plus thin domain↔SPL adapters `SubscriptionStore`, `ResumeSubscriptionStore`, and `ScheduleStore` that map the wire/domain models onto the locked SPL rows and their free-form JSON blobs. - `providers.py`: `InMemoryTriggerMetadataStore` (faithful in-process backend — immutable put, not-found ValueError, TTL-aware reserve-or-read dedup), the `Providers` bundle, and `load_providers()` selecting in-memory by default or `custos_pg.PgMetadataAdapter` when `TRIGGER_METADATA_STORE` is a DSN. No new schema invented. - `dependencies.py`: `get_providers` / `get_metadata_store` FastAPI helpers. - `app.py`: lifespan binds the providers bundle + store adapters onto `app.state`. CRUD round-trips covered against the in-memory backend; provider selection honors the env knob. ruff + mypy strict clean; coverage 99.8%. Closes #638
Contributor
There was a problem hiding this comment.
Pull request overview
Implements Trigger Service’s persistence seam (TS-IMPL-008) by introducing thin domain↔SPL store adapters, provider selection/wiring (in-memory default vs Postgres via TRIGGER_METADATA_STORE), and wiring these into FastAPI lifespan/dependencies, with accompanying tests.
Changes:
- Added
custos_trigger.stores/*adapters (SubscriptionStore,ResumeSubscriptionStore,ScheduleStore) over the SPLMetadataStoreProviderwrite surface. - Added
custos_trigger.providerswithInMemoryTriggerMetadataStore+load_providers()DSN-based selection and aProvidersbundle. - Wired providers + stores into
create_app()lifespan and exposed them via dependency helpers; added tests covering the wiring and CRUD round-trips.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/trigger-service/tests/test_stores.py | New CRUD round-trip tests for store adapters over the in-memory backend. |
| src/services/trigger-service/tests/test_providers.py | New tests for DSN selection and in-memory backend semantics (dedup, schedule updates, etc.). |
| src/services/trigger-service/tests/test_dependencies.py | New tests validating dependency helpers and lifespan wiring onto app.state. |
| src/services/trigger-service/tests/conftest.py | New shared fixtures (frozen clock + providers bundle). |
| src/services/trigger-service/src/custos_trigger/stores/subscriptions.py | SubscriptionStore adapter mapping domain Subscription ↔ SPL rows/selectors. |
| src/services/trigger-service/src/custos_trigger/stores/schedules.py | ScheduleStore adapter for SPL schedule persistence. |
| src/services/trigger-service/src/custos_trigger/stores/resume.py | ResumeSubscriptionStore adapter for SPL resume subscription persistence. |
| src/services/trigger-service/src/custos_trigger/stores/base.py | TriggerMetadataStore Protocol defining the narrow SPL write surface used by Trigger Service. |
| src/services/trigger-service/src/custos_trigger/stores/init.py | Package exports + documentation for the store adapter layer. |
| src/services/trigger-service/src/custos_trigger/providers.py | In-memory backend + Providers bundle + load_providers() env/DSN selection. |
| src/services/trigger-service/src/custos_trigger/dependencies.py | FastAPI dependencies to retrieve providers bundle / metadata store. |
| src/services/trigger-service/src/custos_trigger/app.py | Lifespan wiring for providers + store adapters; optional providers injection for tests. |
| design/components/trigger-service/todos.md | Marks TS-IMPL-008 as completed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ry store Address Copilot review on #661: - append_subscription_selector raises on unknown subscription (FK parity) - put_resume_subscription / put_schedule raise ImmutableViolation on duplicate PK, matching the Postgres adapter's 23505 mapping - fix the default-clock test to assert on the persisted selector's added_at - correct the load_providers cast rationale (NewType param mismatch, not SCHEMA_REVISION)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements TS-IMPL-008 — the persistence seam for Trigger Service: thin store adapters over the SPL
MetadataStoreProviderplus the provider-selection wiring.Changes
stores/packagebase.py—TriggerMetadataStoreProtocol: the narrow subset ofMetadataStoreProviderTrigger Service writes to (the eightSubscription/SubscriptionSelector/ResumeSubscription/DedupKey/Schedulemethods). Both the Postgres adapter and the in-process backend satisfy it structurally.subscriptions.py/resume.py/schedules.py—SubscriptionStore,ResumeSubscriptionStore,ScheduleStore: thin async adapters that map the wire/domain models inmodels.pyonto the locked SPL rows (+ their free-form JSON blobs) and delegate to the provider.providers.pyInMemoryTriggerMetadataStore— faithful in-process backend (immutableput_subscription, not-foundValueErroron state/next-fire updates, TTL-aware reserve-or-read dedup) plus dev/test read accessors.Providersbundle +load_providers()— selects in-memory by default (empty/memorysentinel) orcustos_pg.PgMetadataAdapterwhenTRIGGER_METADATA_STOREis a DSN. LazyPool defers connection; the factory opens no socket. No new schema invented.dependencies.py—get_providers/get_metadata_storeFastAPI helpers (mirror auth-service).app.py— lifespan binds the providers bundle + store adapters ontoapp.state; new optionalproviders=injection point for tests.Acceptance criteria
MetadataStoreProvider.TRIGGER_METADATA_STOREenv knob; no new schema invented.Quality gates
ruff format/ruff checkcleanmypy src tests(strict) cleanpytest— 221 passed, 99.8% coverage (floor 90%)Closes #638