# G9 / R15 · 「发」层加固决策与线上真跑

> 范围：E#7。承接 [`G9-ECONOMICS.md`](./G9-ECONOMICS.md)（规则）与 [`RISK-REGISTER.md`](./RISK-REGISTER.md) §7（R15 残余）。
> 一句话：**不改 wire、不改共识、不加新高度**——只把「谁控制 factory」暴露给读模型、把注定失败的伪造 mint 拦在付费之前，并把剩下两个经济残余**明确记为已接受的开放代价**，附上将来若要采纳的精确改动路径。

## 1. 决策（用户选择：不改规则，只加固）

R15 列出三条残余。E#7 的处理：

| # | 残余 | 处理 | 是否改 wire / 共识 |
| --- | --- | --- | --- |
| ① | 无 BTC 级销毁，摩擦仅 BTC 手续费 | **保持**（已接受的开放代价） | 否 |
| ② | 创建者仍可向任意 `owner` 铸币（无收件人同意） | **保持**（已接受的开放代价） | 否 |
| ③ | 未 mint 的 factory「首位认领」可被抢跑 | **保持**（抢跑者仍需真金） | 否 |
| ④ | creator 未暴露在读模型（`stateJson` 不返回） | **已修**：读模型暴露 `creator` / `since` | 否（读模型新增可选字段） |

理由：①/② 属需求·法务·社区的外部验证问题，不是纯工程（与 `RISK-REGISTER.md` §8 一致）；在测试网冲刺期用一次 wire/共识变更去换它们，收益远小于风险（三实现同步 + 版本前缀 + 新分叉高度 + 成本重估）。③ 的结构性上限已被 G9 的「首笔 mint 认领 + 后续必须含创建者 + 空 factory 零状态」压到最低。

## 2. 本轮工程交付

1. **读模型暴露创建者（闭残余④）** —— `fto/src/logical.js#stateJson` 给每个 factory **按需**附加 `creator`（首笔 mint 的 `signers[0]`，hex8）与 `since`（创建高度）。仅在存在时附加 ⇒ 分叉前 / 未认领 factory 保持原 5 字段形状，**digest / state root 不受影响**（`stateRows` 从不读这些字段）。`/api/state`、explorer、`/api/issue` UI 因此能显示「谁控制这个 factory」。
2. **发层预检拦截（省手续费）** —— `fto/src/issue.js#preflightMint`（纯函数）+ Worker / 本地 `serve.mjs` 的 `/api/issue/psbt`、`/api/issue/broadcast` 在客户端声明 `signer` 时，先比对 factory 的 creator：不一致则 **400**，**在钱包签名付费之前**拒绝注定被链上丢弃的 mint。**advisory**：未声明 `signer`、链读不可达、或 factory 尚未被认领 → 放行，真相层（`indexer.js`）仍是唯一权威。
3. **浏览器「发」层**（`fto/canvas/issue.js`）随请求上报 `signer = 钱包地址`，并在处理器列表里显示创建者 / 「未认领」。
4. **线上真跑工具** `fto/tools/g9_live_drill.mjs`（可复现）：分叉后建厂 → 创建者首铸认领 → 第三方伪造 mint 被拒；`--plan`（离线 CI）/`--dry`/`--send`。
5. **CI**：`g9_live_drill --plan`（12）·`cf/issue_guard_check.mjs`（9，用合成链读驱动**真实** Worker 路由）· `issue_check` 增加纯函数断言（44）。

## 3. 线上真跑（testnet）

- 高度门槛：`ECON_HEIGHT = 1577517`、`AUTH_HEIGHT = 1577499`（测试网）。`--send` 在 tip 之上新建 factory ⇒ 必然走 G9 规则。
- 断言链：`/api/state` 出现 `creator = 创建者`、`since >= ECON_HEIGHT`；`/api/issue/psbt`（`signer=第三方`）→ 400 `creator-bound`；`/api/issue/broadcast`（未声明 `signer`）真实上链后，`/api/state.errors` +1 `mint: unauthorized mint`、第三方余额为 0、`/api/digest` 与伪造前**逐字节相同**。
- 证据（2026-10-03 实跑）：

| 项 | 值 |
| --- | --- |
| factoryId | `fa9405f9b879cfe0a6551534f9d890fb`（symbol `G9murzx3pf`） |
| 建厂 + 首铸 txids | `faf3186dadec68c553ab8dc2be4076547ba5ff4c31100ee8a9681ae695ae3236` · `535c1263331dd074140c9cbdbb4d7b45725e552a093a65685c79e8fdd8828fda` |
| 链上读模型 | `creator=be40c2093d3a70a9`（创建者 `bc1pcjkq…f20k8d`）· `since=1577625 ≥ 1577517` |
| 预检 | `/api/issue/psbt`（`signer=bc1pzsw…fxt7v`）→ **400** `factory … is creator-bound (G9): creator be40c2093d3a70a9 ≠ signer 332941453bfea7f1` |
| 伪造 mint txid | `b350ec2c19e75c5486044ac653fbd31033eab6979d9ba746bdc9eca874b806eb`（提示层放行 → 上链） |
| 真相层 | `/api/state.errors` `{index:42, type:"mint", reason:"unauthorized mint"}`；伪造者余额 **0** |
| digest（伪造前 = 伪造后） | `2a5309fcc52f96a58b0b3ef502c0618bd1015a81a7c393c682f6f0130fe2a9ec`（events 52） |
| `verify_onchain` | 4/0（`factory` + `mint`） |

- 复现：`node fto/tools/g9_live_drill.mjs --send`（创建者密钥 `FTO_WIF_FILE`，伪造者默认 `fto/data/buyer-priv.json`）。
- 部署基线：Pages `316a9085`（production alias `fractaltapeout.pages.dev`）。

## 4. 若将来采纳 ①/②（精确路径，非本轮）

两者都需要**新分叉高度 + wire/解码版本前缀**，历史 digest/root 不变（老事件按老规则重放）：

- **收件人同意**：`mint.owner` 须签名（或引入 `mint` 的 `to` 双签 / claim-override）。改动面：`wire.js`（新增字段/版本）、`logical.js`（携带新签名上下文）、`auth.js`/`indexer.js`（新规则门）、Python/C# 参考实现、`COMPATIBILITY.md` 版本前缀。
- **BTC 可证销毁**：首笔 mint 须含一笔可证销毁输出（对 `factoryId` 承诺）。改动面：`tapeout.js`（交易形状）、成本表（`econ_check` / `spam_check`）、索引器（校验销毁输出）。

采纳前须过 G8（法务）与外部审计（G5），并按 [`MAINNET-PUBLIC-READINESS.md`](./MAINNET-PUBLIC-READINESS.md) 更新决策表。

## 5. 回滚

- **代码**：`preflightMint` 为纯新增、读模型字段为可选新增 ⇒ 移除即回到原状，无状态迁移。
- **规则**：R15 残余本就是「保持」，无需回滚；`ECON_HEIGHT`/`AUTH_HEIGHT` 上主网前改为主网高度并重跑 CI。
