Repository navigation
[seam → cloud] Control plane stamps email_verified: true into a tenant's owner seed without reading the creator's verification state — the verification gap #11977's fork measured #17072
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 9, 2026 Premise re-measured on today's cloud tree: both halves still hold. Plus one constraint on acceptance item 2 that the filing seat could not have seen.
repo:cloudexecution seat, R38, sessionsession_01TAUTP6Yky8QWoHUAPDKNJQ, 2026-09-15T15:41Z. This is the queue-scan staleness check, ⛔ not a claim and ⛔ not a dispatch. The card's readings were taken at cloud58c20e2con 2026-09-03T00:00Z; re-measured atcb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8, the REST tip ofmain(2026-09-15T15:12:03Z) — twelve days and onemainlater.premise verdict at cb8ee7ffenvironment-owner-seed.ts:91stampsemail_verified: trueunconditionallyholds, at the same line number — packages/objectos-runtime/src/environment-owner-seed.ts:91, inside theql.insert(SYS_USER, {…})callenvironment-provisioning.ts:1064-1071copies the creator's email without readingemail_verifiedholds — packages/service-tenant/src/environment-provisioning.tscaptures owner identity into env metadata at that block and readsemail_verifiednowhere in the filethe tenant-side check reads what the control plane asserted holds — packages/objectos-runtime/src/env-owner-admin-grant.ts:144readsrow?.email_verified ?? row?.emailVerified⚠️ Path correction, because the card's citation is ambiguous and I got it wrong on the first attempt. The card writesenvironment-owner-seed.ts:91with no package prefix; the file is inpackages/objectos-runtime/, ⛔ not inpackages/service-tenant/beside the provisioning file it is cited next to. My first lookup returned "no such file" and that zero was false — the control (environment-provisioning.ts, 79KB, 196 hits for a certain term) showed the instrument was fine and the path was mine. Recorded so the executing dev does not lose the same minutes: the two cited files live in different packages.⭐ And the line the card is about says out loud what the card says is false.
environment-owner-seed.ts:86-89:Cloud already verified the upstream email. Marking it verified here is what unblocks better-auth's
accountLinkingcheck on the first SSO callback.⇒ The stamp is not an oversight — it is a load-bearing assumption with a named dependant. Whoever implements acceptance item 1 has to keep that SSO account-linking path working while the stamp becomes conditional; a fix that simply drops
email_verified: truewill break first-SSO-callback linking for verified creators, which is the regression this note exists to prevent.⚠️ Acceptance item 2 needs a channel this seat has not establishedExisting tenants seeded with a false
trueare reported (count + environment ids) by a read-only censusThat census is a query against live control planes, ⛔ not a repo read. Everything above is a source reading, and source access says nothing about database access. This seat has not established that channel, so ⛔ I am not recording item 2 as reachable. Two honest shapes for whoever takes the card: deliver the census as a script plus the exact query for an operator to run, or split item 2 onto its own card so item 1 is not held behind an access question. ⛔ What must not happen is item 2 being quietly dropped because item 1 shipped — the card asks for the count precisely because a silent migration was ruled out.
Card stays
pm:queue, unclaimed and unassigned. Nothing here re-grades it:priority:p2and thesecuritylabel stand, as do the three acceptance items and the ADR-0131 D5 fence.
Generated by Claude Code
派发令 — objectstack#17072(
repo:cloud缝卡)·pm:queue→pm:dispatchedrepo:cloud执行席,sessionsession_01WEsBsV9NHgypgTitazMHbe,R41,于 2026-09-20T06:21Z。取卡依全序:本车道 = cloud 全仓队列 + objectstack 上带repo:cloud的缝卡(座位贴 objectstack#6026「范围」)。缝卡里三张带priority:p2,高于 cloud 队列里目前全部无级的五张;p2 三张内无Bug⇒ 按卡龄,#16084(09-05)· 本卡(09-09)· #17148(09-09)。#16084 因与在飞的 cloud#2324 共用热文件cloud-artifact-helpers.ts被串行到其后(座位贴5748071647),#17148 的 cloud 一半已交付、三选一属domain:engine座位 ⇒ 本卡。⚠️ 本卡与在飞的 cloud#2324 文件面不相交(那张在packages/service-cloud/,本张在packages/service-tenant/与packages/objectos-runtime/),可并行。复测:三条前提全部成立,但两条的落点已经漂移
以下读于 2026-09-20T06:21Z,在 cloud
main436fef7779c32f67de95829ec416366869d4b2ff上逐文件取原文后计数。R38 的复测(5683255225)取于cb8ee7ff,卡面原始读数取于58c20e2c。前提 卡面/R38 行号 本次读到 判定 无条件盖 email_verified: trueenvironment-owner-seed.ts:91packages/objectos-runtime/src/environment-owner-seed.ts:91,在ql.insert(SYS_USER, {…})内成立,行号未动 复制创建者邮箱而不读核验态 environment-provisioning.ts:1064-1071已漂移 ⇒ packages/service-tenant/src/environment-provisioning.ts:1094–:1103成立 租户侧读的是控制面断言的核验 env-owner-admin-grant.ts:143-147packages/objectos-runtime/src/env-owner-admin-grant.ts:144的row?.email_verified ?? row?.emailVerified成立 第二条的证明不是「没找到」:
email_verified在environment-provisioning.ts全文件命中 0,而对照词email命中 3,且那 3 处全部落在:1084(注释)、:1096(if (u?.email))、:1099(email: String(u.email))—— 即同一个ownerSeed组装块内。⇒ 仪器看得见这个词,那个 0 是读数。读于 2026-09-20T06:21Z。⭐ 落点因此比卡面更紧:
:1097–:1102就是ownerSeed的形状(userId/email/name/image),核验态要进的就是这里;environment-owner-seed.ts:91把硬编码的true改成读这个字段。验收第 1 项的全部就是这两处。⛔ 两条会把「修好」变成大面积事故的约束
C1 · 那个
true有具名依赖,⛔ 不许直接删。environment-owner-seed.ts:87–:90(读于 2026-09-20T06:21Z)自己写着:「Cloud already verified the upstream email. Marking it verified here is what unblocks better-auth'saccountLinkingcheck on the first SSO callback (alongside the trustedProviders config inplugin-auth/auth-manager.ts)」。⇒ 已核验的创建者必须仍然拿到true,首次 SSO 回调的账号联结必须仍然通。⛔ 一个只把email_verified: true去掉的改法会打断所有已核验创建者的联结 —— R38 在5683255225里就是为了拦这个才写下那一段。C2 ·
⚠️ ⚠️ 存量环境的ownerSeed里没有这个字段,缺省值选错就是静默的全量事故。 已经 provision 过的环境,元数据里的ownerSeed只有四个键。冷启动时新代码去读一个不存在的字段 ⇒undefined⇒ 若按 falsy 处理,每一个存量环境的 owner 都会被盖成未核验,而其中绝大多数本来是已核验的 ⇒ C1 的事故照样发生,只是换了条路。⇒ 字段缺席时必须保持今天的行为(true),新的条件判断只作用在带该字段的 seed 上。这一条要有自己的钉子:一条「旧形状ownerSeed(无核验字段)仍然盖true」的用例。C3 · ⛔ 控制面
sys_user上那一列的拼写必须读出来**,不许猜。** 租户侧两种拼写都存在 ——env-owner-admin-grant.ts:117–:119(读于 2026-09-20T06:21Z)写着 better-auth 写的是emailVerified: false,而预置的 owner 行是email_verified: true。控制面sys_user用哪一种,本席没有取到(本 session 的search/code被平台拒绝,其错误串 ⛔ 不是一个零)。猜错的后果与 C2 同形:读到undefined⇒ 全量盖成未核验。⇒ 在你的 worktree 里读出那一列的真实拼写并在报告里给出出处;若两种拼写都可能出现,按u.email_verified ?? u.emailVerified的双读写,并双向钉住。验收
- (卡面第 1 项) ownerSeed 携带创建者的真实核验态;控制面上未核验的账号不会以「已核验」的身份到达租户。双向钉住:已核验 ⇒
true,未核验 ⇒false。另加 C2 的第三条:旧形状 ⇒true。 - (卡面第 2 项,本席按一类自裁定形,见下) 交付一个只读普查:一个脚本 + 它跑的确切查询,由运维在真实控制面上执行,输出
count与environment_id列表。⛔ 不做任何迁移,⛔ 不在代码里自动改任何存量行。 - (卡面第 3 项,原样保留) ⛔ 不动锚本身(ADR-0131 D5),⛔ 不动
app.allowOrgOverride,⛔ 不动 platform-admin rung 的推导([Design] Re-anchor platform-admin:admin_full_accessbecomes a kernel metadata declaration; WHO holds it comes from env-configured verified emails — retiring the org-less row anchor #11663 / feat(plugin-security): walled bootstrap stops minting the platform-admin grant row; platformAdmin audit service; legacy-grant deprecation pointer (L4) #13514 L4)。 - C1 的回归钉子:已核验创建者的首次 SSO 账号联结路径仍然通。
- 逐条贴读数,⛔ exit 0 不是任何东西跑过的证据 —— 贴 test body 的实际行。
验收第 2 项的定形 —— 本席一类自裁,并把依据写在这里
R38 在
5683255225里给了两种诚实形状:(a) 交付脚本 + 确切查询给运维跑;(b) 把第 2 项拆成独立卡,让第 1 项不被通道问题卡住。⛔ 它明确排除了第三种:「What must not happen is item 2 being quietly dropped because item 1 shipped」。本席取 (a),三条自裁判据逐条对:① 卡面自己已经把「⛔ 不做静默迁移」定死,两种形状都遵守它,选择只在「拆不拆卡」上 —— 而 ④ 创业阶段不扩散直接判:(a) 零新机制,(b) 多一张卡、多一次调度、且第 2 项会在新卡上再等一次同样的通道;② 失败方向响亮且一次回退可达 —— 一个只读普查脚本查错了库或写错了查询,输出立刻不对,且它不改任何东西;③ 四层零移动 —— 只读脚本不改产品行为、不改契约、不改数据。⇒ 三条齐备。
⛔ 这条自裁不覆盖普查结果之后要怎么办:那些被假
true播过种的存量租户要不要重新核验,是维护者的题,⛔ 不是本席的、更不是 dev 的。把数字取回来,然后停。changeset
两个包代码都动 ⇒ 一条 changeset 同时列
'@objectstack/service-tenant': patch与'@objectstack/objectos-runtime': patch。⛔ 不许删任何.changeset/*.md。⚠️ 本仓没有 Check Changeset workflow,别把它的缺席读成漏掉的门禁,也别因此不写。⚠️ 本容器的既有事实共享检出
/home/user/objectstack与 cloud 的.objectstack-sha(bdea10a185d422ef1f9022e210b87d19882055ee,读于 2026-09-20T06:21Z)相隔数百个提交 ⇒apps/objectos-ee与大部分service-ai-studio套件在收集阶段就抛,没有任何 test body 跑过。⛔ 不是你弄坏的,⛔ 不要去修,⛔ 不要重指或重建共享检出(别的 agent 在用)。CI 在钉版上构建,不受影响。⚠️ 另有一张 cloud#2324 的 dev 同时在飞,落在packages/service-cloud/—— 与本卡文件面不相交。⛔ 不要去动那边的文件。常备 ⛔
⛔ 绝不为让测试过而削弱任何断言(本卡是
security卡,这条尤其);⛔ 绝不 skip / disable / quarantine 任何测试;⛔ 绝不prettier --write;⛔ 绝不动content/docs/releases/;⛔ 绝不设OS_MCP_SERVER_ENABLED,绝不伪造Origin,绝不设OS_TRUSTED_ORIGINS。受管四行AGENTS.md·CLAUDE.md·.claude/**·docs/adr/**:命中任一,整个 PR 留 draft 等人工合并 —— 本卡落点一条都不碰(ADR 只读不写)。一卡一 agent 一 worktree。
⚠️ PR 落在 cloud 仓,卡在 objectstack 仓 ⇒ PR 正文写Part of objectstack-ai/objectstack#17072,⛔ 不要用会关卡的 closing keyword(跨仓关卡在本仓会静默不生效,且本卡的关闭由本席在读回 state 之后做)。完成后按结构化 JSON 回报,open_questions里写清 C3 读到的列名出处,以及普查脚本你没法在本地验证的部分。
Generated by Claude Code
- (卡面第 1 项) ownerSeed 携带创建者的真实核验态;控制面上未核验的账号不会以「已核验」的身份到达租户。双向钉住:已核验 ⇒
os-dev-report
{ "issue": 17072, "status": "done", "branch": "claude/issue-17072-owner-seed-verification", "pr": "https://github.com/objectstack-ai/cloud/pull/2416", "session": "session_01WEsBsV9NHgypgTitazMHbe", "premise_still_valid": true, "summary": "Both halves of the card's item 1 landed in cloud. `packages/service-tenant/src/environment-provisioning.ts` (ownerSeed block, re-read at :1094-:1103 on 436fef77 and found exactly as the dispatch described) now derives the creator's real verification state from the control-plane `sys_user` row via a new single reader `packages/service-tenant/src/owner-seed-verification.ts`, and `packages/objectos-runtime/src/environment-owner-seed.ts` stamps `email_verified: seedEmailVerified(seed)` instead of the literal `true` that sat at :91. C1 is preserved by making the field CONDITIONAL rather than dropped (a verified creator still gets `true`, and a composed pin drives the seed and the env-owner admin-grant middleware through one engine); C2 is preserved on BOTH sides — provisioning OMITS the key when the control-plane row answers neither spelling, and the runtime reads absent/null as `true`. Item 2 shipped as the adjudicated read-only shape: `censusOwnerSeedVerification()` in service-tenant plus `apps/cloud/scripts/census-owner-seed-verification.ts` (count + environment ids, `--print-sql` for the exact hand-run PostgreSQL and libSQL queries), with no `--apply` and a pin that the driver's create/update/delete/execute are never called. No ADR-0131 D5 anchor, `app.allowOrgOverride` or platform-admin rung change; no file under packages/service-cloud/ touched. Changeset lists both packages. The card was assigned to `hotlong` when I picked it up (PM-owned field, untouched by me).", "tests": "All readings at branch head c78b6ae; heavy runs through scripts/pm/os-verify-lock.sh (OS_VERIFY_LOCK_SLOT=issue-17072). [1] pnpm --filter @objectstack/objectos-runtime exec vitest run --reporter=verbose src/environment-owner-seed.test.ts src/env-owner-admin-grant.test.ts -> 'Test Files 2 passed (2) / Tests 33 passed (33)', bodies include '✓ a VERIFIED creator is still stamped email_verified: true', '✓ ⛔ an UNVERIFIED creator is stamped email_verified: false', '✓ ⚠️ C2 — a LEGACY four-key seed (no verification field) still stamps true', '✓ a verified creator's seeded row fires the env-owner admin grant', '✓ ⚠️ C2 — a LEGACY seed keeps firing it too', '✓ ⛔ an unverified creator's seeded row does NOT fire it'. [2] pnpm --filter @objectstack/service-tenant exec vitest run --reporter=verbose src/environment-provisioning-owner-verification.test.ts src/owner-seed-verification-census.test.ts -> 'Tests 23 passed (23)', bodies include '✓ ⚠️ C2 — a row that answers NEITHER spelling leaves the historical four-key seed, ⛔ not a false', '✓ reads better-auth's camelCase spelling when that is the one the row carries', '✓ ⛔ writes nothing at all — not even for the environments it reports'. [3] pnpm --filter @objectstack/service-tenant test -> 'Test Files 29 passed (29) / Tests 450 passed | 2 skipped (452)'. [4] pnpm --filter @objectstack/service-tenant typecheck -> exit 0, 'check:test-typecheck: OK ... 0 file(s) / 0 error(s)'. [5] pnpm --filter @objectstack/objectos-runtime typecheck -> exit 0, same OK line (after building the two unbuilt deps organizations + security-enterprise). [6] pnpm --filter @objectstack/objectos-runtime test -> 'Test Files 2 failed | 80 passed (82) / Tests 2 failed | 1085 passed (1087)'; BOTH failures are pre-existing and were REPRODUCED on pristine origin/main 436fef77 in a throwaway control worktree with byte-identical assertions — capability-coverage.test.ts \"expected [ 'package-registry' ] to deeply equal []\" and time-relative-trigger.e2e.test.ts \"the sweep never launched the flow\" (the .objectstack-sha pin drift the dispatch warned about; control worktree removed afterwards). [7] Gates whose inputs the diff touches: check:control-bytes exit 0 ('2077 tracked text file(s), no raw control bytes'), check:migration-has-invoker exit 0, check:link-deps exit 0, check:declared-twins exit 0. ABLATIONS (node scripts/ablation-replace.mjs, anchor-count + blob-hash + restore all verified on disk; these tests import their subject from SOURCE, so no dist rebuild sits between mutation and reading): (a) 'email_verified: seedEmailVerified(seed),' -> 'email_verified: true,' (the old behaviour): blob 9230d82fa427 -> 132f75f7f39d, 2 failed — the UNVERIFIED stamp pin and the unverified-grant pin; restored 'blob == HEAD (9230d82fa427) and git diff HEAD is empty'. (b) C2 branch 'if (raw === undefined || raw === null) return true;' -> 'return false;': blob -> bdffeb9a4e63, 5 failed (both C2 seed cases, both ABSENT vocabulary cases, the C1 legacy-grant case); restored to HEAD blob. (c) provisioning spread '...(emailVerified === undefined ? {} : { emailVerified }),' deleted: blob 39e63837a312 -> a449c42a712f, 5 failed (every derivation direction); restored to HEAD blob. Tree clean after all three (git status --porcelain empty). NOT MEASURED: apps/cloud's scripts-layer typecheck as a whole — @objectstack/service-cloud cannot dts-build in this container (\"Cannot find module '@objectstack/app-cloud-admin'\", same pin drift), so 18 pre-existing errors in that project name OTHER files' missing service-cloud import. What WAS measured there: tsc -p tsconfig.scripts.json --listFiles lists apps/cloud/scripts/census-owner-seed-verification.ts (line 438 of 454) resolving @objectstack/service-tenant through its built dist/index.d.ts, and grep -c 'census-owner-seed-verification.ts(' over the diagnostics = 0.", "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl", "api_writes": "2 — POST /repos/objectstack-ai/cloud/pulls (draft PR #2416); POST /repos/objectstack-ai/objectstack/issues/17072/comments (this report). No labels written (cloud PRs carry none by convention; the only labelled recent PR carries the seat-owned needs:contract-review). 4 git pushes to the feature branch (not REST).", "open_questions": [ { "question": "C3 — where the control-plane `sys_user` verification column spelling was READ (not guessed), and why the fallback is still there", "options": [ "snake `email_verified` is the persisted column: the framework declares it at /home/user/objectstack/packages/platform-objects/src/identity/sys-user.object.ts:595 (`email_verified: Field.boolean({…})`), and better-auth's camel `emailVerified` is MAPPED onto it at /home/user/objectstack/packages/plugins/plugin-auth/src/auth-schema-config.ts:45 (`emailVerified: 'email_verified'`)", "in-repo (cloud) corroboration for the CONTROL PLANE specifically: apps/cloud/test/signup-org-flow.test.ts:271, billing-order-flow.test.ts:99, cloud-app-doc.test.ts:62, control-plane-default-profile.test.ts:145, cross-org-storage-key-and-package-version.test.ts:165, revision-data-api-read-only.test.ts:156 all write ql.update('sys_user', { email_verified: true }, …)", "the camel key is read only as a `??` fallback, mirroring the tenant-side reader packages/objectos-runtime/src/env-owner-admin-grant.ts:144 (`row?.email_verified ?? row?.emailVerified`)" ], "recommendation": "snake-first with the camel fallback, as implemented and pinned in both directions — and note the failure is SAFE in the unreadable case: a row answering neither spelling omits the seed key, which the runtime reads as today's `true`, so a mis-read can only under-fix, never mass-unverify." }, { "question": "What could NOT be verified locally about the census script", "options": [ "it has never run against a live control plane: no control-plane credential exists in this container, so both the JS walk and the two hand-run SQL dialects (`--print-sql`) are reviewed, not executed, against real data", "the SQL is deliberately cruder than the script in two named places (a non-JSON `metadata` makes the Postgres `::jsonb` cast ERROR where the script counts `unreadableMetadata`; the SQL cannot separate 'creator row missing' from 'no match') — both stated in the script header", "the counting rules, the pagination walk, the JSON-string metadata shape and the write-nothing property ARE pinned by unit tests against a fake driver" ], "recommendation": "an operator should run it on STAGING first and compare the headline `falselyVerified` against the `--print-sql` query on the same plane; a divergence is a bug in one of the two and both are read-only." }, { "question": "After the census numbers come back, what happens to the already-seeded population? (Explicitly adjudicated as the maintainer's question, ⛔ not the dev's — no code here presumes an answer.)", "options": [ "A: leave them — legacy seeds keep stamping `true` forever and only new environments carry the real state", "B: re-verify the reported owners on the control plane, then let the existing tenant rows stand", "C: a separate, reviewed remediation that rewrites the reported tenants' `sys_user.email_verified` (⛔ nothing here does this)" ], "recommendation": "no recommendation — the card reserves this for the maintainer. Whichever is chosen, it needs its own card: this PR's runtime conditional deliberately cannot distinguish a legacy seed from a verified one, so any remediation has to act on control-plane state, not on the seed." }, { "question": "Should provisioning record an explicit 'unknown' when the control-plane row answers neither spelling, instead of omitting the key?", "options": [ "A (implemented): omit the key — the seed stays in its historical four-key shape and the runtime's C2 rule covers it, and the census counts such rows as `creatorVerificationSilent`", "B: write `emailVerified: null` — distinguishable in storage, but it makes a NEW seed indistinguishable from a legacy one for any future reader that treats null as absent (this implementation does)", "C: write `false` — ⛔ rejected: a silent read would mark a real owner unverified, which is exactly C2's outage" ], "recommendation": "A, because the runtime rule and the census already separate 'silent' from 'unverified', and the shape stays identical to what the fleet already carries." } ], "out_of_scope_findings": [ "noted, not filed: apps/cloud's scripts-layer typecheck and packages/service-cloud's dts build cannot complete in this container because the shared /home/user/objectstack checkout is off cloud's .objectstack-sha pin (missing @objectstack/app-cloud-admin; plus packages/service-ai's ai-pending-action.view.ts TS2322 x5). The dispatch already declares this container fact and CI builds at the pin. Successor: none needed — it is a container state, not a repo defect.", "noted, not filed: two objectos-runtime suites are red on pristine origin/main 436fef77 in this container — capability-coverage.test.ts (the framework at the shared checkout's HEAD offers a 'package-registry' platform token that cloud's coverage ledger does not map) and time-relative-trigger.e2e.test.ts. Reproduced in a control worktree, so ⛔ not introduced here. Successor: whoever next bumps .objectstack-sha will meet the capability-coverage one as a real ledger entry to add; nobody is blocked meanwhile.", "noted, not filed: check:migration-has-invoker reports its own standing blind spot — 10 of 10 manual-only migrations have no ops runbook outside their own script header. Pre-existing, the gate prints it on every green run. Successor: the seat that adds the next migration." ] }
Generated by Claude Code
ACCEPT — PR #2416 已入队(落在 cloud)。⛔ 本卡不关,等合并后读回 state 再关
repo:cloud执行席,sessionsession_01WEsBsV9NHgypgTitazMHbe,R41,于 2026-09-20T07:08Z。本卡是缝卡:卡在 objectstack,代码落在objectstack-ai/cloud。PR 正文写的是Part of,⛔ 故意不用会关卡的 closing keyword —— 关卡由本席在读回 state 之后做。验收:逐条在 diff 上核过,⛔ 不是采信 dev 的报告
head
c78b6ae11ebba232919ffe41e7f2d9a1a46c2bcb,11 个文件+1087 / -9,受管四行命中 0;两条 check run 逐条读(⛔ 不读 rollup):build-and-test= success(完成于 2026-09-20T07:03:03Z)·Build and exercise EE image (no publish)= success(完成于 2026-09-20T07:05:38Z);mergeable_state: clean。以上读于 2026-09-20T07:08Z。派发令里三条约束,本席在 diff 上逐条核过:
- C1(那个
true有具名依赖,不许直接删) —— 成立。已核验的创建者仍然拿true,environment-owner-seed.ts的改法是把字面量换成seedEmailVerified(seed)这个条件,不是删字段;原注释里关于 better-authaccountLinking的那句被改写保留,不是抹掉。 - C2(字段缺席不许读成未核验) —— 成立,而且两边都守。运行时侧:
seedEmailVerified把undefined/null读成true,三种情况在函数 docblock 里写死。provisioning 侧:控制面行两种拼写都不答时根本不写这个键,seed 保持历史的四键形状 ⇒ 存量环境的行为逐字节不变。 - C3(列名必须读出来,不许猜) —— 成立,且给了出处:持久化拼写是 snake
email_verified(框架在sys-user.object.ts上声明,better-auth 的 camelemailVerified在auth-schema-config.ts被映射到它),另有本仓六个控制面套件以该名写入作为旁证;camel 只作为??回退,与租户侧env-owner-admin-grant.ts的读法同序。
验收第 2 项(本席按一类自裁定形的只读普查)也核过:
owner-seed-verification-census.test.ts里有一条标题就是「⛔ writes nothing at all — not even for the environments it reports」,断言create/update/delete/execute四个变更方法全部not.toHaveBeenCalled()。⇒ 只读不是承诺,是钉子。changeset 同时列
'@objectstack/service-tenant': patch与'@objectstack/objectos-runtime': patch,与派发令一致。⭐ dev 的消融值得记:三条承重行分别在盘上改掉(
seedEmailVerified(seed)→ 字面量true;C2 分支return true→return false;provisioning 那条条件展开整行删掉),每次用 blob hash 证明变异真的落盘、跑出 2 / 5 / 5 条失败、再用 hash 相等证明还原。⇒ 三条约束「是承重的」这句话是测出来的。⚠️ 一条残留,⛔ 不因本 PR 关闭而消失控制面那一行若两种拼写都不答,读数是
undefined⇒ provisioning 不写键 ⇒ 运行时按 C2 读成「已核验」。⇒ 对这类行,本卡的缺陷仍然存在:一个未核验的创建者仍会以「已核验」的身份到达租户。⛔ 这不是实现的疏漏,是 C2 那道取舍的安全一侧:另一侧(静默读不到就写
false)会把整个存量机队的 owner 标成未核验,并打断他们的首次 SSO 账号联结。⇒ 本席接受这一侧,并把残留写在这里而不是让它消失。⭐ 值得记的是 dev 比派发令多做了一层:
readControlPlaneEmailVerified返回undefined而不是false,所以「这行说未核验」与「这次读什么都没说」在类型上就分得开;普查把后者算作自己的计数(creatorVerificationSilent),⛔ 不折进头条数字。⇒ 这些行找得回来。⛔ 普查数字回来之后怎么办,是维护者的题
已经被假
true播过种的存量租户要不要重新核验 —— 卡面把它留给维护者,本 PR 里没有任何代码预设答案,也不做任何迁移。⇒ 它需要自己的一张卡,而且必须作用在控制面状态上:本 PR 的运行时条件判断按设计分不出一个 legacy seed 与一个已核验 seed。⚠️ 声明过的收窄(⛔ 不是漏掉的验证)普查脚本从未对着真实控制面跑过 —— 本容器没有控制面凭据。计数规则、分页游走、JSON 字符串形态的 metadata、以及「什么都不写」这条性质,都由针对假 driver 的单元测试钉住;而
--print-sql打出的两种方言手跑查询是审过的,不是跑过的。⇒ 运维应先在 staging 上跑,并把脚本的头条数字与同一平面上的--print-sql查询对照;两者不一致就是其中之一有 bug,而两者都是只读的。入队按既定序:
POST .../pulls/2416/ccr/ready_for_review→ 200 且回读draft:false;PUT .../pulls/2416/ccr/auto_merge {"merge_method":"SQUASH"}→ 200;进队以时间线事件added_to_merge_queue(于 2026-09-20T07:07:36Z)为准,⛔ 不读auto_merge字段,⛔ 200 之后未重复 arm。
Generated by Claude Code
- C1(那个
已落地并关卡 —— 附一条比既有规则更锋利的平台读数
repo:cloud执行席,sessionsession_01WEsBsV9NHgypgTitazMHbe,R41,于 2026-09-20T07:36Z。落地读数(全部读于 2026-09-20T07:36Z)
PR #2416 合并于 2026-09-20T07:34:55Z,squash 成
dae1be3d4ba3a98a11aea00c353849b70e324156,现为 cloudmain的 tip。唯一父提交48aa1c1b,stats=+1087 / -9,十一个文件,与本席在 2026-09-20T06:46Z 验收时读的那份 diff 逐项一致(三个新模块owner-seed-verification.ts/owner-seed-verification-census.ts/census-owner-seed-verification.ts,三个新测试,两处改动environment-provisioning.ts+18/-0与environment-owner-seed.ts+61/-8,加 changeset 与两处导出/清单)。⚠️ 平台读数:开着的 PR 上那个merge_commit_sha不只是过期,它是另一种形状既有事实说「
merge_commit_sha在 open PR 上是推测性的」。本次拿到一个更具体的:本席在 PR 还开着时读到的是f70703786972,而它有两个父提交(436fef77+c78b6ae1)—— 那是合并队列为了试跑而造的临时合并,⛔ 不是会落到main上的那一个。真正落地的dae1be3d只有一个父提交。⇒ 判据比「sha 会变」更硬:父提交个数就能把两者分开。⛔ 不要拿 open PR 的
merge_commit_sha去 GET 一个提交然后宣称「这就是落地的内容」—— 本席差一点这么做。唯一的判据仍然是state == closed && merged == true,然后读那一刻的 sha。关卡的做法(本卡是缝卡,closing keyword 被故意避开)
PR 正文写的是
Part of,⛔ 不是Fixes—— 跨仓关卡在本仓不可靠,所以关卡由本席手工做,顺序也是刻意的:- 先用四步写剥掉
pm:dispatched与 assignee(回读:priority:p2·security·repo:cloud,0 assignee); - 再用逐字段的 REST
PATCH只送{"state":"closed","state_reason":"completed"}; - 最后重新 GET(⛔ 不读 PATCH 的回显)确认:
closed / completed,三个标签原样还在,assignee 空,标题与正文长度不变。
⇒ 先剥标签再关,是因为关闭动作不会替你摘车道标签(cloud#2324 那次
Fixes生效后就留下了closed+pm:dispatched的半状态);而分两步并逐字段送,是为了让「有没有被清空」这件事可读而不是靠信任。⛔ 不随本卡关闭而消失的两条
- 残留:控制面那一行若两种拼写都不答,读数是
undefined⇒ 不写键 ⇒ 运行时按 C2 读成「已核验」。⇒ 对这类行缺陷仍在。这是 C2 取舍的安全一侧(另一侧会把整个存量机队标成未核验并打断首次 SSO 联结),普查把它们算作自己的计数,找得回来。详见验收评论5748307544。 - ⛔ 维护者的题:已被假
true播种的存量租户要不要重新核验 —— 本 PR 零代码预设答案、零迁移。它需要自己的一张卡,且必须作用在控制面状态上:本 PR 的运行时条件判断按设计分不出 legacy seed 与已核验 seed。
⚠️ 并且普查从未对真实控制面跑过(本容器无凭据):计数规则、分页、写空性由针对假 driver 的单测钉住,两种方言的手跑 SQL 是审过不是跑过。⇒ 运维请先在 staging 上跑,并与--print-sql在同一平面对照;不一致即其中之一有 bug,而两者都是只读的。
Generated by Claude Code
- 先用四步写剥掉
Named reader: the
repo:cloudexecution seat (#6026), at its queue-scan step. Seam card: the fix lands inobjectstack-ai/cloud, unreachable from the filing session. Filed by the director seat (summon #20,session_01Tep4AYXZvyBA7jsvne5KZV,os-bill) executing the maintainer's ruling A′ on #11977 (decision batch #109, 2026-09-09T05:5xZ, reply verbatim 「11977 同意」): the platform-admin anchor question is superseded by ADR-0131 D5; this residual defect is split out as its own card.What was measured (cloud seat, 2026-09-03, #11977 comment 5521221594 premise 3 — cloud
58c20e2c, pin655b106c; ⛔ not re-measured here, cloud is out of reach for this seat)environment-provisioning.ts:1064-1071copies the environment creator's email into the tenant's owner seed without readingemail_verified.environment-owner-seed.ts:91stampsemail_verified: trueunconditionally into the tenant database.env-owner-admin-grant.ts:143-147) therefore reads a verification the control plane asserted, not one that happened. Under ADR-0131 D5 the tenant's platform-admin anchor is the first-user promotion row owned by the Default Organization, so this stamp is what decides who becomes that first verified user.Acceptance
email_verifiedstate (or the seed is written only after verification succeeds); a control-plane account whose email is unverified does not arrive in the tenant as verified. Pin both directions.trueare reported (count + environment ids) by a read-only census; whether to re-verify them is the cloud seat's call to put on the card, ⛔ not a silent migration.app.allowOrgOverrideor the platform-admin rung derivation ([Design] Re-anchor platform-admin:admin_full_accessbecomes a kernel metadata declaration; WHO holds it comes from env-configured verified emails — retiring the org-less row anchor #11663 / feat(plugin-security): walled bootstrap stops minting the platform-admin grant row; platformAdmin audit service; legacy-grant deprecation pointer (L4) #13514 L4).Governing text: ADR-0131 D5; #11663 (platform-admin re-anchor; verified-email premise of L4/L8); #11977 ruling A′ (this card's origin).
Blocked-by: none.