fix(consider): exclude non-repairable recovery symbols - #720
Conversation
atomchung
left a comment
There was a problem hiding this comment.
Blocking review findings — do not merge this head yet.
- [P1] FX-only
next_actionstill asks for benchmark closes.
The new path correctly emits request={"tickers": [], "currencies": ["TWD"]}, but _consider_price_feed_status delegates to _price_feed_status, whose only branches are “non-empty tickers means holding closes” and “otherwise benchmark symbols.” For the new FX-only shape, the visible instruction therefore points at price_feed.request.benchmarks and says to look up closes, although no benchmark is missing and the only repair is the named FX rate.
That repeats the owner-live failure class at the instruction layer: the structured request is correct, but the actionable step names work that cannot repair the route. Add an explicit currency/FX branch to the shared builder (or an equally single-owner correction), and make the production-shaped FX-only test assert that next_action names FX / request.currencies and contains no benchmark-close task.
- [P1] The recovery currency set is not the canonical missing-FX set.
cmd_consider currently builds currencies from every usable row plus premise_payload.currency or "USD"; _consider_price_feed_status then treats every non-USD currency without a supplied rate as unresolved. This diverges from two existing canonical semantics:
consequence.validate_premiseinherits an omitted currency from the currently held ticker before falling back to USD;- a single-currency book, including an all-TWD book, is self-consistent without FX.
As written, a single-currency TWD book can emit a TWD FX recovery task even though the consequence does not require conversion, and a sell whose premise omits currency can inject a synthetic USD that the normalized premise would not contain. That violates #629's core contract: request only facts that can change the result.
Derive repair currencies from the canonical valuation/projection's actual missing_fx / aggregate requirement, or otherwise preserve the existing single-currency and premise-normalization semantics. Add a single-currency non-USD regression proving no FX request is emitted, including the omitted-currency held-ticker case.
The orphan exclusion itself is correctly removed from price_request.tickers, and the context-preserving/idempotent retry E2E is useful. Keep the correction bounded to these two contract gaps, rerun the focused/full suites and CI, then request re-review on the new exact head.
atomchung
left a comment
There was a problem hiding this comment.
Review status: blocked on the two P1 findings in review #4834663749. This head is not merge-ready even if CI is green, because #629's acceptance is about emitting the exact repairable task, not only a schema-valid request. Re-review should use the next exact head after both regressions are added.
atomchung
left a comment
There was a problem hiding this comment.
还有两个会让 #610 的真实失败继续复现的 blocking 点;修完、标准 CI 绿后可 merge。
BLOCK 1 — FX-only recovery 给了错误的 next_action
位置:skills/fomo-kernel/engine/review.py::_price_feed_status
当前分支只用 request.tickers 区分「持仓价格」和「benchmark」。FX-only 请求的 shape 是 tickers: [], currencies: ["TWD"], benchmarks: [],因此会落入 benchmark 文案,告诉 agent 查 closes / benchmark,而不是补 FX。用户会再次被带进错误恢复路径,正是 #610 已观察到的 recovery loop。
最小修正:把 recovery instruction 明确分成三类:
currencies非空且tickers/benchmarks为空:指向price_feed.request.currencies,要求只补 envelope 的fxblock,并 rerunconsider --prices <path>;tickers非空:沿用持仓 close 文案;benchmarks非空:沿用 benchmark 文案。
回归必须断言 FX-only next_action:包含 FX / price_feed.request.currencies,不包含 benchmark 或查 close 的指令。
BLOCK 2 — recovery 在 premise 未写 currency 时会发明 USD leg
位置:skills/fomo-kernel/engine/review.py::cmd_consider 与 _consider_price_feed_status
当前代码在 recovery 前执行:
premise_currency = str(premise_payload.get("currency") or "USD").strip() or "USD"但 canonical premise 规则不是这样:若 premise ticker 已在持仓且未写 currency,consequence.validate_premise 会继承该持仓 currency;只有新 ticker 才 fallback USD。现状会把「单一 TWD 持仓,卖出同一 ticker,premise 未写 currency」错误扩成 {TWD, USD},继而请求不存在的 FX、甚至拒绝原本不需要 conversion 的单币种交易。
此外 _consider_price_feed_status 当前把所有 non-USD currency 都视为需要 FX;canonical consequence 的规则是只有实际 before/after 跨多个 currency 时才需要 conversion。单一 TWD book 不应要求 TWD→USD。
最小修正:
- recovery 的 premise currency 必须复用与
consequence.validate_premise相同的 canonical normalization(建议抽一个纯 helper,避免复制规则); - FX need 只在实际 consequence currency universe 大于一个 currency 时产生;
- 新 ticker 且 premise 未写 currency 仍可保持 USD fallback。
回归必须覆盖:单一 TWD 持仓 + TWD closes + sell 同 ticker、premise 省略 currency,应成功 considered,normalized premise currency 为 TWD,且没有 price_feed.request。
Merge gate
tests/test_consider.pyfocused suite;tests/run_all.pyfull offline suite;- PR 标准 CI Python 3.11 / 3.12 全绿;
- 不需要新增 abstraction、provider 或持久化对象。
我刚才为尝试通过 connector 自动 patch 加入的临时 workflow/trigger 已从分支删除;squash merge 不会把它们带入 main。
What
considerrecovery tickers to usable canonical current holdings plus the premisecoverage.missing_fxprice_feed.request.currenciesFX rates, in the envelopefxblockWhy
An owner-live book had every usable close but lacked FX. Recovery also included an integrity orphan outside current holdings, so an irrelevant close blocked retry. The follow-up review found two remaining recovery loops: FX-only requests were described as benchmark gaps, and a premise missing currency could invent a USD leg.
Verification
PYTHONPYCACHEPREFIX=/private/tmp/fomo629-pycache python3 tests/test_consider.py— 134/134PYTHONPYCACHEPREFIX=/private/tmp/fomo629-pycache-full python3 tests/run_all.py— full offline suite passedPYTHONPYCACHEPREFIX=/private/tmp/fomo629-pycache312-full python3.12 tests/run_all.py— full offline suite passedgit diff --check— passedprice_feed.request.currenciesand thefxblock, FX retry preserves premise/context and is idempotent; the legacy union mutation fails.dde1ca0: Python 3.11 passed (4m34s); Python 3.12 passed (5m23s).Closes #629