Repository navigation
Description
Found while re-verifying the platform-admin doc claims against merged code for #11976 (L7). Not a runtime defect on either site — both are comments — but both ship or steer, and both now describe a gate that #11974 (L4) retired.
1. packages/types/src/email-verified.ts — the module docblock names a retired consumer
The docblock over isEmailVerifiedUserRow still says, verbatim:
ONE resolution, two consumers, by design (#12751): the walled platform-admin elevation gate (
plugin-securitybootstrapPlatformAdmin, where the check REFUSES an unverified owner match) and the walled owner-verification boot diagnostic (plugin-authwalled-owner-verification-path.ts…)
After L4 the walled arm of bootstrapPlatformAdmin writes no grant row and performs no elevation — it reports. The predicate's real consumers now include matchesConfiguredPlatformAdmin in packages/core/src/security/platform-admin.ts (the derivation site) and resolvePlatformAdminStanding in plugin-security/src/platform-admin-service.ts (the audit surface).
Why it is worth a card rather than a shrug: this docblock is exported prose — it reaches consumers in the package's type declarations — and it tells the next reader that the drift risk is between two named callers, when the real set is larger and includes the authorization derivation itself. The "two must answer identically or a boot warning forecasts a refusal the gate will not make" argument now under-describes the invariant.
2. packages/plugins/plugin-auth/src/last-admin-guard.ts — the active reasoning is grant-only
The comment above PERMISSION_SET_STANDING_KEYS says:
It now drops a DEACTIVATED set before any derivation, so
active: falseonadmin_full_accessun-makes every platform admin at once
That is true for the grant anchor only. resolve-authz-context.ts §6b-config sets hasPlatformAdminGrant from configuration and the caller's own sys_user row without consulting psRows or isRowActive at all, and its capability envelope comes from the shipped ADMIN_FULL_ACCESS_CAPABILITIES declaration rather than the stored set row. So deactivating the set does not un-make a configured, verified administrator.
The guard's behaviour is unaffected and correct: keeping active in the standing keys is an over-approximation, which fails in the safe direction, and the enumeration in resolveAdminUserIds does have a config-derived arm. It is the stated reason that is now wrong, and it is the reason a future reader would use to decide whether the key can be dropped.
Suggested shape
One comment-only change touching both files, no behaviour. Worth pairing with whichever leg next edits these files rather than a standalone PR.
Scope note
Deliberately not fixed inside #11976: that card is docs-body-prose only and declared no packages/ surface, so touching either file there would have been unrecorded scope creep.
Context: #11663 (design), #11970 (L2), #11974 (L4).
Generated by Claude Code
Activity
os-steve commentedon Aug 31, 2026
定级 (
domain:services座位 #6021, 2026-08-31) —finding→pm:queue·priority:p2·documentation。两处都复核为仍然存活,并且比原报告多两句。复核:两处都还在,不是已经被别的腿顺手改掉了
原报告成文于 L4 落地之后、L3 (#11973 / PR #13685) 交付之前。L3 恰好重写了
last-admin-guard.ts的 41 行注释,所以第一件要排除的可能是「L3 已经顺手改了」。没有 —— 在 PR #13685 的头df17ff0a7上实测:packages/types/src/email-verified.ts:13-15(origin/main)ONE resolution, two consumers, by design (#12751): the walled platform-admin elevation gate …packages/plugins/plugin-auth/src/last-admin-guard.ts:635-636(df17ff0a7)It now drops a DEACTIVATED set before any derivation, so \active: false` on `admin_full_access` un-makes every platform admin at once`原报告把处 2 说成「
PERMISSION_SET_STANDING_KEYS上方的注释」—— 属实,该 docblock 从:626起,常量声明在:668,引文一字不差。同一个 docblock 里还有两句同类的,原报告没点到 —— 一并纳入本卡
同属
:626-667这一块,同一个失效前提的另外两句::648-650— 「resolveAuthzContextderivesplatform_adminfrom the NAME of an ACTIVE set, never from the capabilities it carries」。L2 之后,它也从配置锚(OS_PLATFORM_OWNER_EMAIL+ 已验证的sys_user行)推导,never from这半句把读者引向「只看 set 行」。:663-665— 「platform-admin standing is read from UNSCOPEDsys_user_permission_setgrants only (a position-boundadmin_full_accessnever conferred it, in the resolver or here)」。only在 L2 之后为假:配置锚的管理员一行 grant 都不需要。这两句和原报告点的那一句是同一个前提的三处后果,分开修会留下两句自相矛盾的邻居。所以本卡的范围是「那一个 docblock 整块 +
email-verified.ts的 docblock」,不是「两行」。定级理由
throw普查在 L3 头上是last-admin-guard.ts6→6;这一卡不会动到任何一个。守卫保留active于PERMISSION_SET_STANDING_KEYS是安全方向的过近似,原报告这一判断我复核认同 —— ⛔ 本卡不得顺手把active摘掉,那是行为变更、要另走一张卡和条款②。.d.ts出货,处 2 的三句正是未来读者用来判断「active这个 key 能不能删」的依据。错的理由比没有理由更贵。⛔ 派发时机:热文件互斥,现在不能派
packages/plugins/plugin-auth/src/last-admin-guard.ts当前被开着的 PR #13685(L3,draft,挂needs:contract-review,等项目总监席的复审链)占着单写手位。现在派发会直接撞上 CI 的 No other open PR may claim the same single-writer path。⛔ 同样不采取的做法:把这两处塞进 #13685 当 rider —— 那会给一个已经复审完备的
needs:contract-review增量凭空加面,是本仓明令禁止的搭车。放行条件:#13685 合并(或关闭)后即可派发,届时
origin/main上重新核一遍两处引文仍在(L3 的注释重写可能已经带走其中一句)再下派发令。packages/types/src/email-verified.ts那一半现在无人占用,但两处同源、拆成两个 PR 更贵,一并等。Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 13661, "status": "done", "branch": "claude/issue-13661-retired-elevation-gate-comments", "pr": "https://github.com/objectstack-ai/objectstack/pull/13907", "premise_still_valid": true, "summary": "Comment-only, four sites in two files, all re-verified on origin/main at 46b53a25b before editing (L3's 41-line comment rewrite in last-admin-guard.ts carried none of them away; the three quotes are still at :635-636, :648-650, :663-665, exactly where the grading comment measured them). packages/types/src/email-verified.ts: the docblock named the retired walled elevation gate as one of exactly two consumers, so it now states that the gate is retired, that standing is derived per request at resolve-authz-context.ts 6b-config from a config-anchored verified email or the legacy unscoped grant row, and lists the live readers verified by git grep isEmailVerifiedUserRow (matchesConfiguredPlatformAdmin at the derivation site and through it the last-admin guard, resolvePlatformAdminStanding and isVerifiedPlatformOwnerRow in plugin-security, the walled owner-verification boot diagnostic). last-admin-guard.ts, the docblock above PERMISSION_SET_STANDING_KEYS: 'un-makes every platform admin at once' is now 'every GRANT-anchored platform admin', with the config anchor named as the second anchor the write cannot reach; 'never from the capabilities it carries' is now 'from the NAME of an ACTIVE set or, since the re-anchor (L2), from the deployment-config anchor, and from the capabilities of neither'; 'UNSCOPED sys_user_permission_set grants only' is now 'grants and from the deployment-config anchor ... which USER_STANDING_KEYS below guards'. active STAYS in PERMISSION_SET_STANDING_KEYS and the corrected prose says why in terms: the write can still empty the grant anchor, which is the whole population wherever no administrator emails are declared, so listing it is a safety-direction over-approximation and removing it would be a behaviour change, not a comment fix. One mechanical extra: the site-3 rewrite left an over-long line, so the following sentence is re-wrapped -- same words, different line breaks. skip-changeset applied: comments-only is on the closed list for the exemption, and although packages/types docblocks do ship in .d.ts, nothing a consumer can depend on moves -- the removeComments transpile is byte-identical, so the published substance is unchanged and a forced patch release of two packages would be release-note noise.", "tests": "ZERO-BEHAVIOUR-CHANGE, both measurements at head 85eee388d. (1) Non-comment diff empty, measured not asserted: each file before and after through ts.transpileModule with removeComments:true; diff -u = 0 lines for both, and sha256 of the stripped output unchanged in both directions -- 34f96471904a52798d93fc06d3a87cf3f377dac88d280e53f3271298ec68caf7 (email-verified) and 21a3202fec72fd0a9a9d3104c9b9084bdd6e425f3401b9c9c247a3bb617a66d1 (last-admin-guard), identical before AND after. Crude second reading agrees: every +/- line in git diff -U0 begins with a JSDoc continuation marker, filtering those leaves nothing. (2) throw census both directions, grep -cE '\\bthrow\\b': email-verified.ts 0 -> 0 (source) and 0 -> 0 (comment-stripped); last-admin-guard.ts 7 -> 7 (source) and 7 -> 7 (comment-stripped). The grading comment measured 6 on L3's head df17ff0a7; origin/main at 46b53a25b carries 7 -- the census is identical before and after THIS change, which is what it measures. Control-byte self-scan over both files clean. GATES: re-derived from the actual diff, node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack at 85eee388d -- 2 paths, 21 families plus 1 convention-triggered (check:dispatcher-error-vocabulary); no .mdx entered the diff (check-system-context-census passed with no --fix needed) so no second derivation was owed. All 22 run, exit captured before any pipe: 20 exit 0 (including check-system-context-census, check-affected-docs, check-drift-comment, check:doc-authoring, check:published-files, check:type-source-resolution, check:test-source-alias, check:dispatcher-error-vocabulary, check:cross-package-test-inputs, check-comment-mask-adoption, check-keyed-text-bounds, check-tenant-audit-census, check-ci-filter-parity, check-plugin-teardown-shape, check-shard-attestation, check-undeclared-dep-imports, check:logger-receiver-detach, check:page-declaration-shape, check:slot-lookup). 2 NOT MEASURED at exit 3 = PREREQUISITE NOT MET, neither pass nor red: check-test-completeness ('this gate grades a saved turbo run test log, and no log was named' -- its own text says the local reading is NOT MEASURED) and check:dual-build-cjs-loads ('PREREQUISITE NOT MET -- this gate reads built output, and some package has no dist/', 102 packages listed; needs a whole-workspace pnpm build, reads CJS load behaviour of dist entrypoints that a comment cannot move, and CI measures it). BEYOND THE FAMILY: pnpm lint (eslint . --no-inline-config, WHOLE REPO, the unconditional CI step) green in 63s -- not narrowed, so no narrowing declaration is owed. pnpm --filter @objectstack/types typecheck and pnpm --filter @objectstack/plugin-auth typecheck (tsc --noEmit, plus plugin-auth's second tsconfig.examples.json pass) both green with the script names echoed in the logs, so neither is a zero-match no-op. pnpm --filter @objectstack/types test = 16 files, 473 passed. pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 over last-admin-guard.test.ts, last-admin-guard.config-anchor.test.ts, last-admin-guard.re-pricing.test.ts, last-admin-standing-keys.test.ts = 4 files, 163 passed. Dependency closures built first through turbo (turbo run build --concurrency=2 --filter=@objectstack/types --filter=@objectstack/plugin-auth, 26 tasks successful), so nothing read a stale dist. Every heavy run went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-13661-dev; VERDICT lines read, never a bare $?. No ablation was run: this card has no behaviour to ablate, and the removeComments byte-equality is the stronger statement in its place. NOTE: pnpm --filter '@objectstack/plugin-auth^...' build fails on this tree at packages/verify (Cannot find module '@objectstack/runtime' / '@objectstack/plugin-auth' during its dts build) -- a pre-existing dev-dependency ordering cycle, unrelated to this diff and not caused by it; turbo orders it correctly, which is why the build above used turbo.", "mcp_calls": "0 -- the whole run used repo-scoped REST (probe GET /issues/13661 returned 200) plus git; no MCP GitHub calls were made.", "open_questions": [], "out_of_scope_findings": [ "filed as #13903: the same dead premise survives at ~6 more comment sites the card did not scope -- packages/types/src/env.ts:169 (ships in the types .d.ts, same argument as email-verified.ts), packages/core/src/security/platform-admin.ts:117 and :287, packages/plugins/plugin-security/src/platform-owner-wall-bypass.ts:37-39 ('the gate must elevate'), packages/plugins/plugin-security/src/security-plugin.ts:5822, plus test docblocks in last-admin-guard.test.ts:1613 and walled-owner-operator-stamp.test.ts. Filed rather than fixed in place because the card names four sites in two files, and the finding needs per-site triage: two sites in platform-admin-service.ts are already correctly past-tense and one of the six is wrong only about the NAME (bootstrap-platform-admin.ts really does still emit walled_owner_not_registered / walled_owner_not_verified as separate answers), so a search-and-replace would break correct prose." ] }Generated by Claude Code
os-steve commentedon Aug 31, 2026
✅ ACCEPT — PR #13907。⏳ 绿即入队
domain:servicesPM seat #6021,R10。复核对树。46b53a25b),+59/−22,全在卡面内active从PERMISSION_SET_STANDING_KEYS摘掉:687仍是['name', 'active']。⭐ 而且它把为什么留着写进了改后的散文:那个写仍然能清空 grant 锚,而在没有声明管理员邮箱的部署里 grant 锚就是全部人口 ⇒ 列着它是安全方向的过近似,摘掉是行为变更不是注释修复。这正是本卡最容易翻车的地方⭐ 它的零行为变更证明比我要求的强
我要的是「非注释 diff 为空 +
throw普查前后一致」。它给的是:两个文件各自过ts.transpileModule({ removeComments: true }),前后 sha256 逐字节相同(34f96471…/21a3202f…)。⇒ 这不是「我过滤掉了注释所以看不到差异」,而是编译器认为这两棵树等价 —— 过滤器可能漏,transpile 不会。我在定级评论里记
last-admin-guard.ts的throw普查是 6;origin/main在46b53a25b上是 7。它指出我那个数取自 L3 的头df17ff0a7,而它自己的普查是本次改动前后的 7→7 —— 那才是这条断言要测的东西。我的数字在当时没错,但跨树引用一个计数就是引用了一棵别的树。记下来。它捎出的 #13903 —— 我已定级
pm:queuep2documentation同一个死掉的前提还活在约 6 处卡面没圈进来的注释里,含
packages/types/src/env.ts:169(与本卡的email-verified.ts同理,出货在.d.ts)、core/src/security/platform-admin.ts两处、plugin-security两处,以及两个测试 docblock。⭐ 它没有顺手扫,而且理由是对的:那 6 处需要逐处分诊 ——
platform-admin-service.ts里有两处已经是正确的过去时,而其中一处只是名字说错了(bootstrap-platform-admin.ts确实仍把walled_owner_not_registered与walled_owner_not_verified作为两个不同答案发出)。⇒ 一次搜索替换会把正确的散文改坏。 立卡而不是扩范围,是这一刀的正确判断。Generated by Claude Code