docs: define Instance domain model and admin Secret boundary
E2E Tests / Run on Ubuntu (pull_request) Failing after 1m21s
Tests / Run on Ubuntu (pull_request) Successful in 4m12s
Lint / Run on Ubuntu (pull_request) Successful in 4m37s

This commit is contained in:
2026-09-13 12:17:03 +00:00
parent 59303f5276
commit 953878ac47
7 changed files with 399 additions and 27 deletions
+198
View File
@@ -0,0 +1,198 @@
# Instance 领域对象规格
状态:Draft,待批准。日期:2026-09-12。
上层边界见 [领域模型](domain-model.md)。本文只展开 Instance,不包含 Tenant 的供应
实现,也不新增 CRD 字段。设计签名用于评审职责与行为,不是待复制的 Go 接口代码。
## 1. 对象职责与生命周期
Instance 表示一次登记的 PostgreSQL 管理对象,是聚合根。它负责字段与策略校验、
根据观察结果判断能力是否满足要求、保护状态转换规则;不登录数据库,不读取 Bao,
不查询权限或初始化 registry。
本草案选择:**领域对象只接收数据并做业务决策,不直接或通过端口、回调访问外部。**
应用层调用适配器获取事实、执行被允许的操作,并将观察结果交回对象。Instance 不接收
context、客户端或 IO 接口。领域行为不是公共 SetReady:调用方提供事实,不能指定结论。
每轮从 CR 重建一个 Instance;对象不跨 reconcile 缓存,也不是线程共享单例。
管理连接可由装配层跨轮次复用,但连接复用不代表上次能力验证仍然成立。
## 2. 字段与值对象
所有可变状态封装在对象内部。构造后身份和本轮 definition 不可变;配置变更通过
下一轮装载新的 definition 处理,不提供任意 SetPhase/SetReady/SetEndpoint。
| 字段 | 类型与内容 | 来源/持久化 | 修改规则 |
| --- | --- | --- | --- |
| identity | InstanceIdentity:UID、name | CR metadata | 本次对象身份内不可变;同名新 UID 是新对象 |
| revision | 正整数,期望配置版本 | metadata.generation | 本轮不可变;不是物理服务器版本 |
| definition.endpoint | Endpoint:host、hostaddr、port、managementDatabase、tlsMode | CR spec | 本轮不可变;新配置重验 |
| definition.adminCredential | CredentialReference:name、usernameKey、passwordKey | CR spec | 只引用 controller namespace 的管理 Secret,不存明文 |
| definition.allowedExtensions | 去重的 ExtensionName 集合 | CR spec | 本轮不可变;不因 allowlist 缩小卸载扩展 |
| checkpoint | Pending/Validating/InitializingRegistry/Ready/Deleting | CR status.phase | 只能由领域动作变更,应用层负责持久化 |
| observedRevision | 最近完成有结论协调的版本 | CR status.observedGeneration | 成功或已知失败时更新,单纯记录意图不更新 |
| readiness | Unknown/Ready/NotReady,加安全失败类别和操作说明 | 由 status Ready Condition 重建,结果再映射回 Condition | 方法更新;不是第二套持久化状态 |
| reportedVersion | 可选服务器版本字符串 | status.postgresqlVersion;验证后从服务器更新 | 仅供展示,不能证明连接成功 |
| deleting | 是否已请求删除 | metadata.deletionTimestamp 映射 | 本轮不可变;优先于其他动作 |
| evidence | 可选 CapabilityEvidence | 本轮外部回读;不新增 status 字段 | 重建时始终为空,不能从 Ready Condition 伪造 |
Endpoint 的构造约束沿用 API:非空 host、合法 IP、1–65535 端口、合法 PostgreSQL
identifier、显式 TLS mode,禁止隐式降级。CredentialReference 包含合法 Secret 名称
及非空字段名,不包含 namespace 或 Bao path;namespace 由应用层固定为 controller
自身 namespace。这里校验领域值,不在对象里校验整个 controller 部署配置。
CapabilityEvidence 包含本轮目标绑定(Instance UID、revision、endpoint、凭据引用)、
server version、管理能力检查结果、registry 观察结果。registry 结果区分
Absent/NeedsMigration/Usable;连接失败不能当作 Absent。它不包含密码、token 或 DSN。
管理能力要求来自规格中的 role/database/grant/extension 操作,不等价于“能执行
SHOW server_version”。具体权限探测矩阵需在 PostgreSQL 适配器规格中定义,不能
让一个没有定义检查内容的布尔值承担验收。
不属于 Instance 的字段:Tenant 清单、客户端、连接池、token TTL、CA 文件句柄、
Kubernetes resourceVersion。resourceVersion 留在应用层作为乐观并发保存的前提。
## 3. 设计签名
```text
Reconstitute(identity, revision, definition, checkpointSnapshot, deleting)
-> Instance | InvalidDefinition
Instance.BeginValidation() -> Outcome
Instance.AssessManagement(observation: CapabilityObservation) -> Outcome
Instance.PlanRegistryPreparation(observation: CapabilityObservation)
-> AlreadyUsable | PreparationAllowed | PreparationDenied
Instance.AssessRegistryResult(result: RegistryPreparationResult) -> Outcome
Instance.AssessReadiness(observation: CapabilityObservation) -> Outcome
Instance.CheckExtensions(requested: ExtensionSet) -> Accepted | ExtensionsDenied
Instance.RequireProvisioningReady() -> Accepted | InstanceNotReady
Instance.BeginDeletion() -> Outcome
Instance.Snapshot() -> InstanceSnapshot
```
Outcome 是正常推进、已知失败或方法前提不成立,不包含重试秒数、Kubernetes patch
或原始驱动错误。InstanceSnapshot 只包含 checkpoint、observedRevision、readiness、
reportedVersion,不能序列化 evidence。快照与集合访问返回值副本。
CapabilityObservation 是不可变的事实输入:目标绑定、服务器版本、管理能力检查项和
registry 观察结果;各检查项区分成功、失败、未观察,未观察不视为成功。失败只含安全
类别,不含驱动异常或凭据。对象校验目标绑定与当前身份/配置一致,拒绝不匹配输入,
不改变状态;完整性不足不能产生 Ready。观察结果由应用层收集,对象不能自行证明
这些事实的真实性或实时性;采集来源、同轮次关联和并发检查由应用层保证。
RegistryPreparationResult 为操作失败(目标绑定、安全失败类别)或操作后的完整回读
观察。单独的“迁移调用成功”不是就绪证据。CapabilityEvidence 是对象接受并判定满足
要求的观察值,不是调用方传入的 Ready 布尔值。
### 构造与恢复
Reconstitute 校验期望 definition;无效输入不构造一个可参与用例决策的 Instance。
入口把 InvalidDefinition 映射成 InvalidSpec,不必为了报告坏 CR 而制造非法领域对象。
checkpoint 缺失或未知时保守使用 Pending;reportedVersion 和 Ready 都只是旧观察,
evidence 为空。若 observedRevision 与 revision 不一致,旧 Ready 不得通过供应检查。
### 方法合同
| 方法 | 前置条件/输入 | 行为与状态变化 | 失败语义 |
| --- | --- | --- | --- |
| BeginValidation | 未删除;初次登记、配置变更或需重建 checkpoint | 转 Validating,readiness=Unknown,清空 evidence;不做外部 IO,不推进 observedRevision | deleting 时不启动验证 |
| AssessManagement | 未删除;Validating;目标匹配的观察 | 判定管理访问、metadata、权限是否满足;registry 可用或可安全准备时转 InitializingRegistry,仍为 Unknown;不执行探测 | 失败保持 Validating,NotReady,observedRevision=当前版本 |
| PlanRegistryPreparation | 未删除;InitializingRegistry;本轮前置观察 | 根据管理能力及 registry 现状决定无需写入、允许准备或禁止准备;返回决策,不执行迁移、不标 Ready | 访问失败、不兼容或证据不足时禁止写入,NotReady;保持阶段,更新 observedRevision |
| AssessRegistryResult | 未删除;InitializingRegistry;准备结果或无需写入时的完整回读 | 按全部就绪条件判断回读结果;全满足才 Ready,并更新 observedRevision/version/evidence | 操作失败或回读不满足时保持 InitializingRegistry、NotReady;不得提前 Ready |
| AssessReadiness | 未删除;Ready;本轮观察 | 配置版本不一致时仅 BeginValidation;否则根据全部观察判断是否仍满足就绪条件 | 访问失败转 Validating/NotReady;registry 缺失或需迁移时转 InitializingRegistry,保存后下一轮修复 |
| CheckExtensions | 一组规范化 extension 名称 | 检查请求是否为当前 allowlist 子集,返回不允许的名称;无 IO、无状态修改 | ExtensionsDenied;不卸载已存在 extension |
| RequireProvisioningReady | 供 Tenant 用例使用 | 要求未删除、Ready、observedRevision 匹配,并有本次调用链的新鲜完整 evidence | 不满足即 InstanceNotReady;持久化 Ready 本身不构成授权 |
| BeginDeletion | deleting=true | 转 Deleting,清除供应能力,Unknown;不执行任何数据库或凭据删除 | 引用检查/finalizer 处理失败不得恢复成可供应 |
| Snapshot | 任意合法对象状态 | 返回可安全持久化的结果值 | 不触发 IO,也不改变状态 |
领域方法只检查对象状态,不知道 checkpoint 是否已落盘。“已持久化 checkpoint”是
应用用例执行外部写入的前提。内存字段变成 InitializingRegistry 不代表已保存成功;不能
在同一轮无条件接着执行迁移。通过用例测试验证此约束,而不是伪造一个内存事务。
AssessManagement 成功只是中间步骤,observedRevision 不前移;完成就绪判定或
明确失败才产生相应有结论结果。旧版本字符串可供诊断,但失败会清空 evidence。
Instance 不在本轮暴露 CreateDatabase/DeleteDatabase:Tenant 的供应/销毁授权来自
Tenant 和 OwnershipClaim,不是从 Instance.Ready 推导。数据库执行能力如何承接
已授权动作,留到 Tenant 对象规格,不在这里设计第二个万能 service。
## 4. 应用层与外部访问边界
```text
应用层依赖的适配器能力(不传入 Instance):
InspectManagement(context, target) -> ManagementObservation | AccessFailure
InspectRegistry(context, target) -> RegistryObservation | AccessFailure
EnsureRegistry(context, target) -> Completed | AccessFailure
```
应用层在 IO 前绑定目标并关联结果,领域对象在接受观察时检查身份和配置匹配;旧
endpoint 的成功结果不得用于新 endpoint。Inspect 是只读;EnsureRegistry 是幂等初始化/迁移,
不能顺带建立 Tenant 数据库或接管未知 schema。Completed 不足以推进 Ready,必须回读。
适配器由装配层绑定管理连接;凭据源、连接池释放及 Bao token 重新认证留在该边界
之后。适配器不得自行把基础设施异常转换成 Ready。返回的失败至少区分依赖不可用、
认证失败、权限不足和 registry 不兼容;不兼容属于不可安全继续,不自动覆写。
registry 不兼容的具体 Condition 映射须在接口规格中确定,不能统一误报权限不足。
管理连接由应用层从 controller namespace 的 Secret 装配;管理员维护 ExternalSecret,
ESO 负责同步。Instance 路径不直接访问 Bao,也不以 Bao/ESO 当前可用性作为就绪条件。
首次装配缺少有效 Secret 时失败;已有凭据可正常访问 PG 时继续按 PG 能力判定。
Secret 更新后的连接刷新协议仍需细化,不在领域对象中实现监听或密码轮换。
## 5. 状态转换与初始化走查
```text
Pending --BeginValidation/保存--> Validating
Validating --AssessManagement(观察)/保存--> InitializingRegistry
InitializingRegistry --AssessRegistryResult(回读结果)/保存--> Ready
Ready --配置变化或访问失败/保存--> Validating
Ready --registry 需修复/保存--> InitializingRegistry
任意阶段 --删除请求/保存--> Deleting
```
1. 入口读取 CR,装配 definition、checkpointSnapshot;客户端不注入领域对象。
2. 应用层按 checkpoint 协调用例;首次调用 BeginValidation,没有 IO。
3. 保存 Validating。若保存失败,结束本轮,不执行 registry 写入。
4. 下一轮应用层调用适配器探测实例,将观察交给 AssessManagement;领域判定通过后
保存 InitializingRegistry,保存失败则停止,不进行迁移。
5. 再下一轮应用层采集前置观察,调用 PlanRegistryPreparation。仅在意图已持久化且
领域允许时调用 EnsureRegistry;AlreadyUsable 则跳过写入,PreparationDenied 则
保存失败结果并停止。允许的操作完成后回读,交给 AssessRegistryResult 决定能否
Ready;操作失败也用安全结果交回,不在应用层直接修改 phase。
6. 入口用原 resourceVersion 前提保存快照;并发变更导致冲突时重新装载,不覆盖新状态。
7. 后续 Ready 检查先由应用层探测,再调用 AssessReadiness;Tenant 用例同样获取当前事实,不能
仅凭另一个 CR 的 Ready Condition 永久缓存授权。实际资源写入仍须处理并发变化。
阶段调度和外部操作顺序在应用层;“观察是否满足业务要求、是否允许准备 registry、
哪些结果算完成、失败退到哪里”在 Instance 方法内。controller 不重复这些规则,
也不直接把 phase 设置成 Ready。领域允许操作并不锁住外部世界,适配器仍须保障幂等
和并发安全;禁止把旧观察当成永久授权。
## 6. 不变量与恢复验收
- UID 不随名称复用;不同 UID 的 evidence/结果不可互用。
- 未完成当前配置的能力回读,不能新产生 Ready,也不能通过供应检查。
- checkpoint 可以落后或被伪造;每次初始化/供应前都核对事实。status 清空只需重新
验证和幂等准备,不删除 registry,更不能重新生成 Tenant 密码。
- 迁移成功而 status 保存失败:重试回读已存在 registry,安全完成,不重复破坏性写入。
- registry 在 Ready 后消失:下一次回读撤销 Ready,保存修复意图后才能重新准备。
- 外部 IO 超时:产生安全失败结果;保存 status 使用仍有效的外层上下文,不能复用
已超时的 IO 上下文而丢失失败状态。
- 已请求删除的 Instance 不允许新供应;BeginDeletion 不删除 PostgreSQL、Tenant 或
Bao。Tenant 引用检查及 Instance 删除竞争协议仍需另行批准。
- CheckExtensions 失败不能产生任何外部写入;修改 allowlist 不会自行卸载扩展。
- Snapshot、错误、日志和领域对象格式化不输出明文凭据或 token。
- 领域测试只提供观察值,无需数据库、网络、context 或 IO mock;相同状态和输入
得到相同决策。缺少检查项、目标不匹配和旧配置结果不得产生 Ready。
上述每条都对应领域或用例测试;真实权限检查、迁移与并发保障由适配器集成测试
验证。本文为设计文档,未执行或宣称通过这些测试。
## 7. 本轮待评审与后续阻塞项
本轮请先确认字段归属、应用层采集事实/Instance 纯决策的分工、方法与状态转换合同。
管理 Secret 来源和 Bao 故障不单独撤销 Instance Ready 已确认;Secret 更新后的连接
刷新协议仍需细化。其他决策及未决项见总体草案,不增加后台清扫器或状态字段。
批准本对象结构不等于批准这些未决行为,也不意味着立刻实现完整供应链路。