rayhpeng 34ce7c259b refactor(schedule): make the run-completion hook an inbound adapter
`run_outcome_mapping.py` called itself "not a port implementation" and sat in
a package of secondary adapters, while the half that actually invoked the use
case lived as a closure in the composition root. It is one thing, and it is a
primary adapter: the run runtime calls it the way HTTP calls the router and
the clock calls the poller.

`ScheduleRunCompletionListener` now holds the whole responsibility -- decide
whether a finished run is ours, translate it, invoke the use case. Those are
not two jobs: "ignore this run" is only meaningful as "do not call the
service", so splitting them is what left the second half in a place where
behaviour is not asserted.

`build_run_completion_hook` drops to `return
ScheduleRunCompletionListener(service)`. The composition root's own docstring
says no adapter logic lives there; that is now true of it as well as of the
routers it was written about.

Placement
---------
Kept in `app/adapters/schedule/` rather than moved beside the other two
primary adapters. The context stays in one package; direction is stated by
the class name and each module's first line, and the package `__init__` --
previously empty -- now lists which of its modules point which way, so a file
added without that line is visibly a file whose direction nobody decided.
A subdirectory for a single inbound module would have made the other four
look like they had been sorted into something.

Tests
-----
This is the part that was not a rename. The conversion had 24 cases; the
invocation had none, because the composition root is not where behaviour is
asserted, so nothing covered "an ordinary chat run must not reach the
service" as opposed to "produces no outcome object".

The cases now drive `__call__` against a recording service, which asserts the
same mappings plus what was done with them, and adds the two that were
unreachable before: the service left entirely alone for a filtered run, and
the completion stamped with a tz-aware current instant.

`test_composition.py` gains `TestRunCompletionHook` for the assembly decision
that remains -- including that the hook is bound to the service it was given,
which a wrong wiring would type-check past. Confirmed by mutation that this
case fails when the binding is broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:14:27 +08:00

21 lines
896 B
Python

"""Adapters of the schedule context.
Mostly secondary (driven) adapters -- implementations of the output ports the
domain declares, which the service calls out to:
scheduled_task_repository.py owned persistence
scheduled_run_repository.py owned persistence
run_launcher.py anti-corruption layer over the run runtime
thread_lookup.py anti-corruption layer over the thread store
One exception, and the name says so:
run_completion.py PRIMARY (inbound) -- the run runtime calls it
It lives here so the context stays in one place rather than beside the other
two primary adapters, which sit next to whatever drives them (the router under
`gateway/routers/schedule/`, the poller under `scheduler/`). Direction is
stated by each module's own first line; a file added here without one is a
file whose direction nobody decided.
"""