Repository navigation
Exempt enterprise accounts from the personal-only growth center - #65
Conversation
An enterprise credential with a domestic product profile was scheduled for travel and answered every request with HTTP 403 / business code 403 and "growth system is only available for personal users", so the card showed 阶段:状态查询 · 上游 HTTP 错误 · HTTP 403 · 业务码 403 while the account was in fact permanently exempt. The region/product profile alone never described eligibility: the growth center is personal-only. Honour the tenant type before any request and stop recording a capability bound as the account's current travel state. - app/travel.py: supported()/unavailable() take the tenant flag, perform() refuses a tenant without opening a client, and the refusal is labelled personal_only so the message reads as a boundary in every phase rather than a retryable outage. - app/model_policy.py, app/gateway_management.py, app/credential_actions.py, converter.py: carry the tenant flag onto the pool entry and through the manual and automatic travel paths. - clear_unsupported() replaces a stale upstream refusal with the terminal notice once, so the periodic sweep stays write-free for exempt accounts. - The shared test fixture no longer marks every synthetic account as enterprise; no test asserted that value, and it contradicted the real personal-only rule.
Reviewer's GuideThe PR introduces enterprise-aware travel eligibility for the personal-only growth center, propagates that capability boundary through manual, automatic, administrative, and UI flows, and replaces stale upstream 403s with an idempotent exemption state. Tests cover the no-request gate, ledger cleanup, end-to-end automation behavior, personal-account regression, and frontend messaging; documentation and fixtures are updated accordingly. Sequence diagram for enterprise travel exemption and ledger cleanupsequenceDiagram
participant Scheduler
participant PoolEntry
participant Travel
participant Ledger
participant Upstream
Scheduler->>PoolEntry: read enterprise
Scheduler->>Travel: supported(profile, enterprise)
Travel-->>Scheduler: false
Scheduler->>Travel: unavailable(profile, enterprise)
Travel-->>Scheduler: terminal exemption notice
Scheduler->>Travel: clear_unsupported(ledger, cid, notice)
Travel->>Ledger: entry(cid)
alt stale refusal or changed notice
Travel->>Ledger: update_travel(cid, notice)
Ledger-->>Travel: changed
else exemption already recorded
Ledger-->>Travel: unchanged
end
Note over Scheduler,Upstream: travel.perform is not called - no upstream client or request is created
Flow diagram for enterprise-aware travel eligibilityflowchart TD
Entry[Credential pool entry] --> Gate["travel.supported(profile, enterprise)"]
Gate -->|personal domestic| Paths[Manual and automatic travel paths]
Gate -->|enterprise or non-domestic| Exempt["unavailable(profile, enterprise)"]
Exempt --> Ledger[clear_unsupported ledger state]
Exempt --> UI[Admin and WebUI show exemption]
Paths --> Upstream[travel.perform and growth-center requests]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. Enterprise accounts are now skipped before travel requests, and the ledger is updated to show that exemption instead of a stale 403. That status survives a revert, but it is bounded and can be refreshed by running travel again.
上游跟进(maiphucgiang/codebuddy2api 96f5c14 / PR maiphucgiang#65): 成长中心仅面向个人账号,企业账号会被上游以 403 "growth system is only available for personal users" 拒绝。上游把租户类型 物化到池 entry,在发请求之前判定豁免,并标成能力边界而非可重试故障。 - app/travel.py:supported(profile, enterprise) / unavailable(profile, enterprise) 三级文案 / 新增 clear_unsupported() 幂等清台账 / _request 识别 personal_only / perform(enterprise=) - converter.py:两处池 entry 装配点写入 enterprise(缺这一步门控永不生效, 但看起来"移植成功");_sync_credits 在 auto_travel 之前插入能力闸 - app/model_policy.py / app/gateway_management.py / app/credential_actions.py: 自动与手工两条旅行路径都传租户标志 - app/buddy.py:成长中心 5 个端点的 personal_only 映射(上游未做) - web:Credentials.tsx 硬编码文案改走已存在的 isEnterprise() 缺陷修复(本轮审计发现,均与上游无关): - B3 测试依赖 UTC 墙钟:tests/test_fusion_b.py 的小时序列用例在 UTC 00:00-09:00 必挂(写 base+9h 记录但查询上限是 now)。此前一直是 定时炸弹,上轮采集在 09:00 后所以是绿的。改为冻结 store 时钟到 UTC 正午, 断言一条未改。 - B1 渠道容量信号被当成账号限流:上游 6021 queue.full 是临时性排队满, 我们此前按账号级 429 处理 → 6 个账号各撞一次就把该模型对全池下线 600 秒。 新增 _parse_queue_full 防御式识别(512 字节上限、非 JSON 退化字节匹配), 拦在 429 分支之前:不写 _model_fail、不熔断账号,对外 503 + Retry-After: 5。 503 在 FAILOVER_CODES 内,换号链保持(线上 FAILOVER_MAX=2 会重放)。 - B2 过期熔断原因常驻:last_error 只在显式 reset 时清除,冷却过期后 sync_error / last_error_code / cooldown.reason 仍会报告已失效的故障, 诊断页按 fail_until > 0 判定恒真。三处同改(gateway_management、 diagnostic、Diagnostics.tsx 改用 until*1000 > now);sync_error 不门控 (它是余额/目录故障,不是熔断原因)。 并发上限可在 UI 热调: - max_concurrent 此前不在 SCHEMA,只能改 CLI/env 后重启。补进 SCHEMA (0..1024,0 不限制)。同时修正真实语义缺陷:_gate() 懒建 Semaphore 导致容量在首个请求时冻结,改非零值不生效。新增 _ResizableGate 原地改 容量(重建会让在途 permit 悬空)配合 _active/_bypass 记账,保证 locked() ⟺ 在途 == limit,且旁路准入的请求排空后归还 permit。 - .env.example:该键改为注释行并说明——设了就是环境变量锁定、UI 不可改; compose 部署恒为锁定(docker-compose.yml 透传,未动)。 验证:后端 1800 passed / 0 failed / 4204 subtests,CI 逐脚本 75/75; 前端 vp check 0 错、vitest 180 passed、playwright 21 passed。 每个修复都有反证(先删修复确认变红再还原),其中两次抓到"反证假绿" (测试钉的是夹具而非被测代码)。
An enterprise credential whose account happens to sit on a domestic CodeBuddy host is scheduled for travel by 1.3.6, and every request comes back
HTTP 403/ business code403/growth system is only available for personal users. The credentials card then renders that refusal as the account's live state —目录同步中 · 同步正常 · 无模型冷却next to阶段:状态查询 · 上游 HTTP 错误 · HTTP 403 · 业务码 403— which reads as a broken sync rather than a permanent entitlement boundary.Reported-by a operator with 13 credentials; exactly one card showed it, and that credential is
type=ultimatewithenterpriseIdset.A — The region gate never described eligibility
travel.supported(profile)accepted anycn-cli/cn-workcredential. The growth center is personal-only, so the product profile alone is not the predicate: the tenant type has to be honoured before any request is issued.supported()now takes the tenant flag, and the flag travels onto the pool entry (converter.py) so the manual (app/credential_actions.py) and automatic (_sync_credits) paths agree with the admin row (app/gateway_management.py) and the scheduler (app/model_policy.py).An enterprise account now issues zero upstream calls:
travel.performrefuses before opening a client. Verified by a test that asserts no client is constructed — not merely that the result saysskipped.B — A capability boundary was recorded as a transient failure
Even with the gate fixed, the card would have kept showing the old
403, because the ledger row written by the last real attempt was never superseded.clear_unsupported()replaces that stale refusal with the terminal exemption notice, and is idempotent: the periodic sweep checks the stored state and message first, so an exempt account costs no writes after the first pass (asserted directly)._request()also maps the upstream wording to apersonal_onlyreason, so an enterprise account that reaches the endpoint by any future path is labelled as a boundary in every phase — includingdepart, where the old branch only relabelledphase == "depart"refusals and would otherwise have surfaced the raw 403 again.C — Why the suite never caught it
The shared fixture in
tests/test_region_routing.pystamped"enterpriseId": "synthetic-enterprise"on every synthetic account, so the whole travel suite ran against enterprise tenants while treating them as personal. No test asserted that value; removing it (leavingadd_account(uid, profile, enterprise="")opt-in) is what makes the corrected gate testable.Verification
tests/test_travel.pycases: gate truth table, enterprise opens no client,personal_onlylabelling,clear_unsupporteddrops the stale 403 and does not rewrite on a second pass.tests/test_credential_automation.py: end-to-end replay of the reported symptom — seeds the 403 ledger row, runs the sweep, asserts the row clears, the admin row reportstravel_supported=false/auto_travel=false, and manual travel returns the notice without a request. A companion case pins that a personal domestic account still travels.web/src/automation.test.tsx: asserts the card explains the exemption instead of blaming the region.app/+converter.pymakes all 17 new assertions fail, so they are load-bearing rather than tautological.main(Windows tempdir permissions and deployment-config tests) — no new failures, and the relevant files go 162 → 221 tests passing.tsc --noEmitclean, 147 frontend tests pass,vite buildsucceeds.Docs (
README,advanced,webui, both languages) now state that the growth center is personal-only and that enterprise accounts never travel.Patch applies cleanly to
7e6bff5.Summary by Sourcery
Exclude enterprise accounts from growth-center travel and present their ineligibility clearly across automation, administration, and the WebUI.
Bug Fixes:
Enhancements:
Documentation:
Tests: