# 实战：设计 `pg36_shop` 生产蓝图

LLMS 索引： [llms.txt](/llms.txt)

---

第 18 章实战与前几章不同：

```text
no setup
no DDL
no DML
no reset
no service deployment
```

它把现有证据读出来，检查蓝图是否有资格进入下卷。风险等级是 L0。

## 18.6.1 选择保留在 PostgreSQL 内的能力 {#item-18-6-1}

### 前置状态

实验要求第 4、13–17 章的最终 fixture 保留在同一本地开发实例：

```text
database=pg36_shop
PostgreSQL major=18
session_user=postgres
pg36_owner=NOLOGIN non-superuser
pg36_app=LOGIN non-superuser
ch04-v1 physical model
ch13 routine guard
ch14 extension lifecycle
ch15 search quality
ch16 spatiotemporal
ch17 analytics/FDW + two shard database shells
```

缺失时本章直接失败，返回前章重建；它不会悄悄修复。

### 私有连接

沿用：

```ini
[pg36-admin]
host=/path/to/socket-or-host
port=5432
dbname=pg36_shop
user=postgres
```

```bash
chmod 600 /path/to/pg_service.conf
export PGSERVICEFILE=/path/to/pg_service.conf
export PGSERVICE=pg36-admin
```

密码或其他 secret 放在批准的连接机制中，不放命令行、脚本、evidence 或 Git。

### 先读实验合同

[`lab-contract.md`](/labs/ch18/lab-contract.md)
规定：

```text
risk=L0 read-only
target=confirmed local fixture
allowed=catalog reads + JSON validation + evidence files
forbidden=DDL/DML/roles/extensions/deploy/failover/backup/reset
pigsty_l1=not-run
```

本章脚本没有 `setup`/`reset` action。这不是遗漏，而是用接口形状表达安全
边界。

### 上卷前置复核

`task.sh` 依次调用：

```bash
static/labs/ch04/task.sh verify
static/labs/ch13/task.sh verify
static/labs/ch14/task.sh verify
static/labs/ch15/task.sh verify
static/labs/ch16/task.sh verify
static/labs/ch17/task.sh verify
```

它们只验证 retained fixture。第 17 章还分别连接 `pg36_shard_a` 和
`pg36_shard_b`，防止协调端看似正常而远端状态已经漂移。

### 只读事务

每个 catalog capture 都显式开始：

```sql
BEGIN TRANSACTION
ISOLATION LEVEL REPEATABLE READ
READ ONLY;
```

再 include
[`context.sql`](/labs/ch18/context.sql)。

context 验证：

```text
current_database = pg36_shop
server_version_num in 18.x
session_user = postgres superuser
can inspect pg36_owner
owner role = NOLOGIN, non-superuser
app role = LOGIN, non-superuser
ch04 schema_version present
```

脚本结束 `COMMIT`，但 read-only 事务没有业务变更。

### 平台状态

[`platform-state.sql`](/labs/ch18/platform-state.sql)
输出稳定 key/value：

```text
database=pg36_shop
server_major=18
server_version=18.6 (formal run)
session_user=postgres
in_recovery=false
model_version=ch04-v1
relation_checksum=f8a7bfae59c6d16cd323abecfefe1014
pigsty_reference=4.4
pigsty_l1=not-run
mutation=none
```

`mutation=none` 不是只靠自报：`all` 在前置复核后抓两轮，并对状态与 catalog
逐字节 `cmp`。

### 能力快照

[`capability-snapshot.sql`](/labs/ch18/capability-snapshot.sql)
把数据库事实压成九行：

| capability | lifecycle | evidence |
|---|---|---|
| relational core | accepted | `ch04-v1` |
| atomic database logic | accepted with scope | `ch13-routine-guard-v1` |
| lexical/fuzzy search | accepted | `pg_trgm:1.6` |
| semantic search | pilot | `vector:0.8.4` |
| spatiotemporal | conditional | `btree_gist:1.8,postgis:3.6.4` |
| analytical federation | lab-only | `postgres_fdw:1.2` |
| search quality fixture | accepted | `ch15-search-v1` |
| spatiotemporal fixture | accepted | `ch16-spatiotemporal-v1` |
| analytics fixture | accepted | `ch17-analytics-v1` |

这里有意把“扩展安装事实”和“生命周期判断”并列。SQL 能证明版本存在，
生命周期还来自前章的质量、安全与运维边界。

### extension catalog

[`extension-catalog.sql`](/labs/ch18/extension-catalog.sql)
记录：

```text
extension_name
extension_version
schema_name
owner_name
relocatable
comment
```

正式 fixture 精确包含六项：

```text
btree_gist 1.8
pg_trgm 1.6
plpgsql 1.0
postgis 3.6.4
postgres_fdw 1.2
vector 0.8.4
```

教学扩展必须保留 `pg36 chXX ... safe to rebuild` marker。marker 只用于本书
精确识别，不应照搬成生产对象治理方案。

### schema 与 role catalog

[`schema-catalog.sql`](/labs/ch18/schema-catalog.sql)
冻结：

```text
shop / shop_private
shop_ch13 / shop_ch14 / shop_ch15
shop_ch16 / shop_ch16_ext
shop_ch17 / shop_ch17_ext
```

每项必须由 `pg36_owner` 拥有并保留精确 comment。

[`role-catalog.sql`](/labs/ch18/role-catalog.sql)
只导出三种相关身份，避免把环境中其他角色误收入出版 fixture。审查器验证：

```text
pg36_app   LOGIN, !SUPERUSER, !BYPASSRLS
pg36_owner NOLOGIN, !SUPERUSER
postgres   LOGIN, SUPERUSER (formal local admin)
```

### 为什么不探测 Pigsty

当前实例不是本章声明的目标 Pigsty cluster。若脚本从本机进程名或目录猜测
Pigsty 状态，会产生伪证据。

所以蓝图准确写：

```text
Pigsty reference mapping = documented
Pigsty L1 = not-run
```

第 19 章在明确 target/inventory 后才执行环境验收。

## 18.6.2 选择外置组件及其数据契约 {#item-18-6-2}

### 五份合同先于产品选型

[`external-data-contracts.json`](/labs/ch18/external-data-contracts.json)
包含：

```text
product-cache-v1
order-events-v1
product-media-v1
analytics-export-v1
external-search-projection-v1
```

每份必须有 17 个核心字段，包括：

```text
id / kind / status / owner / authority
source / sink / freshness / delivery / ordering
idempotency / rebuild / failure_mode / reconciliation
deletion / security / exit
```

这比简单画一条箭头严格得多。

### cache 合同

关键规则：

```text
business authority=PostgreSQL
cache identity=product_id + source version
TTL <= 300 seconds proposal
stale version never replaces newer
outage falls back to bounded PostgreSQL reads
namespace can be discarded and rebuilt
```

validator 专门扫描 cache authority。若把：

```json
"product_business_state": "cache"
```

则报：

```text
E_CACHE_AUTHORITY
```

### order event 合同

```text
business state + publication intent -> PostgreSQL transaction
delivery/replay log                 -> event bus
delivery                            -> at-least-once
ordering                            -> per order_id, no global order
idempotency                         -> stable event_id
```

publish lag 仍是 `chapter 24 pending`。写出 pending 比伪造一个 P99 更准确。

### media 合同

```text
bytes -> object storage
identity/owner/state/checksum -> PostgreSQL
immutable object version
visible only after checksum + metadata agree
orphan upload quarantined
two-phase deletion
```

它是 `accepted-boundary`，表示“字节外置”这一边界已选择，不表示具体 object
provider 已选择或 L1 已通过。

### analytics 合同

```text
source=snapshot or CDC
sink=versioned immutable analytical tables
watermark on every dataset
last complete state
row/aggregate/partition checksum
tombstone propagation
new generation rebuild
```

这份合同会在第 29 章的数据迁移与 CDC 状态机中具体化。

### external search 合同

状态：

```text
deferred-until-trigger
```

进入条件不是“想用”，而是 PostgreSQL 搜索基线在质量、规模、语言或独立
SLO 上失败。

启用前必须证明：

```text
snapshot + idempotent changes
monotonic product version/tombstone
index generation
quality golden
document count/payload hash
alias swap
fallback classification
exit to PostgreSQL
```

### 正向文档关系

[`baseline-v1.6-proposal.json`](/labs/ch18/baseline-v1.6-proposal.json)
引用所有合同。每项 capability 又引用它需要的合同。

validator 检查：

```text
blueprint contract set == declared contract set
capability references exist
external/pilot/conditional placement has trigger
every capability has owner and evidence
```

这能发现拼写、遗漏和结构漂移。

### 七个对抗性反例

[`negative-cases.json`](/labs/ch18/negative-cases.json)
不是伪造七份静态错误文件，而是对正确文档做 JSON path mutation：

| 反例 | 期望错误 |
|---|---|
| 无 evidence 宣称 Pigsty L1 passed | `E_L1_EVIDENCE` |
| 清空全部 exit path | `E_EXIT_PATH` |
| cache 成为商品业务权威 | `E_CACHE_AUTHORITY` |
| 删除消息合同 rebuild | `E_CONTRACT_FIELD` |
| loopback FDW 允许生产 | `E_FDW_LAB_ONLY` |
| 删除 production offering objective | `E_SERVICE_OBJECTIVE` |
| 删除 vector pilot gate | `E_EXTENSION_GATE` |

测试要求实际错误码与期望码精确相同。若错误文档意外通过，或被另一个更早的
无关规则拦截，negative suite 都失败。

### 为什么 validator 只用 Python 标准库

[`validate.py`](/labs/ch18/validate.py)
只依赖：

```text
argparse
copy
hashlib
json
pathlib
```

目的不是排斥 schema 工具，而是让读者在最小环境中运行并看到业务策略代码。
生产平台可以再加 JSON Schema、OPA、CI policy 或签名。

### canonical hash

报告为四份核心文档计算 canonical JSON SHA-256：

```text
sort object keys
compact separators
UTF-8
preserve array order
```

这避免 indentation/key order 影响内容身份，同时让 gate/capability 顺序仍然
有意义。

注意：canonical hash 证明文档未变，不证明内容正确；内容正确还靠人工决策、
数据库证据和负例。

## 18.6.3 输出服务目录草案、架构 ADR 与下卷验收问题 {#item-18-6-3}

### 资产目录

```text
static/labs/ch18/
├── lab-contract.md
├── architecture-adr.md
├── platform-map.mmd
├── pigsty-declaration.example.yml
├── service-catalog.json
├── external-data-contracts.json
├── baseline-v1.6-proposal.json
├── lower-volume-gates.json
├── negative-cases.json
├── context.sql
├── platform-state.sql
├── extension-catalog.sql
├── schema-catalog.sql
├── role-catalog.sql
├── capability-snapshot.sql
├── validate.py
├── review.py
└── task.sh
```

没有生成的 evidence 被提交到源码目录。

### 单独验证文档

```bash
python3 static/labs/ch18/validate.py \
  --blueprint static/labs/ch18/baseline-v1.6-proposal.json \
  --catalog static/labs/ch18/service-catalog.json \
  --contracts static/labs/ch18/external-data-contracts.json \
  --gates static/labs/ch18/lower-volume-gates.json
```

预期：

```json
{
  "status": "ok",
  "counts": {
    "offerings": 4,
    "extension_bundles": 5,
    "contracts": 5,
    "capabilities": 9,
    "lower_volume_gates": 18,
    "exit_paths": 5
  }
}
```

加负例：

```bash
python3 static/labs/ch18/validate.py \
  --blueprint static/labs/ch18/baseline-v1.6-proposal.json \
  --catalog static/labs/ch18/service-catalog.json \
  --contracts static/labs/ch18/external-data-contracts.json \
  --gates static/labs/ch18/lower-volume-gates.json \
  --negative-cases static/labs/ch18/negative-cases.json
```

预期 `case_count=7` 且全部 actual/expected code 相等。

### 单轮 capture

```bash
evidence="$(mktemp -d /tmp/pg36-ch18.XXXXXX)"

PG36_EVIDENCE_DIR="$evidence" \
  static/labs/ch18/task.sh capture
```

输出：

```text
status=capture-ok
mutation=none
evidence=/tmp/...
```

每个 cycle 的
[`review.py`](/labs/ch18/review.py)
验证：

- manifest target/version/hash；
- relation checksum；
- extension/schema/role exact identity；
- capability lifecycle；
- normal/negative policy report；
- stderr 为空。

### 两轮正式运行

```bash
evidence="$(mktemp -d /tmp/pg36-ch18-final.XXXXXX)"

PG36_EVIDENCE_DIR="$evidence" \
  static/labs/ch18/task.sh all
```

正式 PostgreSQL 18.6 开发 fixture 的结果：

```text
status=ok
preflight=ch04+ch13+ch14+ch15+ch16+ch17
cycles=2-byte-identical
documents=catalog+contracts+blueprint+18-pending-gates
counterexamples=7-rejected
pigsty_l1=not-run
mutation=none
release_candidate_checksum=beec6b6d47075a7b3b4a6aa6ee3ca2902ef8d555547fe6c2b2b009e56c25c9eb
```

源码后续改变时 checksum 会改变；应以当次 manifest 与 validator report 为准。

### 两轮比较什么

```text
platform-state.csv
extension-catalog.csv
schema-catalog.csv
role-catalog.csv
capability-snapshot.csv
validation-report.json
negative-report.json
review.txt
```

`manifest.txt` 含 capture 时间，故不做 byte compare；其余确定性证据必须一致。

### 运行后状态不需要复位

本章没有数据库写操作。若运行前后出现状态变化，应当视为：

- 外部并发变更；
- 某个前置 verify 实现违反只读预期；
- capture SQL/任务脚本缺陷；
- 环境不再适合作为冻结 fixture。

不要用 reset 掩盖，应先保留 evidence 并诊断。

### 架构 ADR

[`architecture-adr.md`](/labs/ch18/architecture-adr.md)
记录：

```text
context
decision per capability
external contracts
service offerings
Pigsty reference mapping
proposed topology
positive consequences
costs/risks
rejected alternatives
revision triggers
```

明确拒绝：

```text
everything in PostgreSQL
everything split immediately
loopback FDW as production proof
Pigsty install as production readiness
```

ADR 的 status 是：

```text
proposed; accepted for lower-volume validation,
not production approval
```

### 18 个下卷 gate

[`lower-volume-gates.json`](/labs/ch18/lower-volume-gates.json)
严格映射第 19–36 章：

| 章 | gate |
|---:|---|
| 19 | deployment baseline |
| 20 | HA |
| 21 | backup/restore |
| 22 | access/routing |
| 23 | security |
| 24 | governance |
| 25 | observability |
| 26 | capacity |
| 27 | tuning |
| 28 | vacuum/maintenance |
| 29 | migration |
| 30 | upgrade |
| 31 | incident framework |
| 32 | PITR |
| 33 | failover/rebuild |
| 34 | overload |
| 35 | forensics |
| 36 | postmortem/platform improvement |

每个 gate 有 owner、两个核心问题、required evidence 与 `pending` 状态。

### gate 不是章节阅读打卡

`chapter completed` 不等于 `gate passed`。例如读完第 21 章但没有在目标环境
恢复，`ch21-backup-restore` 仍然 pending。

通过 gate 应产生：

```text
target identity
version
procedure
raw evidence
review result
owner approval
limitations
expiry/review trigger
```

### 服务目录如何升级

下卷完成后，不直接覆盖 `1.6-proposal`。应：

1. 收集每个 gate evidence；
2. 修改不成立的 topology/objective/bundle；
3. 记录 ADR revision；
4. 生成新的 catalog/blueprint release；
5. 重新跑正负 policy；
6. 由 owner 批准；
7. 保留 proposal 历史。

### 本章通过后能说什么

可以说：

> 在 PostgreSQL 18.6 的受控本地 fixture 上，第 4、13–17 章证据仍然成立；
> `pg36_shop` 的服务目录、能力决策、五份外置合同和 18 个下卷 gate 引用闭合，
> 七个危险反例被拒绝，两次只读快照一致。

不能说：

> Pigsty 生产 cluster 已部署、SLO 已实现、备份可恢复、HA 可达目标、安全已
> 通过、容量足够。

这条语言边界，也是本章最后一项验收。

### 进入下卷

上卷回答：

```text
PostgreSQL 如何正确建模、查询、扩展与交付应用能力
```

下卷开始回答：

```text
这些能力如何在真实环境中持续、可恢复、可观察、可升级地成为服务
```

下一步是
[第 19 章：环境规划与部署基线](/deployment-baseline/)：
把 proposal 中的主机、软件、网络、存储、故障域和 Pigsty inventory 变成第一
份目标环境证据。

---

[上一节：Pigsty 作为参考实现](../05/) · [返回本章目录](../) · [下一章：开天辟地：环境规划与部署基线](/deployment-baseline/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
