---
title: FR-182 P1 端到端證據（License Center DEV）
status: L4 實跑
lede: "DEV 從後台簽 3 張、離線開通 1 張、下載、CLI 驗章、內部 API 簽 2 張、線上開通 1 張、鎖定與型別拒收，每步指令與回應。序號與 token 已遮。"
---

# FR-182 P1 端到端證據

- 環境：DEV（`license_center_dev`，188:25432），LC commit `e255d00`（branch `ws-CM-3609-CM-3613`），2026-11-07 實跑。
- 服務：本機 `flask run --port 5072`（5062 被另一個程序占用，改用 5072）。`/healthz` → `{"checks":{"db":"ok","keys":"ok"},"status":"ok","version":"e255d00"}`。
- 打折說明：後台頁用 Flask `test_client`（帶 CSRF token 與已登入 session）而非真瀏覽器，工作坊不開視窗；機器 API 兩支（內部簽發、查清單）用真 `curl`；線上開通因本機 IP 先前已被鎖（見步驟 8 說明），改用 `test_client` 指定來源位址。
- 序號、token 一律遮罩；機器碼為假資料（`sha256("e2e-l4-machine")` 之類）。

## 1. 後台簽 3 張

`POST /agent-licenses/issue`（customer_code=E2E-L4、order_no=ORD-E2E-L4、issued_to=「L4 驗收客戶」、formal、365 天、count=3、issuer=e2e-l4）

```
→ 302 /agent-licenses/batch/ab-20261106-ff36ca
批次頁三張照號：alic-7f00c4aba500、alic-235f8591a2e4、alic-ffdbcad276b3
```

## 2. 離線開通 1 張

`POST /agent-licenses/activate`（序號 `<遮>`、機器碼 `fcf2dff5f1d52916b53af00c6b52b6e9813921d827f3279dbd090e38cad520e5`）

```
→ 302 /agent-licenses/activate/alic-f6e55c70a4d8/result
```

開通後出現綁機的新照號 `alic-f6e55c70a4d8`（接力鏈事件 `activate`，沿用該台 `lineage_id`）。

## 3. 下載

```
GET /agent-licenses/alic-f6e55c70a4d8/download → 200
Content-Disposition: attachment; filename="alic-f6e55c70a4d8.license"（948 bytes）
開頭：-----BEGIN GUIDANT LICENSE-----  eyJhbGciOiAiRWQyNTUxOSIsICJmb3JtYXRfdmVyc2lvbiI6IDIs…
GET /agent-licenses/batch/ab-20261106-ff36ca/download.zip → 200 application/zip（1551 bytes）
```

## 4. CLI 驗章

```
$ PYTHONPATH=src poetry run python -m license_center.cli.main agent-verify alic-f6e55c70a4d8.license
驗章通過
payload_type = agent_license
license_id = alic-f6e55c70a4d8
customer_code = E2E-L4
order_no = ORD-E2E-L4
issued_to = L4 驗收客戶
parent_license_id = None
type = formal
issuer = e2e-l4
issued_at = 2026-11-06T19:34:24.019613+00:00
starts_at = 2026-11-06T19:34:23.841017+00:00
expires_at = 2027-11-06T19:34:23.841017+00:00
expiry_policy = {'notify_days_before': 30, 'grace_days': 14}
machine_fingerprint = fcf2dff5f1d52916b53af00c6b52b6e9813921d827f3279dbd090e38cad520e5
product_major = 1
kid = 2ce3bb59a6f5a2ce
status = valid（正常，距到期 364 天）
```

## 5. 內部 API 簽 2 張

token 名 `e2e-l4`（明文不貼）。

```
$ curl -s -X POST http://127.0.0.1:5072/api/internal/agent/issue \
    -H "X-API-Token: <遮>" -H 'Content-Type: application/json' \
    -d '{"customer_code":"E2E-L4-API","order_no":"ORD-E2E-L4-API","issued_to":"L4 API 客戶","days":365,"count":2}'
```
回應（信封省略）：
```
{"batch_id":"ab-20261106-65539b",
 "licenses":[{"activation_code":"<遮>","license":{…v2 信封…},"license_id":"alic-e2bf6e8ca02c"},
             {"activation_code":"<遮>","license":{…v2 信封…},"license_id":"alic-dd906d670944"}]}
```

```
$ curl -s "http://127.0.0.1:5072/api/internal/agent/licenses?customer_code=E2E-L4-API" -H "X-API-Token: <遮>"
```
兩筆 `status:"valid"`、`activated:false`、`machine_fingerprint_prefix:null`。

## 6. 線上開通 1 張

序號取自步驟 5 第一張；機器碼 `sha256("e2e-l4-online")`；來源位址 10.20.30.41。

```
POST /api/activation/agent/activate  {"activation_code":"<遮>","machine_fingerprint":"7538f858…c3d4"}
→ 200 {"license":{…v2 信封…},"license_id":"alic-99bc2379c0be"}
同序號再開通        → 409 {"error":"此開通序號已被使用"}
機器碼 "abc"        → 400 {"error":"machine_fingerprint 形狀不正確：應為 64 個十六進位字元（收到 3 字元）——請對照 Agent 主控台顯示的機器碼重新確認"}
```

線上開通取得的照 `agent-verify`：`驗章通過`、`license_id = alic-99bc2379c0be`、`machine_fingerprint = 7538f8586bf55310527c897f981b7b381a7cdeb0ece3202a1181d9a721adc3d4`、`status = valid（正常，距到期 364 天）`。

## 7. 清單（五張照現況）

| 客戶 | 現行照 | 狀態 |
|---|---|---|
| E2E-L4 | 3 台中 1 台已離線開通（`alic-f6e55c70a4d8`，綁機）、2 台未開通 | valid |
| E2E-L4-API | `alic-99bc2379c0be` 已線上開通（指紋前 12 碼 `7538f8586bf5`）、`alic-dd906d670944` 未開通 | valid |

查清單 API（步驟 6 之後）：`alic-99bc2379c0be valid activated=True 7538f8586bf5`、`alic-dd906d670944 valid activated=False None`。
後台清單頁 `GET /agent-licenses/` → 200，含 E2E-L4 的客戶欄位。

## 8. 失敗鎖定（與主系統開通同 IP 合併計數）

先前在本機 IP（127.0.0.1）驗證時已觸發過 429：
`{"error":"此序號嘗試失敗次數過多，請稍後再試或改走離線開通"}`，所以步驟 6 改用 `test_client` 的 `environ_base={"REMOTE_ADDR": …}` 換來源。

來源 10.20.30.42，錯序號連打：

```
第1次～第5次  → 404 {"error":"找不到此開通序號"}
第6次         → 429 {"error":"此序號嘗試失敗次數過多，請稍後再試或改走離線開通"}
同 IP 打主系統 POST /api/activation/activate        → 429（同一句）
同 IP 用「正確」Agent 序號打 Agent 開通             → 429（鎖定期間連對的也擋）
```

## 9. 主系統照餵 agent-verify 被拒

取 `license_issuance` 最新一筆主系統照（`lic-9bcece489f42`）：

```
$ PYTHONPATH=src poetry run python -m license_center.cli.main agent-verify main.license
驗章失敗: payload 類別不符：預期 agent_license，實際 license
```
log：`WARNING … CLI Agent 照驗章失敗 file=… reason=payload 類別不符：預期 agent_license，實際 license`

## 結論

後台簽發／離線開通／下載／內部 API／線上開通／共用鎖定／型別拒收皆符合設計。DEV 殘留測試資料：客戶碼 `E2E-L4`、`E2E-L4-API` 共 5 台，以及 api_tokens 一筆 `e2e-l4`、`login_attempts`／鎖定計數若干（15 分鐘後自然失效）。未動 STG／POC；STG／POC 上版時需 `alembic upgrade head`（含 `6a2b3b681046`）。
