Feat/dr1001 token whitelist wildcard#5768
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-agent research synthesis: mirrors the Stripe auto-topup path for Airwallex (Customer + PaymentConsent + PaymentMethod), with data-model changes, webhook hardening, provider-split charge seam, AUD/USD currency unification, risks, test plan, and a 9-PR phased rollout where only the final PR enables real off-session charging. Design only — no code shipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds airwallex_customer / airwallex_consent_id / airwallex_payment_method / airwallex_original_txn_id to model.User (GORM auto-migrate, 3-DB compatible). Foundation for Airwallex off-session auto-charge per the design doc — no behaviour change yet; nothing reads these columns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t fields - Add a 5-min freshness window on the webhook x-timestamp (fail-OPEN on an unparseable format so live manual-topup webhooks are never broken; the HMAC already covers the timestamp so genuine replays are still caught). - Extend AirwallexPaymentIntent to parse customer_id / payment_consent_id / latest_payment_attempt.payment_method.id / payment_method_transaction_id (paths to confirm against real payloads before PR-3 reads them). - Unit test for the freshness check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- auto_topup_setting: AirwallexEnabled master flag (default off) + MinChargeAUDCents. - RechargeAirwallex: accept + persist airwallex_customer/consent_id/payment_method/ original_txn_id onto the user (only when non-empty → zero change until PR-4). - handleAirwallexSucceeded passes the webhook-parsed consent fields through. - createAirwallexPaymentIntent gains an optional customer_id; ensureAirwallexCustomer helper added (used by the PR-4 save-for-future flow). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arges Adds quotaUnitsToMajorAmount (major units, not cents) alongside the Stripe cents helper, at the same SellMultiplier markup — for the Airwallex AUD off-session charge path. Unit-tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…red) service/auto_topup_airwallex.go: airwallexOffSessionCharge — create intent + confirm against a saved PaymentConsent (triggered_by:merchant, unscheduled MIT, original_transaction_id). Pure request-body builders are unit-tested; the charge fn is behind airwallexChargeFn seam, NOT yet called from MaybeAutoTopup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ag-gated) MaybeAutoTopup now routes to the Airwallex off-session path when the operator master flag (AutoTopupAirwallexEnabled, default OFF) is on AND the user has a saved consent — otherwise the existing Stripe path runs unchanged. Adds decideAirwallexAutoTopup (pure, unit-tested) + maybeAirwallexAutoTopup (lock → charge → credit → log, mirrors the Stripe flow). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Webhook handles payment_consent.* (disabled/revoked/expired) early → model.ClearAirwallexConsent wipes the saved consent so off-session charging stops (payload path noted as to-confirm). - maybeAirwallexAutoTopup disables a user's auto-topup after 3 consecutive charge failures (Redis counter, resets on success). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… plumbing) - Backend: AirwallexPayRequest.save_for_future → RequestAirwallexPay ensures an Airwallex Customer (ensureAirwallexCustomer) and attaches the intent to it, so the card/consent can be reused off-session. Falls back to one-time on error. - Frontend: thread save_for_future through the airwallex pay request/hook/handler (defaults false). The visible "save card" checkbox is deferred until the operator enables AirwallexEnabled + real-card verification (PR-9) — the current "Airwallex is one-time only" hint stays accurate until then. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r flag) Exposes airwallex_autotopup_enabled in topup info; the Airwallex form shows a "save this card for auto-recharge" checkbox only when the operator has enabled it (otherwise the "one-time only, use Stripe" hint stays). The checkbox threads save_for_future through the existing pay path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…etting The /about page rendered the /api/about system setting — seeded as a short placeholder HTML by deploy/preset-content.sql — which overrode and blanked out the built-in DeepRouter brand About page. Render DefaultAboutContent unconditionally so a stray admin/seed value can never override it. Drop the now unused api.ts/types.ts and the data-fetch path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKUmGXKsn5ihKU6KP8BRHs
…etting The /about page rendered the /api/about system setting — seeded as a short placeholder HTML by deploy/preset-content.sql — which overrode and blanked out the built-in DeepRouter brand About page. Render DefaultAboutContent unconditionally so a stray admin/seed value can never override it. Drop the now unused api.ts/types.ts and the data-fetch path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKUmGXKsn5ihKU6KP8BRHs
- Hero: region/model/use-case wizard ("Your AI, unlocked"); headline
switched to "One account. Every AI model." (pitch-deck positioning);
Australia is the default region; demand-first overseas-model framing.
- Smart Routing demo (auto-cycling prompt -> deeprouter-auto -> model) with
animated savings cost bar (I1/I2).
- Value calculator: USD slider -> live usage estimates via usage-estimate (I4).
- Hero <-> wizard reactive background (I3), brand-strip hover/tooltip (I5),
pointer parallax (I7), spring microinteractions (I6). Framer Motion,
design tokens, prefers-reduced-motion safe, no chat (red line).
- Fix: minimum top-up stat shows $5 (was incorrectly ¥5).
- Brand favicon: favicon-32/64 + apple-touch-icon from logo.png.
- Docs: OPTIMIZATION-PRD.md; tasks/home-motion-interactive-prd.md (v0.2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…44) New homepage acquisition pieces on top of the existing SmartRouting section: - Hero: "Your AI, unlocked" wizard (region -> overseas-model access copy; model/use-case -> illustrative routing preview). Default region Australia. Headline switched to "One account. Every AI model." (pitch-deck positioning, CNY demoted from the headline to a payment detail). - Value calculator: USD slider -> live usage estimates (chats / images / videos / per-model chars) reusing lib/usage-estimate (same figures as the wallet). - Hero polish: background reacts to wizard use, brand-strip hover tooltips, pointer parallax, spring microinteractions. Framer Motion, design tokens, prefers-reduced-motion safe, no chat (red line). - Fix: minimum top-up stat shows $5 (was incorrectly rendered as ¥5). - Brand favicon (favicon-32/64 + apple-touch from logo.png). - Docs: OPTIMIZATION-PRD.md; tasks/home-motion-interactive-prd.md (v0.3). Note: the animated Smart Routing demo (I1/I2 in the PRD) was intentionally dropped — origin/main already ships an equivalent SmartRouting section, which this PR keeps. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
docs: update skill marketplace PRD for R2 downloadable package model
Resolves "价格未配置 / price not configured" for models that are offered in Quick Import presets (metadata-only, no embedded price) but were missing from defaultModelRatio, and for Claude -thinking variants. GetModelRatio now falls back to the base model's ratio for any plain "-thinking" name (extended-thinking output bills at the normal output-token rate, so the input ratio is identical) — so future thinking variants can't regress to 价格未配置. Added explicit entries: claude-sonnet-4-6(+thinking), qwen-max, doubao-seed-1-6-thinking-250715, gpt-image-1.5 (ratio + completion), gemini-3.1-pro-preview, gemini-3-flash-preview, flux-1.1-pro, flux-schnell, doubao-seedream-4-0-250828, doubao-seedance-2-0-260128, kling-v2-master. Uncertain image/video/qwen prices are bootstrap defaults (≈sibling), commented "refine via models.dev sync". New regression test TestEveryCacheModelHasModelRatio guards the recurring class: a model with a cache/completion ratio but no model ratio. Claude-Session: https://claude.ai/code/session_01WsrDW2XYNDRRVDe9iFP3AY Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ail API (DR-53) (#46) * feat(skill-detail): add requires_deeprouter_key and download_cta to detail API (DR-53) GetMarketplaceSkill now returns PublicSkillDetail which extends the existing PublicSkill with two new fields: - requires_deeprouter_key: true — advertises DeepRouter runtime dependency - download_cta: { url, method } — points to the DR-81 download endpoint The list endpoint (ListMarketplaceSkills) is unchanged and does not expose these detail-only fields. No provider credentials or routing logic are exposed. Slug is percent-encoded via url.PathEscape in the CTA URL to prevent broken links from URL-unsafe characters in historical or future slug values. Note: /api/v1/marketplace/skills/:slug/download route is not registered in this PR. The CTA advertises the pointer only; the download endpoint is provided by DR-81. Frontend must not wire the download button until DR-81 lands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): eliminate SIGPIPE false-negative in kids matrix check On Linux with pipefail, `printf | grep -q` reports the pipeline as failed when grep exits early (after finding a match) and printf gets SIGPIPE. This caused the matrix check to mark a present test function as missing. Fix: write AVAILABLE to a tempfile and grep the file directly, removing the pipeline from the comparison loop entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt (DR-81) - SkillUserAuth middleware (common user role, login required) added to skill-auth.go - DownloadSkillPackage handler: published-only lookup, plan entitlement check, in-memory zip build, UES upsert (download == enable in V1), zip response - Zip contains manifest.json (schema_version, skill_id, slug, name, required_plan, category, requires_deeprouter_key) + SKILL.md (frontmatter + description + input_hints); instruction_template deferred to DR-41 (skill_versions) - downloadRoute group in skill-router.go uses SkillUserAuth + SKD rate-limit tag - 9 new tests: happy path, zip contents, 404 on not-found/non-published, 403 plan hierarchy (free/pro/enterprise), UUID lookup, no credentials in zip - Total: 38/38 pass, go vet clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ng (DR-81) Four issues found in CTO review: - BLOCKING: Content-Disposition built by string concat — slug containing `"` or CRLF could inject arbitrary response headers. Fixed: mime.FormatMediaType(). - BLOCKING: ShortDescription embedded raw in YAML frontmatter — quotes or newlines broke the SKILL.md frontmatter block. Fixed: strings.NewReplacer escaping. - SHOULD FIX: NoProviderCredentials test searched raw zip bytes (binary), which is unreliable. Fixed: open zip, check each entry's content individually; also added instruction_template to the forbidden list. - SHOULD FIX: NonPublished test did not cover deprecated status. Fixed: added deprecated to the status loop (now draft/archived/deprecated, 3 subtests). 38/38 pass, go vet clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…source (DR-81) Four gaps identified in NEW-3 acceptance review: 1. BUG: SkillUserAuth token-auth path set group from empty session, causing pro/enterprise users to be treated as free tier for plan entitlement. Fixed: when useAccessToken=true, load group via model.GetUserGroup(). 2. manifest.json missing skill_version_id. Now populated from skills.active_version_id (nil until DR-41; omitted via omitempty). Added tests: nil case + non-nil case (versioned skill). 3. EntryPoint enum missing skill_package value. Added EntryPointSkillPackage = "skill_package" + registered in validEntryPoints. analytics emit deferred — no emit infrastructure exists yet. 4. UES written with source="marketplace"; corrected to source="skill_package" to distinguish download-triggered entries from manual enables. Test updated to assert source value. Deferred (separate tickets): - instruction_template in zip (requires DR-41 skill_versions table) - skill_enabled analytics event (no emit infrastructure) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements skill_enabled analytics event required by tasks/03 §4.4 §8.2: skill_usage_events table: - New SkillUsageEvent model with int64 user_id/tenant_id (D1 deviation matching UES; no UUID mapping needed in V1). - MigrateSkillUsageEvents: SQLite CREATE TABLE IF NOT EXISTS + PG/MySQL AutoMigrate; three indexes (event_type+time, user+skill+time, entry_point+time) created via HasIndex pattern for MySQL 5.7 compatibility. - EmitSkillEnabled writes event_type=skill_enabled, entry_point=skill_package, plan from skill.required_plan, skill_version_id from active_version_id (nil until DR-41). Download handler: - Calls EmitSkillEnabled after UES write; logs on failure, does not block the download response. Tests: - testDownloadDB now migrates skill_usage_events. - TestDownloadSkillPackage_EmitsSkillEnabledEvent asserts event row written with correct entry_point, user_id, plan. Enum tests: - EntryPointSkillPackage added to TestEntryPoint_Valid and TestEntryPoint_StringValues. Deferred (hard dependency on DR-41 / NEW-1): - instruction_template in zip (requires skill_versions table) - Versioned zip pinned to active skill_version_id with version existence check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The consent checkbox sat below the (disabled-until-checked) Sign in button, so users saw a greyed-out button with no visible reason. Move Turnstile + LegalConsent above the submit button to match the sign-up form, making the gating checkbox visible before the button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M44JTP8yhj1RpeeR8X6bt2
…speech (#48) The channel test button always probed with /v1/chat/completions, so every ElevenLabs (channel type 58) test failed with the adaptor guard "elevenlabs channel only supports text-to-speech (/v1/audio/speech)" — a false negative even when the upstream key is valid. Route TTS-only channels (currently only ElevenLabs) to the audio-speech endpoint instead: - isAudioSpeechOnlyChannel() detects ChannelTypeElevenLabs - testChannel forces requestPath /v1/audio/speech + RelayFormatOpenAIAudio - buildTestRequest returns *dto.AudioRequest for those channels - convert switch handles RelayModeAudioSpeech (ConvertAudioRequest → io.Reader) - body serialization reads io.Reader bodies (audio) vs marshalling structs Adds TestBuildTestRequestForElevenLabsIsAudioSpeech. Claude-Session: https://claude.ai/code/session_01KJvoRH7Xzw1gaUJgd6Rqya Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation (DR-81) Adds MigrateSkillUsageEvents(DB) to migrateDB() in model/main.go so that the skill_usage_events table is created at startup in all environments. Without this, EmitSkillEnabled would silently log errors in production instead of recording analytics events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same fix as merged to main in PR #45, applied on this branch. Prices the models that hit "价格未配置" (Quick Import presets are metadata-only, so pricing relies on defaultModelRatio) and adds a "-thinking" suffix fallback in GetModelRatio so thinking variants resolve to their base model's ratio. Also adds TestEveryCacheModelHasModelRatio as a regression guard. Note: this branch is behind main; model_ratio.go here is the older-base variant of the same fix already live on main — reconcile on next sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WsrDW2XYNDRRVDe9iFP3AY
The consent checkbox sat below the (disabled-until-checked) Sign in button, so users saw a greyed-out button with no visible reason. Move Turnstile + LegalConsent above the submit button to match the sign-up form, making the gating checkbox visible before the button. Claude-Session: https://claude.ai/code/session_01M44JTP8yhj1RpeeR8X6bt2 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…guide WIP Home: rework hero + stats sections (motion/interactive). Keys: integration dialog, success dialog, cells/table tweaks, integration lib, persona presets, wallet recharge copy. i18n: en/zh strings for the above. Docs: BUSINESS-LOGIC, CONNECT, DeepRouter BP/brand PRD, casual-journey & key-setup-guide task PRDs. Also carries the already-merged ElevenLabs channel-test fix (matches main). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJvoRH7Xzw1gaUJgd6Rqya
…ll-blocked feat(skill): emit skill_blocked for relay block paths
…e-skill-packaging
…ll-packaging DR-79: Add publish-time Skill packages
…ment-check DR-67: Add use-time skill entitlement gate
DR-69 Provider response return and usage events
…sh-time-skill-packaging # Conflicts: # CHANGELOG.md
…me-skill-packaging Revert DR-79 publish-time Skill packages
…e-artifacts DR-79: Build package artifacts on version activation
…actions) Replace the placeholder My Skills grid with the tasks/02 §4.3 management surface: header count, All/Available/Locked/Deprecated filters, desktop table + mobile list, six row states (available/plan-locked/quota-exceeded/deprecated/ archived/kids-blocked), Remove from My Skills (DR-56 DELETE) with confirm, and Use → Skill Detail (D-09; navigation only, no skill_used). Use and the skill-name link are gated to published rows (canUse/canOpen), since Skill Detail is published-only — deprecated/archived show warning/reason + Remove only, with plain-text names, to avoid a 404 dead path. Frontend-only: no backend / availability resolver / ListMySkills changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or-ui DR-50 Admin Skill editor UI
# Conflicts: # CHANGELOG.md # web/default/src/i18n/locales/en.json # web/default/src/i18n/locales/zh.json
Add the required DR-59 task PRD (docs/tasks/dr59-my-skills-ui-prd.md) that was missing — Rule 11 (PRD-first-per-task). It records the §4.3 management-surface scope, the D-09 Use→Skill Detail interpretation, the published-only Use/name gating, and the deliberate FR-U6 lock-state-CTA deferral (pending explicit reviewer/product sign-off, else a CTA-routing follow-up ticket). Adds the corresponding CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y, ref path) - Add an explicit "Merge condition" to the FR-U6 staged deviation so it reads as a formally controlled exception, not a passing note. - Unify the empty-state copy in Acceptance Criteria to "Explore Skills (/skills)" (matches Scope + the actual button), dropping the stray "Explore Marketplace". - Use the full path for the FR-U5 PRD ref. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(DR-59): My Skills management UI (table + filters + row states + actions)
Per-key model whitelist auth used exact map lookup, so chips like `claude-*` never matched `claude-opus-4-8` (gateway returned "This token has no access to model claude-opus-4-8"). Switch the relay gate to model.MatchModelLimit (exact-first + trailing-"*" prefix, matching the convention in setting/operation_setting/tools.go). The whitelist stays a pure subset filter — channel selection still gates on group/ability, so a wildcard cannot grant access beyond the account (DR-1001 PRD §5, verified). /v1/models listing: concrete entries keep the existing contract (listed directly); wildcard entries expand best-effort against the account/group enabled models so the listing matches what actually routes. Tests: MatchModelLimit (15 cases) + controller wildcard-listing case; existing token-limit listing contract test still passes. Refs: docs/tasks/dr1001-token-model-whitelist-wildcard-prd.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 626 files, which is 476 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (29)
📒 Files selected for processing (626)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)