* feat: add data capability contract
* fix(review-feedback-2289): preserve unknown status until availability is checked and Make
* fix(review-feedback-2289): Aggregate daily quality across supported markets and add kline
* fix(review-feedback-2289): Honor daily-source circuit breakers in quality selection and Make
* fix(review-feedback-2289): Scope market-overview quality by market and Do not select a news
* fix: align data capability with runtime routes
* fix: align data capability runtime coverage
* fix: align source and index capability routes
* fix: preserve runtime capability uncertainty
* fix: include US index capability routes
* fix: align realtime and monitor capabilities
* fix: remove unsupported Tushare index capability
* fix: align capabilities with runtime routes
* fix: model realtime and breaker routes
* fix: align US realtime request priority
* fix: align realtime circuit coverage
* fix: filter unavailable daily priorities
* fix: align US realtime capability claims
* fix: align executable US data routes
* fix(#1970): 关闭认证强制要求当前管理员密码二次确认
后端 api/v1/endpoints/auth.py 的 auth_update_settings 在 disable 路径上即使携带有效 session cookie 也强制要求 current_admin_password,否则返回 400;密码错误统一返回 401,命中 rate limit 与 enable 路径一致返回 429。enable 与 initial setup 路径行为保持不变。
前端 AuthSettingsCard 在关闭认证场景下若 currentPassword 缺失,submit 按钮保持可点击(disabled 仅由 isDirty 决定),handleSubmit 校验后给出内联错误,避免用户面对一个长期 disabled 的按钮但不知所缺。新增 i18n key settings.authDisableRequiredCurrentPassword 中英文本,同步修订 authHelperTurnOff / authPasswordHintOff 文案以反映新契约,并避免 hint 文案与 inline error 文案完全重复导致测试 findByText 多匹配。
测试覆盖:
- tests/test_auth_api.py 新增 disable 路径在有/无 session、有/无 current_password、密码对错、rate limit 命中 6 种分支用例,全部通过。
- apps/dsa-web/__tests__/AuthSettingsCard.test.tsx 把原 'missing current password when session valid' 反向为 'blocks disabling when current password missing',并补 'disables auth with current password provided'。本机 vitest run AuthSettingsCard.test.tsx 6/6 通过。
issue #1970
* test(#1970): 关闭认证回归补真实 ASGI 端到端用例并清理无效 mock
针对 PR #2050 review 反馈,补齐两条回归用例并修正既有用例的误导性 mock:
1. 删除 AuthApiTestCase 三个 valid-session 用例中对 verify_session 的 patch。
Disable 分支不会调用 verify_session(仅在 enable 分支的 TOCTOU 复检里用到),
旧 mock 既不生效也容易让读者误以为 disable 路径会做 session 校验。
2. 新增 AuthDisableViaRealASGITestCase 通过真实 ASGI / AuthMiddleware / auth
路由组合链路(create_app + httpx.ASGITransport,与 test_api_health.py 同路径)
验证 Issue #1970 修复:
- 真实 POST /api/v1/auth/login 拿到签名 cookie 后,仅带 session 不带
currentPassword 调 /api/v1/auth/settings 关闭认证 -> 400 current_required;
- 同上下文携带正确 currentPassword 关闭认证 -> 200,.env 翻转至
ADMIN_AUTH_ENABLED=false,响应头携带 Set-Cookie 轮换 session secret。
3. 同步 /api/v1/auth/settings OpenAPI description:明确「关闭认证时
currentPassword 必填、有效 session 不足够」这一新契约,与 endpoint
行为及 Issue #1970 上下文对齐。
测试:tests/test_auth_api.py 35/35 全过。
* test(auth): fix rate-limit trigger test — needs MAX+1 iterations to reach 429
Previously range(RATE_LIMIT_MAX_FAILURES) ran only 5 iterations, but
check_rate_limit returns False only when count >= MAX. This means the
5th request enters with count=4 (4 < 5), passes check_rate_limit,
runs verify_stored_password + record_login_failure, and returns 401.
Only a 6th request - entering with count=5 (5 >= 5) - is rejected
early by check_rate_limit and returns 429.
Fix the loop range to RATE_LIMIT_MAX_FAILURES + 1 and update the
assertion: the first MAX attempts return 401 (each recording a
failure), and the final attempt returns 429. Also normalise a
mixed Chinese/English docstring to English-only. 35 tests pass.
* test: tighten ASGI auth disable coverage
- Drop the middleware-is_auth_enabled patch now that the endpoint
disables auth by rotating the session secret in a single in-memory
transition. Subsequent middleware checks in the same client see the
disabled state via the auth module, so the patch was masking a
state-leak instead of testing the real path.
- Make the positive disable test assert cookie deletion semantics
(empty value + Max-Age=0/Expires-past + jar cleared) instead of just
a presence check on Set-Cookie. A leaked pre-disable cookie must not
remain usable after disable, and the previous assertion would still
pass if the endpoint rotated to a fresh session id.
* test(auth): strip surrounding quotes when asserting empty dsa_session value
Starlette's delete_cookie serializes the empty cookie value as
dsa_session=""; Max-Age=0; ... — i.e. with surrounding double quotes.
The previous assertion expected the raw value to equal '' and so
failed on CI (which uses Starlette's stock serializer); locally the
TestClient happened to round-trip the same way but the strip happened
to make the assertion spuriously true (or the assertion was correct
against an older Starlette). Strip surrounding double quotes before
comparison so the test matches the actual deletion-form emitted by
delete_cookie.
* docs(changelog): move #1970 entry to [Unreleased] + drop 5 stray 3.28.0 文档段 entries
OR-COR-3defa936 blocker fix: 上轮 commit (`5219a43b`) 把 6 条 bullet 错误地追加到了已发布的 `## [3.28.0] - 2026-07-26` -> `### 文档` 段落,而不是 `[Unreleased]`。其中:
- 1 条属于本 PR (#1970) — 应放进 [Unreleased]
- 5 条属于其他 PR / issue (#2026 / #1985 / #2051 / Windows mimetypes / TUSHARE_HTTP_URL 工作流映射),且这些条目在 `3.28.0` 的 `### 新功能` / `### 改进` / `### 修复` 段已经发布过,再放进 `### 文档` 段属于重复 + 跨段误归类。
修复:
- 删除 `3.28.0 -> ### 文档` 段下的 6 条新增 bullet,恢复该段原本只剩「修复文档中的失效相对链接。」的 upstream/main 原貌;
- 在 `[Unreleased]` 段追加本 PR 的 #1970 单条扁平条目(与仓库约定一致:本 PR 自身只追加自身条目,不替其他 PR 处理)。
合并后 `docs/CHANGELOG.md` 的 `3.28.0 -> ### 文档` 段恢复 1 条原貌;`[Unreleased]` 段只新增 1 条本 PR 的条目,不再污染已发布版本历史。
* chore: trigger CI re-run after changelog fix (e01e0cf7 didn't fire pull_request event)
---------
Co-authored-by: xxiaoxiong <xxiaoxiong@users.noreply.github.com>
* feat: add reliable home watchlist workspace
* fix(review-feedback-1984): Don't trust stale stock-bar rows after refresh failures
* fix(review-feedback-1984): apps/dsa-web/src/stores/stockPoolStore.ts 的 refreshStockBar
* fix(review-feedback-1984): Clear loading when refresh supersedes initial stock-bar load
* fix(review-feedback-1984): Type stock-bar test fixtures as StockBarItem and 跟进结论 - 结论 :不接受;最新
* fix(review-feedback-1984): tying the Today fetch to the same refresh path while this tab is
* feat(i18n): add Korean to report language label maps
Extend the report language layer with a third code `ko` so reports can
render deterministic labels in Korean. Adds `ko` to the supported list,
aliases (korean/kr/ko-kr), every translation map, the full report label
set, sentiment bands, the config registry enum and `.env.example`.
`zh`/`en` behavior is unchanged and unknown languages still fall back to
the default. Prompt and Web surfaces follow in later changes.
Refs #1614
* feat(i18n): emit Korean output directives in analysis prompts
Make the analysis and market-review prompts produce Korean output when
REPORT_LANGUAGE=ko. The decision agent now appends a Korean
output-language directive (JSON keys and decision_type enum unchanged),
and the market-review prompt reuses the English structural scaffolding
while instructing the model to write the shell, headings and conclusion
in Korean. Market-phase and context-pack prompt sections route ko to the
English structural base. The market-review payload keeps the truthful
language code via a dedicated output-language helper, leaving zh/en
behavior unchanged.
Refs #1614
* fix(i18n): localize phase and market-context guardrails for Korean
Route Korean reports through the English structural scaffolding for
market-phase and market-context prompt sections, and add Korean output,
detection markers, negations and recap patterns to the phase-decision and
daily-market-context guardrails so they operate on Korean model output.
Confidence and operation labels now flow through the shared localize
helpers. zh/en behavior is unchanged.
Refs #1614
* feat(i18n): localize Korean fallback output across analysis pipeline
Add Korean output to the deterministic strings emitted outside the LLM:
per-stock and executor output-language directives, no-API-key / backend
/ parse error fallbacks, hold-watch advice and reasons, market-review
titles and summaries, and history and notification report labels. Fund
flow, trend and confidence values now flow through the shared localize
helpers, and language-keyed advice tables no longer raise KeyError for a
third language. Structural prompt sections route ko to the English
scaffolding. zh/en output is unchanged.
Refs #1614
* feat(web): add Korean report language rendering
Extend the Web ReportLanguage type with ko and add Korean copy to the
report detail surfaces: report text, sentiment labels, market-phase
labels, analysis-context summary, market-review view, diagnostics and
news source. Run-flow chrome that is keyed by UI language falls back to
English for ko. The report-language selector is driven by the backend
config schema, so Korean appears automatically. zh/en rendering is
unchanged.
Refs #1614
* docs(i18n): document Korean report language support
Note that REPORT_LANGUAGE accepts ko in the bilingual guides and the
analyze / market-review request-language parameters, and add a
CHANGELOG entry for Korean report output.
Refs #1614
* fix(i18n): canonicalize Korean values and accept ko in API schemas
Add Korean aliases to the operation-advice, trend, confidence, chip and
bias canonical maps so Korean model output (매수/매도/보유/관망 etc.)
resolves to the correct decision_type and signal level instead of
falling back to hold or a score-band signal. Accept ko in the
analyze, market-review and decision-signal request schemas (and the
static API spec) so the typed client and backend agree and per-request
Korean analysis is not rejected with 422.
Refs #1614
Follow-up to the #1773 data-layer MVP (Taiwan suffix-only detection + routing,
merged in 2086e3c). That MVP deferred the service/API/frontend layers, leaving a
live defect: tw was absent from the DecisionSignal/Portfolio service VALID_MARKETS,
so _normalize_market("tw") raised ValueError on the decision-signal write path.
The analysis pipeline auto-extracts a DecisionSignal after history save
(_extract_decision_signal_after_history_save), so every tw analysis silently
failed to persist a signal while jp/kr succeeded -- tw was the only
yfinance-supported market that could be analyzed but never produced a signal.
Converge the tw market contract for DecisionSignal + Portfolio + Intelligence in
one pass (mirroring jp/kr #1720), per the human review on #1801 asking not to
land it piecemeal:
Backend service + API:
- src/services/{portfolio,intelligence}_service.py: VALID_MARKETS /
_ALLOWED_MARKETS + _normalize_market error strings accept tw
- src/services/decision_signal_service.py: _normalize_market error string
(VALID_MARKETS is imported from portfolio_service, so the set change propagates)
- src/services/decision_signal_extractor.py: drop the now-stale "(e.g. tw)" guard
comment (tw is supported; the guard still protects genuinely-unsupported markets)
- api/v1/schemas/{decision_signals,intelligence,portfolio}.py: Pydantic Literals + tw
- api/v1/endpoints/decision_signals.py + docs/architecture/api_spec.json: market
filter description + DecisionSignalMarket enum gain tw; test_api_schema_pydantic
exact-match vs create_app().openapi() passes (api_spec kept CRLF)
Frontend (DecisionSignal + Portfolio typed consumers only; tsc + vitest pass):
- apps/dsa-web/src/types/{decisionSignals,portfolio}.ts + pages/{DecisionSignalsPage,
PortfolioPage}.tsx + utils/{decisionSignalLabels,stockCode}.ts + i18n/uiText.ts:
add tw to the DecisionSignalMarket / portfolio market unions, the market filter
options, the tw display label, and .TW/.TWO stock-code normalization
- the alert Market-Light surface (types/alerts.ts MarketRegion, featureText
ALERT_MARKET_REGION_*) is intentionally LEFT OUT: the backend market_light_service
is cn/hk/us only, so exposing tw there would be a front/back mismatch
Tests:
- flip the two #1773 graceful-skip regressions to first-class assertions and add
test_extract_and_persist_writes_tw_signal (end-to-end persist guard)
- frontend: PortfolioPage + stockCode vitest gain tw cases
Docs (reconcile the tw contract so changelog/topic docs/code state one fact):
- docs/CHANGELOG.md: rewrite the #1772 [Unreleased] entries so they no longer say
"service/API deferred" + "tw gracefully skipped" alongside "tw now supported"
- docs/market-support.md, docs/decision-signals.md, docs/intelligence-sources.md:
sync the tw market enum / filter / examples; keep the boundary note
Still deferred (separate follow-ups): the Taiwan stock-index/seed + Web autocomplete,
and the alert (大盘红绿灯) Market-Light tw support (needs a market_light backend change).
Refs #1772
* fix: backfill report decision signals
* fix(review-feedback-1719): 确认并修复“不明确建议”可能被默认回填成 hold 信号的正确性风险 and persist the report date in
* fix(review-feedback-1719): 修复 legacy decision type 被当作明确 action 导致误回填的问题
* fix(review-feedback-1719): Prevent stale backfills from staying active
* fix(review-feedback-1719): anchor the same metadata-based TTL that default expires at uses to the
* fix(review-feedback-1719): 修复或明确处理懒回填时间锚点的时区/时间域不一致风险
* feat: add intelligence source ingestion baseline
* feat: feed local intelligence into analysis contexts
* fix(review-feedback-1708): Reject DNS names that resolve privately and Validate redirect targets
* fix(review-feedback-1709): 处理大盘复盘本地资讯可能被搜索结果截断掉的问题,并澄清结构化检测到的外部模型/API 或运行时配置风险是否为真实变更
* fix(review-feedback-1708): Pin DNS resolution before fetching and Stream feeds before enforcing
* fix(review-feedback-1709): filter by published at for analysis evidence, or keep missing publish
* fix(review-feedback-1708): Sanitize fetch errors before returning them and Normalize nullable
* fix(review-feedback-1709): Normalize symbol scope before lookup
* fix(review-feedback-1708): Avoid rolling back prior item inserts on duplicate races and Reject
* fix(review-feedback-1709): Use the effective news window for local evidence and add the plain
* fix(review-feedback-1708): 落地可配置 RSS/Atom 情报源、存储、查询、retention 和基础安全边界
* fix(review-feedback-1709): 补充“revert 本 PR 或移除本地资讯接入入口/清退本地资讯源配置数据”级别说明即可
* fix(review-feedback-1708): 落地 RSS/Atom 情报源的存储、拉取、查询、retention 和基础安全边界
* fix(review-feedback-1709): 补 Refs 1707
* fix(review-feedback-1709): 解决冲突后再合入
* fix(review-feedback-1709): src/services/intelligence service.py 回退了资讯源 URL 安全防护:移除了 hostname DNS
* fix: harden intelligence source ingestion
* fix(review-feedback-1709): Sanitize upstream fetch errors before returning them
* feat: add NewsNow intelligence sources
* fix(review-feedback-1709): 确认并修复
* fix(review-feedback-1709): 补一个未命中敏感规则的异常回归测试
* fix(review-feedback-1709): 处理
* docs: enhance NEWSNOW_BASE_URL compatibility guidance with official links
- Add official NewsNow GitHub deployment guide link to .env.example
- Include curl-based API contract verification example for production validation
- Update CHANGELOG.md with official repository reference and deployment recommendation
- Clarify risk of public example instance and necessity for self-hosted in production
- All HTTP 500 responses already use sanitize_diagnostic_text for privacy
Ref #1707
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(review-feedback-1709): 补齐官方 NewsNow 实例链接文档或 API 契约确认,明确指出公开实例风险(需结合当前部署指南文档,更新 .env.example
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add intelligence source ingestion baseline
* fix(review-feedback-1708): Reject DNS names that resolve privately and Validate redirect targets
* fix(review-feedback-1708): Pin DNS resolution before fetching and Stream feeds before enforcing
* fix(review-feedback-1708): Sanitize fetch errors before returning them and Normalize nullable
* fix(review-feedback-1708): Avoid rolling back prior item inserts on duplicate races and Reject
* fix(review-feedback-1708): 落地可配置 RSS/Atom 情报源、存储、查询、retention 和基础安全边界
* fix(review-feedback-1708): 落地 RSS/Atom 情报源的存储、拉取、查询、retention 和基础安全边界