Skip to content

fix(consider): exclude non-repairable recovery symbols - #720

Merged
atomchung merged 6 commits into
mainfrom
codex/issue-629-repairable-recovery
Aug 1, 2026
Merged

fix(consider): exclude non-repairable recovery symbols#720
atomchung merged 6 commits into
mainfrom
codex/issue-629-repairable-recovery

Conversation

@atomchung

@atomchung atomchung commented Aug 1, 2026

Copy link
Copy Markdown
Owner

What

  • scope consider recovery tickers to usable canonical current holdings plus the premise
  • keep integrity, unusable-quantity, unavailable-cost, and unmatched-history exclusions in disclosure/provenance rather than price recovery
  • derive FX recovery only from the canonical valuation frame's exact coverage.missing_fx
  • give an FX-only structured request its own next action: only the named price_feed.request.currencies FX rates, in the envelope fx block
  • preserve canonical premise normalization: a held ticker whose premise omits currency stays in its recorded currency; single-currency TWD needs no FX request

Why

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/134
  • PYTHONPYCACHEPREFIX=/private/tmp/fomo629-pycache-full python3 tests/run_all.py — full offline suite passed
  • PYTHONPYCACHEPREFIX=/private/tmp/fomo629-pycache312-full python3.12 tests/run_all.py — full offline suite passed
  • git diff --check — passed
  • Synthetic E2E proves the FX-only request contains no orphan / tickers, its next action names only price_feed.request.currencies and the fx block, FX retry preserves premise/context and is idempotent; the legacy union mutation fails.
  • Synthetic single-currency TWD sell with omitted premise currency completes as TWD and emits no FX request.
  • Standard CI for head dde1ca0: Python 3.11 passed (4m34s); Python 3.12 passed (5m23s).

Closes #629

@atomchung atomchung left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review findings — do not merge this head yet.

  1. [P1] FX-only next_action still 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.

  1. [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_premise inherits 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 atomchung left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 atomchung left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还有两个会让 #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 明确分成三类:

  1. currencies 非空且 tickers/benchmarks 为空:指向 price_feed.request.currencies,要求只补 envelope 的 fx block,并 rerun consider --prices <path>
  2. tickers 非空:沿用持仓 close 文案;
  3. 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.py focused suite;
  • tests/run_all.py full offline suite;
  • PR 标准 CI Python 3.11 / 3.12 全绿;
  • 不需要新增 abstraction、provider 或持久化对象。

我刚才为尝试通过 connector 自动 patch 加入的临时 workflow/trigger 已从分支删除;squash merge 不会把它们带入 main

@atomchung
atomchung merged commit 86a9cf2 into main Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[implementation·M1] Yahoo unavailable on a consider call means recover the prices, not answer on cost

1 participant