---
phase: 01-credential-slot-api
plan: 02
type: execute
wave: 2
depends_on:
  - 01-01
files_modified:
  - docs/修改记录.md
autonomous: true
requirements:
  - CRED-FE-01

must_haves:
  truths:
    - "docs/修改记录.md 顶部（『修改历史』段第一条）追加 [2026-05-08] Phase 1 条目"
    - "条目包含『配套服务端 Phase』『覆盖前端需求』前置元数据，引用 commit 46d72b8"
    - "条目跨项目联动字段写明：本 phase 不引入新跨项目契约，无需再次互引（后端 Phase 2 已建立互引）"
    - "npm run lint 退出码为 0（ESLint 检查 lib/api/credential-slot.ts 与 lib/api/index.ts）"
    - "npx tsc --noEmit 退出码为 0（项目级类型检查全部通过，含 plan 01 新增的 CredentialSlot / CredentialSlotUpdatePayload）"
    - "外部组件文件能成功 import { getCredentialSlot, type CredentialSlot } from '@/lib/api'（通过临时 .tsx 探针验证）"
  artifacts:
    - path: "docs/修改记录.md"
      provides: "本仓库变更日志，顶部新增 Phase 1 条目"
      contains:
        - "[2026-05-08] Phase 1"
        - "CRED-FE-01"
        - "lib/api/credential-slot.ts"
        - "lib/api/index.ts"
        - "46d72b8"
        - "accessTokenMasked"
        - "accessToken"
  key_links:
    - from: "docs/修改记录.md 顶部新条目"
      to: "qy_lty 后端 Phase 2 commit 46d72b8 已建立的前后端互引"
      via: "条目『服务端联动』字段中文引用"
      pattern: "46d72b8"
    - from: "外部消费者（Phase 2/3 组件文件）"
      to: "lib/api 入口 barrel"
      via: "import { getCredentialSlot, type CredentialSlot } from '@/lib/api'"
      pattern: "from\\s+['\"]@/lib/api['\"]"
---

<objective>
为 plan 01 落地的代码补齐两件事：
1. 在 `docs/修改记录.md` 顶部按项目「修改格式说明」追加一条 Phase 1 条目（CLAUDE.md L70-95 强制规则）。
2. 跑两条**独立**验证命令，确认 plan 01 的代码质量：
   - `npm run lint` —— ESLint 检查（`next lint`）
   - `npx tsc --noEmit` —— TypeScript 类型检查（**不**能省，per RESEARCH 问题 7：`npm run lint` 不跑 tsc）

并通过一个临时探针 `.tsx` 文件验证外部消费者可以从 `@/lib/api` 入口解析新增的 4 个符号；探针验证完成后删除。

Purpose：把 Phase 1 的「成功 = 代码 + 文档 + 类型可被消费」三件套全部落地，为 Phase 2/3 提供干净起点。

Output：`docs/修改记录.md`（修改）；plan 落地后无新代码文件残留（探针文件验证后删除）。
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/01-credential-slot-api/01-CONTEXT.md
@.planning/phases/01-credential-slot-api/01-RESEARCH.md
@.planning/phases/01-credential-slot-api/01-01-SUMMARY.md

@CLAUDE.md
@docs/修改记录.md
@package.json
@next.config.mjs
@lib/api/credential-slot.ts
@lib/api/index.ts
</context>

<tasks>

<task type="auto" tdd="false">
  <name>Task 1：在 docs/修改记录.md 顶部追加 Phase 1 条目</name>

  <read_first>
    - `docs/修改记录.md` L1-50（特别是 L9-20 头部「修改格式说明」 + L24-47 已存在的 [2026-05-07] Phase 2 条目作为格式模板）
    - `CLAUDE.md` L70-95「项目修改记录规则」 — 强制每次代码改动追加到顶部、跨项目独立维护
    - `.planning/phases/01-credential-slot-api/01-CONTEXT.md` <decisions> 节「修改记录」段（L152-156）— 锁定的跨项目联动文案
    - `.planning/phases/01-credential-slot-api/01-RESEARCH.md` 「Code Examples」节内的「`docs/修改记录.md` 顶部追加条目」完整模板
  </read_first>

  <files>docs/修改记录.md</files>

  <action>
  在 `docs/修改记录.md` 中找到这一行：

  ```
  <!-- 新的修改记录添加在此处下方，最新的在最前面 -->
  ```

  在该注释行之后、紧贴现有 `### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约（消费方文档化）` 条目之前，**插入**以下完整 Markdown 块（之间留 1 个空行）：

  ```markdown

  ### [2026-05-08] Phase 1（前端）凭据槽位 API 客户端

  配套服务端 Phase：[../qy_lty/.planning/phases/02-admin-rest/](../../qy_lty/.planning/phases/02-admin-rest/)（已落地，commit 46d72b8）
  覆盖前端需求：CRED-FE-01

  - **文件路径**：
    - `lib/api/credential-slot.ts`（新增）
    - `lib/api/index.ts`（修改）
  - **修改类型**: 新增（API 客户端层；纯逻辑，无 UI 改动）
  - **修改内容**:
    - 新建 `lib/api/credential-slot.ts`，封装：
      - 类型 `CredentialSlot { appId, accessTokenMasked, updatedAt }`（脱敏掩码语义命名）
      - 类型 `CredentialSlotUpdatePayload { appId, accessToken }`（明文语义命名）
      - 适配器 `mapBackendCredentialSlot()`（snake_case → camelCase）
      - API 函数 `getCredentialSlot()` / `updateCredentialSlot(payload)`，分别走 `apiClient.get` / `apiClient.put` 命中 `/v1/admin/credential-slot/`，沿用仓库统一的 `response.data?.data || response.data` 双保险解包；PUT body 仅传 `{ app_id, access_token }`，不携 `updated_at`（与 `updateAiModel` / `updateOutfit` 风格一致）
    - `lib/api/index.ts` 末尾追加具名 re-export，让组件层可 `import { getCredentialSlot, type CredentialSlot } from '@/lib/api'`
  - **修改原因**:
    - 启动 Milestone v1.0「通用凭据槽位前端集成」，本 phase 为后续 Phase 2（RBAC + 入口控件）、Phase 3（编辑对话框 + Sonner 反馈）提供调用层基础
    - `accessTokenMasked` vs `accessToken` 故意命名不同，让 TS 编译期捕捉「把脱敏字符串当真值回写 PUT」的 bug
  - **服务端联动**: 无 — Phase 1 是纯 API client 层落地（无 UI 改动），调用的后端接口由 qy_lty 后端 Milestone v1.0 Phase 2 提供（commit `46d72b8` 已建立前后端互引修改记录）；本 phase 不引入新跨项目代码契约，无需再次互引。前端 UI 集成（Phase 2 + 3）引入实质用户能力时再评估是否需要新一轮互引

  ```

  **关键约束**：
  1. 插入位置**必须**在 `<!-- 新的修改记录添加在此处下方，最新的在最前面 -->` 注释**之后**、`### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约（消费方文档化）` 标题**之前**（项目约定：最新在前）
  2. 顶部 `### [2026-05-08]` 必须用今天日期 `2026-05-08`（与 RESEARCH 的 「Researched: 2026-05-08」一致；不是 2026-05-07）
  3. 必须出现关键字符串：`CRED-FE-01`、`46d72b8`、`accessTokenMasked`、`accessToken`、`lib/api/credential-slot.ts`、`lib/api/index.ts`、`/v1/admin/credential-slot/`
  4. 「服务端联动」字段必须明确写出「无 — ... commit 46d72b8 已建立 ... 本 phase 不引入新跨项目代码契约，无需再次互引」（per CONTEXT.md 锁定文案）
  5. **不要**修改 `docs/修改记录.md` 中任何已有条目；只做顶部插入
  6. **不要**在 qy_lty 项目侧建立新条目（CONTEXT.md 锁定：本 phase 不需要新建跨项目互引）
  </action>

  <acceptance_criteria>
  - `grep -nF "[2026-05-08] Phase 1（前端）凭据槽位 API 客户端" docs/修改记录.md` 命中 1 次
  - 该新条目的行号 < 现有 `[2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约` 标题的行号（最新在前顺序正确）
  - `grep -F "CRED-FE-01" docs/修改记录.md` 命中 ≥1 次
  - `grep -F "46d72b8" docs/修改记录.md` 命中 ≥1 次（plan 01 之前已存在 1 次，本 plan 后应为 ≥2 次）
  - `grep -F "accessTokenMasked" docs/修改记录.md` 命中 ≥1 次
  - `grep -F "lib/api/credential-slot.ts" docs/修改记录.md` 命中 ≥1 次
  - `grep -F "/v1/admin/credential-slot/" docs/修改记录.md` 命中 ≥1 次
  - `grep -F "无需再次互引" docs/修改记录.md` 命中 ≥1 次
  - 现有 [2026-05-07] Phase 2 条目内容**完全不变**（行级 diff 仅为顶部插入，不修改既有行）
  </acceptance_criteria>

  <verify>
    <automated>node -e "const c = require('fs').readFileSync('docs/修改记录.md','utf8'); const newIdx = c.indexOf('[2026-05-08] Phase 1（前端）凭据槽位 API 客户端'); const oldIdx = c.indexOf('[2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约'); if (newIdx < 0) { console.error('FAIL: 新条目缺失'); process.exit(1); } if (oldIdx < 0) { console.error('FAIL: 旧条目消失'); process.exit(1); } if (newIdx >= oldIdx) { console.error('FAIL: 新条目未置顶'); process.exit(1); } const must = ['CRED-FE-01', '46d72b8', 'accessTokenMasked', 'accessToken', 'lib/api/credential-slot.ts', 'lib/api/index.ts', '/v1/admin/credential-slot/', '无需再次互引', '配套服务端 Phase', '覆盖前端需求']; for (const s of must) { if (!c.includes(s)) { console.error('FAIL: 缺失关键字 ' + s); process.exit(1); } } console.log('OK'); "</automated>
  </verify>

  <done>
  - `docs/修改记录.md` 顶部「修改历史」段第一条为 `[2026-05-08] Phase 1（前端）凭据槽位 API 客户端`
  - 现有 [2026-05-07] Phase 2 条目位置后移、内容不变
  - `verify.automated` 命令退出码为 0 且打印 `OK`
  - 所有关键字段（CRED-FE-01 / 46d72b8 / accessTokenMasked / 文件路径 / 服务端联动文案）齐全
  </done>
</task>

<task type="auto" tdd="false">
  <name>Task 2：跑双重验证（npm run lint + npx tsc --noEmit）+ 临时探针验证 barrel 入口可解析</name>

  <read_first>
    - `package.json` L9 — 确认 `lint` 脚本是 `next lint`（per RESEARCH 问题 7：只跑 ESLint，不跑 tsc）
    - `next.config.mjs`（L17 / L20）— 确认 `eslint.ignoreDuringBuilds` 与 `typescript.ignoreBuildErrors` 仅影响 `next build`，不影响显式 `next lint` 与 `npx tsc --noEmit`
    - `tsconfig.json` — 确认 strict 模式开启
    - `lib/api/credential-slot.ts`（plan 01 落地）+ `lib/api/index.ts`（plan 01 落地）— 类型与导出已就位
    - `.planning/phases/01-credential-slot-api/01-RESEARCH.md` 「Pitfall 5: 包管理器混用导致 lockfile 漂移」节 — 验证步骤**只读不写**，不要跑 `npm install`
  </read_first>

  <files>（不创建持久化新文件；仅临时探针 `lib/api/__phase1_probe__.ts`，验证后删除）</files>

  <action>
  按以下顺序**严格**执行，每步退出码必须为 0；任一步失败必须先排错再继续：

  **步骤 1：创建临时类型探针文件 `lib/api/__phase1_probe__.ts`**（用 `.ts` 而非 `.tsx`，因不引入 React；`__` 前后缀降低被 IDE/lint 误识别为业务文件的风险）

  写入以下完整内容：

  ```typescript
  // 临时探针 — Phase 1 plan 02 验证 barrel 入口可正确解析新增符号；验证后立即删除
  import {
    getCredentialSlot,
    updateCredentialSlot,
    type CredentialSlot,
    type CredentialSlotUpdatePayload,
  } from "@/lib/api"

  async function __probe(): Promise<void> {
    const slot: CredentialSlot = await getCredentialSlot()
    const payload: CredentialSlotUpdatePayload = {
      appId: slot.appId,
      accessToken: "plaintext-not-masked",  // 故意写明文，type 系统应允许
    }
    const next: CredentialSlot = await updateCredentialSlot(payload)
    void next.accessTokenMasked  // 触达字段证明类型形状
  }
  void __probe
  ```

  **步骤 2：跑 `npx tsc --noEmit`**

  ```bash
  npx tsc --noEmit
  ```

  退出码必须为 0。如有错误：
  - 若错误指向 `__phase1_probe__.ts` 的 `import "@/lib/api"` —— 说明 plan 01 的 `index.ts` re-export 有问题，回 plan 01 排错
  - 若错误指向 `lib/api/credential-slot.ts` —— 说明 plan 01 的类型定义有问题，回 plan 01 排错
  - 若错误指向**其他文件**（项目中的存量类型问题，与本 phase 无关）—— 记录错误清单到 SUMMARY，但本 task 仍判定**通过**，因为本 phase 的责任范围是新增文件不引入类型回归

  **关键判定规则**：把 `npx tsc --noEmit` 输出存到临时文件，过滤出**仅与 `lib/api/credential-slot.ts` 或 `__phase1_probe__.ts` 或 `lib/api/index.ts` 相关**的错误行；这三处错误数必须为 0。其他文件的存量错误不算 phase 1 失败。

  **步骤 3：跑 `npm run lint`**

  ```bash
  npm run lint
  ```

  退出码必须为 0。如有警告/错误：
  - 若错误/警告指向新增文件 `lib/api/credential-slot.ts` 或 `__phase1_probe__.ts` —— 必须修复
  - 若错误/警告指向 `lib/api/index.ts` 但**仅限**新增的具名 re-export 块 —— 必须修复
  - 若错误/警告指向**其他存量文件** —— 记录到 SUMMARY，本 task 仍判定通过

  注意 `next lint` 默认对项目所有 `.ts` / `.tsx` 跑；如果 ESLint 配置严格，`__phase1_probe__.ts` 中的 `void __probe` 与未使用变量可能触发 `no-unused-vars`。如真触发，添加文件级 `// eslint-disable-next-line @typescript-eslint/no-unused-vars` 注释或将 `__probe` / `__probe2` 等命名调整避免触发，但**优先**调整代码而非禁用规则。

  **步骤 4：删除临时探针文件**

  ```bash
  rm lib/api/__phase1_probe__.ts
  ```

  （Windows PowerShell 等价：`Remove-Item lib/api/__phase1_probe__.ts`）

  **步骤 5：再跑一次 `npx tsc --noEmit` 与 `npm run lint`**，确认删除探针后两条命令仍全部退出码 0（防止漏删导致后续 phase 把临时文件当真）

  **关键约束（per RESEARCH Pitfall 5）**：
  1. **不**要跑 `npm install` / `pnpm install` / `yarn install`（lockfile 漂移风险，本 phase 不引入新依赖）
  2. **不**要修改 `package.json` / `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`
  3. **不**要修改 `next.config.mjs` / `tsconfig.json` / `.eslintrc*`（CONTEXT.md / RESEARCH.md 均隐含锁定）
  4. 探针文件**必须**删除，不允许残留任何临时文件到 phase 末态
  5. 两条验证命令的退出码与「与新增文件相关的错误数」都必须为 0；不可用 `--force` / `--no-warnings` 等屏蔽手段
  </action>

  <acceptance_criteria>
  - 步骤 2：`npx tsc --noEmit` 在创建探针后退出码 0；如有非 0，输出中**没有**任何包含 `lib/api/credential-slot.ts` / `lib/api/__phase1_probe__.ts` / `lib/api/index.ts` 路径的错误行（执行者负责把过滤判定写入 SUMMARY 证据段）
  - 步骤 3：`npm run lint` 退出码 0；同上规则，新增/修改的文件零错误零警告
  - 步骤 4：`lib/api/__phase1_probe__.ts` 不存在（grep / Test-Path 验证）
  - 步骤 5：删除探针后 `npx tsc --noEmit` 与 `npm run lint` 仍退出码 0
  - `git status --short`（如可用）显示只有 `lib/api/credential-slot.ts`（新增）+ `lib/api/index.ts`（修改）+ `docs/修改记录.md`（修改）三个改动；不允许有 `__phase1_probe__.ts` / `pnpm-lock.yaml` / `yarn.lock` / `package-lock.json` / `package.json` 出现在 diff 中（探针残留或包管理器混用信号）
  </acceptance_criteria>

  <verify>
    <automated>node -e "const fs = require('fs'); const cp = require('child_process'); function run(cmd){ try { cp.execSync(cmd, { stdio: 'pipe' }); return { code: 0, out: '' }; } catch (e) { return { code: e.status || 1, out: (e.stdout?.toString() || '') + (e.stderr?.toString() || '') }; } } if (fs.existsSync('lib/api/__phase1_probe__.ts')) { console.error('FAIL: 临时探针文件未删除'); process.exit(1); } const tsc = run('npx tsc --noEmit'); if (tsc.code !== 0) { const lines = tsc.out.split(/\r?\n/).filter(l => /lib\/api\/(credential-slot|__phase1_probe__|index)\.ts/.test(l)); if (lines.length > 0) { console.error('FAIL: tsc 在新增/修改文件上报错:\n' + lines.join('\n')); process.exit(1); } else { console.error('WARN: tsc 在存量文件上有错误，但与本 phase 无关:\n' + tsc.out.split(/\r?\n/).slice(0, 20).join('\n')); } } const lint = run('npm run lint'); if (lint.code !== 0) { const lines = lint.out.split(/\r?\n/).filter(l => /lib\/api\/(credential-slot|__phase1_probe__|index)\.ts/.test(l)); if (lines.length > 0) { console.error('FAIL: lint 在新增/修改文件上报错:\n' + lines.join('\n')); process.exit(1); } else { console.error('WARN: lint 在存量文件上有错误，但与本 phase 无关:\n' + lint.out.split(/\r?\n/).slice(0, 20).join('\n')); } } console.log('OK'); "</automated>
  </verify>

  <done>
  - 探针验证完整跑过：先创建 -> tsc 0 (新文件零错) -> lint 0 (新文件零错) -> 删除探针 -> 再跑 tsc 0 + lint 0
  - 临时探针 `lib/api/__phase1_probe__.ts` 已删除，git diff 不残留它
  - 包管理器 lockfile 状态未变（git status 不显示 `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml` / `package.json` 的改动）
  - `verify.automated` 命令退出码为 0 且打印 `OK`
  - SUMMARY 中记录两条验证命令的退出码、与新增文件相关的错误清单（应为空）、存量错误清单（如有，仅作信息）
  </done>
</task>

</tasks>

<verification>
本 plan 完成意味着 Phase 1 全部交付完成。汇总验证：

1. **结构性**（plan 01 + plan 02 联合）：
   - `lib/api/credential-slot.ts` 文件存在、4 个公共符号导出、路径与解包行齐全
   - `lib/api/index.ts` 末尾具名 re-export 4 个符号
   - `docs/修改记录.md` 顶部新增 [2026-05-08] Phase 1 条目

2. **工具链**（本 plan task 2 兜底）：
   - `npx tsc --noEmit` 退出码 0（新增/修改文件零类型错误）
   - `npm run lint` 退出码 0（新增/修改文件零 ESLint 错误）

3. **可消费性**（本 plan task 2 探针验证）：
   - `import { getCredentialSlot, type CredentialSlot } from '@/lib/api'` 在临时探针文件中类型解析通过
   - 探针文件删除后两条验证命令仍退出码 0（确认 plan 02 没引入残留文件依赖）

4. **跨项目联动**：
   - 修改记录条目明确写出「无需再次互引（后端 commit 46d72b8 已建立）」
   - **不**修改 `qy_lty/docs/修改记录.md`（CONTEXT.md 锁定）

5. **包管理器零漂移**：
   - `git status` 不显示 `package.json` / `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml` 的任何修改
</verification>

<success_criteria>
- [ ] `docs/修改记录.md` 顶部第一条为 `[2026-05-08] Phase 1（前端）凭据槽位 API 客户端`
- [ ] 该条目包含全部锁定关键字：`CRED-FE-01`、`46d72b8`、`accessTokenMasked`、`accessToken`、`lib/api/credential-slot.ts`、`lib/api/index.ts`、`/v1/admin/credential-slot/`、`无需再次互引`
- [ ] 现有 [2026-05-07] Phase 2 条目内容不变、位置下移
- [ ] `npx tsc --noEmit` 退出码 0；新增/修改文件零类型错误（存量错误不影响本 phase 判定）
- [ ] `npm run lint` 退出码 0；新增/修改文件零 ESLint 错误
- [ ] 临时探针 `lib/api/__phase1_probe__.ts` 已删除，git diff 不残留
- [ ] `git status --short` 仅显示 `lib/api/credential-slot.ts`（新增） + `lib/api/index.ts`（修改） + `docs/修改记录.md`（修改）+ `.planning/...` 内的 PLAN/SUMMARY 文档；**不**显示 `package.json` / 任一 lockfile 改动
</success_criteria>

<output>
完成后创建 `.planning/phases/01-credential-slot-api/01-02-SUMMARY.md`，记录：
- 修改记录条目最终落地行号区间（在 docs/修改记录.md 中的位置）+ 关键字命中清单
- `npx tsc --noEmit` 退出码 + 与新增文件相关的错误行数（应为 0）
- `npm run lint` 退出码 + 与新增文件相关的错误行数（应为 0）
- 探针文件删除前后两次验证的输出对比（最关心：删除后命令仍 0）
- 存量类型/lint 错误数量（仅作信息，不计入失败判定；为后续 PERM-06 候选优先级 #3 留追踪锚点）
- 跨项目联动确认：本 phase 未修改 `../qy_lty/docs/修改记录.md`

完成本 SUMMARY 即可宣告 Phase 1 全部交付完成；下一步运行 `/gsd-plan-phase 2` 启动 RBAC 收敛 + AI 模型页入口。
</output>
