Port the #4504 retention semantics (#4452 duplicate-execution fix) into
the hexagonal dispatch path. Once launch() returns -- or raises without
being able to say whether a run started -- a live run may exist, so
bookkeeping failures must not release the task's single active slot:
- The two post-launch writes are best-effort: failures are logged and
surfaced on DispatchResult.error while the outcome stays LAUNCHED.
- New LaunchIndeterminateError expresses main's launch_succeeded-before-
unpack semantics at the port boundary: the adapter raises it when the
side effect may have happened but the identity is unknown, and the
service retains the slot with run_id=None.
- LaunchFailedError is narrowed to "the adapter is CERTAIN no run
started", since that path releases the slot.
Regression tests ported from tests/test_scheduled_task_service.py.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The inner ring of the schedule slice, added on its own so it can be read
as domain modelling rather than as a diff against the old implementation:
two aggregates with their state machines, the policy value object, the
output ports the service depends on, and the errors it raises.
Nothing wires it up yet -- no existing code path changes. The service is
exercised end to end against in-memory fakes, which is what makes the
rules (overlap policy, lease handling, which write owns which timestamp)
assertable without a database at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Introduces the ports-and-adapters standard new backend modules are
expected to follow, plus the two pieces that make it more than prose:
`deerflow/domain/` as the inner-ring namespace, and an AST test that
fails when anything under it imports infrastructure.
The spec is normative rather than descriptive -- the existing modules
predate it, so the test guards the namespace, not the whole backend.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>