TDX quote servers stopped answering the client protocol mootdx/tdxpy
speak (2026-07, mootdx/mootdx#157): every kline category, market and
server returns empty, realtime quotes return zero rows, and get_k_data
dies on a KeyError inside mootdx. Upstream has been dormant since 2024,
and the weekly health lane has been red on mootdx since it started.
Each auto-mode fetch still spent ~9.5s inside mootdx before falling
through to eastmoney, so the dead slot was pure latency tax on every
A-share call.
Drop mootdx from FALLBACK_CHAINS["a_share"] and from the canary set
(tracked under EXCLUDED_PUBLIC_SOURCES with the upstream issue as the
reason). The loader stays registered: explicit source="mootdx" calls
keep working if a compatible fork ever restores the protocol. Docs
(en/zh/ja/ko/es/ar/id), tool schema hints, env examples and the
data-routing/mootdx skills updated to match.
Closes#1729
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
The 3.11 leg's pytest step took 520s of a job that ended at exactly the
10-minute limit (run 36553260060); the next push (36f1698e, run
36556726032) was cancelled at 99% of the suite with no failing test. The
suite fills the budget, and runner speed varies by minutes between runs of
the same tree.
Each section carried its own copy of the header: Home's nav had no Home,
the docs' nav had no Docs, the research post had no star or theme button,
and the generated Alpha Library pages had no header at all. Only the docs
could switch language, and they guessed Chinese from the browser while
every other page stayed English.
- partials/ holds one head block, header and footer; sync_site_chrome.py
writes them into every page between markers and, with --check in CI,
also requires <body data-section> (the current section is highlighted by
CSS), the language attributes and a single ?v= token site-wide. The
Alpha Library generator inserts the same partials.
- site.js is the shared shell (theme, stars, language, footer traffic on
home only). English is the HTML; Chinese comes from locales/zh.json via
data-i18n hooks, and check_i18n.py fails on a key with no Chinese or a
Chinese string nothing uses. A URL that names a language (/docs/zh/,
the two articles) shows it and remembers it; everything else follows the
stored choice, English by default. theme-init.js keeps a page hidden until
its Chinese text is in, so it never flashes English.
- English tutorial and Chinese research post added. The Chinese tutorial
gets the corrections its translation surfaced: 462 alphas in five zoos,
AAPL.US, the mandate caps trades per day (it has no daily loss limit),
Shadow Account extracts up to five rules, the web UI needs Docker or a
checkout.
- Alpha Library: the zoo pages were never deployed — content/ is
gitignored and nothing built it, so every zoo link served the root
redirect. The deploy now exports the manifest and renders the pages (and
redeploys when agent/src/factors changes); the fifth zoo, fundamental, is
listed; generated pages allow same-origin scripts only.
- Phones keep the nav as a scrollable row instead of hiding it.
The docs still described 0.1.11: seven engines, six data sources, a
Quick start whose `vibe-trading serve` step opens no UI after a PyPI
install. Every count on the pages is now measured from the code (28
sources, 11 engines, 90 skills, 30 presets, 74 MCP tools, 18 brokers,
462 alphas), and five pages are new: Troubleshooting (no_progress,
"0 steps", cut figures, bare US tickers), Checked numbers, Alpha Zoo,
Brokers and live trading, Web UI and API.
Chinese pages are served at /docs/zh/<page>. The header toggle switches
language and remembers it; a first visit from a Chinese browser opens
the Chinese pages. The version selector offered 0.1.7-0.1.11 while
rendering one content set; it now lists only 0.1.16.
wiki/scripts/check_docs_parity.mjs (run in CI) fails when the two
languages differ in pages, sections, order, empty fields or internal
links. A #section in a docs URL now scrolls to it.
Landing page: "Seven engines" -> eleven, the uv tab no longer ends in
`serve`, and a 中文文档 button.
Bound BaoStock messages and serialize SDK sessions; verify Copilot credential
lookup expiry without changing the SDK's per-call credential resolution.
Run public-loader health checks in isolated, credential-free processes and a
separate dependency environment, with explicit failure reports and a weekly
workflow. Render broker capabilities per profile and document verification
requirements. Update September 28 News in all seven README languages.
Closes#1626Closes#1623Closes#1617Closes#1492
Each of the three was merged on evidence I could not reach myself. This
replaces that trust with a test that runs on every platform, on every CI run.
1. Windows lock (#1261). The no-sentinel fix was unverifiable on POSIX —
msvcrt is None, so the branch never executes and a regression would be
invisible to Linux and macOS CI. tests/test_windows_lock_no_sentinel.py
swaps the module-level fcntl/msvcrt handles for stubs and drives the
Windows branch anywhere, asserting behaviour (an empty file stays empty,
the first ledger append succeeds and verify_chain passes) rather than
'was locking() called'. Restoring the sentinel in all three lock sites
fails 3 of the 4 tests, including the first-append regression itself.
2. Dividend cash column (#1356). A dividend row has no quantity and no price,
so 'amount or qty * price' collapses to 0.0 when the export books the
payout under a cash-movement heading instead of the fill column. That is a
silent zero — the shape #1207 item 15 is about. Dividend rows now also try
发生金额 / 资金发生额 / 红利金额 / 实发金额 / 税后金额 and Futu's net-amount
columns, across all four parsers; anything still zero is counted into a new
dividend_rows_missing_cash profile field so the caller can SEE that the
journal was read but the money was not.
3. Docker action majors (#1359). The v4/v6/v7 bump had never executed in this
repository: docker-build.yml is workflow_dispatch only, total_runs=0. Every
input the workflow passes was checked against each new major's action.yml
at the pinned SHA (13 inputs, all present; the two setup actions take none).
A validate-build job now runs the SAME pins with push: false on any change
to the Dockerfile, this workflow, or pyproject, and the publish job is
gated to workflow_dispatch so the new triggers cannot publish an image.
That gate is one 'if:' line, so it is pinned too — removing it, flipping
validate-build to push: true, or unpinning an action each fail a test.
The file comment claiming these majors were 'a deliberate, separately testable
bump, not a drive-by change here' contradicted the file after #1359 and is
replaced by what is actually true now.
The frontend job on be623c1b spent 357 of its 407 seconds inside `npm ci`; the
build it exists to run (tsc + vite) took 13. The install itself was never the
problem — the audit request npm makes after installing was:
npm warn audit 503 Service Unavailable -
POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk
npm notice This endpoint is being retired. Use the bulk advisory endpoint instead.
npm retries that call before printing "added 380 packages in 6m". Measured
locally against the same lock file with the same warm cache:
npm ci 61s
npm ci --no-audit --no-fund 4s
NPM_CONFIG_AUDIT=false NPM_CONFIG_FUND=false npm ci 4s
This is the same slowdown that timed out `test / Python 3.11` on 8555a05a, so
splitting the frontend into its own job treated the symptom; this is the cause.
Turning the audit off costs no coverage:
- `npm ci` exits 0 no matter what the audit reports — verified by running it
against this tree, not assumed from the docs. It never gated anything.
- The endpoint currently returns no advisories at all, so the call is buying
zero information even when it does answer.
- Dependabot watches the same dependency tree and is the channel that actually
reports: the 7 open alerts on this repo (fast-uri, @xmldom/xmldom,
browserslist, postcss-selector-parser) all come from it, none from here.
Set as workflow-level env rather than flags on four separate `npm ci` lines, so
any npm invocation added later inherits it. It applies to CI only — a
developer's own `npm ci` still audits once npmjs.com recovers.
`test / Python 3.11` on 8555a05a reported `cancelled` with every step green
except the last: it hit `timeout-minutes: 10` partway through `npm ci`.
The backend was not the cause. Across the nine previous CI runs on main the
Frontend build step took 14-24s; on this run it took 87s on the 3.14 leg and was
still installing at 131s on the 3.11 leg when the budget ran out. What changed
was not the tree:
frontend/package-lock.json 7b43ba37 -> HEAD a7aafd3c -> a7aafd3c (identical)
setup-node cache key both runs node-cache-Linux-x64-npm-be942593
tsc -b --force, local pre-merge 12.3s post-merge 12.5s
Same lock file, same restored npm cache, same compile time, so `npm ci` was
doing identical work more slowly — a registry or runner effect, reproduced on a
re-run an hour later.
What made a slow install fatal is the budget. The frontend build and tests were
the tail of BOTH Python matrix legs, running the same npm ci + tsc + vite +
vitest twice per push against a tree with no Python in it, and the 3.11 leg was
already at 539s of its 600s ceiling on the last green run. Sixty seconds of
headroom is not enough to absorb anything.
Splitting the frontend into its own job removes the duplicated run, gives it its
own 10-minute budget, returns ~65s to each Python leg, and lets the two run in
parallel. The commands and the tree they run against are unchanged, so nothing
is tested less than before. No required status check names change either: main
currently has none configured.
Dropping the <3.14 cap in 0bb1d870 left 3.14 supported but unexercised: both
jobs pinned python-version 3.11. Claiming a version nothing runs is how the
cap went stale in the first place, so the test job now matrices over
["3.11", "3.14"] with fail-fast off.
Verified locally before adding the leg: CPython 3.14.4 with CI's own extras
([dev,openbb,stats]) is 10774 passed / 23 skipped / 0 failed.
Four issues in the new workflow and Dockerfile:
- Every action floated on a mutable major tag, and BuildKit was pinned to
moby/buildkit:master. This was the only workflow in the repo doing so; the
other four already pin full commit SHAs. All seven actions are now SHA-pinned
with a version comment, and the custom driver-opts block is dropped, which
also removes an unneeded network=host.
- id-token: write was granted with no OIDC consumer anywhere in the workflow —
no cosign, attestation, provenance, or OIDC login step. Dropped.
- The Dockerfile installed channel extras with 'pip install -e ".[extras]"'
after the --require-hashes lock install. lark-oapi, qrcode and
python-telegram-bot are in neither requirements-lock.txt nor its source, so
the build resolved them and their transitive deps unhashed from PyPI at build
time — six lines below a comment stating deps come from the hash-pinned lock.
They now come from requirements-channels-lock.txt (16 pins, all hashed,
generated under the same constraint as the main lock: 12 packages are shared
with it and none diverge in version). The project install is --no-deps, which
is safe because all 44 pyproject base deps are already in the main lock.
- The workflow_dispatch input was interpolated directly into the Summary step's
run: body. It now passes through env, and the tag is validated against the
Docker tag grammar before any registry work.
CI verifies the new lock too. The existing 'Verify hash-locked dependencies'
step only dry-ran requirements-lock.txt, so a second lock could have rotted
undetected until a release build failed; both are now dry-run separately, since
the Docker build installs them as two independent --require-hashes passes.
The summary also states that a new GHCR package is private until its visibility
is set once, so the pull command it prints is not read as working out of the box.
Twelve commits landed on main on 2026-08-06 (f8e888c7..8452a844) without a
single workflow run being created. The push webhooks reached GitHub - the
Cursor app opened a check suite on each head commit at push time - but no CI
run was enqueued, while pull_request runs on the same workflow kept working.
The repository side is clean: the workflow is active, unchanged since the
last green push run, has no path filters, no skip-ci token in any of the
twelve messages, no rulesets and no branch protection.
The same window shows GitHub-side queueing trouble independently: four runs
created between 08-05T15:37Z and 08-06T09:33Z all have run_started_at within
four seconds of 08-06T17:36:56Z, having sat queued for 8 to 26 hours against
a 10-minute job timeout.
Whatever the cause, `push` was the only trigger that could produce a verdict
on main, so losing it left no way to get one short of pushing an empty
commit. This adds the manual trigger.
The quantlib timeseries suite added on 2026-08-06 calls functions that
lazy-import statsmodels, but CI installs only ".[dev,openbb]", so all 56
statsmodels-dependent tests failed with the ImportError those functions
raise by design. Two-sided fix, following the openbb precedent already in
the workflow: CI now installs ".[dev,openbb,stats]" and fails loudly if
statsmodels or arch stops resolving, and the 41 statsmodels-dependent
tests (plus the tool-envelope test that reaches compute_half_life) carry
a skip guard so a base install reports skips instead of failures. The
missing-dependency error-path tests still run exactly where they should:
only in an environment without the extra.
Exposes the agent to OpenBB Workspace over the openbb-ai SDK, behind an
optional `openbb` extra.
Security repairs on top of the contribution:
- pro.openbb.co is no longer a default CORS origin. CORS is installed
globally with credentials and keyless loopback mode admits safe GETs by
peer address, so that default would have let scripts on the OpenBB
origin read /sessions and /runs. Extra origins are now an additive
opt-in (VIBE_TRADING_EXTRA_CORS_ORIGINS) declared in the typed config
layer; the loopback-only defaults are unchanged.
- POST /v1/query carries require_auth. It reaches the full agent registry,
which auto-registers the order tools, so an unauthenticated caller could
have driven actions inside an already-active mandate.
- The three unadvertised LLM routes (chat title, dashboard title, prompt
enhancement) are removed rather than authenticated: they were absent
from the manifest and duplicated the existing authenticated title path.
- Conversation identity no longer hashes the first message. Any two
conversations opening with the same text mapped onto one persistent
session, leaking history between them, and later requests' full history
was ignored. openbb-ai 2.1.0 exposes no conversation id, and the SDK
contract is stateless with full history per request, so each request now
runs an ephemeral session replaying the supplied history.
- The SDK is wired into the dev install and CI, with an explicit import
check. It previously landed only in agent/requirements.txt while CI
installs the extras, so every bridge test silently skipped and the
feature could look green while being uninstallable.
- Workspace context is now ingested under an 8k budget instead of ignored,
and widget-dashboard-search is advertised as false: widget groups carry
metadata only, and the value round-trip is not implemented, so the model
is told values are absent rather than left to invent them.
The contributor's requirements-lock.txt rewrite was dropped (stale
Python-3.12 regen with unrelated downgrades; the runtime targets 3.11).
Note for operators: a browser POST from a remote Workspace origin is still
refused by the pre-existing cross-site guard, independently of CORS and of
a valid token. Self-hosted same-origin Workspace and server-side callers
work; that boundary is pinned by a test.
React Router 7.12.0 through 8.2.x carry a high-severity RSC-mode CSRF bypass
(GHSA-qwww-vcr4-c8h2). We were on 7.18.1, inside that range. The advisory is not
reachable here — the app is a client-side SPA on createBrowserRouter with no
@react-router/server and no RSC entry point, and the frontend ships as static
Vite output — but 7.18.1 is the final 7.x release, no backport exists, and 8.3.0
is the only patched version, so staying put means staying on an EOL line with a
permanent open alert.
react-router-dom has no 8.x at all; the DOM exports moved into react-router
itself. So the dependency is swapped and every import rewritten. All ten APIs in
use here are unchanged in 8.3.0, and none of the data-layer surface that changed
most between majors is used.
react-router 8 requires Node >=22.22.0, which is the same wall that had jsdom 30
and @testing-library/jest-dom 7 sitting in the Dependabot ignore list. Node 20
reached EOL on 2026-04-30, so the toolchain moves to Node 22 across CI, the wiki
workflow, the Dockerfile and docker-compose. That unblocks all three at once:
jsdom and jest-dom are taken here too, and their ignores are removed rather than
left carrying a rationale that no longer holds.
Verified on the frontend suite: tsc -b and vite build clean, 310 tests across 35
files pass before and after the jsdom/jest-dom bumps.
ccxt does not use ranges — it exact-pins its entire transitive closure. All 23
of those packages that appear in our lock sit at precisely ccxt's pin, with no
mismatches, so none can move until ccxt itself releases, and ccxt 4.5.68 is
already the latest. Any Dependabot PR touching one is unmergeable by
construction: the pip group PR proposed five of them and died with
ResolutionImpossible on certifi against the ccxt line in the lock.
Re-running the resolver confirms this is the ceiling rather than a stale lock —
pip-compile --upgrade under python:3.11-slim, matching CI, reproduces every one
of those versions, so there is no lock change to make. pydantic-core is in the
same position via pydantic's own == pin, and was the sixth package in that PR.
Ignore the set. ccxt itself is deliberately not ignored: it is the package whose
bump legitimately carries all of them. This suppresses only the automated PRs —
Dependabot alerts come from the dependency graph and are unaffected by this
file, so a CVE in cryptography, urllib3, requests or certifi still surfaces in
the Security tab, where the remedy is a ccxt bump.
jsdom 30.x requires Node ^22.22.2 || ^24.15.0 || >=26 while CI and Docker are
on Node 20. On Node 20 the incompatibility surfaces indirectly as vitest
failing to start its pool workers rather than as a clean engine error, so #877
reads like a flaky frontend failure. Same wall as @testing-library/jest-dom 7.
Dependabot could not land either half of a mutually-blocking pair: the
minor/patch group wanted ccxt 4.5.68 (which pins setuptools==83.0.0) while
keeping setuptools at 82.0.1, and the separate setuptools 83.0.0 PR conflicted
with the ccxt 4.5.67 already in the lock. Neither can go green alone because
setuptools is a major bump and is excluded from the minor/patch group by
design, so it cannot ride along.
Regenerate the lock so both move together. Verified with pip-compile under
Python 3.11 on Linux (matching CI) and checked with CI's own gate,
pip install --dry-run --require-hashes. Note the lock is platform-sensitive:
regenerating on macOS drops jeepney/secretstorage and swaps py-mini-racer for
mini-racer, so it must be produced on Linux.
Also ignore websockets, capped at <16 by langgraph-sdk 0.4.2, and move all
three ecosystems to monthly with 2 concurrent PRs. Security updates are a
separate channel and are unaffected.
caio: aiofile 3.11.1 (latest) requires caio~=0.9.0, so bumping caio to 0.10.x
makes pip unresolvable and the grouped PR fails CI. Ignore only caio, not the
whole minor/patch group.
@vitejs/plugin-react 6.x peers vite@^8 (we are on 6.x) and
@testing-library/jest-dom 7.x requires Node >=22 (CI/Docker use 20). Both are
migrations, not dependency bumps.
Add task-scoped cancellation, reject broad Python process-name kills, expose timeout context, and cover the lifecycle on Windows 11 and Windows x64 with Python 3.14.
Signed-off-by: Haozhe Wu <haozhe_wu@connect.hku.hk>