Skip to content

feat: add admin cost calculation table - #855

Open
yedouo wants to merge 174 commits into
stagingfrom
codex/staging-cost-calculation
Open

feat: add admin cost calculation table#855
yedouo wants to merge 174 commits into
stagingfrom
codex/staging-cost-calculation

Conversation

@yedouo

@yedouo yedouo commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

变更内容

  • 在管理员「计费与支付」中新增成本核算表
  • 支持修改套餐、优惠券、赠送额度、售价系数和全局折扣
  • 支持模型新增、删除、官方价格、备注、单模型折扣覆盖
  • 支持动态增加/删除自定义字段并保存
  • 使用 FlatkeyCostCalculation 系统选项,增加后端结构校验

验证

  • 前端定向测试、typecheck、build:check 通过
  • Go 模型定向测试通过
  • 整仓 go build 受基线缺失 web/classic/dist embed 目录阻断
  • 整仓 controller 测试存在与本功能无关的既有 asset/channel 场景失败

请在 staging 环境验证管理员页面与保存/刷新流程。

Constraint: TokenSpace and Seedance proxy scopes are 87 and 82 characters while production columns are varchar(80).

Rejected: Truncate scope hashes | it would change binding identity and invalidate reusable bindings.

Confidence: high

Scope-risk: narrow

Directive: Keep all grouped Seedance providers on one provider-configured asset lifecycle with no channel-ID branches.

Tested: Design self-review, whitespace validation, and clean origin/main code-path inspection.

Not-tested: No implementation changes are included in this design-only commit.
Constraint: Wallet pricing and subscription enforcement must remain independently testable

Rejected: One monolithic implementation plan | the wallet and billing paths have separate regression surfaces

Confidence: high

Scope-risk: broad

Directive: Execute each task with a red-green test cycle and preserve monthly/media enforcement

Tested: plan self-review for spec coverage, placeholders, and interface consistency
Constraint: Stripe validation and wallet rendering must share code-defined amounts
Confidence: high
Scope-risk: narrow
Tested: focused controller top-up contract tests
Constraint: Unmapped or unconfigured languages must fall back to USD

Rejected: Trust raw stripe_currency_prices from API | parser must discard unsupported currencies and invalid minor-unit values

Confidence: high

Scope-risk: narrow

Directive: Keep checkout display math based on minor units; JPY remains zero-decimal while USD/BRL/INR divide by 100

Tested: bun test src/features/wallet/lib/stripe-currency.test.ts

Tested: bun run typecheck

Tested: bun x prettier --check src/features/wallet/types.ts src/features/wallet/hooks/use-topup-info.ts src/features/wallet/lib/stripe-currency.ts src/features/wallet/lib/stripe-currency.test.ts

Tested: git diff --check
Constraint: A wallet tier may not display one currency and submit another
Rejected: Region-based default currency | Locale must choose the initial checkout currency while region only gates selector visibility
Confidence: high
Scope-risk: moderate
Directive: Preserve URL/manual checkout currency precedence before applying language defaults
Tested: bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/lib/stripe-currency.test.ts
Tested: bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/components/subscription-plans-card.test.tsx src/features/wallet/lib/stripe-currency.test.ts
Tested: bun run typecheck
Not-tested: Full web/default test suite
Constraint: Inbound/manual currency must not force an unconfigured checkout price into the recharge UI
Rejected: Let touched currency always bypass fallback | It hides purchasable USD presets when a localized URL currency lacks prices
Rejected: Reuse wallet tier value for recall pricing | Localized Stripe prices can differ from the tier value
Confidence: high
Scope-risk: moderate
Directive: Treat touched currency as a language-default guard only; still validate it against configured checkout prices
Tested: bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/index.test.tsx src/features/wallet/lib/stripe-currency.test.ts
Tested: bun test src/features/wallet/components/subscription-plans-card.test.tsx
Tested: bun run typecheck
Not-tested: Full web/default test suite
Constraint: Bun module mocks are process-global across files in the same test invocation
Rejected: Mock shared lucide/UI/component modules narrowly | Later wallet tests import additional exports and fail by order
Rejected: Split the relevant suite as the only gate | The normal runner may execute these files in one process
Confidence: high
Scope-risk: narrow
Directive: Wallet integration tests must either use real shared modules or provide compatible complete mock surfaces
Tested: bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/index.test.tsx src/features/wallet/lib/stripe-currency.test.ts
Tested: bun test src/features/wallet/index.test.tsx src/features/wallet/components/subscription-plans-card.test.tsx
Tested: bun test src/features/wallet/components/subscription-plans-card.test.tsx src/features/wallet/index.test.tsx
Tested: bun run typecheck
Not-tested: Full web/default suite
Constraint: Bun wallet tests can share one process with process-global browser shims
Rejected: Assigning storage through window Object.assign | captures the mock descriptor and misses sessionStorage restoration
Confidence: high
Scope-risk: narrow
Directive: Register global shims through descriptor restore helpers before any globalThis mutation
Tested: bun test src/features/wallet/index.test.tsx; bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/index.test.tsx src/features/wallet/lib/stripe-currency.test.ts; bun test src/features/wallet/index.test.tsx src/features/wallet/components/subscription-plans-card.test.tsx; bun test src/features/wallet/components/subscription-plans-card.test.tsx src/features/wallet/index.test.tsx; bun run typecheck
Not-tested: Full bun test suite
Constraint: Monthly pool and media credit enforcement must remain active

Rejected: Dropping legacy Redis helpers | historical records still need compatibility

Confidence: high

Scope-risk: moderate

Directive: Keep async task window compatibility helpers until persisted historical task data no longer needs them

Tested: go test ./service -run 'Test(SubscriptionFundingWeightedRounding|SubscriptionFundingDoesNotExposeLegacyWindowSnapshot|BillingSession)' -count=1; git diff --check

Not-tested: full go test ./service; GitNexus impact/detect-changes blocked by stale incompatible index
Constraint: Weighted monthly-pool settlement must remain restart-safe

Rejected: Removing TaskBillingContext.SubscriptionWindow | legacy serialized tasks still need to deserialize

Confidence: high

Scope-risk: moderate

Directive: Do not reintroduce async task Redis window adjustment; keep monthly pool settlement weighted by SubscriptionWeight

Tested: go test ./service ./controller -run 'Test.*(Task.*Subscription.*Window|SubscriptionSnapshot|AcceptedTaskSubscription)' -count=1; go test ./service -run 'Test(RefundTaskSubscriptionWindowSnapshotLeavesRedisCounters|AcceptedTaskSubscriptionWindowStepOnlyMarksCompatibilityLedger)$' -count=1; go test ./controller -run 'Test(AssetTaskQueuePersistsSubscriptionSnapshot|AssetTaskAcceptedAccountingExternalStepsAreIdempotent|AssetTaskWorkerAcceptedSubscriptionUsesSnapshotForSettlement)$' -count=1; git diff --check

Not-tested: Full go test ./service ./controller -count=1 was interrupted after more than two minutes per parent instruction because focused verification already passed
Constraint: Legacy columns stay readable but new plan writes normalize them to zero

Rejected: Removing model or admin legacy fields | database compatibility still requires them

Confidence: high

Scope-risk: moderate

Directive: Keep monthly_bucket, quota, media_credits, and admin legacy structs available while short windows are absent from public/self contracts

Tested: go test ./controller -run 'Test.*Subscription(Self|Plan).*Window' -count=1; split public response and plan lifecycle tests; git diff --check

Not-tested: go test ./controller -count=1 has unrelated existing asset worker and test environment failures
Constraint: Flatkey short windows must disappear without hiding active limits

Rejected: Removing admin plan short-window amount fields | Task 5 owns admin form cleanup and current plan parsing still needs compatibility

Confidence: high

Scope-risk: moderate

Directive: Keep wallet and profile self/public subscription summaries free of 5h/7d fallback fields

Tested: bun test src/features/wallet/components/subscription-plans-card.test.tsx src/features/wallet/lib/subscription-plan-lifecycle.test.ts src/features/profile/components/profile-header.test.tsx src/features/profile/lib/subscription-summary.test.ts; bun test src/features/profile/hooks/use-profile-subscription.test.ts; bun run typecheck

Not-tested: full frontend test suite
Constraint: Older API payloads cannot re-enable inactive enforcement

Rejected: Removing legacy SubscriptionPlan response fields | Old admin responses still need to parse

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce short-window admin inputs or payload fields unless enforcement is restored end to end

Tested: bun test src/features/subscriptions/lib/plan-form.test.ts src/features/subscriptions/components

Tested: bun run typecheck

Tested: rg drawer window fields and labels returned no matches

Not-tested: Full web test suite
Constraint: Task 4 review found remaining user-facing short-window semantics in profile media normalization and purchase review copy

Rejected: Preserving backend media unlimited for zero total | product semantics require zero media credits to mean Not included

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce 5h/7d copy or zero-media unlimited fallback on wallet/profile purchase surfaces

Tested: bun test src/features/profile/lib/subscription-summary.test.ts; bun test src/features/wallet/components/subscription-plans-card.test.tsx; bun test src/features/wallet/components/subscription-plans-card.test.tsx src/features/wallet/lib/subscription-plan-lifecycle.test.ts src/features/profile/components/profile-header.test.tsx src/features/profile/lib/subscription-summary.test.ts; bun run typecheck

Not-tested: full frontend test suite
Constraint: Subscription short-window removal must not hide visible top-up packages when a localized currency is only partially priced.

Rejected: Keeping partial local-currency package display | It hid unpriced visible presets instead of falling back the whole checkout tier to USD.

Confidence: high

Scope-risk: moderate

Directive: Keep legacy subscription window snapshots compatibility-only; do not reintroduce active short-window wallet/profile/admin surfaces.

Tested: focused wallet currency tests; focused subscription window Go tests; frontend typecheck; production build; changed-file lint; git diff --check.

Not-tested: Exact broad Go command stalled and was interrupted; full frontend directory test command remains affected by existing Bun module mock leakage; full lint has pre-existing repo-wide violations.
Constraint: Inbound or manual localized checkout currency must not render or submit a partial preset tier, and wallet/profile/subscriptions tests must run in one Bun process.

Rejected: Correcting invalid currencies with a post-render timer | It allowed a partial BRL UI to commit before fallback and triggered React compiler lint pressure.

Rejected: Keeping wallet integration tests on top-level shared module mocks | Bun keeps those mocks process-global and they polluted neighboring wallet tests.

Confidence: high

Scope-risk: moderate

Directive: Derive Stripe display and submission currency from the same synchronous effective currency resolver; keep Bun test mocks complete when they target shared modules.

Tested: bun test wallet resolver/card focus; bun test wallet/profile/subscriptions directories; bun test subscription-purchase then billing-history order; bun run typecheck; changed-file eslint; bun run build; git diff --check.

Not-tested: Full repo-wide lint in Fix Round 1; original Go broad/split concerns unchanged because this round touched only frontend currency/tests.
Constraint: Bun module mocks are process-global, so subscription dialog tests must not replace shared pure format exports used by later plan-form tests.

Rejected: Adding only quotaUnitsToDollars and parseQuotaFromDollars to the mock | The shared format module can drift and break the next consumer again.

Confidence: high

Scope-risk: narrow

Directive: Prefer real pure modules over partial top-level mocks in shared Bun test processes.

Tested: bun test subscription-purchase plus plan-form --randomize --seed 1; reverse order --seed 2; bun test wallet/profile/subscriptions directories; bun run typecheck; changed-file eslint; git diff --check.

Not-tested: Full repo-wide lint/build in Fix Round 2; no production code changed.
Constraint: JPY fixed discounts are zero-decimal in Stripe, while USD/BRL/INR remain two-decimal currencies.

Rejected: Keeping the Button test via a shared UI mock | Bun module mocks leak process-wide and caused order-dependent failures.

Rejected: Removing Codex upstream 5-Hour Window and generic 7d copy | Call-site search shows they still serve non-Flatkey UI paths.

Confidence: high

Scope-risk: moderate

Directive: Keep fixed-discount amount conversions currency-aware and avoid process-global mocks for shared UI modules.

Tested: bun test src/features/wallet/lib/recall-claim.test.ts src/features/wallet/components/recharge-form-card.test.tsx src/i18n/config.test.ts; bun test src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/index.test.tsx src/features/wallet/lib/recall-claim.test.ts --randomize --seed 1; bun test src/features/wallet/index.test.tsx src/features/wallet/components/recharge-form-card.test.tsx src/features/wallet/lib/recall-claim.test.ts --randomize --seed 2; bun test src/features/wallet src/features/profile src/features/subscriptions; bun run typecheck; bun x eslint changed files; bun run build; go test ./i18n; git diff --check.

Not-tested: Full repository Go suite was not rerun in this final wave; earlier Task 6 report documents broad Go command constraints.
Constraint: Display only configured plan prices and fall back the full grid to USD when a language currency is incomplete.

Rejected: Client-side currency conversion | It can diverge from configured and charged amounts.

Confidence: high

Scope-risk: narrow

Directive: Keep server-issued checkout quotes authoritative over language-derived display prices.

Tested: Spec self-review for placeholders, contract consistency, fallback behavior, and verification coverage.

Not-tested: Implementation and staging behavior are covered by the following execution plan.
Read supported Stripe Price currency options on the server, merge explicit BRL and INR plan prices, and select one fully covered language currency for the wallet plan grid.

Constraint: Japanese uses JPY, Portuguese uses BRL, other languages use USD, and incomplete plan sets fall back entirely to USD.

Rejected: Client-side exchange-rate conversion | It can diverge from configured and charged prices.

Confidence: high

Scope-risk: moderate

Directive: Keep signed payment preview quotes authoritative over language-derived card prices.

Tested: Targeted Go controller tests; 206 wallet tests; TypeScript typecheck; production frontend build; git diff check.

Not-tested: Live Stripe currency options and staging browser rendering require the staging deployment.
Use R.90 and R.90 for the 100 and 200 credit Stripe top-up tiers.

Constraint: The wallet must validate against the exact Stripe BRL minor-unit amounts.

Rejected: Frontend-only formatting | Checkout validation would still reject the configured Stripe prices.

Confidence: high

Scope-risk: narrow

Directive: Keep top-up display and Stripe validation driven by the same minor-unit contract.

Tested: Stripe top-up price contract, top-up info filtering, and top-up info response tests.

Not-tested: Live staging values require the next deployment.
Constraint: Pull requests must pass git diff --check before review.

Confidence: high

Scope-risk: narrow

Tested: git diff --check origin/main...HEAD

Not-tested: Functional behavior is unchanged.
Constraint: Bun module mocks remain process-global across the full frontend suite.
Rejected: Assert less in the SMTP test | The placeholder is user-facing behavior and should stay covered.
Confidence: high
Scope-risk: narrow
Directive: Shared component mocks must forward unrelated native props.
Tested: bun test; bun run typecheck; targeted eslint; bun run build
Not-tested: Full Go suite remains blocked by missing web/classic/dist and long-running unrelated packages.
Preserve staging onboarding and model-handoff behavior while routing implemented image and video models through session-authenticated Playground endpoints with model-specific controls.

Constraint: Only expose media families backed by concrete relay support.

Rejected: Broadly expose image and video names by pattern | Unsupported models would present controls that cannot complete requests.

Confidence: high

Scope-risk: moderate

Directive: Keep Playground model profiles aligned with backend relay capabilities.

Tested: 82 Playground tests, targeted ESLint and Prettier, TypeScript typecheck, web production build, targeted Go relay/middleware/router tests, controller build.

Not-tested: Live upstream media generation before staging deployment.
Reuse existing Resolution and seconds translation keys so image and Seedance controls do not leak English labels in localized Playground sessions.

Constraint: Avoid adding duplicate locale entries when shared translations already exist.

Confidence: high

Scope-risk: narrow

Tested: 83 Playground tests, targeted ESLint and Prettier, TypeScript typecheck, web production build.

Not-tested: Live UI recheck before the updated staging revision deploys.
Use the existing anchored popover surface so generation parameters stay next to their trigger without blurring the full workspace.

Constraint: Match the supplied parameter-panel prototype and deploy staging only.

Rejected: Remove blur from the shared Dialog overlay | That would alter unrelated dialogs globally.

Confidence: high

Scope-risk: narrow

Directive: Keep media parameter controls anchored and non-modal unless product design explicitly changes.

Tested: 84 Playground tests, targeted ESLint and Prettier, TypeScript typecheck, production web build.

Not-tested: Live staging interaction before deployment.
Request construction must serialize visible user configuration instead of normalizing it again immediately before the backend call.

Constraint: Apply model constraints while editing and deploy to staging only.

Rejected: Keep defense-in-depth normalization in the request builder | It creates a hidden submit-time mutation.

Confidence: high

Scope-risk: narrow

Directive: Keep model parameter normalization in Playground configuration state; request builders remain serialization-only.

Tested: 85 Playground tests, targeted ESLint and Prettier, TypeScript typecheck, web production build.

Not-tested: Live staging behavior before deployment.
Constraint: Port only Sub2 v0.1.178 identity-risk behavior; exclude account scheduling and capacity failover.
Rejected: Channel setting JSON seed storage | dedicated hidden column prevents API leakage and accidental replacement.
Confidence: high
Scope-risk: moderate
Directive: Preserve seed across edits and credential refreshes; never expose it to clients.
Tested: Spec self-review, placeholder scan, and git diff --check.
Not-tested: Implementation and runtime behavior are deferred to the execution plan.
Constraint: Existing channels must not change mode during deployment.
Rejected: Treating missing persisted mode as full | that would mass-switch existing subscriptions.
Confidence: high
Scope-risk: narrow
Directive: Persist full on new-channel creation while preserving explicit or missing legacy modes.
Tested: Spec consistency scan and git diff --check.
Not-tested: Frontend and backend implementation remain pending.
Constraint: Full mode must not forward original client identity through metadata, headers, alternate paths, or cloned database seeds.
Rejected: Rejecting every unknown metadata key | stripping unknown keys preserves compatibility while failing closed on leakage.
Confidence: medium
Scope-risk: broad
Directive: Keep the allowlists explicit and apply finalization after header overrides on every Codex subscription egress path.
Tested: Spec contradiction scan and git diff --check.
Not-tested: Runtime compatibility of strict full-mode sanitization remains to be proven by TDD and staging.
think-back and others added 27 commits August 24, 2026 19:06
…t-routing

fix: route TokenSpace real-person assets through Doubao video channels
Prevent duplicate Google accounts for verified emails
…quota

Unify subscription quota and retire media credits
…sibility

feat(visibility): apply hidden models to public model catalogs
Filter administratively hidden models only for Playground display, add the backend-safe Seedance 2.5 profile, and route image/video quick starts through visible compatible models.

Constraint: Seedance 2.5 must match the current relay contract: 480p/720p, 4-30 seconds, no 21:9 or seed.

Rejected: Reusing the Seedance 2.0 profile | it exposes unsupported 2.5 parameters.

Confidence: high

Scope-risk: moderate

Directive: Keep /api/user/models hidden filtering opt-in so availability semantics remain unchanged for other consumers.

Tested: Go operation_setting tests; targeted controller tests; 1725 frontend tests; scoped ESLint and Prettier; TypeScript typecheck; production build.

Not-tested: Browser E2E against the deployed console.
…y-seedance25

feat(playground): honor hidden models and support Seedance 2.5
Parse numeric subscription tiers without float coercion and expose the two supported Grok video models through the existing Playground video flow.

Constraint: Media writes remain single-submit and account status refresh remains explicit.

Rejected: Treat usage percentages as paid evidence | display-only usage can authorize unknown or free accounts.

Confidence: high

Scope-risk: narrow

Directive: Keep Grok video payload fields synchronized with the strict task DTO; never rename aspect_ratio to ratio.

Tested: Grok relay/task tests; Playground 106 tests; TypeScript; ESLint; Go vet; frontend production build.

Not-tested: Production Grok video generation awaits deployment and authoritative paid-tier refresh.
Fix Grok numeric tier eligibility and Playground video access
…d language

Constraint: Preserve existing save, retry, restore, and toast trigger behavior.
Rejected: Component-scoped translation hooks | persistence warnings run inside async hook callbacks and the repo already uses the i18next singleton for this pattern.
Confidence: high
Scope-risk: narrow
Directive: Keep new Playground persistence warnings covered across every supported locale.
Tested: 1732 Bun tests; TypeScript typecheck; targeted ESLint and Prettier; production frontend build.
Not-tested: Manual fault injection of browser storage failure in every browser locale.
TokenSpace legacy real-person assets already carry a channel-owned upstream asset id, but strict worker materialization was falling through to the generic asset table and failing before provider submit. Failed preparation also exposed no structured task error to video polling clients.

Constraint: TokenSpace real-person groups are upstream LivenessFace groups while normal material assets use scoped AIGC bindings.

Rejected: Converting legacy real-person rows into generic assets in this fix | would widen migration and ownership risk under production pressure.

Confidence: high

Scope-risk: moderate

Directive: Keep TokenSpace real-person fallback limited to enabled DoubaoVideo TokenSpace channels with a same-channel active upstream binding.

Tested: go test ./service -run "TestMaterializeAssetBindingsForChannel|TestAssetReference|TestTokenSpace|TestRealPerson" -count=1

Tested: go test ./controller -run "TestTechMobiAssetTaskWorker|TestAssetTaskWorkerPreparationFailureRefundsOnceForLateResult" -count=1

Tested: go test ./model -run "TestTaskQueuedFailureCAS|TestTaskToOpenAIVideo" -count=1

Not-tested: go test ./... because the root package still requires generated web/classic/dist assets in this worktree.
The async worker must validate current profile ownership and status before direct TokenSpace reuse, and the default non-strict path must recognize an authorized same-channel real-person binding before materializer scoring rejects it.

Constraint: TokenSpace real-person assets remain channel-owned legacy bindings and are not recoverable ordinary AIGC sources.

Rejected: Enabling strict coverage as an operational workaround | production defaults to non-strict and authorization must not depend on that feature flag.

Confidence: high

Scope-risk: moderate

Directive: Keep direct reuse gated by legacy provenance, active owning profile, matching channel, active binding, and TokenSpace capability.

Tested: targeted service and controller real-person worker tests, middleware routing tests, model failure/refund tests, go vet on affected packages.

Not-tested: full go test ./... because generated web/classic/dist assets are absent and an overbroad service regex triggers unrelated million-row tests.
…er-routing

Fix TokenSpace real-person asset worker routing
Route active real-person profiles through provider bindings while retaining credential-backed native storage and provider-independent persisted GCS cleanup.

Constraint: Preserve the existing client contract and native BytePlus TOS/GCS behavior.

Rejected: Requiring clients to pre-upload media | The public multipart endpoint already owns temporary private storage.

Confidence: high

Scope-risk: moderate

Directive: Keep persisted GCS cleanup independent of current channel ability.

Tested: gofmt; eight focused multipart, resume, provider, final-probe, and cleanup regressions; go build ./...; go test ./service/... (baseline failures disclosed).

Baseline-failure: Representative full-service failures reproduce on unchanged origin/main.
Constraint: Only server-rewritten asset URI occurrences may cross the ModelAPI boundary.

Rejected: Trust every asset URI value present in the rewrite map | it permits same-request direct URI smuggling.

Confidence: high

Scope-risk: narrow

Directive: Preserve per-occurrence rewrite provenance when changing Seedance media validation.

Tested: adaptor package; targeted service middleware controller tests; go vet; affected package build; diff check.

Not-tested: production end-to-end before deployment.
…rite

Fix channel 156 bound asset submission
* feat(admin): add guarded user impersonation sessions

* fix(admin): sync client identity during impersonation

* fix(auth): keep request uid in sync with session user

* feat(i18n): localize user impersonation controls

---------

Co-authored-by: 1111 <a1111@1111deMac-Studio.local>
* fix(admin): keep impersonation audit out of user logs

* fix(log): filter impersonation audit from user logs

---------

Co-authored-by: 1111 <a1111@1111deMac-Studio.local>
fix: support TokenSpace real-person multipart assets
Keep Flatkey's existing lease-guarded billing snapshot while aligning account status with Grok2API credit fields and derived remaining balances.

Constraint: Preserve existing Grok media eligibility, stale-evidence, and secret-redaction boundaries.

Rejected: A new quota table or separate account service | Adds migration and duplicate lease state without improving the current channel-scoped flow.

Confidence: high

Scope-risk: moderate

Directive: Extend the versioned sanitized snapshot only; never expose raw billing responses or credentials.

Tested: Spec self-review and placeholder scan.

Not-tested: Implementation behavior is intentionally pending.
Preserve Grok2API-style package balances and render them in the existing account status dialog while keeping the persisted projection strictly non-secret. Overage is retained for accounting and clamped only for display.\n\nConstraint: Keep the existing versioned snapshot, channel-scoped refresh flow, media eligibility evidence, and secret-redaction boundary backward compatible.\nRejected: Add a new quota table or expose raw upstream JSON | unnecessary schema churn and leakage risk.\nConfidence: high\nScope-risk: moderate\nDirective: Future quota fields must be added to the parser whitelist, controller projection, and frontend formatter together; never serialize credentials or raw upstream payloads.\nTested: go test ./relay/channel/groksubscription -count=1; go test ./controller -run 'TestGrok(AccountStatus|ChannelDetail|Auth)' -count=1; go test ./model -run 'TestGrok' -count=1; go build ./...; go vet ./relay/channel/groksubscription ./controller ./model; Bun Grok formatter tests; TypeScript build check; ESLint; Prettier; frontend Rsbuild build.\nNot-tested: Full controller package suite was not used because the unfiltered suite exceeded the local command window; targeted Grok controller/auth/channel tests passed.
feat(i18n): refine sidebar onboarding labels
Adds the isolated Seedance-2.5 static prototype under website/public with local-only interactions and no router/runtime changes.
@KingCesc

Copy link
Copy Markdown

🤖 OpenCodeReview · 评审 commit 4f20cac7 · 共 5 条

.github/workflows/gcp-deploy.yml

  • L244: [严重] 这里把 GROK_CREDENTIAL_CIPHER_KEY 绑定到了 console 服务,但同一套 Grok 认证/刷新控制器是共享代码,router 进程也会调用 service.LoadGrokCredentialCipher()。如果 router 仍然不注入这个 secret,相关接口在生产上会直接 fail-closed,导致 Grok 账号授权/刷新不可用。建议在 deploy-router--update-secrets 里也同步加入同名映射,并确认 staging 对应的 router 部署也已覆盖。
--update-secrets="BYTEPLUS_REAL_PERSON_CIPHER_KEY=${BYTEPLUS_REAL_PERSON_CIPHER_SECRET}:latest,GROK_CREDENTIAL_CIPHER_KEY=${GROK_CREDENTIAL_CIPHER_SECRET}:latest" \
            --update-secrets="BYTEPLUS_REAL_PERSON_CIPHER_KEY=${BYTEPLUS_REAL_PERSON_CIPHER_SECRET}:latest,GROK_CREDENTIAL_CIPHER_KEY=${GROK_CREDENTIAL_CIPHER_SECRET}:latest" \

controller/pricing.go

  • L250-256: [严重] 这里先解锁 websitePricingCache 再清空 websiteMetadataCache,两者之间存在并发窗口:如果此时有请求重新构建定价缓存,会先读取到旧的 metadata 缓存并把过期目录信息写回 websitePricingCache,随后再清空 metadata 缓存也无法回滚已经重建的旧结果,导致变更后仍可能短暂甚至持续返回陈旧的目录信息。建议把两个缓存的失效放在同一临界区内完成,或先失效 metadata 再失效 pricing,并配合版本号/单飞控制避免重建时命中旧值。
func InvalidateWebsitePricingCache() {
	websitePricingCache.Lock()
	defer websitePricingCache.Unlock()
	invalidateWebsiteMetadataCache()
	websitePricingCache.body = nil
	websitePricingCache.expiresAt = time.Time{}
}
  • L0: [严重] 这里在持有全局写锁期间执行 getEnabledModelDirectoryMetadataMap(modelNames),该调用会访问数据库;一旦查询变慢或抖动,所有并发请求都会被阻塞在这把锁上,连缓存命中和失效都会被拖住,容易把网站定价接口放大成串行瓶颈。建议把 DB 查询移出全局锁,只保留“二次检查 + 写入缓存”的短临界区,或者按 key 使用 singleflight/分段锁合并并发 miss。
websiteMetadataCache.Lock()
// Re-check after acquiring the writer lock.
now = websiteMetadataNow()
if entry, ok := websiteMetadataCache.entries[key]; ok && now.Before(entry.expiresAt) {
	websiteMetadataCache.Unlock()
	return cloneWebsiteMetadataMap(entry.metadata), nil
}
websiteMetadataCache.Unlock()

metadata, err := getEnabledModelDirectoryMetadataMap(modelNames)
if err != nil {
	return nil, err
}
websiteMetadataCache.Lock()
defer websiteMetadataCache.Unlock()
// 写入缓存前再次确认未被其他 goroutine 更新

controller/playground.go

  • L88-92: [严重] 这里把 PlaygroundVideoFetch 也包进了 runPlaygroundRelay,会在每次轮询任务状态时都重新执行 preparePlayground(包括 GenRelayInfo、用户缓存查询、邮箱校验、token 上下文初始化),并且在返回 2xx 时重复上报 playground_used。视频 fetch 本身通常是前端高频轮询接口,这样会带来不必要的数据库/上下文开销,也会把“使用量”埋点放大成轮询次数,污染运营统计,后续如果复用到配额/计费口径还可能引发误判。建议将 fetch 接口改为更轻量的鉴权/上下文路径,且只在真正的提交入口上报 activation 事件。
func PlaygroundVideoFetch(c *gin.Context) {
	if newAPIError := preparePlayground(c, types.RelayFormatTask); newAPIError != nil {
		c.JSON(newAPIError.StatusCode, gin.H{
			"error": newAPIError.ToOpenAIError(),
		})
		return
	}
	RelayTaskFetch(c)
}

controller/subscription.go

  • L1472-1473: [阻塞] 这里在创建/更新订阅套餐时把 Window5hAmountWindowWeekAmount 直接强制写成 0,会把前端/接口传入的窗口额度静默清空。仓库里这些字段仍被订阅开通、续费、升级、窗口限流等逻辑读取,结果是只要管理员保存一次套餐,原本的短窗额度就会被破坏,可能直接导致权益/计费错误。建议不要在这里覆盖用户输入;如果确实要废弃字段,需要先在所有依赖链路完成迁移后再做数据清理,并提供显式迁移方案。
// 保留客户端传入值,或在完成全链路迁移后再做字段废弃
// req.Plan.Window5hAmount = 0
// req.Plan.WindowWeekAmount = 0

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.

7 participants