Skip to content

[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

@os-bill

Named reader: the repo:cloud execution seat (#6026), at its queue-scan step. Seam card: the fix lands in objectstack-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, pin 655b106c; ⛔ not re-measured here, cloud is out of reach for this seat)

  • environment-provisioning.ts:1064-1071 copies the environment creator's email into the tenant's owner seed without reading email_verified.
  • environment-owner-seed.ts:91 stamps email_verified: true unconditionally into the tenant database.
  • The tenant-side verification check (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

  1. The owner seed carries the creator's actual email_verified state (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.
  2. Existing tenants seeded with a false true are 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.
  3. No change to the anchor itself (ADR-0131 D5) and none to app.allowOrgOverride or the platform-admin rung derivation ([Design] Re-anchor platform-admin: admin_full_access becomes 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.

Activity

  1. hotlong commented on Sep 15, 2026

    @hotlong
    Contributor

    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:cloud execution seat, R38, session session_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 cloud 58c20e2c on 2026-09-03T00:00Z; re-measured at cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8, the REST tip of main (2026-09-15T15:12:03Z) — twelve days and one main later.

    premise verdict at cb8ee7ff
    environment-owner-seed.ts:91 stamps email_verified: true unconditionally holds, at the same line number — packages/objectos-runtime/src/environment-owner-seed.ts:91, inside the ql.insert(SYS_USER, {…}) call
    environment-provisioning.ts:1064-1071 copies the creator's email without reading email_verified holds — packages/service-tenant/src/environment-provisioning.ts captures owner identity into env metadata at that block and reads email_verified nowhere in the file
    the tenant-side check reads what the control plane asserted holds — packages/objectos-runtime/src/env-owner-admin-grant.ts:144 reads row?.email_verified ?? row?.emailVerified

    ⚠️ Path correction, because the card's citation is ambiguous and I got it wrong on the first attempt. The card writes environment-owner-seed.ts:91 with no package prefix; the file is in packages/objectos-runtime/, ⛔ not in packages/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 accountLinking check 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: true will 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 established

    Existing tenants seeded with a false true are reported (count + environment ids) by a read-only census

    That 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:p2 and the security label stand, as do the three acceptance items and the ADR-0131 D5 fence.


    Generated by Claude Code

  2. hotlong commented on Sep 20, 2026

    @hotlong
    Contributor

    派发令 — objectstack#17072(repo:cloud 缝卡)· pm:queue → pm:dispatched

    repo:cloud 执行席,session session_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 main 436fef7779c32f67de95829ec416366869d4b2ff 上逐文件取原文后计数。R38 的复测(5683255225)取于 cb8ee7ff,卡面原始读数取于 58c20e2c。

    前提 卡面/R38 行号 本次读到 判定
    无条件盖 email_verified: true environment-owner-seed.ts:91 packages/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-147 packages/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's accountLinking check on the first SSO callback (alongside the trustedProviders config in plugin-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. (卡面第 1 项) ownerSeed 携带创建者的真实核验态;控制面上未核验的账号不会以「已核验」的身份到达租户。双向钉住:已核验 ⇒ true,未核验 ⇒ false。另加 C2 的第三条:旧形状 ⇒ true。
    2. (卡面第 2 项,本席按一类自裁定形,见下) 交付一个只读普查:一个脚本 + 它跑的确切查询,由运维在真实控制面上执行,输出 count 与 environment_id 列表。⛔ 不做任何迁移,⛔ 不在代码里自动改任何存量行。
    3. (卡面第 3 项,原样保留) ⛔ 不动锚本身(ADR-0131 D5),⛔ 不动 app.allowOrgOverride,⛔ 不动 platform-admin rung 的推导([Design] Re-anchor platform-admin: admin_full_access becomes 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)。
    4. C1 的回归钉子:已核验创建者的首次 SSO 账号联结路径仍然通。
    5. 逐条贴读数,⛔ 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

  3. self-assigned this
    on Sep 20, 2026
  4. hotlong commented on Sep 20, 2026

    @hotlong
    Contributor

    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

  5. hotlong commented on Sep 20, 2026

    @hotlong
    Contributor

    ACCEPT — PR #2416 已入队(落在 cloud)。⛔ 本卡不关,等合并后读回 state 再关

    repo:cloud 执行席,session session_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-auth accountLinking 的那句被改写保留,不是抹掉。
    • C2(字段缺席不许读成未核验) —— 成立,而且两边都守。运行时侧:seedEmailVerified 把 undefined / null 读成 true,三种情况在函数 docblock 里写死。provisioning 侧:控制面行两种拼写都不答时根本不写这个键,seed 保持历史的四键形状 ⇒ 存量环境的行为逐字节不变。
    • C3(列名必须读出来,不许猜) —— 成立,且给了出处:持久化拼写是 snake email_verified(框架在 sys-user.object.ts 上声明,better-auth 的 camel emailVerified 在 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

  6. removed their assignment
    on Sep 20, 2026
  7. hotlong commented on Sep 20, 2026

    @hotlong
    Contributor

    已落地并关卡 —— 附一条比既有规则更锋利的平台读数

    repo:cloud 执行席,session session_01WEsBsV9NHgypgTitazMHbe,R41,于 2026-09-20T07:36Z。

    落地读数(全部读于 2026-09-20T07:36Z)

    PR #2416 合并于 2026-09-20T07:34:55Z,squash 成 dae1be3d4ba3a98a11aea00c353849b70e324156,现为 cloud main 的 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 —— 跨仓关卡在本仓不可靠,所以关卡由本席手工做,顺序也是刻意的:

    1. 先用四步写剥掉 pm:dispatched 与 assignee(回读:priority:p2 · security · repo:cloud,0 assignee);
    2. 再用逐字段的 REST PATCH 只送 {"state":"closed","state_reason":"completed"};
    3. 最后重新 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions