Skip to content

feat(postgresql): add service-owned admission and restore-incarnation rotation - #4334

Merged
huangruiteng merged 5 commits into
mainfrom
codex/postgresql-service-admission
Sep 14, 2026
Merged

huangruiteng merged 5 commits into
mainfrom
codex/postgresql-service-admission

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Summary

This is an independent PostgreSQL P-lane PR based directly on main. It is
not stacked on or a replacement for #4317 (local provider routing) or #4328
(SQLite D2), and it does not change the file/SQLite default or add a runtime
caller/promotion claim.

  • Add a service-owned PostgreSqlAuthorityService admission seam.
  • Require injected principal authentication and tenant authorization before a
    PostgreSQL connection is opened.
  • Add typed restore-incarnation rotation with stale-revision rejection and
    explicit post-COMMIT ambiguity.
  • Add a public-safe synthetic fixture, focused unit tests, a real PostgreSQL
    integration test, and bilingual reference documentation.

Semantic changes and fixes

  1. PostgreSQL admission is now an explicit typed boundary: opaque transport
    credentials stay in the service, while the provider receives only the
    authorized tenant/goal context.
  2. Authentication, tenant denial, policy-unavailable, metadata-unavailable,
    and identity-drift outcomes fail closed with stable reason codes and no
    premature provider connection.
  3. A restored database can receive a newly minted incarnation identity under a
    metadata-row lock. Existing heads, commits, events, receipts, and operation
    IDs remain untouched; revision tokens from the prior incarnation conflict.
  4. A lost response after COMMIT is reported as ambiguous, requiring a
    metadata read before retry instead of guessing the outcome.

Validation

  • npm run typecheck:control-plane
  • npm run test:postgresql-authority-service — 9/9, including the disposable
    real PostgreSQL service path
  • npm run test:postgresql-authority-store — 53/53 against an isolated real
    PostgreSQL database
  • npm run test:control-plane — 1347/1347, 0 skipped, with isolated
    PostgreSQL conformance and service databases
  • Three-arm production-complex rehearsal using the local real PostgreSQL
    backend — source unchanged, provider heads exact, receipts found
  • loopx canary premerge --from-git-diff --git-diff-base origin/main
    — 17/17 checks passed, no manual holds
  • Change-quality receipt cqr_d8a4135e63438828d3d3 — exact scope valid

The real integration requires an isolated disposable database through
LOOPX_TEST_POSTGRES_SERVICE_URL; it never uses an active goal database.

Duplication and scope

The implementation reuses the existing AuthorityStore contract, PostgreSQL
metadata schema, ID validation, and rollback/release lifecycle. The identity
and schema constants are exported once instead of being copied into a second
service contract. The new service does not introduce a provider registry,
generic pool framework, default-routing branch, or runtime authority caller.
Existing connection lifecycle code remains local to the provider because its
read/write transaction boundaries differ; the rotation helper reuses the
existing rollback/error discipline rather than adding another abstraction.

Follow-up boundary

Production transport authentication, tenant policy implementation, pool
capacity/failover qualification, runtime caller wiring, and provider promotion
remain deployment- or follow-up work. This PR establishes the service-owned
contract needed for that switchable PostgreSQL path without changing defaults.

@huangruiteng
huangruiteng force-pushed the codex/postgresql-service-admission branch from 4973ca9 to 8f716c7 Compare September 14, 2026 16:33
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The control-plane coverage job runs without PostgreSQL, so the provider-backed
integration suite cannot cover the new admission and rotation failure paths and
coverage on new code stayed below the quality gate. Exercise those paths with a
scripted provider instead: malformed memberships, verifier and authorization
outages, invalid authenticated principals, invalid or unchanged incarnations,
stored-incarnation drift, transaction failure, and protocol violation.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/postgresql-service-admission branch from 8f716c7 to 7f6b334 Compare September 14, 2026 16:54
The new service module and its suites were absent from the control-plane
TypeScript program, so npm run typecheck:control-plane never checked them. Add
them to the program and keep the real-path URL assertion type-safe.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

详细中文评审

Exact head: e4eb78457d06ed7ca755784b4b1eb52c28019f77(base origin/main = 5fdda4044)

动机

PostgreSQL authority store 之前只能由任何持有数据库连接的进程内调用方直接打开,没有服务层的认证与租户授权边界;而数据库从备份恢复后会沿用旧的 store_identity,恢复前签发的 provider revision token 仍然可读。这两点各自带来持续成本:每个部署都要自己发明凭据处理与租户校验(规则会漂移且无法被验证),每次恢复都要临时修补 identity。本 PR 把两件事收进一个 seam:openStore 返回类型化结果(未认证、未授权、验证不可用、身份不可用、身份不匹配),rotatePostgreSqlAuthorityStoreIdentity 在行锁下原子地把旧 incarnation 换成新 incarnation,使恢复前签发的 opaque revision token 冲突,而 head/commit/receipt 历史保持可读。只写一条 SQL 旋转 identity 的更小修法会让准入决策继续隐式,并且没有类型化失败与 ambiguous 语义,因此不够。

改动思路

入口是 PostgreSqlAuthorityService.openStore(postgresql_authority_service.ts:102)与 rotatePostgreSqlAuthorityStoreIdentity(postgresql_authority_store.ts:451)。权威状态是 loopx_control_plane.authority_store_metadata 单例行(schema_version + store_identity);决策归属保持三方清晰:认证与租户授权由部署注入的校验函数提供、由 service 拥有,LoopX authority 仍拥有领域决策,PostgreSQL 只拥有持久化 incarnation 身份。正向路径为:校验请求 → 认证 principal → 校验 principal id → 租户授权 → 构造 store → 读取并比对 store identity → 返回 opened 与绑定好的 store;负向路径在构造 store 之前就以类型化 reason code 返回,identity 比对是唯一在读取元数据之后发生的拒绝。

复用上,新模块没有另起一套 provider:它导入既有 store 的 database/options 类型,共享 POSTGRESQL_STORE_IDENTITY_PATTERN 与 POSTGRESQL_SCHEMA_VERSION,旋转复用既有 connect/rollback/oneRow/AuthorityStoreProtocolError 原语,因此没有第二条 SQL 写路径,也没有第二个状态源。凭据只作不透明转发,不入库、不进 provider 中性的 AuthorityStore 契约,准入与 provider 边界因此不互相污染。

具体改动

12 个文件、+1285/−2:生产 343 行(service 模块 212 行新文件 + store 131 行新增),测试与 fixture 807 行,文档 130 行,构建/类型检查 5 行。删除的 2 行只是 store 中两个常量由私有改为导出,值未变。分类上:生产改动是准入 seam 与旋转事务;测试改动包含确定性 seam 覆盖、一个真机套件与一份公开合成 fixture;文档是中英双语参考页与其索引条目;构建改动是一个 npm script 与 control-plane TypeScript program 的两个新条目。

关键代码讲解

  • PostgreSqlAuthorityService.openStore(postgresql_authority_service.ts:102):准入口。先做请求形状校验与 tenant/goal id 校验,再调用 authenticatePrincipal,再校验返回的 principal id,再 authorizeTenant,最后才构造 store 并做 incarnation 比对。关键不变量是"任何被拒绝的调用都不会拿到 store 句柄",以及注入函数抛错时返回 *_unavailable 而不是猜测通过。
  • PostgreSqlAuthorityServiceOpenResult(postgresql_authority_service.ts:47):把失败原因建模为闭集 reason code 的判别联合(invalid_service_request、principal_unauthenticated、principal_verification_unavailable、tenant_unauthorized、tenant_authorization_unavailable、store_identity_unavailable、store_identity_mismatch),调用方只能按类型判断,不依赖文案,非法状态无法表达。
  • rotatePostgreSqlAuthorityStoreIdentity(postgresql_authority_store.ts:451):BEGIN 后以 SELECT ... FOR UPDATE 锁住元数据单例行,再在事务内复验 schema version 与期望 identity,通过才 UPDATE store_identity 并 COMMIT;期望 identity 不匹配时抛类型化拒绝且不写入。commitStarted 在 COMMIT 之前置位,因此丢失响应会返回 ambiguous 而不是伪成功,恢复路径明确要求"先读元数据再重试"。
  • PostgreSQL service admits an authorized tenant and rotates a restored incarnation(postgresql_authority_service.integration.test.ts:50):真机套件,在一次性 PostgreSQL 上安装 schema、经 service 打开、旋转 incarnation,并断言恢复前的 revision 在旋转后冲突。缺 URL 时该文件只注册名称不同的 skip 占位,所以真机证据必须以名字断言,不能依赖"套件整体是绿的"。
  • control-plane TypeScript program entry(tsconfig.control-plane.json:40):本轮评审修复的一处具体缺陷——新增模块与套件原本不在显式 include 列表内,npm run typecheck:control-plane 根本没有读取它们;现在六个 PostgreSQL authority 文件都在 program 内(可用 tsc --listFiles 核对)。

对主干的风险

最强回归场景是"旋转结果被误判":若部署把丢失 COMMIT 响应的旋转当作成功,继续使用旧 revision token;或恢复后的库因为静默 no-op 继续接受陈旧 token。触发状态是 COMMIT 边界上的连接中断,或操作者用过期期望 identity 执行旋转。代码路径上,commitStarted 在 COMMIT 前置位使任何后续错误返回 ambiguous;期望 identity 在事务内被复验,过期或已旋转的 identity 会以 store_identity_mismatch 失败且不写入,因此这两条路都被显式阻断。影响面是数据库级 identity(不是单个 goal),所以文档把"先读元数据再重试"写成恢复流程而不是可选项。

第二类风险是范围风险,必须如实记录:scope_fit 显示本模块没有已发布运行时的调用方,参考页也明确声明它是 opt-in Stage 2B seam,不改变 file/SQLite 选择、不宣称 promotion。也就是说今天支撑这份契约的是两套套件加 ladder row,而不是生产流量;将来第一个部署适配器才是真正消费者,届时应补调用方层级的 parity 覆盖。此外,共享文件 postgresql_authority_store.ts 只改了常量可见性,store 自身 103 例套件在同 head 上保持 103 通过、0 失败、0 跳过,可用于判断默认关闭隔离未被破坏。

本轮评审发现并已在本 head 修掉的缺陷是 tsconfig 缺口(P2):tsc --listFiles 证实新增模块与套件不在 program 内,补入后立刻暴露出一个真实的 strict-mode 类型错误(真实路径断言把可能为 undefined 的环境变量当字符串用),两者在 e4eb78457 一并修复,因此不再是未决阻塞项。

我的整体评价

observable_semantics 判定为 intentional_change_validated:base 与 head 相比没有既有可观测行为被改写——provider 选择未变、元数据 schema 未变、identity 形状未变,唯一共享改动是常量导出可见性,而 store 套件在 head 上给出与 base 相同的 103/103 结果。default_off_isolation 为 isolated:模块只能被显式 import 与构造,没有任何自动安装、prompt 或调度副作用;authority_semantics 为 aligned:名称描述的是进程内准入边界,文档显式排除了网络访问、actor 所有权、lease 所有权、跨主机同步与 promotion 权限。change_proportionality 为 proportionate:机制 343 行生产代码换取一个可复用、可真机验证的边界,无需迁移。

验证(全部在同一 exact head):npm run typecheck:control-plane 干净;npm run test:postgresql-authority-service 10/10 通过(含 95ms 真机用例);npm run test:postgresql-authority-store 103/103 通过(28.7s,全新一次性库);ladder row s2b.postgresql_conformance_live 通过(tap_pass 103、tap_fail 0、tap_skipped 0、unverified 0);loopx canary premerge --from-git-diff 无失败命令、无 manual hold;change-quality 回执 cqr_19897393d56d0dd8cb23 验证为 valid。残留风险即上文无调用方 seam 这一点,已按 P3 记录。任何 head 变更(含 rebase)都会重启本评审,合并前需要对该 head 重新做一次 review 与 merge-readiness 检查。

English verdict: APPROVE — exact head e4eb78457d06ed7ca755784b4b1eb52c28019f77 (base 5fdda4044). Service-owned admission plus locked, atomic restore-incarnation rotation; reviewed against PostgreSQL 16.15 for real (service suite 10/10 including a 95 ms real-server case, store suite 103/103, ladder row pass with unverified 0) and clean typecheck. One P2 defect found and fixed in this head: the new module and its suites were missing from the control-plane TypeScript program, so tsc had never read them. Residual risk, recorded as P3: the seam has no shipped runtime caller, so a future deployment adapter should add caller-level parity coverage. Two non-blocking notes stay as follow-ups, not merge gates.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Self-merge decision record

Reviewed exact head: e4eb78457d06ed7ca755784b4b1eb52c28019f77 (base main 5fdda4044).
Review: #issuecomment review (COMMENTED author-owned approval fallback; GitHub blocks formal self-approval).

Changed surfaces. Production: loopx/control_plane/coordination/postgresql_authority_service.ts (new service-owned admission seam) and the restore-incarnation rotation added to postgresql_authority_store.ts. Build: one package.json script and the control-plane TypeScript program entries in tsconfig.control-plane.json. Tests/fixture: two new suites, one fixture helper, an extended store suite and a public synthetic fixture. Docs: the bilingual reference page plus its index entry.

Checks that ran. npm run typecheck:control-plane (clean, and the new module plus its suites are now program members), npm run test:postgresql-authority-service (10 pass, 0 fail, 0 skipped, including a 95 ms real-server case against a disposable PostgreSQL 16.15 instance), npm run test:postgresql-authority-store (103 pass, 0 fail, 0 skipped, 28.7 s), python3 -m loopx.control_plane.testing.authority_e2e_ladder --row s2b.postgresql_conformance_live (pass, unverified 0, privacy_violations 0), loopx canary premerge --from-git-diff (no failed command, no manual hold), and the pull-request checks on this head (24 pass, 4 skipped).

Failures and skips. One test-shard job failed on the first attempt in test_local_authority_shadow_cli_e2e.py (local authority shadow CLI e2e, unrelated to the changed surfaces); the job passed on re-run with no code change, so it is recorded as a runner-side flake rather than a regression. Skipped checks are the release/presentation jobs that do not apply to a pull request (deploy, presentation, upload-release, publish-pypi). No manual holds were raised.

Why this coverage is enough. The two changed behaviors are admission decisions and an atomic incarnation rotation, and both were exercised against a real PostgreSQL server rather than a mock, including the negative paths (rejected credential, unauthorized tenant, unavailable verifier, mismatched incarnation, wrong expected identity, lost COMMIT response). The repository-owned premerge gate passed with zero failures and zero manual holds, the change-quality receipt cqr_19897393d56d0dd8cb23 verifies as valid for this exact scope, and the remaining residual risk — the seam has no shipped runtime caller yet — is recorded in the review as a P3 follow-up instead of being treated as shipped runtime behavior.

Merge uses the maintainer admin path because the ruleset requires an approving review and GitHub does not allow self-approval; the gate reports admin_bypass_required: true for that reason only, after the exact-head review and merge-readiness checks were satisfied.

@huangruiteng
huangruiteng merged commit 7ab81bb into main Sep 14, 2026
45 of 48 checks passed
@huangruiteng
huangruiteng deleted the codex/postgresql-service-admission branch September 14, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant