Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cbb054b
feat: introduce openapi-zod-client + generate src/generated/api/ (AAV…
0xPabloLI Jul 21, 2026
df07dd0
chore(openapi): sync spec from backend
0xPabloLI Jul 21, 2026
ea526bf
ci: upgrade openapi-sync to include generated schemas (AAV-1215)
0xPabloLI Jul 21, 2026
fa72ac5
docs: add schema pipeline automation spec (AAV-1209)
0xPabloLI Jul 21, 2026
a73faf3
docs: add ADR-0026 schema pipeline automation + update DOCS-INDEX (AA…
0xPabloLI Jul 21, 2026
047a319
chore(hardcode): sync hardcoded assets and mappings
0xPabloLI Jul 22, 2026
dbf6a65
refactor: migrate schemas to generated wrapper + cleanup reverse-gen …
0xPabloLI Jul 22, 2026
5ea6795
docs: update ADR-0026 to reflect Phase 2 completion (AAV-1214)
0xPabloLI Jul 22, 2026
c00c4b2
refactor: remove .passthrough() for strict schema contract (AAV-1216)
0xPabloLI Jul 23, 2026
cf305c1
docs: update ADR-0026 to reflect Phase 3 completion (AAV-1216)
0xPabloLI Jul 23, 2026
e71848b
Merge pull request #463 from 0xPabloLI/bot/openapi-sync-29838713064
0xPabloLI Jul 23, 2026
2128785
fix(deps): add @zodios/core zod peer dep override for zod@4 compatibi…
0xPabloLI Jul 23, 2026
0ab9fa2
docs: update frontend triage plan structure
0xPabloLI Jul 23, 2026
687125e
Merge branch 'lovable' into bot/hardcode-sync-lovable
0xPabloLI Jul 23, 2026
69039f4
Merge pull request #460 from 0xPabloLI/bot/hardcode-sync-lovable
0xPabloLI Jul 23, 2026
36b7105
feat(AAV-962): BORROW_BL incentive zeroing in simulation (#458)
0xPabloLI Jul 24, 2026
d1ce9c5
Merge remote-tracking branch 'origin/dev' into lovable
0xPabloLI Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,13 @@ jobs:
- name: Fetch OpenAPI spec from backend
run: npm run openapi:fetch

- name: Generate Zod schemas from spec
run: npm run schema:codegen

- name: Check for spec changes
id: diff
run: |
if git diff --quiet -- public/openapi.json; then
if git diff --quiet -- public/openapi.json src/generated/api/; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
Expand All @@ -288,14 +291,18 @@ jobs:
if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: "chore(openapi): sync spec from backend"
commit-message: "chore(openapi): sync spec + generated schemas from backend"
branch: "bot/openapi-sync-${{ github.run_id }}"
delete-branch: true
title: "chore(openapi): sync spec from backend (${{ github.ref_name }})"
title: "chore(openapi): sync spec + generated schemas from backend (${{ github.ref_name }})"
body: |
Auto-generated OpenAPI spec sync.
Auto-generated OpenAPI spec + Zod schema sync.

Trigger: `${{ github.event_name }}` on `${{ github.ref_name }}`
The `openapi-check` job detected a spec drift; this PR syncs `public/openapi.json` from the live backend.
The `openapi-check` job detected a spec drift; this PR syncs:
- `public/openapi.json` from the live backend
- `src/generated/api/schemas.ts` via `npm run schema:codegen`

⚠️ Generated code may break compilation — review before merge.
labels: openapi-sync
base: ${{ github.ref_name }}
8 changes: 7 additions & 1 deletion docs/DOCS-INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,17 @@ _Last inventory pass: 2026-07-06._

- Canonical: `docs/specs/reserve-table-market-hub-filtering.md`
- Canonical: `docs/specs/v4-reserveId-uniqueness.md`
- Canonical: `docs/specs/schema-pipeline-automation.md` (后端驱动全链路 schema 自动生成;Phase 1 ✅, Phase 2–3 pending AAV-1214/1216; ADR-0026)
- Implemented: 移动端 Simulation 表格 Grid 布局改造(✅ 已实施 2026-05-10);核心结论已合入 `frontend-interaction-guardrails.md` § Simulation breakdown table — Grid layout (mobile)

### ADRs (Architecture Decision Records)

- Canonical: `docs/adr/` (001–026)
- Latest: ADR-0026 (Schema Pipeline Automation — backend-driven codegen, Phase 1 complete)

### Plans and PRDs

- Active plans: `docs/plans/frontend-triage-2026-06/` (7 phases, indexed by `00-overview.md`)
- Active plans: `docs/plans/frontend-triage-2026-06/` (14 phases, indexed by `00-overview.md`; 10 issues confirmed done via code comparison and removed 2026-07-21)
- Completed plans: `docs/plans/completed/` (8 handoff documents for done issues)
- Active PRDs: `docs/prd/PRD-simulation-sources-dispatch.md`, `docs/prd/input-surface-normalization.md`
- Completed PRDs: `docs/prd/completed/` (7 PRDs for done issues: AAV-761, AAV-978, AAV-979, AAV-833, AAV-952, brevis-sum-unification, incentive-note-copy)
Expand Down
74 changes: 74 additions & 0 deletions docs/adr/0026-schema-pipeline-automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# ADR 0026: Schema Pipeline Automation — Backend-Driven Full-Chain Codegen

## Status

Accepted (All 3 phases complete: Phase 1 AAV-1210~1213, Phase 2 AAV-1214, Phase 3 AAV-1216)

## Context

The frontend Zod schemas and TypeScript types in `src/lib/apiSchemas.ts` and `src/types/aave.ts` were entirely hand-written. The backend OpenAPI spec (`public/openapi.json`) was fetched from the backend but **not consumed** by frontend code — it existed only for Swagger UI and architecture-guard tests. This created a **bidirectional manual sync problem**:

1. **Backend**: `generate-openapi.ts` was hand-written with explicit property constants (`RESERVE_PROPERTIES`, `MERIT_CAMPAIGN_BREAKDOWN_PROPERTIES`, etc.). When a developer added a field to a TypeScript interface but forgot to update the spec, the spec became stale. Example: `borrowBlacklist` was defined in the backend `CampaignGroup` interface and serialized by the fetcher, but missing from the OpenAPI spec.

2. **Frontend**: Even when `public/openapi.json` was correct, frontend schemas remained hand-written. New backend fields required manual addition to `apiSchemas.ts` and `aave.ts`. The `field-canary.test.ts` caught renames, but missing fields silently passed.

3. **CI**: The `openapi-sync` workflow only synced `openapi.json` — it did not regenerate frontend code, so even a perfect spec didn't prevent frontend schema drift.

## Decision

Establish a **backend-driven full-chain automation pipeline**:

```
Backend TS interface
→ ts-json-schema-generator (backend gen:openapi)
→ static/openapi.json
→ frontend openapi:fetch (pulls to public/openapi.json)
→ openapi-zod-client (frontend schema:codegen)
→ src/generated/api/schemas.ts + types.ts
```

Zero manual sync at any stage.

### Backend (`aave-protocol-analysis` repo)

1. **Upgrade `generate-openapi.ts`**: Replace hand-written property constants with `ts-json-schema-generator` that reads API-layer TypeScript types (`MarketWithSpread`, `MarketsResponse`, `SideDataPayload`, etc.) and emits JSON Schema definitions. Response metadata (429/503 templates) remains hand-written — git history shows zero changes in 12 commits, validating the "schema auto-gen + metadata template" split.

2. **Shared contracts extraction**: Move `SideDataPayload` from inline `metaController.ts` to `@internal/aave-shared-contracts` so the generator can reference it. Remove `partial: boolean`; replace with `errors?: SideDataSubSourceErrors` (precise per-subsource error type).

3. **CI gate**: New `openapi-consistency` job — runs `gen:openapi` then `git diff --exit-code static/openapi.json`. Fails if spec is stale.

### Frontend (`aaveapy` repo)

4. **Introduce `openapi-zod-client`**: New `schema:codegen` script reads `public/openapi.json` and generates Zod schemas + TypeScript types to `src/generated/api/`. Generated code is committed to git (PR diff visibility + buildable without codegen tooling).

5. **CI upgrade**: `openapi-sync` workflow now runs `schema:codegen` after fetching spec, creating a PR that includes **both** `public/openapi.json` and `src/generated/api/`. Not auto-merged (generated changes may break compilation — requires human review).

6. **Migration strategy (3 phases)**:
- **Phase 1** (✅ complete — AAV-1213): `src/generated/api/` exists alongside hand-written schemas. No business code imports generated code yet. `.passthrough()` tolerance will be applied in Phase 2 wrapper.
- **Phase 2** (✅ complete — AAV-1214): `schemas.ts` / `apiSchemas.ts` become wrappers that re-export from generated + apply `.passthrough()`. Business code import paths unchanged. Deleted `scripts/generate-openapi.ts` (frontend reverse-generation, wrong direction) and related tests. `architecture-guard.test.ts` reads `public/openapi.json` instead of calling `generateOpenApiDocument()`. Added `schema-equivalence.test.ts` for transition safety.
- **Phase 3** (✅ complete — AAV-1216): Removed all `.passthrough()` from wrapper schemas — unknown keys are now stripped (Zod default strip mode). Spec is a strict contract. Deleted transition-period `schema-equivalence.test.ts`. Fixed broken `schema:codegen` script (was referencing non-existent config file, now uses direct CLI flags). Regenerated `schemas.ts` with `.partial()` on `SideDataSubSourceErrors` (spec sync). `aave.ts` types remain hand-written (all are frontend-derived: inheritance chains, generics, `BannedReserveUsdFields` guard — no pure API types to migrate).

### Key implementation details

- `ts-json-schema-generator` outputs `$ref: "#/definitions/Foo"` — must `rewriteRefs` to `"#/components/schemas/Foo"` for OpenAPI 3.1 compliance.
- Generic type names (`CampaignGroup<ApiMeritCampaignBreakdown>`) break `openapi-zod-client` — must `renameGenericTypes` to simple names (`ApiMeritCampaignGroup`) before emission.
- `ajv` must be installed as peer dependency for `openapi-zod-client`.

## Consequences

- **Positive**: Adding a field to a backend interface automatically propagates to OpenAPI spec → frontend Zod schema → frontend TypeScript type. No manual sync at any stage.
- **Positive**: `openapi-consistency` CI gate catches stale backend specs before merge.
- **Positive**: `schema:check` CI gate catches stale frontend generated code.
- **Negative**: `openapi-zod-client` has a small community — mitigated by pinning version + committing generated artifacts + CI diff check.
- **Negative**: Two-phase migration (`.passthrough()` → strict) adds temporary complexity in wrapper layer — justified by risk of spec incompleteness during transition.
- **Risk**: Generated Zod schemas may not match hand-written schemas exactly (e.g., optional vs required, enum vs string) — Phase 2 includes equivalence tests to catch mismatches before removing hand-written code.

## Related

- Spec: `docs/specs/schema-pipeline-automation.md`
- Parent issue: AAV-1209
- Completed tickets: AAV-1210, AAV-1211, AAV-1212, AAV-1213, AAV-1214, AAV-1215, AAV-1216

## Date

2026-07-21
111 changes: 93 additions & 18 deletions docs/plans/frontend-triage-2026-06/00-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,105 @@

> `docs/plans/frontend-triage-2026-06/`

## Projects & Phases

| Phase | File | Issue(s) | Project | Scope | Status |
|-------|------|----------|---------|-------|--------|
| 1 | `phase1-borrow-bl.md` | AAV-962 | Incentive Source Upper-Layer Unification | `CampaignGroup.borrowBlacklist` + `merklGroupMultiplier` 归零 + current 也乘 groupMul + 测试 | Ready for agent |
| 2 | `phase2-borrow-blacklist-tooltip.md` | AAV-1013 (剩余) | Incentive Source Upper-Layer Unification | IncentiveTooltip 传 `userHasBorrow` + BORROW_BL 归零文案 + `CampaignAccessEntry.borrowHookProtocols` | Ready for agent (blocked by Phase 1) |
| 3 | `phase3-hub-borrowed.md` | AAV-1017 | 独立 PRD | 确认已有实现 + dev server 验证 EURC utilization ~68% | Done |
| 4 | `phase4-url-market.md` | AAV-755 | Frontend Bug | URL 只指向 chain → 改为指向 market | Ready for agent |
| 5 | `phase5-plasma-console-error.md` | AAV-802 | Frontend Bug | Console 报 plasma chain 请求错误 | Ready for agent |
| 6 | `phase6-recently-ended-campaign.md` | AAV-951 | Incentive Source Upper-Layer Unification | recently ended campaign 没起作用 | Backlog |
| 7 | `phase7-offset-reserve-table.md` | AAV-1023 + AAV-1024 | 独立 | Reserve table offset 规则改造 + Shared scenario 同步(待 AAV-1022 定) | Backlog |
## lovable 分支恢复记录(2026-07-21)

`lovable` 分支于 2026-07-18 后被删除(无分支保护,最可能由 Lovable 平台 bot 在 sync 操作中删除)。已从本地 `refs/remotes/origin/lovable` 恢复到远程。`lovable` 分支缺少 `allow_deletions: false` 保护规则(仅 `dev` 和 `main` 有),建议后续补充。

---

## 代码对比结果(2026-07-21 更新)

以下 issue 经代码对比确认已完成,已从 plan 中剔除并同步 Linear 状态为 Done:

| Issue | 原属 Phase | 代码证据 |
|-------|-----------|----------|
| AAV-951 | 原 Phase 6 | IncentiveTooltip `RecentlyEndedSection` 已实现;Linear 已是 Done |
| AAV-1133 | 原 Phase 13 | commit `bb72e5b2`;SimulationLane 已移除 headlineIncentive |
| AAV-1105 | 原 Phase 13 | `buildMerklCampaignDetails/Merit/Brevis` 全部改为 options object |
| AAV-1190 | 原 Phase 9 | `addReserve` 签名有 `hubName?/hubId?`;`walletPositionToPortfolio` 已传递 |
| AAV-1191 | 原 Phase 9 | `ReserveIdentity.tsx` 已提取,有 compact/stacked 两种 variant + 测试 |
| AAV-1142 | 原 Phase 9 | Fix 22 Playwright failures — Done ✅ |
| AAV-1145 | 原 Phase 9 | `playwright.config.ts` 已用 `dev:staging` ✅ |
| AAV-1152 | 原 Phase 9 | `PortfolioUnifiedTable.tsx` 12 列全部有 `data-cell` 属性 ✅ |
| AAV-1153 | 原 Phase 9 | `MobilePortfolioCard.tsx` 有 `data-testid="delta-current/after/value"` ✅ |
| AAV-1199 | — | `loadRegistryChainIds` regex 修复 — Done ✅ |

部分完成的 issue(保留在 plan 中,标注进度):

| Issue | 当前 Phase | 已完成 | 未完成 |
|-------|-----------|--------|--------|
| AAV-1096 | Phase 7 | IncentiveTooltip 主体 grid→flex + 测试 | RecentlyEnded section 仍有 `grid-cols-[1fr_5rem]`(第 339/381 行) |
| AAV-1192 | Phase 3 | PortfolioUnifiedTable + PopularTokenChip 已用 ReserveIdentity | MobilePortfolioCard 内联 hubName 未用 ReserveIdentity(可能不需要改) |
| AAV-734 | Phase 8 | PortfolioPanel 已用 `destructive` token | PortfolioTokenRow 已被 UnifiedTable 替代;全项目审查未完成 |

---

## Phase 列表(2026-07-21 全面审查后)

| Phase | File | Issue(s) | Scope | Status | Branch |
|-------|------|----------|-------|--------|--------|
| 1 | `phase1-borrow-bl.md` | AAV-962 | `CampaignGroup.borrowBlacklist` + `merklGroupMultiplier` 归零 + current 也乘 groupMul + 测试 | PR #458 In Review | `feat/aav-962-borrow-bl-incentive` |
| 2 | `phase2-borrow-blacklist-tooltip.md` | AAV-1013 (剩余) | IncentiveTooltip 传 `userHasBorrow` + BORROW_BL 归零文案 + `CampaignAccessEntry.borrowHookProtocols` | Backlog (blocked by 1) | `feat/aav-1013-borrow-bl-tooltip` |
| 3 | `phase3-reserve-identity.md` | AAV-1192, AAV-1193, AAV-1194 | ReserveIdentity 补全:MobilePortfolioCard 评估 + 测试完善 + 文档更新 | Backlog (T1/T2 已 Done) | `refactor/aav-1192-reserve-identity` |
| 4 | `phase4-portfolio-ltv.md` | AAV-756 | Portfolio LTV constraint + Net Effective APY (with LTV) + Health Factor | Todo (阻塞于后端 API) | `feat/aav-756-portfolio-ltv` |
| 5 | `phase5-url-market.md` | AAV-755 | URL 只指向 chain → 改为指向 market | Ready for agent | `fix/aav-755-url-market` |
| 6 | `phase6-plasma-console-error.md` | AAV-802 | Console 报 plasma chain 请求错误 | Ready for agent | `fix/aav-802-plasma-console` |
| 7 | `phase7-incentive-tooltip-layout.md` | AAV-1096 | IncentiveTooltip RecentlyEnded section grid→flex 统一 | Backlog (部分完成, 测试与代码不一致) | `ui/aav-1096-tooltip-layout` |
| 8 | `phase8-frontend-infra.md` | AAV-1104, AAV-734, AAV-1095, AAV-783, AAV-1141 | 前端基础设施:URL query 优化 + destructive hover 统一 + Zod schema 统一 + memory leak 验证 + 性能 | Backlog (部分完成) | `refactor/aav-1141-frontend-infra` |
| 9 | `phase9-e2e-test-hardening.md` | AAV-1144→1145~1149; AAV-1151→1152~1158; AAV-1150 | E2E 测试加固:staging API ✅ + testid ✅ + skip→describe 迁移 (大量未完成) | Backlog (核心已完成, skip 迁移待做) | `test/aav-1144-e2e-hardening` |
| 10 | `phase10-reserve-table-expand-ui.md` | AAV-1107, AAV-1084, AAV-1121, AAV-1114, AAV-1113, AAV-738 | Reserve table 展开部分 UI 修复 + 优化 | Backlog | `fix/aav-1107-reserve-expand-ui` |
| 11 | `phase11-portfolio-simulation-ui.md` | AAV-1136, AAV-1135, AAV-1123, AAV-1122, AAV-1110, AAV-1102, AAV-1162, AAV-1160, AAV-1159, AAV-733 | Portfolio simulation UI 全面优化 | Backlog | `ui/aav-1136-portfolio-sim-ui` |
| 12 | `phase12-offset-reserve-table.md` | AAV-1023 + AAV-1024 | Reserve table offset 规则改造 + Shared scenario 同步 | Backlog (blocked by AAV-1022) | `refactor/aav-1023-offset-reserve` |
| 13 | `phase13-brevis-user-dashboard.md` | AAV-843 | Brevis per-user API 接入:Dashboard + Claim | Ready for agent | `feat/aav-843-brevis-dashboard` |
| 14 | `phase14-frontend-roadmap.md` | AAV-364, AAV-564, AAV-333+482, AAV-1071, AAV-248, AAV-512 | 前端功能扩展(长期 roadmap) | Backlog | 按子项创建 |

### Needs Info(暂不分配 phase)

| Issue | 说明 |
|-------|------|
| AAV-751 | 钱包下拉菜单字体不一致 |
| AAV-891 | 监控 Aave 官方前端 repo 变更 |

---

## 依赖关系

大部分 phase 之间**无强依赖**,按编号顺序从前往后做即可。仅有的依赖:

```
Phase 1 ──→ Phase 2 (tooltip 文案依赖归零逻辑)
Phase 3 (独立,验证型) ✅ Done
Phase 4, 5 (互相独立)
Phase 6 (独立)
Phase 7 (依赖 AAV-1022 规则确定)
Phase 5 ←──→ Phase 8 / AAV-1104 (URL 路由重构有重叠,建议合并)
Phase 12 (阻塞于 AAV-1022 — 外部 issue,不在本 plan 内)
Phase 4 (阻塞于后端 API — ltv/liquidationThreshold 字段)
Phase 13 (阻塞于 AAV-842 — Brevis 后端 distributedSoFarUsd)
```

## 建议执行顺序
其余各 phase 互相独立。

## Branch & Worktree 规范

1 → 4 → 5 → 2 → 6 → 7
每个 phase 使用独立 worktree + feature branch。

### Branch 命名

```
{type}/aav-{id}-{kebab-slug}
```

| type | 用途 |
|------|------|
| `feat` | 新功能 |
| `fix` | bug 修复 |
| `refactor` | 重构 |
| `test` | 测试 |
| `perf` | 性能 |
| `ui` | UI 优化 |

### Worktree 创建模板

```bash
git worktree add /tmp/aaveapy-{type}-aav-{id} \
-b {type}/aav-{id}-{slug} lovable
```

(Phase 3 已完成)
> **命名来源说明**:branch 命名 `{type}/aav-{id}-{slug}` 是参考 conventional commits 风格设计的,不是项目已有的既定规范,可以根据你的偏好调整。
45 changes: 45 additions & 0 deletions docs/plans/frontend-triage-2026-06/phase1-borrow-bl.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,51 @@
> Issue: AAV-962
> Project: Incentive Source Upper-Layer Unification
> 估计: 1 session
> **Status: In Review** — commit `041909b9`, branch `feat/aav-962-borrow-bl-incentive`, PR #458

## 标准工作流合规性审查(2026-07-21)

> ⚠️ Phase 1 实施未走完标准工作流,以下是逐项对照:

| 步骤 | 状态 | 说明 |
|------|------|------|
| 1. Grill with Docs | ❌ 未执行 | 无 grill-with-docs skill 记录;设计决策直接在 plan 中确认,未经 ADR/glossary 审查 |
| 2. To Spec | ❌ 未执行 | 无 spec 文档(`docs/specs/` 下无 borrow-bl 相关文件) |
| 3. To Tickets | ❌ 未执行 | 未拆分为 tracer-bullet tickets,直接整块实现 |
| 4. TDD Implement | ⚠️ 部分 | 17 个测试与实现在同一 commit 中,无法确认 red→green→refactor 顺序 |
| 5. Code Review | ❌ 未执行 | 无 code-review skill 记录(Standards + Spec 双轴审查) |
| 6. Dev Server + Playwright | ❌ 未执行 | 纯 calculator 逻辑无 UI 交互,但未做浏览器验证 |
| 7. Commit | ✅ 已执行 | 单 commit `041909b9`,7 文件 331 行 |
| 8. 更新文档及 Issue | ❌ 未执行 | Linear AAV-962 未更新状态;Phase 1 plan 已有实施结论但未补充工作流审查 |

### 补救措施

- [ ] 补走 Code Review(Standards + Spec 双轴审查)
- [ ] 补更新 Linear AAV-962 状态
- [ ] 后续 Phase 必须严格走完标准工作流

---

## 实施结论(2026-07-21)

### 设计决策确认

1. **`current` 也乘 `groupMul`** — plan 描述已过时。代码中 `current` 已通过 `walletMerklGroupMultiplier` 乘 groupMul(AAV-1101)。真正的问题是 unified eligibility 路径(`crossReserveNetEligibleUsdFn`)忽略 `merklGroupMultiplier`,需要单独加 BORROW_BL 归零。
2. **`walletBorrowUsd` 参与归零判断** — 确认。`current` 用 `walletBorrowGrossForEligibility`,`after` 用 `borrowGrossForEligibility`。Golden Rule #1 保持。
3. **`borrowBlacklist?: true`** — 确认。与 `isActive?: false` 模式一致。

### 改动清单(实际执行)

1. `src/types/aave.ts` — `CampaignGroup` 新增 `borrowBlacklist?: true` ✅
2. `src/shared/market-contract/schemas.ts` — `MerklOpportunityGroupSchema` 新增 `borrowBlacklist: z.literal(true).optional()` ✅
3. `public/openapi.json` — 新增 `borrowBlacklist` 字段 ✅
4. `src/types/field-canary.test.ts` — 4 个 canary 测试 ✅
5. `src/lib/rateSimulationCalculator.ts` — 4 个函数新增 BORROW_BL 归零分支 ✅
- `merklGroupMultiplier` (after, fallback path)
- `walletMerklGroupMultiplier` (current, fallback path + per-campaign)
- `crossReserveNetEligibleUsdFn` (after, unified eligibility path)
- `walletCrossReserveNetEligibleUsdFn` (current, unified eligibility path)
6. 测试:5 aggregation + 8 simulation + 4 field-canary = 17 个新测试 ✅

## 问题

Expand Down
Loading