# FR-072 Console 安裝教學影片套件（ttyd + Playwright 錄影管線）— 設計文件

> 狀態：✅ 全案收口（2026-09-03，母卡 CM-1517 Done）｜建立日期：2026-09-02｜無前作｜無獨立討論稿——設計討論在對話中完成

## 變更紀錄

| 日期 | 變更 | 對應 |
|------|------|------|
| 2026-09-02 | 初版設計定案，D1–D6 全數拍板（含 2026-09-02 spike 驗證結果） | FR-072 母案 |
| 2026-09-03 | 全案收口:12 子卡+母卡全 Done。六支影片+完整版交付(④以 Agent 1.0.0 重錄);install-test 已接線未實跑(出貨前先試跑,build README 已標)。衍生:FR-073(Agent 安裝形態)、FR-074(檢測工具影片) | CM-1517 收口 |
| 2026-09-02 | 影片系列 5→6 支（增⑥日常維運 CLI，CM-1532）；查證確認安裝/CLI/license/精靈全段已實作可實錄（B3 依賴解除）；步驟細節回寫 B 段各卡 | CM-1524~1532 |
| 2026-09-02 | 錄影標準帳號定為 `guidantai`（user 拍板）：190 上新建、ssh 免密＋docker group＋sudo NOPASSWD 已實測；所有錄影與 install-test 一律走此帳號，spike 當時用的 `jedi` 停用於本案 | CM-1521 / CM-1522 |

---

## 1. 需求背景與目標

- 現有教育訓練影片走 test repo `training` 套件（Cucumber + Playwright + overlay 字幕），**只能錄 web UI**。
- FR-065 落地版安裝包需要 **console 操作**的教學影片：系統需求、主產品安裝、License 申請匯入、Agent 安裝、首次登入——這些流程的主戰場是 terminal，不是瀏覽器。
- **Spike 已驗證（2026-09-02）**：ttyd 起在 Mac 包 `ssh guidantai@192.168.50.190`，Playwright 開 `localhost:7681` 打指令、疊 overlay、錄影出片，**全鏈可行**。
- 關鍵技術發現：ttyd 的 xterm 是 **canvas 渲染，DOM 讀不到畫面文字**；但 ttyd 掛了全域 `window.term`，走 xterm buffer API（`term.buffer.active.getLine(i).translateToString(true)`）可讀全畫面文字——**等待/斷言靠它**。
- 錄影目標機 190（guidant-ai-e2e，Ubuntu 22.04，4C/31G/293G，Docker 29.2.1）已於 2026-09-02 **全清**（容器/image/volume/檔案全清，只留 OS + Docker），狀態＝檢查點 `clean`。

---

## 2. 分工概述（30 秒版）

整件事分三段做：**A 先把「用瀏覽器錄 terminal」的地基蓋好 → B 用地基錄出五支安裝教學影片 → C 同一組腳本翻面當自動化安裝測試，掛進出包驗收**。

| 階段 | 做什麼（白話） | 產出 | 完成怎麼判定（決策者檢查法） |
|------|--------------|------|---------------------------|
| **A 錄影地基** | 讓既有的 Cucumber 錄影套件學會操作 terminal：新的 step（「在終端機執行…」「等待終端機出現…」）、ttyd 起停、190 主機的檢查點探測與重置 | console steps + ConsoleTerminal 封裝 + ttyd fixture + 檢查點腳本 + 撰寫指南 | 跑一次示範場景，打開產出的 mp4 看：terminal 畫面清楚、指令有打出來、overlay 說明字卡有疊上去 |
| **B 五支影片** | 照安裝流程順序寫五支 feature 檔並逐支出片：①系統需求 ②主產品安裝 ③License 申請匯入 ④Agent 安裝 ⑤首次登入 | 5 支獨立 mp4 + 1 支合併版 | 逐支打開 mp4 從頭看到尾：步驟看得懂、畫面上沒有任何密碼/金鑰/license 內容（對照 §4.4 檢查清單逐項打勾） |
| **C 安裝測試** | 同一組 feature 關掉字卡、加上硬斷言（ssh 直查服務真的起來了），變成「乾淨機實裝驗收」，掛進出包流程 | @install-test 執行模式 + 出包驗收文件指標 | 對一個安裝包跑一次 `@install-test`：包好的過、故意弄壞的包（如抽掉一顆 image）要紅 |

順序 **A → B → C**；B3（License 影片）依賴 license 流程文件定稿。

---

## 3. 決策定案（D1–D6）

| # | 決策 | 定案 |
|---|------|------|
| **D1** | 錄影引擎 | **ttyd + Playwright（方案 B）**，非 VHS / asciinema。理由：overlay 說明系統（training-helper.js 8 種效果）是 DOM 注入，ttyd 把 terminal 變網頁後**零改動繼承**；且是唯一能 web + console 混排的方案。VHS 排除：無 overlay 能力，說明插入只能後製或 echo 土法。asciinema 排除：偏人工實錄，不適合正式管線。 |
| **D2** | BDD 同軌 | **console 走既有 Cucumber 套件，不另起爐灶**。混排場景（如首次登入：瀏覽器＋terminal 同片）必須同一 world；overlay / SKIP_OVERLAYS / preview / 錄影管線全繼承；feature 檔雙用途（training mode＝錄影、test mode＝安裝測試）。 |
| **D3** | 分段錄製 | **5 檢查點狀態機** `clean → bundle-uploaded → installed → licensed → agent-ready`，每段影片＝兩檢查點之間。每支 feature 開頭跑**不入鏡的 ssh 前置檢查**（190 狀態不符檢查點直接 fail，不會錄出錯誤前提的片）。重錄第 N 段＝把 190 回到檢查點 N-1：**VM snapshot 優先**，不可行則替補腳本用 SKIP_OVERLAYS 全速重放前段指令。合併走既有 `merge-phase-videos.sh` 模式加 install 章節清單，各段亦保留獨立成片。 |
| **D4** | 敏感內容三層防線 | ①**分鏡設計讓敏感值不上畫面（首選）**：私鑰/license/token/API key 一律禁 `cat`，只准 `ls` / 指紋（`ssh-keygen -lf`）/ `.sample` 佔位符檔；密碼走 `read -s` 或免密；敏感 env 走「以隱藏方式載入環境變數」step（畫面只見 source 指令）。②**overlay 區域遮罩**：從 `window.term` buffer + cell 尺寸算行座標蓋色塊，留給預期外輸出。③**後製 ffmpeg 打碼**：事故補救，不當常規。紀律：feature 檔是版控檔案，**禁真值**（「憑證不入版控」延伸＝「憑證不入影片」）；出片前過敏感內容檢查清單（§4.4）。 |
| **D5** | 自動化安裝測試 | 同一組 feature 加 **@install-test 模式**＝關 overlay＋硬斷言（畫面文字之外，ssh 直查 exit code / 服務狀態）＋環境重置，**掛進出包驗收當 release gate**。呼應 T-6.6 教訓（驗收環境打折掩蓋真缺口——乾淨機實裝才能抓到「第三方 image 沒進包」類問題）。 |
| **D6** | skill / 文件落點 | **canonical 在 test repo**（`console-recording-guide.md` + test repo CLAUDE.md 補段），BE repo 只在 build 文件加指標。暫不開 `.claude/skills` 條目，流程跑穩後再升格。 |

---

## 4. 詳細設計

### 4.1 架構

Mac 端一機包辦錄影管線，190 **零安裝**（只被 ssh 進去操作）：

<pre class="mermaid">
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
    subgraph MAC["Mac（錄影主機）"]
        CUKE["Cucumber feature<br/>（training 套件）"] --> PW["Playwright<br/>（開 localhost:7681<br/>打字 / overlay / 錄影）"]
        PW --> TTYD["ttyd -p 7681 -W<br/>包 ssh 指令"]
        PW --> MP4["mp4 出片<br/>（既有錄影管線）"]
    end
    TTYD -- "ssh guidantai@192.168.50.190" --> TARGET["190 錄影目標機<br/>（guidant-ai-e2e，零安裝）"]
    PW -. "window.term buffer API<br/>讀畫面文字（等待/斷言）" .-> TTYD
</pre>

### 4.2 新增元件（全在 test repo）

| 元件 | 內容 |
|------|------|
| `training/steps/console/console.steps.js` | 六個 step：`終端機已連線到安裝目標主機`／`主機狀態符合檢查點「」`／`在終端機執行「」`／`等待終端機出現「」逾時「」秒`／`確認上一個指令成功`／`以隱藏方式載入環境變數「」` |
| `training/pages/console/ConsoleTerminal.js` | 封裝 `window.term` buffer 讀取（`term.buffer.active.getLine(i).translateToString(true)`）、打字、開場 clear |
| ttyd 起停 fixture | Before/After hook 或外部腳本：起 `ttyd -p 7681 -W ssh guidantai@192.168.50.190`，場景結束關閉 |
| 檢查點定義＋探測 | ssh 查 `/srv/guidant-ai`、`docker ps`、license 狀態等，對應 §4.3 五檢查點 |

### 4.3 檢查點狀態機（D3）

<pre class="mermaid">
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
stateDiagram-v2
    [*] --> clean : 190 全清（2026-09-02 已達成）
    clean --> bundle_uploaded : 影片②前段（傳包）
    bundle_uploaded --> installed : 影片②（install.sh → 服務起）
    installed --> licensed : 影片③（License 申請匯入）
    licensed --> agent_ready : 影片④（Agent 安裝）
    agent_ready --> [*] : 影片⑤（首次登入，不再改機器狀態）
    note right of clean
        重錄第 N 段＝回到檢查點 N-1：
        VM snapshot 優先；
        不可行則替補腳本
        SKIP_OVERLAYS 全速重放前段指令
    end note
</pre>

- 每支 feature 開頭跑**不入鏡的 ssh 前置檢查**：190 狀態不符對應檢查點直接 fail。
- 每段影片＝兩檢查點之間；合併走既有 `merge-phase-videos.sh` 模式加 install 章節清單，各段亦保留獨立成片。

### 4.4 敏感內容防線（D4 落地細則）

| 層 | 手段 | 定位 |
|----|------|------|
| ① 分鏡設計 | 敏感值不上畫面：私鑰/license/token/API key 禁 `cat`，只准 `ls`／指紋（`ssh-keygen -lf`）／`.sample` 佔位符檔；密碼走 `read -s` 或免密；敏感 env 走「以隱藏方式載入環境變數」step（畫面只見 source 指令） | **首選，設計時就解決** |
| ② overlay 區域遮罩 | 從 `window.term` buffer + cell 尺寸算行座標蓋色塊 | 留給預期外輸出 |
| ③ 後製 ffmpeg 打碼 | 逐幀打碼 | 事故補救，**不當常規** |

紀律：
- feature 檔是版控檔案，**禁真值**——「憑證不入版控」延伸為「憑證不入影片」。實際值一律「請查 `.env` 或部署文件」。
- **出片前過敏感內容檢查清單**（收進 A3 的 console-recording-guide.md）：逐支影片檢視有無密碼/私鑰/license 內容/token 入鏡。

### 4.5 已知坑（spike 實測）

| 坑 | 對策 |
|----|------|
| xterm canvas 渲染，DOM 無文字 | 走 `window.term` buffer API 讀畫面 |
| ssh 開場有 Ubuntu motd 雜訊 | 錄影開始前先 `clear` |
| ttyd `--once` 一連線即退場 | 正式 fixture **不用 `--once`**，用 After hook 關 |
| 「確認上一個指令成功」兩模式語意不同 | training mode 靜默（不打斷錄影）、install-test mode 硬斷言 |

### 4.6 影片系列（6 支；2026-09-02 查證後定稿，全段已實作可實錄）

查證來源：`docs/user-manual/onprem/` 02/03/04 章＋189 實機盤點（1.18.0 bundle `--help` 實測）。

| # | 影片 | 內容 | 卡 |
|---|------|------|----|
| ① | 系統基本需求 | `sha256sum -c` 驗包＋`sudo ./install.sh --check-only`（只驗不動） | CM-1524 |
| ② | 主產品安裝 | tar 解包 → `sudo ./install.sh`（互動只問主機名/資料目錄/port 三題，不問密碼）→ 八步自動 → 印站台網址＋一次性設定碼＋機器碼 | CM-1525 |
| ③ | License 申請與匯入 | 機器碼（精靈第 3 步／保底 `sudo guidant fingerprint`）＋開通序號 → 原廠回 `.license` → 登入後「License 開通」頁上傳開通；線上路徑輸序號直接開通 | CM-1526 |
| ④ | Agent 安裝 | 檢測 Agent 安裝＋`guidant agent-check` 確認對接 | CM-1527 |
| ⑤ | 首次登入 | **web + console 混排**：開站 → 貼一次性設定碼 → 自建管理員（帳號名禁用 `admin`）→ 顯示機器碼 | CM-1528 |
| ⑥ | 日常維運指令 | `guidant status/logs/restart/credentials/stop/start` 高頻示範；`rotate-credentials`/`uninstall` 只講不跑；credentials 輸出含真密碼依 D4 遮罩 | CM-1532 |

### 4.7 依賴與風險

- ~~FR-065 installer 進行中~~ → **2026-09-02 查證：影片所需全段已實作**（安裝/CLI 10 支/license 離線+線上/精靈，188 真機全鏈走通）。殘餘風險僅剩 GA 前若流程再變更需重錄對應段——分段制（D3）把成本壓到最小。
- **PROD 簽章鑰未完成**（FR-065 唯一出貨前置）：**不擋錄影**——錄影用 STG/POC 測試鑰包。
- **190 是否 VM／能否 snapshot 待 user 確認**：不阻塞——A2 兩路線並行設計（snapshot 優先、替補重放腳本兜底）。

---

## 5. 拆分（3 子需求 × 12 子任務；2026-09-02 增 B7）

順序 **A → B → C**；~~B3 依賴 license 流程文件定稿~~（2026-09-02 查證：license 流程已實作走通，依賴解除）。

### FR-072.A console 錄影基礎設施 — 依賴：無

| # | 子任務 | 驗收 |
|---|--------|------|
| A1 | console steps + ConsoleTerminal + ttyd fixture | 重跑 spike 等價場景出片 |
| A2 | 檢查點定義＋重置機制 | clean 重置腳本實跑一次＋各檢查點探測函式 |
| A3 | console-recording-guide.md + test repo CLAUDE.md 補段 | 含 D4 三層防線、金鑰紀律表、出片前檢查清單 |

### FR-072.B 安裝教學影片系列 — 依賴：A

| # | 子任務 | 驗收 |
|---|--------|------|
| B1–B5、B7 | 六支 feature（①系統需求 ②主產品安裝 ③License ④Agent ⑤首次登入 ⑥日常維運） | 每支獨立出片＋敏感內容檢查過 |
| B6 | 合併出片（六支） | merge 清單＋全系列檢視 |

### FR-072.C 自動化安裝測試 — 依賴：A、B

| # | 子任務 | 驗收 |
|---|--------|------|
| C1 | @install-test 模式（關 overlay＋硬斷言＋重置串接） | 對安裝包實跑一次全綠 |
| C2 | 掛進出包驗收流程（BE build 文件加指標） | 出包 SOP 文件含 install-test 步驟 |

---

## 6. 驗收（端到端）

1. A 段完成後重跑 spike 等價場景，產出 mp4 可正常播放且 overlay 字卡正確疊加。
2. 五支影片逐支獨立出片，逐支過敏感內容檢查清單（畫面無密碼/私鑰/license/token）。
3. 合併版影片章節完整、順序正確；各段獨立成片保留。
4. `@install-test` 模式對一個安裝包實跑：正常包全綠；缺件包（如抽掉第三方 image）確實變紅。
5. console-recording-guide.md 落地 test repo，BE build 文件有指標指過去。
