3 Commits

Author SHA1 Message Date
rayhpeng
cb49dd67dc refactor(feedback): move the secondary adapters to app/adapters
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.
2026-07-28 17:34:10 +08:00
rayhpeng
808e31e2ee refactor(harness): drop legacy feedback repository
The ORM row stays behind (single alembic chain until the infrastructure
move); everything else is served by the domain slice. Repository tests
become a contract suite that runs the same cases against the SQL adapter
and an in-memory fake; ownership and timezone tests use explicit user_id
instead of the AUTO-sentinel context.
2026-07-23 15:30:56 +08:00
Xinmin Zeng
31513c2ccb
fix(persistence): emit tz-aware timestamps from SQLite-backed stores (#3130)
SQLAlchemy's DateTime(timezone=True) is a no-op on SQLite (the backend
has no native tz type), so values round-tripped through the DB come
back as naive datetimes. The four SQL _row_to_dict helpers were calling
.isoformat() directly on those naive values, shipping timezone-less
strings like "2026-05-20T06:10:22.970977" out of the API. The browser's
new Date(...) then parses them as local time, shifting recent threads
in /threads/search by the local UTC offset (about 8h in Asia/Shanghai).

Route the four call sites through coerce_iso() instead — it already
normalizes naive values as UTC and emits "+00:00" so the wire format
always carries tz. No data migration is needed; existing SQLite rows
read back via the corrected serializer.

PostgreSQL deployments are unaffected because timestamptz preserves
tzinfo end-to-end.

Closes #3120
2026-05-21 16:22:09 +08:00