rayhpeng
eb37df0779
refactor(schedule): wire the slice through a composition root
...
Switches production onto the hexagonal path. The legacy modules still
compile and still have tests, but nothing assembles them any more; deleting
them is the next commit, kept separate so it stays reviewable.
Composition root
----------------
`app/composition.py::build_domain_services()` is now the only place an
adapter is instantiated. It was extracted from `deps.py::langgraph_runtime`
rather than added to it: wiring there was tangled with engine startup,
orphan recovery and shutdown, so the one rule that governs it -- no SQL
backend means no service and the routes answer 503 -- could not be tested
without booting the whole application, and was held up by a single comment.
It is a pure function of already-built infrastructure, so that rule is now
an assertion. Feedback moved with it; doing this while adding schedule's
five objects costs one change instead of two.
Primary adapter
---------------
The router is protocol translation only. What is gone is the giveaway: cron
normalisation, `next_run_at` arithmetic, the re-arm rule and hand-written
ownership checks all now live in the aggregate. Domain errors map to status
codes through one table, so a new error surfaces as a 500 to be classified
rather than being swallowed by whichever `except` was nearest.
`spec_mapping` split in two (AWS's own layout puts the wire model under the
entrypoint that owns it, and a primary adapter must not import a secondary
one): `adapters/schedule/spec_column.py` for the JSON column,
`routers/schedule/spec_wire.py` for the HTTP body. The two shapes are equal
only by coincidence, so `test_schedule_spec_parity.py` runs every case
against both and compares their outputs and messages directly. Function
names differ per side so an import from the wrong one is visible.
Explicit responses
------------------
Routes returned the ORM row's `to_dict()`, leaking `user_id`,
`assistant_id`, `overlap_policy` and the two lease columns. The response
models publish exactly the field set the frontend declares -- asserted in
both directions, since an extra field is a leak and a missing one breaks a
client.
One wire detail was nearly changed by accident: Pydantic v2 serializes a UTC
datetime as `...Z`, while the legacy `coerce_iso` path emitted `+00:00`.
`UtcTimestamp` pins `isoformat()` so adopting a model does not silently
alter the wire format for every client parsing these.
Tests
-----
73 new cases: router behaviour driven through a real `ScheduleService` over
in-memory fakes (a mocked service would let the error mapping pass without
a domain error ever being raised), response shape, and the composition root.
Router mappings verified by mutation -- a wrong status code or a dropped
timezone fallback turns 9, 2 and 1 cases red respectively.
Two lifespan tests carried a `SimpleNamespace` config that predates this
change; `langgraph_runtime` now reads `config.scheduler`, so they were given
one. Tolerating the gap with `getattr` was rejected: `AppConfig.scheduler`
always exists, so the fallback would be unreachable in production and exist
purely to excuse an incomplete test double.
Full suite is back to its 24 pre-existing failures.
2026-07-28 19:09:22 +08:00
..
2026-07-27 14:19:39 +08:00
2026-07-22 14:02:56 +08:00
2026-07-19 18:26:26 +08:00
2026-07-22 13:55:40 +08:00
2026-05-12 23:18:54 +08:00
2026-07-26 14:43:08 +08:00
2026-04-26 15:09:25 +08:00
2026-06-01 15:50:39 +08:00
2026-07-28 07:13:25 +08:00
2026-07-27 10:58:45 +08:00
2026-07-21 11:46:33 +08:00
2026-06-21 20:56:42 +08:00
2026-07-28 11:59:39 +08:00
2026-06-08 12:35:03 +08:00
2026-07-22 14:47:08 +08:00
2026-07-17 15:11:15 +08:00
2026-07-22 13:44:55 +08:00
2026-07-23 08:03:21 +08:00
2026-07-23 08:03:21 +08:00
2026-07-22 13:44:55 +08:00
2026-07-21 09:09:40 +08:00
2026-07-26 09:47:57 +08:00
2026-07-27 07:47:39 +08:00
2026-07-27 07:47:39 +08:00
2026-06-23 10:39:40 +08:00
2026-07-22 08:33:29 +08:00
2026-07-19 07:31:29 +08:00
2026-05-29 23:05:59 +08:00
2026-05-16 09:24:40 +08:00
2026-04-26 11:08:11 +08:00
2026-07-27 14:19:04 +08:00
2026-07-18 22:17:15 +08:00
2026-07-26 09:52:16 +08:00
2026-07-23 22:51:35 +08:00
2026-07-17 14:49:51 +08:00
2026-07-17 14:49:51 +08:00
2026-07-27 14:19:04 +08:00
2026-07-23 22:51:35 +08:00
2026-07-23 22:51:35 +08:00
2026-06-24 13:57:12 +08:00
2026-07-27 11:47:49 +08:00
2026-07-27 11:47:49 +08:00
2026-07-27 11:47:49 +08:00
2026-07-28 07:49:13 +08:00
2026-07-26 08:00:48 +08:00
2026-07-02 11:00:49 +08:00
2026-07-21 11:46:33 +08:00
2026-07-21 11:46:33 +08:00
2026-07-28 07:56:04 +08:00
2026-07-14 07:37:59 +08:00
2026-06-16 23:04:39 +08:00
2026-06-18 10:15:31 +08:00
2026-07-12 08:54:03 +08:00
2026-07-25 21:55:31 +08:00
2026-07-17 14:49:51 +08:00
2026-06-18 10:45:33 +08:00
2026-07-27 23:07:40 +08:00
2026-04-14 10:29:44 +08:00
2026-07-27 11:47:49 +08:00
2026-07-26 10:44:50 +08:00
2026-07-22 08:33:29 +08:00
2026-07-12 08:15:32 +08:00
2026-07-27 11:47:49 +08:00
2026-07-27 14:05:31 +08:00
2026-03-30 07:41:18 +08:00
2026-04-25 19:40:06 +08:00
2026-04-07 18:21:22 +08:00
2026-07-22 14:02:56 +08:00
2026-07-23 22:51:35 +08:00
2026-03-29 21:03:58 +08:00
2026-05-04 09:56:16 +08:00
2026-07-27 11:47:49 +08:00
2026-05-02 15:04:11 +08:00
2026-06-10 21:36:25 +08:00
2026-07-28 19:09:22 +08:00
2026-07-22 08:33:29 +08:00
2026-07-25 23:18:34 +08:00
2026-07-26 07:39:39 +08:00
2026-04-26 11:05:47 +08:00
2026-07-05 09:54:55 +08:00
2026-03-29 15:31:18 +08:00
2026-07-22 08:33:29 +08:00
2026-05-07 16:15:15 +08:00
2026-06-12 15:24:58 +08:00
2026-07-22 07:46:03 +08:00
2026-07-23 22:56:12 +08:00
2026-07-17 19:16:42 +08:00
2026-06-08 07:59:50 +08:00
2026-07-26 21:16:36 +08:00
2026-07-13 17:57:34 +08:00
2026-06-02 22:43:22 +08:00
2026-06-05 15:21:41 +08:00
2026-07-12 00:17:11 +08:00
2026-07-04 23:09:29 +08:00
2026-06-02 22:43:22 +08:00
2026-07-01 22:49:17 +08:00
2026-07-13 19:26:07 +08:00
2026-07-22 08:33:29 +08:00
2026-07-27 11:47:49 +08:00
2026-07-04 09:21:19 +08:00
2026-07-22 13:55:40 +08:00
2026-07-04 21:31:01 +08:00
2026-07-26 09:56:17 +08:00
2026-06-12 17:16:01 +08:00
2026-06-09 15:29:40 +08:00
2026-07-27 14:19:39 +08:00
2026-06-12 15:24:58 +08:00
2026-06-13 23:27:17 +08:00
2026-03-31 22:19:27 +08:00
2026-07-02 11:00:49 +08:00
2026-07-26 07:39:39 +08:00
2026-07-16 09:39:09 +08:00
2026-07-28 07:49:13 +08:00
2026-04-26 11:09:56 +08:00
2026-04-08 17:13:39 +08:00
2026-07-05 09:54:55 +08:00
2026-07-21 11:46:33 +08:00
2026-07-23 15:30:33 +08:00
2026-07-27 10:58:45 +08:00
2026-07-28 17:34:10 +08:00
2026-07-17 11:18:20 +08:00
2026-07-02 08:24:15 +08:00
2026-07-04 21:31:01 +08:00
2026-07-19 22:12:30 +08:00
2026-07-14 11:57:48 +08:00
2026-07-25 23:18:34 +08:00
2026-07-22 08:33:29 +08:00
2026-05-11 17:38:37 +08:00
2026-07-26 08:09:17 +08:00
2026-07-27 23:17:18 +08:00
2026-07-28 19:09:22 +08:00
2026-07-28 19:09:22 +08:00
2026-07-18 17:50:20 +08:00
2026-07-27 07:13:06 +08:00
2026-07-04 22:56:24 +08:00
2026-07-04 22:56:24 +08:00
2026-07-04 22:56:24 +08:00
2026-07-27 23:07:40 +08:00
2026-07-04 22:56:24 +08:00
2026-07-14 22:56:50 +08:00
2026-07-04 22:56:24 +08:00
2026-07-04 22:56:24 +08:00
2026-07-16 09:12:29 +08:00
2026-07-23 14:32:49 +08:00
2026-07-15 22:30:34 +08:00
2026-07-25 23:50:21 +08:00
2026-06-21 15:55:10 +08:00
2026-07-23 21:33:57 +08:00
2026-03-14 22:55:52 +08:00
2026-07-23 15:30:33 +08:00
2026-07-06 07:21:12 +08:00
2026-07-27 22:46:51 +08:00
2026-07-27 14:05:31 +08:00
2026-07-10 11:08:30 +08:00
2026-07-27 23:07:40 +08:00
2026-03-26 14:20:18 +08:00
2026-06-28 16:10:44 +08:00
2026-07-08 17:10:27 +08:00
2026-07-22 14:02:56 +08:00
2026-07-04 13:54:04 +08:00
2026-06-17 15:29:22 +08:00
2026-07-22 14:47:08 +08:00
2026-06-08 23:25:29 +08:00
2026-07-12 11:51:31 +08:00
2026-06-10 16:11:00 +08:00
2026-07-26 08:09:17 +08:00
2026-07-26 14:43:08 +08:00
2026-07-24 21:56:11 +08:00
2026-07-27 11:47:49 +08:00
2026-07-23 08:22:15 +08:00
2026-07-23 22:56:12 +08:00
2026-04-11 16:52:10 +08:00
2026-07-20 23:33:09 +08:00
2026-07-09 17:21:25 +08:00
2026-07-03 07:51:22 +08:00
2026-07-13 00:01:22 +08:00
2026-07-26 08:09:17 +08:00
2026-07-13 00:01:22 +08:00
2026-07-04 13:54:04 +08:00
2026-07-03 08:01:46 +08:00
2026-04-30 22:27:14 +08:00
2026-05-07 16:15:15 +08:00
2026-07-19 17:13:44 +08:00
2026-07-26 14:43:08 +08:00
2026-07-19 22:12:30 +08:00
2026-07-03 15:39:12 +08:00
2026-07-09 16:26:31 +08:00
2026-04-25 09:18:13 +08:00
2026-07-25 21:43:33 +08:00
2026-07-22 19:58:43 +08:00
2026-07-10 07:54:36 +08:00
2026-07-09 16:26:31 +08:00
2026-07-09 16:26:31 +08:00
2026-07-14 09:49:43 +08:00
2026-07-04 09:40:05 +08:00
2026-06-25 22:57:00 +08:00
2026-07-14 09:49:43 +08:00
2026-07-26 21:16:36 +08:00
2026-07-22 14:40:57 +08:00
2026-07-26 21:16:36 +08:00
2026-07-22 07:46:03 +08:00
2026-07-15 11:21:04 +08:00
2026-07-26 21:16:36 +08:00
2026-07-26 21:16:36 +08:00
2026-07-27 23:17:18 +08:00
2026-07-22 14:40:57 +08:00
2026-07-15 11:21:04 +08:00
2026-07-26 21:16:36 +08:00
2026-07-22 07:46:03 +08:00
2026-07-22 07:46:03 +08:00
2026-07-22 07:46:03 +08:00
2026-04-26 11:09:55 +08:00
2026-07-23 22:51:35 +08:00
2026-07-15 11:21:04 +08:00
2026-07-22 14:40:57 +08:00
2026-07-22 14:02:56 +08:00
2026-07-26 21:16:36 +08:00
2026-07-26 21:16:36 +08:00
2026-07-28 07:13:25 +08:00
2026-07-28 07:13:25 +08:00
2026-07-27 23:07:40 +08:00
2026-07-04 13:54:04 +08:00
2026-07-17 14:23:44 +08:00
2026-03-22 20:39:26 +08:00
2026-07-22 13:44:55 +08:00
2026-07-26 14:43:08 +08:00
2026-07-26 14:43:08 +08:00
2026-07-24 22:41:07 +08:00
2026-07-27 23:07:40 +08:00
2026-07-26 21:45:47 +08:00
2026-07-27 07:25:34 +08:00
2026-06-22 18:55:20 +08:00
2026-07-18 17:48:42 +08:00
2026-06-21 15:47:53 +08:00
2026-05-28 08:20:52 +08:00
2026-07-28 17:34:10 +08:00
2026-07-04 21:44:22 +08:00
2026-05-28 18:24:32 +08:00
2026-06-08 22:04:38 +08:00
2026-03-26 15:07:05 +08:00
2026-06-09 18:01:43 +08:00
2026-07-26 08:09:17 +08:00
2026-06-24 13:57:12 +08:00
2026-07-28 07:13:25 +08:00
2026-06-24 13:57:12 +08:00
2026-07-28 07:13:25 +08:00
2026-06-24 13:57:12 +08:00
2026-06-24 13:57:12 +08:00
2026-07-28 07:13:25 +08:00
2026-07-21 08:52:31 +08:00
2026-06-24 13:57:12 +08:00
2026-07-19 17:01:28 +08:00
2026-07-28 17:34:10 +08:00
2026-04-26 11:13:01 +08:00
2026-07-20 07:24:34 +08:00
2026-04-10 20:40:30 +08:00
2026-07-26 08:09:17 +08:00
2026-07-26 08:09:17 +08:00
2026-07-14 07:51:59 +08:00
2026-07-21 09:23:14 +08:00
2026-07-06 20:57:41 +08:00
2026-07-11 23:20:53 +08:00
2026-06-17 21:11:44 +08:00
2026-03-14 22:55:52 +08:00
2026-03-14 22:55:52 +08:00
2026-07-03 08:01:46 +08:00
2026-07-26 08:09:17 +08:00
2026-06-08 17:32:41 +08:00
2026-06-09 21:58:31 +08:00
2026-07-14 23:08:33 +08:00
2026-07-11 15:58:07 +08:00
2026-07-25 23:50:21 +08:00
2026-06-24 17:47:04 +08:00
2026-07-02 07:43:09 +08:00
2026-04-26 15:09:25 +08:00
2026-07-26 21:45:47 +08:00
2026-07-23 21:33:57 +08:00
2026-07-16 09:39:09 +08:00
2026-07-26 21:45:47 +08:00
2026-07-26 21:57:39 +08:00
2026-05-21 14:48:28 +08:00
2026-07-27 07:25:34 +08:00
2026-07-27 22:27:16 +08:00
2026-07-27 22:27:16 +08:00
2026-07-27 11:32:10 +08:00
2026-07-27 22:46:51 +08:00
2026-07-23 15:30:48 +08:00
2026-06-24 10:24:36 +08:00
2026-07-22 08:33:29 +08:00
2026-07-19 22:12:30 +08:00
2026-07-23 22:56:12 +08:00
2026-07-23 22:56:12 +08:00
2026-05-22 21:20:28 +08:00
2026-06-25 22:43:28 +08:00
2026-06-03 22:02:27 +08:00
2026-07-26 09:02:40 +08:00
2026-04-09 17:21:23 +08:00
2026-07-26 09:47:57 +08:00
2026-07-21 09:09:40 +08:00
2026-07-13 18:34:41 +08:00
2026-06-25 08:06:17 +08:00
2026-07-28 07:49:13 +08:00
2026-07-26 08:09:17 +08:00
2026-07-04 13:54:04 +08:00
2026-06-12 10:20:38 +08:00
2026-07-28 12:08:17 +08:00
2026-07-28 18:14:46 +08:00
2026-07-28 18:28:46 +08:00
2026-07-28 19:09:22 +08:00
2026-07-28 19:09:22 +08:00
2026-07-28 18:28:46 +08:00
2026-07-28 18:28:46 +08:00
2026-07-28 12:08:17 +08:00
2026-07-28 19:09:22 +08:00
2026-07-28 18:28:46 +08:00
2026-07-04 21:51:57 +08:00
2026-07-24 21:41:09 +08:00
2026-07-04 21:51:57 +08:00
2026-07-04 21:51:57 +08:00
2026-07-24 21:41:09 +08:00
2026-07-04 21:51:57 +08:00
2026-07-04 21:51:57 +08:00
2026-07-04 21:51:57 +08:00
2026-07-24 21:41:09 +08:00
2026-07-28 11:20:31 +08:00
2026-06-12 09:45:26 +08:00
2026-07-21 23:41:07 +08:00
2026-06-19 11:23:07 +08:00
2026-03-26 14:20:18 +08:00
2026-07-21 08:15:33 +08:00
2026-06-27 23:30:54 +08:00
2026-07-07 23:09:06 +08:00
2026-07-23 08:03:21 +08:00
2026-05-12 23:18:54 +08:00
2026-07-23 08:03:21 +08:00
2026-07-05 19:55:39 +08:00
2026-06-19 21:57:03 +08:00
2026-07-04 23:09:29 +08:00
2026-07-04 11:27:19 +08:00
2026-07-04 11:27:19 +08:00
2026-07-04 23:09:29 +08:00
2026-07-20 08:18:23 +08:00
2026-07-13 10:40:22 +08:00
2026-05-28 15:48:32 +08:00
2026-07-17 14:39:35 +08:00
2026-07-23 21:33:57 +08:00
2026-07-24 21:25:53 +08:00
2026-07-11 15:58:07 +08:00
2026-07-04 13:54:04 +08:00
2026-07-16 14:12:02 +08:00
2026-03-25 16:28:33 +08:00
2026-07-16 14:12:02 +08:00
2026-07-07 21:44:28 +08:00
2026-07-19 22:36:15 +08:00
2026-07-16 14:12:02 +08:00
2026-07-04 23:09:29 +08:00
2026-07-17 23:22:16 +08:00
2026-07-21 09:39:44 +08:00
2026-07-24 21:25:53 +08:00
2026-07-21 00:02:16 +08:00
2026-06-18 10:09:46 +08:00
2026-07-08 21:58:33 +08:00
2026-07-23 21:33:57 +08:00
2026-07-13 19:33:40 +08:00
2026-03-30 16:02:23 +08:00
2026-06-12 15:24:58 +08:00
2026-07-22 09:20:54 +08:00
2026-07-27 07:13:06 +08:00
2026-06-14 10:30:45 +08:00
2026-06-08 23:17:22 +08:00
2026-07-14 08:14:21 +08:00
2026-07-23 22:51:35 +08:00
2026-07-24 21:56:11 +08:00
2026-07-14 22:51:21 +08:00
2026-05-02 06:37:49 +08:00
2026-07-11 15:41:57 +08:00
2026-07-11 15:41:57 +08:00
2026-07-13 19:26:07 +08:00
2026-06-19 21:42:42 +08:00
2026-07-08 17:10:27 +08:00
2026-07-26 07:39:39 +08:00
2026-07-07 19:55:33 +08:00
2026-07-27 11:47:49 +08:00
2026-07-27 11:47:49 +08:00
2026-07-22 08:33:29 +08:00
2026-06-24 07:28:17 +08:00
2026-07-23 22:56:12 +08:00
2026-05-21 21:18:10 +08:00
2026-06-18 10:15:31 +08:00
2026-07-28 07:49:13 +08:00
2026-07-11 15:53:14 +08:00
2026-07-10 22:13:50 +08:00
2026-07-28 07:13:25 +08:00
2026-07-28 07:13:25 +08:00
2026-07-27 22:18:02 +08:00
2026-07-27 22:46:51 +08:00
2026-07-04 09:21:19 +08:00
2026-07-27 22:46:51 +08:00
2026-06-02 22:43:22 +08:00
2026-07-22 08:33:29 +08:00
2026-05-22 21:42:14 +08:00
2026-07-22 08:33:29 +08:00
2026-07-26 20:47:58 +08:00
2026-07-08 14:41:16 +08:00
2026-07-15 11:21:04 +08:00
2026-03-14 22:55:52 +08:00
2026-07-11 23:15:03 +08:00
2026-06-12 22:48:47 +08:00
2026-07-08 22:26:06 +08:00
2026-07-04 23:14:46 +08:00
2026-05-10 22:00:57 +08:00
2026-06-23 23:37:41 +08:00
2026-07-27 11:47:49 +08:00
2026-07-27 22:46:51 +08:00
2026-05-19 22:11:46 +08:00
2026-07-26 07:39:39 +08:00
2026-07-04 11:27:19 +08:00
2026-07-16 16:41:04 +08:00
2026-04-06 15:09:57 +08:00
2026-07-06 20:57:41 +08:00
2026-07-06 20:57:41 +08:00
2026-07-12 11:24:24 +08:00
2026-07-14 09:49:43 +08:00
2026-07-19 09:13:51 +08:00
2026-07-19 09:13:51 +08:00
2026-07-14 08:58:06 +08:00
2026-07-14 08:58:06 +08:00
2026-07-03 08:01:46 +08:00
2026-07-22 07:53:50 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-07-22 07:53:50 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-06-25 20:10:49 +08:00
2026-07-15 22:27:00 +08:00
2026-06-25 20:10:49 +08:00
2026-07-20 23:56:37 +08:00
2026-05-12 23:18:54 +08:00
2026-07-23 08:03:21 +08:00
2026-07-26 21:16:36 +08:00
2026-07-26 21:16:36 +08:00
2026-07-01 16:43:56 +08:00
2026-07-22 14:02:56 +08:00
2026-07-19 17:24:31 +08:00
2026-04-26 15:09:25 +08:00
2026-07-26 08:09:17 +08:00
2026-07-11 09:18:12 +08:00
2026-07-14 10:43:13 +08:00
2026-05-02 15:16:16 +08:00
2026-06-09 15:29:40 +08:00
2026-07-22 08:41:50 +08:00
2026-07-24 13:12:43 +08:00
2026-04-06 15:18:34 +08:00
2026-07-27 07:13:06 +08:00
2026-07-06 07:21:12 +08:00
2026-07-20 23:05:47 +08:00
2026-07-18 17:53:30 +08:00
2026-07-12 07:52:45 +08:00
2026-07-25 23:50:21 +08:00
2026-07-25 23:50:21 +08:00
2026-07-12 11:51:31 +08:00
2026-07-27 22:27:16 +08:00
2026-06-07 17:47:11 +08:00