Repository navigation
feat(certs): keep tenant states from identity events and fail closed - #798
Merged
Merged
Conversation
The certs service subscribes to the identity service's outbox from the beginning: identity.tenant_created stores the state active at version 1, identity.tenant_state_changed stores its snapshot when the version is newer. Deleted is final and an unrecognized state takes a known state at the same version again. Every failure of a tenant event is retried, never dead-lettered, so a suspension is never skipped; the stall shows as lag. Only a stored active state renews: suspended, deleted, unrecognized and a tenant without a row read as suspended. The renewal answers 503 while either consumer's lag is unknown or too high. Refs #778 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
…unknown tenant events The constraint swap waits at most 5 seconds for the table's lock instead of queueing every renewal behind it. An event type of the tenant aggregate this build does not know is still skipped, now with a WARN. The docs state what a rollback and the outbox's own dead letters mean for a suspension. Refs #778 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
Refs #778 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
The registry parser's check pins each service's consumed producers; certs now reads the identity service's events for each tenant's state. Refs #778 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
…-state # Conflicts: # docs/requirements/rebuild-checklist.md
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
certs 新增第二個 outbox 消費端,從頭訂閱身分服務的 log(
certs.identity-events),以identity.tenant_created與identity.tenant_state_changed維護每個租戶的狀態(清單 B1 的通知模式:只保留版本較大的快照)。換發只給狀態是「正常」的租戶:停權、刪除、這個版本不認得的狀態,以及沒有紀錄的租戶都讀成停權(fail closed)。兩個消費端任一個落後或 lag 不明時,換發回 503。變更
services/certs/src/tenant_states.rs(新):TenantStateHandler。tenant_created存成正常、版本 1;tenant_state_changed存 payload 的狀態與state_version。租戶取自 envelope,並檢查 aggregate 是該租戶的tenant。租戶事件的任何失敗(不認得的事件版本、解碼失敗、沒有租戶、aggregate 不符、state_version小於 2、資料庫錯誤)都重試,處理常式不送進 dead letter,略過就可能遺失一次停權;卡住時 lag 增加、換發回 503。不認得的state值存成unrecognized。租戶 aggregate 上不認得的事件類型略過並記 WARN。services/certs/migrations/postgres/0007_tenant_state_events.sql(新):tenant_statuses的 CHECK 加上deleted與unrecognized,lock_timeout5 秒。services/certs/src/bindings.rs:store_tenant_status讓deleted成為終態,unrecognized可以被同一個版本號的已知狀態取代(升級後重播)。reads_as_suspended:只有存下active不是停權。Projection::tenant_suspended改用它。services/certs/src/renewal.rs:換發的 JOIN 讀出的狀態也經reads_as_suspended判斷,讀取日誌加上tenant_status。slowest取兩個消費端中較慢的 lag:任一個不明就不明,事件數相加,最舊的事件取較舊的。services/certs/src/lib.rs:啟動第二個消費端,換發的 lag 改為兩個消費端的slowest。平台角色連線池維持 4 條。tools/migrate/src/registry.rs:certsconsumes: &["assets", "identity"],Job 讓certs_platform讀identity.outbox_event。tests/tenant_states.rs與tests/tenant_state_lag.rs。tests/renewal.rs、tests/bindings.rs、tests/dynsec.rs改成先寫入正常的狀態,並加上刪除、沒有紀錄的案例。tools/migrate/tests/fresh_database.rs檢查certs_platform在identity只有outbox_event的SELECT。scripts/tests/product-logins.test.ts跟著改成 certs 消費assets與identity。certs加上 dev-dependencyidentity,以身分服務自己的程式碼建立租戶。services/certs/README.md(「租戶狀態」重寫,以及換發的檢查順序、紀錄、規則與取捨、資料庫、權限、測試)、docs/architecture/decisions.md決策 12 (3)、docs/requirements/rebuild-checklist.md的 B1 與 D2、tools/migrate/README.md、deploy/chart/README.md。驗證
證據報告:
docs/test-reports/2026-10-30-778-certs-tenant-state.md。以下都在建置主機(arm64 Linux VM)上以CI=1執行,容器測試沒有略過:CI=1 cargo test --locked -p certs:全部通過,包含新的tenant_states、tenant_state_lag。CI=1 cargo test --locked -p migrate:全部通過,包含fresh_database。cargo fmt --all --check、cargo clippy --locked --workspace --all-targets -- -D warnings、CI=1 cargo test --locked --workspace --exclude agent(含 sql-check、repo-check):在建置主機上通過,202 個測試執行檔 0 失敗。bun test scripts抓到scripts/tests/product-logins.test.ts仍寫著 certs 只消費assets,已修正。修正後在本機(x86_64 Linux)1711 個通過、0 失敗;建置主機沒有 helm,無法在那裡跑。bun run --cwd scripts typecheck也通過。cargo deny --locked check:advisories、bans、licenses、sources 都 ok。git archive HEAD的樹都沒有發現。驗收條件逐條對照
tests/tenant_states.rs。晚到的舊快照與整個訂閱重播(38 個事件重送)都沒有改變任何一列,包含updated_at。tests/tenant_states.rs。版本 5、2、9、3、7、4、8、6 最後是suspended9;版本 4、7、2、10、3、6 最後是active10;8 個並行變更的版本連續。tests/tenant_states.rs中,身分服務的 5 個租戶與 certs 的副本相同;tests/renewal.rs中,刪除、沒有紀錄的租戶都回 403tenant_suspended。tests/tenant_state_lag.rs回 503bindings_stale、Retry-After: 30,0 筆 dead letter。CI=1 cargo test -p certs、CI=1 cargo test -p migrate、cargo clippy --workspace --all-targets -- -D warnings通過。審查
另一個 Opus 審查者做過資安審查,沒有 Critical、High 或 Medium。Low 的處理:
lock_timeout;租戶 aggregate 上不認得的事件類型記 WARN;README 與 migration 註解寫清楚降版與 outbox 本身的 dead letter 對停權的影響。FOR SHARE)。讀取與 commit 之間只有毫秒級的時間差,比已接受的 lag 時間窗小得多。certs.identity-events的 lag 與 dead letter 告警。審查分類建議:review:human
這個 PR 決定停權租戶的閘道器還能不能拿到新憑證(對外的換發端點),又加了 migration、資料庫權限(
certs_platform讀identity.outbox_event)與新的事件消費契約。依 review:ai 的條件(沒有權限、密鑰、對外協定的變更),它不符合。Closes #778
🤖 Generated with Claude Code
https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W