mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-01 19:06:01 +00:00
Group them by bounded context instead of by technology, one file per
port, and align the directory name with the AWS Prescriptive Guidance
layout (entrypoints / domain-with-ports / adapters).
app/infra/persistence/feedback.py
-> app/adapters/feedback/feedback_repository.py owned persistence
-> app/adapters/feedback/run_lookup.py anti-corruption layer
`persistence/` promised a technology-first classification that its own
contents contradicted: RunStoreRunLookup lived there while its docstring
said "no new SQL". Splitting per port makes that distinction structural.
SqlFeedbackRepository and _tz_aware move unchanged -- verified by
comparing their AST against the original rather than by eye. run_lookup.py
additionally gains a RunStore annotation behind TYPE_CHECKING (the module
is imported lazily by the composition root, so this keeps the runtime
import cost at zero), a docstring stating that this context owns no table
and writes no SQL against it, and a TODO recording the condition under
which the body is replaced: when the run context publishes a contract of
its own, the RunLookup port itself does not move.
Each module docstring opens with a fixed marker so the two kinds of
secondary adapter stay greppable:
grep -rl "anti-corruption layer" app/adapters/
Filenames deliberately carry no sql_ / acl_ prefix: a prefix encodes an
implementation property, so switching storage would force a rename even
though the port -- and therefore the import path -- has not changed. The
class name already carries it. A prefix earns its place once one port has
several production implementations, which is not yet the case here.
app/infra/ held nothing else and is removed.
48 lines
2.0 KiB
Python
48 lines
2.0 KiB
Python
"""Secondary adapter (anti-corruption layer) -- narrowing RunStore to RunLookup.
|
|
|
|
Implements ``RunLookup`` from ``deerflow.domain.feedback.ports``. Unlike its
|
|
sibling ``feedback_repository.py``, this context does not own the ``runs``
|
|
table and writes no SQL against it: it asks its one question through the
|
|
component the run context already provides.
|
|
|
|
Being the downstream consumer of an upstream context is the normal shape
|
|
here, not debt. Until that context publishes a contract of its own, a thin
|
|
translation layer is what keeps its wide interface -- and its untyped dict
|
|
rows -- out of the domain.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from deerflow.domain.feedback.ports import RunLookup
|
|
|
|
if TYPE_CHECKING:
|
|
from deerflow.runtime.runs.store import RunStore
|
|
|
|
|
|
class RunStoreRunLookup(RunLookup):
|
|
"""Adapts the framework ``RunStore`` (wide interface) to the narrow
|
|
``RunLookup`` port -- reuses the existing lookup, no new SQL.
|
|
|
|
Used by the service to confirm a run belongs to the thread before
|
|
writing feedback. That is referential integrity, not authorization:
|
|
the router's ``@require_permission(..., owner_check=True)`` proves the
|
|
caller owns the thread, and this check proves the run belongs to it --
|
|
only together do they establish that the caller owns the run. Which is
|
|
why the port takes no ``user_id``.
|
|
|
|
TODO(hexagonal): this depends on ``RunStore``, an infrastructure
|
|
component, rather than on a contract published by the run context --
|
|
that context has not been through a hexagonal slice yet. When it
|
|
publishes one (a DTO, not its aggregate and not its repository),
|
|
replace the body of this class. The ``RunLookup`` port does not move.
|
|
"""
|
|
|
|
def __init__(self, run_store: RunStore) -> None:
|
|
self._run_store = run_store
|
|
|
|
async def thread_of(self, run_id: str) -> str | None:
|
|
run = await self._run_store.get(run_id)
|
|
return run.get("thread_id") if run else None
|