# INFRA-F73 收口发版 + 回主线排期 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 把 `@ux-team/tvu-design-system@1.1.0` 真正发到团队 Gitea registry 并全链核验（含 docs 站与文档收口），同时利用「等 owner 建 token」这段阻塞期把主线 INFRA-F67/F66 的剩余 triage 做掉，最后给出 v1.x 排期决议与跨仓库独立项的登记。

**Architecture:** 三条轨道并行而非串行。**轨道 A（发版）** 卡在一个纯人工动作（owner 在 Gitea 建 PAT），因此它是 *事件驱动* 的：token 一到就插队执行到底；**轨道 B（主线）** 完全不依赖 token，是等待期的默认工作面，两个 entry 都是「先用实测把清单分成真缺陷/噪声」而非直接改代码；**轨道 C（收口）** 在 A、B 各自产出后统一更新 SoT 文档并登记不属本仓库的后续项。轨道 A 的每一步都以「读回 registry / 读回线上 bundle / 人眼看页面」而非「命令退出码 0」作为完成判据。

**Tech Stack:** `scripts/publish-to-gitea.sh`（本机发布，四重护栏 + 自跑 `prepare` + `prepublishOnly` + 发完读回）· Gitea npm registry API（`http://product-demo.tvustream.com:3001/api/packages/ux-team/npm/`）· Playwright（`pnpm audit:docs-overflow` 溢出扫描 / docs 站 React CE 态复现）· changesets · `tvu-host-monitor`（launchd，本机外部心跳）。

---

## Global Constraints

以下为全局约束，**每个 Task 的要求都隐含包含本节**。数值/路径/措辞逐字取自仓库真源。

- **包名与 registry**：`@ux-team/tvu-design-system`，`publishConfig.registry = http://product-demo.tvustream.com:3001/api/packages/ux-team/npm/`。GitHub Packages 上 15 个旧名版本（`@nancyzeng0210/tvu-design-system`，≤1.1.0）**刻意保留不删**（rollback 退路 + MicroApps/vue-app 仍钉 `^0.6.1`）。
- **不重打 `v1.1.0` tag**：该 tag 指向改名前的 `0f7fd8aa`，重打会篡改 tag 语义。1.1.0 走**本机脚本**发布，不走 tag→CI。
- **硬纪律：tag pushed + CI green ≠ publish 成功**。必须人眼看 `http://product-demo.tvustream.com:3001/ux-team/-/packages` 实物（v0.1.0/v0.1.1 曾 silent fail 1-2 周）。API 绿不免除此步。
- **不改 `package.json` engines**：`>=20` 是对 consumer 的承诺；`>=22` 只是仓库 tooling 要求（3 个 audit 脚本靠 Node 22 原生 TS 类型剥离）。
- **render-gate 不在 `prepublishOnly`**：`test:render-verification` + `audit:render-drift-gate` 由 `scripts/release.mjs` 在切 tag 前强制，无 skip flag。本机发布走的是 `prepublishOnly` 16 个 audit，**不含** render-gate；这是既有设计，不要临时往脚本里加。
  > ⚠️ **2026-07-30 订正**：本条原本还写着「1.1.0 的产物与已发布版本内容等价，render 面无变化」——**该前提已失效**，见下方 §当前事实基线 的「HEAD vs v1.1.0 内容漂移」行。当前工作树含 `Tab.vue`/`TabItem.vue`/`FormItem`/`InputBoxFilled`/`UserMenu` 等 render 面改动，「render 面无变化」不再是不跑 render-gate 的理由；发什么版本号取决于 owner 对该漂移的裁定（A2 Step 0）。
- **本 host 无干跑能力**：Gitea 1.22.6 无 `workflow_dispatch`（1.23.0 才有），且它在 `on:` 里会连坐压死 `push` 触发。**tag→CI 那条链从未干跑过，第一次 tag-push 就是真发**。
- **Git 纪律（多 session 并行）**：commit 一律用 `git commit -- <显式路径列表>`，**禁 `git add -A` / `commit -a`**；要建分支先开 worktree，**不在共享工作树 `git checkout -b`**。改 STATUS/backlog 前先 `git fetch` + `git pull`。Owner 直 commit master 是默认（AGENTS 硬规则 #7），commit 含 push 到 `origin`（Gitea）。
- **commit message 结构化 body**（AGENTS §直 commit master）：`改动内容:` / `改动类型:` / `测试:`——测试只写**真实跑过**的，没跑的写「未跑：xxx」。
- **AI 不得编造工具输出**（硬规则 #9）：commit hash / 版本号 / 时间戳 / HTTP code / passed 数一律从原始 stdout 逐字复制。超时/截断/矛盾 → STOP 交 owner 在终端核。
- **结论对活源验证**（硬规则 #10）：判断远端/仓库当前状态前直接读活源，不拿摘要、memory、面板 UI 当依据。
- **告警/回滚类代码必须注入失败来测**（2026-07-29 retrospect B6）：只跑 happy path 会交付一个「看起来装好了、实际什么都不报」的东西。
- **runner 跳板法三坑**（若本计划任何一步要动主机，先读 backlog INFRA-F72 §那段）：① 联网 git 一律 `docker exec` 走容器，runner shell 里裸跑会挂在 credential prompt 上不退；② 推任何分支都触发 deploy webhook，会抢掉你造的故障；③ 多轮别 force-push 同一报告分支。
- **docs dev-server**：跑 Playwright 前先 `lsof -i:5173` 核实无并发 vite server（并发同 config 会污染共享 `.vite` 缓存 → 全量误报）。

---

## 当前事实基线（**2026-07-30 09:5x 重验**，取代 07-29 14:0x 那版；执行前如再超过一天请重验）

| 事实 | 值 | 取证方式 |
|---|---|---|
| 工作树 | **干净**（`git status --porcelain` 空） | 本地实测 2026-07-30 |
| 分支 | `master` == `origin/master` == `github/master`，HEAD **`d6f9686d`**（07-29 那版基线写的 `dc9c4999` 已被 13 个 commit 超过） | `git fetch --all` + `git log --oneline -1 HEAD/origin/master` |
| 包 | `@ux-team/tvu-design-system@1.1.0`，publishConfig 指 Gitea | `node -p require('./package.json')` |
| 发布脚本 | `scripts/publish-to-gitea.sh` 在位，7023 B，可执行 | `ls -l` |
| Gitea token | ✅ **已就位**（owner 2026-07-30 09:51 落盘）：`-rw-------`、40 字节、40 位小写 hex、无尾换行、非 `ghp_*`/`github_pat_*`/`gho_*`。**真写探针四条全绿**：A `GET /api/v1/packages/ux-team` → **200** · B generic `PUT` → **201** · C 负控（改坏一位的 token 同一 PUT）→ **401** · D `DELETE` → **204** + 重拉列表亲验查不到 | `ls -l ~/.config/tvu/gitea-token`；`bash verify-gitea-package-scope.sh`（`pass=4 fail=0`） |
| repo secret | ✅ **`TVU_GITEA_PACKAGES_TOKEN` 已设**（`PUT .../actions/secrets/…` → **201**，重拉列表亲验在列）。⚠️ **原计划/原 workflow 的名字 `GITEA_PACKAGES_TOKEN` 建不出来** —— Gitea 保留 `GITEA_`/`GITHUB_` 前缀，实测 400 `invalid secret name`；已连同 workflow 一起改名。旧 `GH_PACKAGES_TOKEN` 已删（204，列表亲验已无） | `PUT/GET /repos/ux-team/tvu-design-system/actions/secrets` |
| **待发 changeset** | ⚠️ **不再是零** —— 3 个 patch changeset：`f66-ce-host-sizing` / `light-theme-on-fill-and-tab-shell-tokens` / `tab-slot-composition-and-figma-resync` | `ls .changeset/` + `pnpm changeset:status` → `Packages to be bumped at patch` |
| **⚠️ HEAD vs v1.1.0 内容漂移** | **v1.1.0 tag（`0f7fd8aa`）..HEAD 有 33 个 consumer-facing 文件改动**：`src/components/Tab/Tab.vue` +131 行（槽位组合激活态修复）· `src/canonical/TabItem.vue` · `FormItem.vue` / `InputBoxFilled.vue`（`:host` 尺寸）· `UserMenu.vue` · `token-aliases.ts` · `components.config.ts` · `divergences-decisions.json` · eslint-plugin/templates 全量改名 | `git diff --stat v1.1.0..HEAD -- src package.json eslint-plugin templates` |
| APID-01 / APID-02 | **已随 1.1.0 发布**（CHANGELOG 1.1.0 段含 `7edcff6`/`786195b`） | `CHANGELOG.md` 首段 |
| host-monitor | 07-29 实测在跑、四项全 `up`；本轮**未重测** | 未跑（本轮无需） |
| Slack 告警 | `SLACK_BOT_TOKEN` **已可用**（`~/.claude/daily-reports/.env` fallback），**缺的只有 `SLACK_CHANNEL`**（owner 给频道） | `check-host.sh:86` fallback 逻辑 |

> ⚠️ **07-29 那版基线的两处已失效，别再照它行动**：
> 1. 「待发 changeset = 零」→ **假**。`3a8faac9` / `59b870c4` / `ab1283ac` / `9f14f125` 那批组件改动带进 3 个 patch changeset，**下一个语义正确的版本号是 `1.1.1`**（不是 1.2.0——「禁造 1.2.0」的纪律仍成立，因为 1.1.1 有真实内容、不是为基建凭空造版本号）。
> 2. 「1.1.0 内容等价、consumer 可拿『代码没变』当迁移验证手段」→ **假**。owner 那句「把现有 1.1.0 以新名发到 Gitea」的决策 commit 是 `04462c27`，上述组件改动**全落在它之后**；从当前工作树直接发 `1.1.0`，Gitea 上的 1.1.0 会比 GitHub 上的 1.1.0 多一个 Tab 槽位修复 + 3 处 token/尺寸修复。**这是 owner 未被告知的前提变化，必须先裁定（见 A2 Step 0），不得默认按老决策发。**
>
> （07-29 基线原本纠正的那处 STOT stale——APID-01/02「待下个 release」——已在当天 wrap-up 修掉，仍成立。）

---

## 轨道概览与执行顺序

```
                      token 到了吗？
                       ├── 是 ──→ 轨道 A（A1→A2→A3）插队做完，再回轨道 B
                       └── 否 ──→ 轨道 B（B1→B2）默认工作面，token 一到立刻切 A
                                            ↓
                            A、B 各自产出后 ──→ 轨道 C（C1→C2→C3）统一收口
```

**排期依据（tracker §排期原则 5 问自检，逐条作答）**：

1. **对齐哪个能力？** 轨道 A = 能力 1（开发 npm install 即用——registry 空着等于能力 1 断线）；轨道 B = 能力 1 的文档站可用性面（受众表 P0 开发 + P1 QA）。
2. **unblock 多少其它项？** 轨道 A unblock 全部 consumer 迁移（MicroApps/vue-app、NOC upstream-gate）+ 所有后续 release；轨道 B 两项 unblock 0（report-only bucket）。
3. **有没有按「小/快/简单」把主线推后？** 没有——轨道 A 客观上卡在人工步骤，轨道 B 是等待期填空而非插队。
4. **自动化前置是否在主线前？** 是，`publish-to-gitea.sh` 与 pre-flight 凭据闸已 ship。
5. **semver 命名是否合规？** ⚠️ **2026-07-30 变更**：原答「本计划不 bump 版本（1.1.0 已存在），无 semver 决策」已不成立 —— `.changeset/` 现有 3 个 patch changeset，且 HEAD 内容已偏离 v1.1.0 tag，**存在一个真实的 semver 决策**（发 `1.1.1` 还是重建一份等价的 `1.1.0`），交 owner 在 A2 Step 0 裁定。

**结论：A > B**。A 不能开工时 B 才是当前最高价值工作面。**「不为跑通 tag→CI 凭空造版本号」这条纪律仍然成立**，但它的事实前提变了：`changeset:status` 现在报 `Packages to be bumped at patch`（3 个 changeset），所以「有没有内容可发」的答案已从"没有"变成"有"——`1.1.1` 是有真实内容的版本号，**不属于**「为基建凭空造版本」。该链的首跑风险已独立登记为 backlog **INFRA-F77**（原计划写「登记在 Task C2」，C2 已于 2026-07-30 执行完毕）。

---

# 轨道 A — 把包发到 Gitea（token 已解除；现阻塞于 owner 裁定发哪个版本号）

### Task A1: 建 Gitea PAT 并独立验证它真的能写包 — ✅ **DONE 2026-07-30**

**Files:**
- Create: `~/.config/tvu/gitea-token`（chmod 600，**不进任何仓库**）

**Interfaces:**
- Produces: 一个 Gitea PAT，scope 含 `read:package` + `write:package`；后续 A2 从本机环境变量 `GITEA_PACKAGES_TOKEN` 或该文件读取。tag→CI 路径另读 repo secret **`TVU_GITEA_PACKAGES_TOKEN`**（名字不能带 `GITEA_`/`GITHUB_` 前缀，见 Step 1 的订正）。

- [x] **Step 1: owner 在 Gitea 建 token（人工，AI 不能代做）** — ✅ **2026-07-30 09:51 owner 已完成**

浏览器打开 `http://product-demo.tvustream.com:3001` → 右上角头像 → **Settings** → **Applications** → **Generate New Token**，勾选 **`read:package`** 和 **`write:package`** 两项。生成后页面只显示一次，立刻复制。

> 顺带（可选）：同页删除已无用的 `GH_PACKAGES_TOKEN` repo secret（它是 `ghp_*` GitHub PAT，对 Gitea registry 完全无效，Gitea 在 `authGroup.Verify` 就拒）。
> 同时建议把同一个 token 存成 repo secret **`TVU_GITEA_PACKAGES_TOKEN`**（Settings → Actions → Secrets），否则将来 tag→CI 发版时 pre-flight 会红——这是设计意图，但没必要留着以后再撞。
>
> ⚠️ **2026-07-30 订正：这里原本写的名字是 `GITEA_PACKAGES_TOKEN`，那个名字建不出来。** Gitea **保留 `GITEA_` / `GITHUB_` 前缀**，实测 `PUT /repos/ux-team/tvu-design-system/actions/secrets/GITEA_PACKAGES_TOKEN` → **400 `{"message":"invalid secret name"}`**（`GITEA_PROBE_X`/`GITHUB_PROBE_X` 同样 400；`TVU_PACKAGES_TOKEN` → 201，前缀就是全部原因）。`.gitea/workflows/publish.yml` 已同批改名并留实测注释。**这条只约束 Actions secret 名**——`scripts/publish-to-gitea.sh` 读的本机环境变量 `GITEA_PACKAGES_TOKEN` 不受影响。
> **本步已由 AI 从终端代做**（值从 `~/.config/tvu/gitea-token` 读、不进 argv，鉴权借旧 git-push PAT 的仓库面权限）：`PUT` → 201、重拉列表亲验在列、`GH_PACKAGES_TOKEN` DELETE → 204 亲验已无。**旧 git-push PAT 未被改动**（`GET /repos/…` → 200，admin/push/pull 全 true）。

- [x] **Step 2: 落盘（二选一）** — ✅ 走方式 2，`~/.config/tvu/gitea-token`（`-rw-------`，40 字节）

```bash
# 方式 1 — 仅本次 shell 有效
export GITEA_PACKAGES_TOKEN='<粘贴 token>'

# 方式 2 — 存一次，长期复用（脚本自读）
mkdir -p ~/.config/tvu && pbpaste > ~/.config/tvu/gitea-token && chmod 600 ~/.config/tvu/gitea-token
ls -l ~/.config/tvu/gitea-token   # 期望：-rw-------
```

- [x] **Step 3: 独立验证凭据（不依赖发布脚本，先拿到确定性答案）** — ✅ **2026-07-30 通过**：A `200` / B `201` / C 负控 `401` / D `204` + 列表亲验查不到（`pass=4 fail=0`）

> ⚠️ **2026-07-30 订正 —— 本步原探针是假闸，已整段重写。** 原探针是 `GET .../npm/@ux-team%2fnope-preflight` 期望 404，判据写「404 = 凭据 OK，进 A2」。**问题**：读一个不存在的包只需 `read:package`，**一个纯 read-only token 会照样返 404 被放行**，而真正要验的 `write:package` 完全没被触碰 —— 这正是本仓 `render-gate 假闸` / `on-fill-content-color 第一版假闸` 同一个病（跑绿了但根本没测到目标）。原判读表还有一行错：「`reqPackageAccess` = 缺 `write:package`」**不准** —— 2026-07-29 五路实测证明**缺 `read:package` 同样报 `reqPackageAccess`**（那份 git-push PAT 打 `GET /api/v1/packages/ux-team` 得 403 `required scope(s): [read:package]`，打 npm registry 得 401 `reqPackageAccess`），所以该中间件名只能证明「是 Gitea 凭据但 package scope 不够」，**不能区分缺哪一个**。
>
> **真探针 = 必须真写一次 + 负控 + 清场亲验，四步缺一不算过。** 用 generic registry 发一个一次性探针包，不碰 npm registry、不占 `tvu-design-system` 任何版本号。token 只从文件读，**任何一步都不回显 token**。

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
TOK="${GITEA_PACKAGES_TOKEN:-$(tr -d '[:space:]' < ~/.config/tvu/gitea-token)}"
[ -n "$TOK" ] || { echo "STOP: token 为空，别往下跑"; exit 1; }
BASE=http://product-demo.tvustream.com:3001
GEN="$BASE/api/packages/ux-team/generic/f73-scope-probe/0.0.0-probe"

# ① read:package —— 列 owner 的包，期望 200
curl -s -o /tmp/f73-p1.txt -w '① GET /api/v1/packages/ux-team -> HTTP %{http_code}\n' --max-time 20 \
  -H "Authorization: token $TOK" "$BASE/api/v1/packages/ux-team"
head -c 200 /tmp/f73-p1.txt; echo

# ② write:package 真写 —— 期望 201
echo probe > /tmp/f73-probe.txt
curl -s -o /tmp/f73-p2.txt -w '② PUT generic probe -> HTTP %{http_code}\n' --max-time 30 \
  -X PUT -H "Authorization: token $TOK" --upload-file /tmp/f73-probe.txt "$GEN/probe.txt"
head -c 200 /tmp/f73-p2.txt; echo

# ③ 负控 —— 同一个 PUT 换成改坏一位的 token，必须 ≠ 201
BADTOK="$(printf '%s' "$TOK" | sed 's/.$//')x"
curl -s -o /tmp/f73-p3.txt -w '③ PUT with corrupted token -> HTTP %{http_code}\n' --max-time 30 \
  -X PUT -H "Authorization: token $BADTOK" --upload-file /tmp/f73-probe.txt "$GEN/probe-neg.txt"
head -c 200 /tmp/f73-p3.txt; echo

# ④ 清场 + 亲验查不到（不信 204）
curl -s -o /dev/null -w '④ DELETE probe -> HTTP %{http_code}\n' --max-time 20 \
  -X DELETE -H "Authorization: token $TOK" "$GEN"
curl -s --max-time 20 -H "Authorization: token $TOK" "$BASE/api/v1/packages/ux-team" \
  | grep -c f73-scope-probe   # 期望 0
rm -f /tmp/f73-probe.txt /tmp/f73-p*.txt
```

**通过判据（四条全中才算）**：① `200` · ② `201` · ③ **≠ 201**（`401`/`403` 均可） · ④ `DELETE` 得 `204` **且**随后列表 `grep -c` 得 `0`。

判读表（**不要**把拒绝当成不可达——这是本仓反复犯过的错）：

| 结果 | 含义 | 处置 |
|---|---|---|
| ① `200` + ② `201` + ③ ≠201 + ④ `204`/`grep 0` | 凭据真能写包，且探针未在团队 registry 留残留 | 进 A2 |
| ① `403` body 含 `required scope(s): [read:package]` | 是 Gitea 凭据，但没勾 `read:package` | 回 Step 1 重勾 scope 重建 token |
| ② `401` body 含 `reqPackageAccess` | 是 Gitea 凭据但 **package scope 不够**（⚠️ **不能据此断定缺的是 `write:package`——缺 `read:package` 也报这个**，2026-07-29 实测）；结合 ① 的结果判：① 通了 ② 没通 → 缺 `write:package` | 回 Step 1 重勾 scope 重建 token |
| 任一步 `401` body 含 `authGroup.Verify` | 根本不是 Gitea 凭据（比如贴成了 `ghp_*` GitHub PAT；⚠️ 两者长度**都是 40**，靠长度判断会误判，看前缀） | 回 Step 1 重建 |
| ② 得 `404` / `405` | generic registry 路由与本 Gitea 版本不符 | **STOP 问 owner。不要改脚本硬凑路径**——凑出来的 200 不构成 `write:package` 证据 |
| ③ 竟然也 `201` | **整个探针无意义**（说明请求根本没在校验凭据，或写到了别处） | STOP，先查为什么，别拿 ② 的 201 当结论 |
| 任一步 `000` | 主机不可达 | 查 `~/.claude/host-monitor/monitor.log` 最近几行 |

> **⚠️ 不要复用那份 keychain 里给 `git push` 用的 Gitea PAT（acct `NancyZeng`，40-hex）。** 2026-07-29 五路实测证明它发不了包：`GET /api/v1/packages/ux-team` → 403 `required scope(s): [read:package]` · npm registry → 401 `reqPackageAccess` · 改走 basic auth 仍 401（传法绕不过） · live swagger 证明 **PAT scope 创建后不可改**（token 端点只有 GET/POST/DELETE，无 PATCH） · 用它代签发新 token 需 `write:user`，也没有。反证凭据本身没坏：`GET /repos/ux-team/tvu-design-system` → 200，permissions `admin`/`push`/`pull` 全 true。**那份 token 不要删、不要动** —— `git push` 与 open-PR radar 都依赖它。所以本 Step 只能等 owner 新签一份带 `read:package`+`write:package` 的。

- [x] **Step 4: 确认 token 未泄漏进仓库** — ✅ `git status --porcelain` 只有本轮 docs/workflow 改动，无 token 文件、无 `~/.config` 外泄

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
git status --porcelain    # 期望：空。token 只在 ~/.config 和环境变量里，不应产生任何仓库改动
```

---

### Task A2: 发布并三路核验 — ✅ **DONE 2026-07-30（发的是 `1.1.1`，见 Step 0）**

**Files:**
- 只跑脚本，不改仓库文件。`prepare` 会重建 gitignored 的 `dist/` 与 `dist-wc/`（不产生 git 改动）。

**Interfaces:**
- Consumes: A1 的 token。
- Produces: registry 上 `@ux-team/tvu-design-system@1.1.0` 实物；A3 依赖它存在。

- [ ] **Step 0（2026-07-30 新增，阻塞 Step 2）: 先让 owner 裁定「发哪份内容」——老决策的前提已失效**

owner 07-29 拍「把现有 1.1.0 以新包名发到 Gitea、不造 1.2.0」时，工作树内容与已发布的 1.1.0 等价；那之后 `3a8faac9` / `59b870c4` / `ab1283ac` / `9f14f125` 落了 33 个 consumer-facing 文件改动 + 3 个 patch changeset（证据见 §当前事实基线）。**直接发 `1.1.0` 会让两个 registry 上同名同版本号的包内容不同**，并且正好废掉 owner 当初看重的那条「代码没变本身就是迁移的验证手段」。三个选项，按推荐排序：

| # | 方案 | 得到什么 | 代价 |
|---|---|---|---|
| **1（推荐）** | Gitea 首份发 **`1.1.1`**：`pnpm changeset version` 消化 3 个 changeset → 本机脚本发 1.1.1 | 版本号与内容诚实对应；CHANGELOG 如实记这三项修复；GitHub 停在 1.1.0、Gitea 从 1.1.1 起，历史清晰 | 失去「两 registry 同版本号」这条弱验证；consumer 迁移时版本号会变（但 `MIGRATION_TO_V1` 本来就要改 `.npmrc` + 包名，顺带改版本号不增成本） |
| 2 | 严格照老决策：从 `v1.1.0` tag 的树 + 仅改名补丁重建，发一个与 GitHub 1.1.0 字节等价的 `1.1.0` | 完全兑现「内容等价」前提 | 要在 detached worktree 上做树考古（`publish-to-gitea.sh` 和改名 commit 都在 tag 之后，得手工拼），且发完 Gitea 的 latest 会立刻落后于 master 一个 Tab 修复 |
| 3 | 从当前 HEAD 发 `1.1.0` | 最省事 | **不做** —— 两 registry 同版本号不同内容，等于版本号说谎；也违反「版本号该表达代码变化」（owner 07-29 原话） |

**owner 未答前不要跑 Step 2。** 答案落定后回填这里，并同步改 §Global Constraints 那条 render-gate 注（选 1 意味着有 render 面改动进包，需按 `scripts/release.mjs` 的既有设计考虑是否补跑 render-gate，而不是沿用「render 面无变化」的免跑理由）。

- [ ] **Step 1: 起手先确认工作树干净且与远端同步**

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
git fetch --all -q && git status -sb && git status --porcelain
```

期望：`## master...origin/master`（无 ahead/behind），porcelain 空。
**若有并行 session 的 dirty 文件 → 不要碰它们，也不要 stash**；发布脚本只读 `package.json` + 重建 gitignored 产物，dirty 文件不影响发布，但要在报告里如实记下当时的 dirty 清单。

- [ ] **Step 2: 跑发布脚本**

```bash
./scripts/publish-to-gitea.sh
```

脚本会依次做（这几层都已实测过，不要绕过任何一层）：
1. 取 token（env 优先，其次 `~/.config/tvu/gitea-token`）；
2. `ghp_*` / `github_pat_*` / `gho_*` 前缀在**任何网络调用之前**直接拒；
3. pre-flight 读一次 registry，401/403 时按 Gitea 中间件名打印是哪种错；
4. 查该版本是否已存在——**已存在则拒绝**（Gitea 不允许覆盖同 name@version）；
5. 交互确认 `[y/N]`；
6. `pnpm run prepare`（typecheck + build + token/composition/icon 出口 + `build:wc`）——**这一步是必要的**：`dist/` 是 gitignored 且只由 `prepare` 生成，改了源码没重装的工作树带 stale dist，`audit:composition-exports` 会 FAIL（2026-07-29 实际撞过）；
7. `pnpm publish --no-git-checks`，pnpm 自身触发 `prepublishOnly` 16 个 strict audit；
8. 发完读回 registry 列出全部版本，`1.1.0` 不在列表里就 `die`。

**期望结尾输出**：`✅ @ux-team/tvu-design-system@1.1.0 is live on Gitea.`

失败时**不要**改脚本重试。按报错分类：`build failed` → 修构建；某个 audit FAIL → 看它打印的具体项；`already published` → 停下问 owner（说明有人已经发过，本任务的前提变了）。

- [ ] **Step 3: 第二路核验——独立 API 读回（不复用脚本的输出）**

```bash
TOK="${GITEA_PACKAGES_TOKEN:-$(tr -d '[:space:]' < ~/.config/tvu/gitea-token)}"
curl -s --max-time 20 -H "Authorization: token $TOK" \
  'http://product-demo.tvustream.com:3001/api/packages/ux-team/npm/@ux-team%2ftvu-design-system' \
  | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{const j=JSON.parse(d);console.log('versions:',Object.keys(j.versions||{}).join(' '));console.log('dist-tags:',JSON.stringify(j['dist-tags']||{}))})"
```

期望：`versions: 1.1.0`、`dist-tags: {"latest":"1.1.0"}`。**把这行原始输出逐字贴进报告**，不要概括成「已核验」。

- [ ] **Step 4: 第三路核验——人眼看 Packages 页（硬纪律，不可省）**

浏览器打开 `http://product-demo.tvustream.com:3001/ux-team/-/packages`，确认列表里**看得见** `tvu-design-system`，版本 `1.1.0`。

> 为什么 API 绿了还要看页面：v0.1.0/v0.1.1 曾 silent fail 1-2 周无人发现。这条纪律没有例外。

- [ ] **Step 5: 记录证据（本任务无 commit）**

在报告里落三样：脚本结尾那行原文、Step 3 的 `versions:` 原文、Packages 页看到了什么。**不 commit**——本任务不改仓库文件（`dist/` 是 gitignored）。

---

### Task A3: docs 站同步（RELEASING Step 4）+ 线上核验 — ✅ **DONE 2026-07-30**：线上 bundle `index-zYBKDapF.js` 与本地同名，sha256 `a7de0ddb567cfc03dac8e613c0d04b3034ac57a9048f41b63615cfacfdcad3a5` 两侧逐字一致，线上站内 `gp="1.1.1"`，`Last-Modified: Thu, 30 Jul 2026 02:48:30 GMT`（commit `94c5e862`）

**Files:**
- Modify（由构建产生）：`playground-dist/`、`react-pilot/dist/`
- 参考：`docs/RELEASING.md:116-128`、`docs/DEPLOY.md`

**Interfaces:**
- Consumes: A2 已把 1.1.0 发上去。
- Produces: docs 站显示新包名的安装说明；C1 更新 STATUS 时引用本任务的线上 bundle hash。

> ⚠️ **2026-07-30 订正：本段原来的理由是错的。** 原文写「README/GETTING_STARTED 的安装命令 `?raw` 在构建时烤进 bundle」—— **查源不成立**：站点没有任何地方引这两份文档（`playground/` 全量搜无 `GETTING_STARTED`/`README.md` 的 `?raw` import，构建产物全量搜 `pnpm add` **零命中**）。
>
> **真实机制**（`playground/docs/docs-meta.ts`）：`import pkg from '../../package.json'` 提供站上显示的版本号（构建产物里那个 `gp="1.1.1"`），`import changelogRaw from '../../CHANGELOG.md?raw'` 提供 Changelog 页。所以 Step 4 的实际价值 = **版本号跟上 + Changelog 页出现新版本段 + 组件修复进入站点 demo**；至于新包名，它出现在 demo「show source」的 `import … from '@ux-team/tvu-design-system'` 里（13 个 chunk 命中），那部分早在 F73 改名后的重建里就正确了。

- [ ] **Step 1: 重建**

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
pnpm build
```

- [ ] **Step 2: 本地验包名真的烤进去了（不是看构建有没有报错）**

```bash
NEWJS=$(ls -t playground-dist/assets/index-*.js | head -1); echo "$NEWJS"
grep -c '@ux-team/tvu-design-system' "$NEWJS"          # 期望 > 0
grep -c '@nancyzeng0210/tvu-design-system' "$NEWJS"    # 期望 0（或仅出现在 CHANGELOG 历史文本里）
```

若第二条 > 0，先 `grep -o '.\{80\}@nancyzeng0210/tvu-design-system.\{80\}' "$NEWJS" | head -3` 看上下文——出现在 CHANGELOG 历史段落里是**正常的**（历史如实，F73 刻意不改 CHANGELOG）；出现在安装说明里才是漏改。

- [ ] **Step 3: 提交（显式路径，视觉闸放行）**

```bash
git status --porcelain    # 先看清楚有哪些改动，确认没有并行 session 的文件混进来
VISUAL_COMMIT_APPROVED=1 git commit -m "$(cat <<'EOF'
chore(docs): rebuild playground-dist for the @ux-team registry migration

改动内容:
- playground-dist/ + react-pilot/dist/ 重建，把 F73 改过的安装说明（@ux-team 包名 + Gitea registry）烤进站点 bundle
改动类型: chore
测试: pnpm build ✅；本地 grep 新 bundle 确认含 @ux-team 安装说明；线上核验见下一步
EOF
)" playground-dist react-pilot/dist
git push origin master
```

> `VISUAL_COMMIT_APPROVED=1` 的正当性：本次 `playground-dist` 差异全部来自上游文本改动的重新烘焙，无手写视觉改动。若 `git diff --stat` 显示 `src/` 或组件 CSS 也进来了 → **不要设这个变量**，停下查为什么。

- [ ] **Step 4: 线上核验（Gitea → 主机自动同步，可能有延迟）**

⚠️ **2026-07-30 订正：这两条命令用 `http` 会得到 `301` 而不是内容**，`grep last-modified` 因此空手而归 —— 会被误读成「线上没更新」。站点在 443 上有有效 Let's Encrypt 证书，`http` 一律 301 跳 `https`。必须用 `https`（或加 `-L`）：

```bash
sleep 60
curl -sIL --max-time 30 https://product-demo.tvustream.com/tvu-design-system/playground-dist/index.html | grep -iE 'http/|last-modified'
curl -sL  --max-time 30 https://product-demo.tvustream.com/tvu-design-system/playground-dist/index.html | grep -o 'assets/index-[A-Za-z0-9_-]*\.js'
```

期望：`Last-Modified` 是今天；bundle 名与本地新产物同名。再做字节级对照：

```bash
NEWJS=$(ls -t playground-dist/assets/index-*.js | head -1); BASE=$(basename "$NEWJS")
shasum -a 256 "$NEWJS" | awk '{print $1}'
curl -sL -H 'Cache-Control: no-cache' "https://product-demo.tvustream.com/tvu-design-system/playground-dist/assets/$BASE" | shasum -a 256 | awk '{print $1}'
```

两个 sha256 必须相同。

- [ ] **Step 5: 若线上没更新——按 INFRA-F72 的三分法判，别盲目 rebuild**

| 症状 | 含义 | 处置 |
|---|---|---|
| `Last-Modified` 旧 | 同步还没跑 / webhook 没触发 | 再等一轮；查 deploy 通知 |
| `Last-Modified` 新，但 bundle 名是**旧** hash | 就是 F72 那个 bug（主机构建残留卡死 `git pull`） | **rebuild+push 无效，别做**。主机侧已有两条防线（marker 停构建 + `_sync_repo()` 自愈 clean&retry），若仍复发说明 marker 被删或容器重建丢了补丁 → 按 backlog INFRA-F72 §落地方式走 runner 跳板，先读那里的三个坑 |
| bundle 名对但内容 sha 不符 | 传输/缓存问题 | 加 `-H 'Cache-Control: no-cache'` 重取确认 |

---

# 轨道 B — 主线（不依赖 token，等待期的默认工作面）

### Task B1: INFRA-F67 (c) — clipped-unreachable 16 条 triage

**Files:**
- Read: `tests/docs-overflow/docs-viewport-overflow.spec.ts`（167 行，报告在 console，`clippedUnreachable` 数组 = 那 16 条）
- Create: `docs/internal/_reports/2026-07-29-f67-clipped-unreachable-triage.md`
- Modify: `docs/internal/backlog.md`（INFRA-F67 §剩余 (c)）

**Interfaces:**
- Consumes: 无。
- Produces: 一张 16 行的分类表（真差异 / 字体噪声 / 误报），每行带处置结论；C1 据此改 backlog。

> **本任务是 triage，不是修复。** backlog 明确该 bucket 是 report-only、不阻塞。先分类，修不修由分类结果决定（真差异若数量少且改法明确，可在本任务尾部顺手修；若牵出布局重构 → 停，标独立项交 owner，别顺着根因滑进大工程）。

- [ ] **Step 1: 确认没有并发 vite server**

```bash
lsof -i:5173 | head
```

期望：无输出。有输出说明并行 session 起着 dev server——**先问清楚再跑**，并发同 config 会污染共享 `.vite` 缓存导致整轮误报。

- [ ] **Step 2: 跑扫描并把完整报告落盘**

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
pnpm audit:docs-overflow 2>&1 | tee /tmp/f67-overflow-$(date +%H%M).log
```

**期望结尾**：`page-level-overflow=0`（这是阻塞门，非 0 说明出了回归，优先处理它而不是 triage）；`clipped-unreachable=16`（若不是 16，以实测为准并在报告里说明差异，不要硬套 backlog 里的数字）。

- [ ] **Step 3: 抽出 16 条明细**

```bash
sed -n '/## Clipped & unreachable/,/## /p' /tmp/f67-overflow-*.log
```

每行形如 `pageId / viewport / selector / scrollWidth / clientWidth`。

- [ ] **Step 4: 逐条分类（16 条，每条给一个结论）**

对每一条，用三分法：

| 类别 | 判据 | 处置 |
|---|---|---|
| **A. 真差异** | `scrollWidth - clientWidth` ≥ 8px，且在浏览器里肉眼可见内容被切 | 记录最小修法（哪个选择器加 `min-width:0` / `max-width:100%` / `overflow-x:auto`），进 Step 6 |
| **B. 字体噪声** | 差值 ≤ 2-3px，且不同 run 之间数值抖动 | 判定为测量噪声，建议给 spec 加下限阈值而非改样式 |
| **C. 结构性可接受** | 元素本身在一个 `overflow-x:auto` 的祖先里但探测没识别到（tooltip / 绝对定位浮层） | 记录为探测器的已知盲区，建议加豁免清单 |

backlog 已点名的两处优先看：`tooltip 193/122`、`form-item member-card 293/234`（backlog 记「疑真差异」）。**在浏览器里亲眼看一次再下结论**，别只凭数字——数字是探测器给的，不是视觉事实。

```bash
pnpm dev   # 另开一个终端，逐个打开报告里的 pageId，按 viewport 宽度调窗口
```

- [ ] **Step 5: 写 triage 报告**

`docs/internal/_reports/2026-07-29-f67-clipped-unreachable-triage.md`，结构：

```markdown
# INFRA-F67 (c) clipped-unreachable triage — 2026-07-29

> 数据来源：`pnpm audit:docs-overflow` 实跑（原始日志见下方附录），非静态推断。

## 汇总
| 类别 | 条数 | 处置 |
|---|---|---|
| A 真差异 | N | … |
| B 字体噪声 | N | … |
| C 探测盲区 | N | … |

## 逐条明细
| # | pageId | viewport | selector | scroll/client | 类别 | 亲眼所见 | 处置 |
|---|---|---|---|---|---|---|---|

## 建议
- （若 A 类可低风险修）最小修法清单
- （若 B/C 占多数）建议给 spec 加阈值 / 豁免清单，把这个 bucket 收敛到可翻阻塞门的状态

## 附录：原始日志
```

- [ ] **Step 6: 只在「A 类少且改法明确」时顺手修**

修法参照 F67 已 shipped 的方案 A（靶向 CSS containment：在正确层加 `min-width:0` / `max-width:100%` / `overflow-x:auto`，不做架构改动、不 clipping、不 full-bleed）。改完重跑 Step 2 确认 A 类归零且 `page-level-overflow=0` 不变。

**若 A 类需要动三栏布局 / 断点 / Contents 面板宽度 → STOP**，写进报告的「建议」段交 owner 拍板，不在本任务做。

- [ ] **Step 7: Commit**

```bash
git fetch -q && git pull --ff-only
git status --porcelain    # 确认只有自己的文件
git commit -m "$(cat <<'EOF'
docs(f67): triage the 16 clipped-unreachable findings with a live sweep

改动内容:
- 新增 _reports/2026-07-29-f67-clipped-unreachable-triage.md（16 条逐条分类 + 原始日志）
- backlog INFRA-F67 §剩余 (c) 按 triage 结果更新
改动类型: docs
测试: pnpm audit:docs-overflow ✅ page-level-overflow=0 clipped-unreachable=<实测值>
EOF
)" docs/internal/_reports/2026-07-29-f67-clipped-unreachable-triage.md docs/internal/backlog.md
git push origin master
```

（若 Step 6 改了样式文件，把它们加进上面的显式路径列表，并在 body 的「测试」行补重跑结果。）

---

### Task B2: INFRA-F66 — 13 个 CE `:host` 尺寸候选的 live 复现判定

**Files:**
- Read: `src/canonical/*.vue`（查哪些已有 `<style>` 且含 `:host` 尺寸声明）
- Create: `docs/internal/_reports/2026-07-29-f66-host-sizing-live-repro.md`
- Modify（仅确认复现的组件）：对应 `src/canonical/<Name>.vue`

**Interfaces:**
- Consumes: 无。
- Produces: 13 个候选的「复现 / 不复现」判定表；backlog INFRA-F66 据此收敛。

> **backlog 明写这条是 live-repro 驱动、Low 优先级**：静态 grep 只能给候选，不能断定必现（grid 容器会掩盖，纯 flex 无 `flex-grow` 才暴露）。**未证实前不批量改动**——这正是 `no-fabrication-on-uncertain-tool-output` 那条的适用场景。

- [ ] **Step 1: 先把候选名单从活源重建（不信 backlog 里的名单）**

```bash
cd /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
grep -l "width: 100%" src/components/*.vue | sed 's#.*/##'
echo "--- canonical 里已有 :host 尺寸声明的 ---"
grep -l ":host" src/canonical/*.vue | xargs grep -l "display\s*:\s*\(block\|inline-flex\|flex\)" | sed 's#.*/##'
```

把「base 组件靠 `width:100%` 撑满」减去「canonical 已声明 `:host` 尺寸」= 真候选集。**以本次实测为准**，backlog 那份是 2026-07-14 的快照。

- [ ] **Step 2: 起 docs 站，切到 React（CE）态**

```bash
lsof -i:5173 | head    # 先确认没占用
pnpm dev
```

docs 站自带 Vue/React toggle（ReactIsland），**不必单起 react-pilot**。

- [ ] **Step 3: 逐个候选看 React 态下是否塌陷**

对每个候选组件，打开它的组件页，切 React，看：宽度是否塌成内容宽 / 元素是否不可见。判据是**肉眼 + DevTools 量到的 host 宽度**，不是推断。

在 DevTools console 里量：

```js
document.querySelectorAll('tvu-progress, tvu-table, tvu-topbar')  // 换成当前候选标签名
  .forEach(el => console.log(el.tagName, el.getBoundingClientRect().width, getComputedStyle(el).display))
```

`display: inline` + 宽度明显小于父容器 = 复现。

- [ ] **Step 4: 只给确认复现的组件加修复**

范式（与已修的 `Logo.vue` / `Progress.vue` 一致，只影响 CE build，Vue SFC 无 shadow host 是 no-op）：

```vue
<style>
:host {
  display: block;
  width: 100%;
}
</style>
```

`inline-flex` 用于本身是行内控件的（参 `Logo.vue`）。

- [ ] **Step 5: 改完逐个回验 + 跑双框架 parity 闸**

```bash
pnpm audit:demo-slot-boolean 2>&1 | tail -5
pnpm test 2>&1 | tail -8
```

期望：audit PASS；vitest 全绿（基线 857 pass / 10 skip，以实跑为准）。

- [ ] **Step 6: 写判定报告 + Commit**

报告 `docs/internal/_reports/2026-07-29-f66-host-sizing-live-repro.md` 必须含：候选集重建过程、逐组件「复现/不复现 + 量到的宽度」、改了哪些、回验输出。

```bash
git fetch -q && git pull --ff-only
git status --porcelain
git commit -m "$(cat <<'EOF'
fix(canonical): add :host sizing to the CE wrappers that live-repro'd (INFRA-F66)

改动内容:
- <逐条列出改了哪些 canonical 组件，各自复现时量到的宽度>
- 新增 _reports/2026-07-29-f66-host-sizing-live-repro.md（含未复现清单，说明为何不改）
改动类型: fix, docs
测试: pnpm test ✅ <实测 passed 数>；pnpm audit:demo-slot-boolean ✅
EOF
)" src/canonical docs/internal/_reports/2026-07-29-f66-host-sizing-live-repro.md docs/internal/backlog.md
git push origin master
```

⚠️ **若有组件改动 → 必须写 changeset**（AGENTS §直 commit master 写 changeset：`src/**` 改动影响发布产物）：

```bash
cat > .changeset/f66-ce-host-sizing.md <<'EOF'
---
"@ux-team/tvu-design-system": patch
---

Custom elements: declare explicit `:host` sizing on the canonical wrappers that
collapsed inside plain flex containers, so `<tvu-*>` no longer shrinks to content
width when the inner component relies on `width: 100%`.
EOF
```

把它加进同一个 commit 的显式路径列表。

**若 Step 3 判定 13 个全部不复现** → 不改任何代码，只写报告，把 backlog INFRA-F66 从 Active 移除（改为「已 live 核验，无残余」），无需 changeset。

---

# 轨道 C — 收口与登记

### Task C1: 更新 SoT 文档（STATUS + backlog）

**Files:**
- Modify: `docs/STATUS.md`（顶部摘要 + §当前 open + §当前版本表「包名」「安装」两行 + Version Roadmap 表 + §Active 计数）
- Modify: `docs/internal/backlog.md`（INFRA-F73 待办①⑤ + INFRA-F67 + INFRA-F66）
- Modify: `docs/internal/retrospection/design-spec-canonical-alignment-tracker.md`（wrap-up 协议要求追加本 session 完成项）

- [ ] **Step 1: 先同步远端（并行 session 可能已改过这些文件）**

```bash
git fetch -q && git pull --ff-only && git status --porcelain
```

- [x] **Step 2: 修掉已核实的三处 stale** — ✅ **已于 2026-07-29 wrap-up 完成**（本计划成文当天同批 commit）

三处都已改：STATUS 第 15 行「二者随下个 release 发」、原第 90 行表格项「changeset `minor` 待下个 release」、原第 112 行「owner-gated：发布 v1.x（…待起 release）」→ 全部改为「已随 v1.1.0 发布」，并写明证据（`CHANGELOG.md` 1.1.0 段含 `7edcff6`/`786195b` + `pnpm changeset:status` 三项 `NO packages to be bumped`）。

**本步无需重做**，但 C1 收口时**要顺手复核**这三处没被并行 session 改回去：

```bash
grep -n "待起 release\|待下个 release\|随下个 release 发" docs/STATUS.md
```

期望：**只命中第 13 行与第 18 行**——那两处是订正说明本身（引述了旧措辞并标注为 stale），是正常的。若在别处再次出现（尤其表格行 / §当前 open 里的断言句），说明 stale 复活了。

- [ ] **Step 3: 按 A、B 的实际产出改 STATUS**

- 顶部 `Last updated` 改到今天 + 本 session 摘要（旧摘要 prepend 进 `docs/internal/STATUS-CHANGELOG.md`）。
- §当前版本表「包名」行：去掉「迁移中」措辞与「以下为旧值」，改为 Gitea 上 1.1.0 的实际状态 + 发布时间戳（**从 A2 Step 3 的原始输出取，不要自己写时间**）。
- §当前版本表「安装」行：`pnpm add @ux-team/tvu-design-system` + Gitea `.npmrc`，删掉「当前实际可安装的 1.1.0 仍是 `@nancyzeng0210/...`」这句（A2 之后它不再成立）。
- §当前 open 第 -1 条：「阻塞发版」解除，只留真正残余（TLS / NOC / MicroApps / tag→CI 从未干跑）。
- §Active 计数（当前 13）按 B1/B2 结果调整——该行由 `audit:status-consistency` 核，改错会红。

- [ ] **Step 4: 改 backlog**

- **INFRA-F73**：待办① 与⑤ 标完成并给证据（版本+时间戳+Packages 页已人眼确认）；优先级从 **High（阻塞下次发版）** 降为 Medium（残余风险登记）；「残余风险」段保留（自建单点 / 旧 15 版本刻意保留 / 本机核验单路）。
- **INFRA-F67**：§剩余 (c) 按 B1 结论重写。
- **INFRA-F66**：按 B2 结论重写或整条移除。

- [ ] **Step 5: 跑一致性闸再提交**

```bash
pnpm audit:status-consistency && pnpm audit:doc-sync && pnpm audit:doc-de-mirror
```

三个都必须 PASS（`status-consistency` 核 STATUS/CHANGELOG/package.json 版本三角 + Active 计数；`doc-de-mirror` 会拦「在 STATUS 以外的地方写字面版本号」）。

- [ ] **Step 6: Commit**

```bash
git commit -m "$(cat <<'EOF'
docs(status): close INFRA-F73 — 1.1.0 live on the Gitea registry

改动内容:
- STATUS: 发版阻塞解除；包名/安装行改为 @ux-team + Gitea；修两处 stale（APID-01/02 已随 1.1.0 发布、无待发 changeset）
- backlog INFRA-F73 待办①⑤ 关闭并留证据，优先级 High→Medium
- backlog INFRA-F67/F66 按本轮 triage 结论更新
- tracker 追加本 session 完成项
改动类型: docs
测试: pnpm audit:status-consistency ✅ / audit:doc-sync ✅ / audit:doc-de-mirror ✅
EOF
)" docs/STATUS.md docs/internal/STATUS-CHANGELOG.md docs/internal/backlog.md docs/internal/retrospection/design-spec-canonical-alignment-tracker.md
git push origin master
```

---

### Task C2: 登记跨仓库/跨主机的独立项（本仓库**不做**，只登记）— ✅ **DONE 2026-07-30**（INFRA-F74/F75/F76/F77）

**Files:**
- Modify: `docs/internal/backlog.md`

> 这四项分属别的仓库/主机。**不要在 DS repo 里做**——2026-07-17 有过「修 bug 顺着根因滑进相邻大工程」的实证。逐条登记成可独立排期的条目，交 owner 决定何时开。

- [ ] **Step 1: 给 Gitea 加 TLS（安全，净退化中）**

登记要点：现在 consumer 每次 `pnpm install` 都把 Gitea token 明文发过**公网** IP（实测 `https` 打 `:3001` 不连接、443 无反代）。相对 GitHub Packages 的 https 是净退化。方案 = 主机侧加反代（服务器已有 **Caddy**，`caddy:2-alpine` 容器；`/etc/nginx` 不存在——DEPLOY.md 原写 nginx 已订正）。落地手段可复用 INFRA-F72 的 runner 跳板（act_runner 是 hostexecutor）。**动手前必读 backlog INFRA-F72 §三个坑。** 缓解（已落文档）= consumer token 只给 `read:package`。

- [ ] **Step 2: NOC `upstream-gate`（属 NOC 仓库）**

跑在 GitHub Actions，需 ① 验证 GitHub runner 能否出网到 `product-demo.tvustream.com:3001`（**未验**，非标准端口 + 自建主机）② 改 registry/token 配置。⚠️ 该账户 Actions 额度曾被吃光（INFRA-F71），可能暂时验不了。

- [ ] **Step 3: MicroApps/vue-app 迁移（属该仓库）**

`.npmrc` + `package.json` dep + import 全改。当前钉 `^0.6.1`，本来就落后 5 个版本。迁移指引已在 `docs/MIGRATION_TO_V1.md` 新增节（含 import 批量替换命令 + eslint plugin 名变更 + CI 改法）。⚠️ **consumer CI 的隐形断点**：原来教 consumer 在 GitHub Actions 里用自动注入的 `secrets.GITHUB_TOKEN` 装包，迁移后彻底失效，必须自备 Gitea token secret。

- [ ] **Step 4: 登记「tag→CI 发布链从未干跑过」为独立风险项**

本计划**不**为跑通它而凭空造版本号（`changeset:status` 已证明无待发内容；owner 2026-07-29 已就同性质问题拍板「版本号该表达代码变化，不该表达基建变化」）。登记内容：下一次真有 consumer-facing 改动要发版时，那是这条链的首跑；缓解 = `pr-checks.yml` 在 `push:master` 已跑同一套 vitest + `prepublishOnly`，闸先绿了再切 tag；恢复干跑能力的前置 = Gitea 升到 ≥1.23。

- [ ] **Step 5: 新 ID 先 grep，再写入**

```bash
grep -o "INFRA-F[0-9]*" docs/internal/backlog.md | sort -u -t F -k2 -n | tail -3
git log --oneline --all | grep -o "INFRA-F[0-9]*" | sort -u -t F -k2 -n | tail -3
```

取两者最大值 +1（`scripts/new-backlog.mjs` 可代劳）。**不要凭印象选号。**

- [ ] **Step 6: Commit**

```bash
git commit -m "docs(backlog): register the F73 follow-ups that belong to other repos/hosts" docs/internal/backlog.md
git push origin master
```

---

### Task C3: 让主机告警真的进 Slack（小项，收益高）— ✅ **DONE 2026-07-30**

> **实测结果（owner 2026-07-30 给出频道后当场闭环）**：`SLACK_CHANNEL=D0ANJFDQ431` 写进 `~/.claude/host-monitor/.env`（`-rw-------`）。四条断言的实际证据：① **负控先跑**（scratch `STATE_DIR`、无 `.env`、`STALE_DAYS=0`）→ 告警成立（`ALERT.txt` 生成、`freshness down`）且 `slack: sent` **0 次**，坐实原来的静默点就是 `slack_post` 那条 guard；② 正向跑 → log `slack: sent to D0ANJFDQ431`，并**独立**用 `conversations.history` 读回消息原文核实（`ts 1785386086.541249`、`bot_id B0APCQZ29MX`），不采信脚本自报；③ 无 `WARN slack post failed`；④ **此处与原计划有意偏离**：原计划要求「真实 state 未被改动」，实际**故意用了 production `STATE_DIR`** —— 因为用 `SLACK_CHANNEL=` 环境变量覆盖只能验 curl 通不通、验不到「`.env` 文件被读到」这条真链路（launchd 跑的就是这条）。代价 = 真实 state 的 `freshness` 被置 `down` 约 90 秒，随即用常规参数跑一遍恢复：`RECOVERED` 告警也真发到、`state` 四项回全 `up`、`ALERT.txt` 已删（三项均 `cat`/`ls` 亲验）。
>
> **顺带修掉两个缺陷**（tvu-host-monitor `0486885`）：① **监控在看错的门** —— [[INFRA-F74]] 几小时前把 registry 挪到 `https://…/gitea/`（consumer `.npmrc` 与 `git push` 都走这条），监控却仍探 `:3001`；两者今天都答 403/401 所以看着正常，但反代/TLS 层单独挂掉时监控会保持全绿而全公司 `pnpm install` 失败。已改探 https 入口，`:3001` 降级为**诊断行**（只 log、不告警、不进 state —— 那个端口日后可能被合法防火墙掉）。② **`registry` 把 `404` 当健康**是包还没发布时留的宽容，包已发布后变成假绿：反代故障注入返回 404，该检查照样 `up`。已收紧为 `200 401` —— 这个假绿正是靠注入才暴露的。
>
> **owner 待决（不是技术问题）**：`D0ANJFDQ431` 是 **DM**（`is_im: true`）而非频道 → 只有 owner 看得见告警，且日报发在同一条 DM 里、两者混流。要改团队频道 = 换那行 ID，且需先把 bot 邀进去（否则 `not_in_channel`）。
>
> 以下为原计划步骤，保留作对照。

**Files:**
- Create: `~/.claude/host-monitor/.env`（chmod 600，**不进仓库**）
- 参考：`tvu-design-system/tools/host-monitor/.env.example`、`bin/check-host.sh:80-125`

> **实测发现，这项比预想的小**：`SLACK_BOT_TOKEN` **已经可用**——`check-host.sh` 会 fallback 读 `~/.claude/daily-reports/.env`，而那里已有 `SLACK_BOT_TOKEN`。**缺的只有 `SLACK_CHANNEL`**（`slack_post()` 要求两者都非空才发）。

- [ ] **Step 1: 问 owner 要频道（这是需求，不是技术选择）**

2026-07-29 retrospect B1 的教训原文：告警类功能「送到哪里」和「检查什么」同等重要。要一个频道 id（`C…`，比 `#name` 稳，改名不会断）或频道名。**别自己挑。**

- [ ] **Step 2: 写 env**

```bash
mkdir -p ~/.claude/host-monitor
printf 'SLACK_CHANNEL=%s\n' '<owner 给的频道>' > ~/.claude/host-monitor/.env
chmod 600 ~/.claude/host-monitor/.env
ls -l ~/.claude/host-monitor/.env    # 期望 -rw-------
```

- [ ] **Step 3: 注入失败来测——不能只跑 happy path**

这是本计划里最容易糊弄过去的一步。retrospect B6：给这个 monitor 写的三条告警路径，注入失败后发现**三个 bug 全在「负责告诉你是否正常」的代码里**。所以必须真造一次故障。

`BASE_HTTP`（`bin/check-host.sh:46`）是**硬编码的、不吃环境变量**，所以注入方式 = 改一份临时副本，并把状态目录也换成临时的，免得污染真实 `state`（污染了会让下一次真实告警被边沿去重吃掉）：

```bash
SRC=tvu-design-system/tools/host-monitor/bin/check-host.sh
sed 's#^BASE_HTTP=.*#BASE_HTTP="http://127.0.0.1:59999"#' "$SRC" > /tmp/check-host-faulty.sh
grep -n '^BASE_HTTP=' /tmp/check-host-faulty.sh      # 断言改到了，且只有一行

mkdir -p /tmp/f73-monitor-test
SLACK_CHANNEL='<owner 给的频道>' STATE_DIR=/tmp/f73-monitor-test bash /tmp/check-host-faulty.sh
```

（`SLACK_CHANNEL` 在 `check-host.sh:85` 接受环境变量覆盖；`SLACK_BOT_TOKEN` 仍由 `~/.claude/daily-reports/.env` fallback 提供——这一跑同时验证了那条 fallback 链真的通。）

**四条断言，缺一不算通过**：
1. **故障确实被造出来了**：`/tmp/f73-monitor-test/monitor.log` 里 gitea / registry 两项为 `down`（若仍是 `up`，说明 sed 没生效，此时的 PASS 是空过 —— 这正是 INFRA-F72 round 3 踩过的坑）；
2. **Slack 频道里真的收到消息**（人眼看，不是看退出码）；
3. log 里有 `slack: sent to <channel>`，而**不是** `WARN slack post failed:`；
4. 真实 `~/.claude/host-monitor/state` **未被这次测试改动**（`cat` 一下，四项仍全 `up`）。

若出现 `WARN slack post failed`，看 body：`not_in_channel` → 需要 `/invite @<bot>` 到该频道；`channel_not_found` → 频道 id 写错；`invalid_auth` → token 问题。

- [ ] **Step 4: 清理注入 + 跑一次真实探测确认恢复态也正常**

```bash
rm -rf /tmp/f73-monitor-test /tmp/check-host-faulty.sh
bash tvu-design-system/tools/host-monitor/bin/check-host.sh
cat ~/.claude/host-monitor/state    # 期望四项全 up
tail -6 ~/.claude/host-monitor/monitor.log
```

- [ ] **Step 5: 若过程中改了 `check-host.sh`，提交到它自己的仓库**

```bash
cd tvu-design-system/tools/host-monitor
git status --porcelain
git commit -m "fix(alerts): <实际改了什么>" bin/check-host.sh
```

（`.env` 是 gitignored，**永远不提交**。该 repo 与 DS repo 无关，别混在一起提交。）

---

## 完成判据（整份计划）

- [ ] `http://product-demo.tvustream.com:3001/ux-team/-/packages` 页面上**用眼睛看到** `tvu-design-system 1.1.0`
- [ ] 独立 API 读回 `versions: 1.1.0` / `dist-tags: {"latest":"1.1.0"}`（原始输出已贴进报告）
- [ ] docs 站线上 bundle 与本地 sha256 一致，且安装说明是 `@ux-team/*` + Gitea registry
- [ ] `pnpm audit:status-consistency` / `audit:doc-sync` / `audit:doc-de-mirror` 三绿
- [ ] STATUS 顶部 `Last updated` = 今天；两处 stale（APID-01/02 待发）已修
- [ ] backlog INFRA-F73 优先级降级并留证据；F67/F66 按实测结论更新；四个跨仓库项各有独立 entry
- [ ] F67 16 条逐条有分类结论（不是「大部分是噪声」这种概括）
- [ ] F66 每个候选有「复现/不复现 + 量到的宽度」，未复现的**没有**被顺手改
- [ ] Slack 告警经**注入失败**验证过，频道里真收到消息
- [ ] 全程无 `git add -A` / `commit -a`；无在共享工作树上 `checkout -b`

## 已知反模式（本计划明确不做）

| ❌ 不做 | 理由 |
|---|---|
| 重打 `v1.1.0` tag 走 CI 发布 | 该 tag 指向改名前 `0f7fd8aa`，重打篡改 tag 语义 |
| 为「跑通 tag→CI」凭空造 v1.2.0 | `changeset:status` 证明无待发内容；owner 已就同性质问题拍板 |
| 删 GitHub Packages 上的 15 个旧版本 | rollback 退路 + vue-app 仍用 `^0.6.1` |
| 改 `package.json` engines 去对齐 CI | `>=20` 是对 consumer 的承诺 |
| 往 Gitea workflow 加 `playwright install --with-deps` | runner 非特权，必挂（INFRA-F40） |
| 恢复 GitHub `publish.yml` 的 tag trigger | 双 remote 会重复发布撞 409 |
| F66 按静态 grep 名单批量加 `:host` | 未 live 复现前不批量改动 |
| 在 DS repo 里做 TLS / NOC / MicroApps | 属别的仓库/主机，范围膨胀 |
| 无条件给主机 deploy hook 加 pre-pull `git clean` | 兄弟站点依赖 untracked 产物，会把一个 bug 换成另一个 |
