Uh oh!
There was an error while loading. Please reload this page.
docs(permissions): re-anchor the platform-admin pages on the landed config derivation, and give OS_PLATFORM_OWNER_EMAIL an operator runbook (L7) - #13659
Conversation
…onfig derivation `PLATFORM_ADMIN` now derives from two anchors, not one: the deployment's configured administrator list (`OS_PLATFORM_OWNER_EMAIL`, matched against the caller's own stored and email-verified `sys_user` row) and the original unscoped `admin_full_access` grant row. The permissions pages still described the grant row as the only route, and no page documented the variable at all. Verified against the merged code, not the design: the config branch in `packages/core/src/security/resolve-authz-context.ts` (6b-config), the parser and predicates in `packages/core/src/security/platform-admin.ts`, the posture-keyed bootstrap in `packages/plugins/plugin-security/src/bootstrap-platform-admin.ts`, the walled boot refusal in `packages/plugins/plugin-auth/src/auth-plugin.ts`, and the config-derived arm of `plugin-auth/src/last-admin-guard.ts`. Body prose only — no frontmatter, no headings, no `content/docs/releases/`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
|
| channel | state |
|---|---|
| MCP GitHub tools | exceeded |
| this seat's repo-scoped REST | 14,925 / 15,000 remaining, read probe HTTP 200 |
| REST GraphQL | 10,000 / 10,000 — but restricted to "the pinned set of PR-review operations" (403 for anything else) |
⇒ the MCP server carries its own quota, accounted separately from the REST channel. The REST channel is nearly untouched and still answering; it simply cannot perform this particular write.
⛔ What this seat is NOT doing
⛔ Not merging directly — AGENTS.md §7 states that whether a direct merge is refused here is "deliberately unmeasured — do not establish it by attempting one."
⛔ Not looking for another route around the quota.
⛔ Not claiming it is armed.
What happens next
This seat retries on its own check-in cadence until the quota clears. ⭐ Anyone with the means may simply mark it ready and arm it — there is nothing to review that has not been reviewed, and the ordering question the card raised (whether L5 must land first) was answered NO with evidence in the ACCEPT on #11976.
Generated by Claude Code
✅ Resolved — armed and in the merge queue. The earlier stand-down note is now superseded.
The channel recovered on its own and the arm went through on a bounded retry — no route-around, no change of method.
⭐ Two readings worth keeping:
⛔ Nothing in the prior note was worked around: no direct merge was attempted (whether it is refused here is deliberately unmeasured and this seat did not establish it by trying), and no alternate tool was used to reach the same mutation. The remedy was the one the standing ruling on #11742 prescribes — space it and retry, bounded. Measurements from this outage are recorded on #11742, including one that cuts against its currently-recorded recovery window. Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33363096644 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
⛔ Merge-queue build red — not this PR's failure. Diagnosis, then the one re-queue.
Evicted from the merge queue after queue build 33363096644 went red. The failure
Mechanism, from the job log (not inferred from the summary line): That test deliberately simulates an unbuilt workspace ( Why it is not this PR's⭐ This PR's entire diff is four It touches ⛔ What I am not doing
The one re-queue, and what it is forRe-queueing once — spending the single re-run allowance — precisely to test the "not this PR's" reading. Two outcomes, both informative:
Filing the diagnosis as a finding either way, so the analysis is not lost with this queue run. Context: the queue had 5 other failed builds in the last 24 h, and the triage bot notes its cross-PR ledger was not fully read, so its "only this PR hit it" line is a lower bound, not a census. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11976
Leg L7 (docs) of the #11663 platform-admin re-anchor. Every claim below was
re-derived from the merged code on
origin/main, not from the designdocument — the design is provenance only, and the line numbers it cites
(
authorization.mdx116/336/385/412) had drifted, so each site was re-locatedby content.
What was wrong
PLATFORM_ADMINnow derives at one site from two anchors. Everypermissions page still described the grant row as the only route, and
OS_PLATFORM_OWNER_EMAILappeared on no page at all — includingenvironment-variables.mdx, whose own description promises a canonical list ofOS_*variables.Each changed claim, and the code it was verified against
PLATFORM_ADMINderives from two anchors — the configured list, or an unscopedadmin_full_accessgrant row — either sufficientpackages/core/src/security/resolve-authz-context.ts§6b (hasPlatformAdminGrant) and §6b-config (configConfersPlatformAdmin), which write the same flagplatform-admin.tsPLATFORM_ADMIN_EMAIL_SEPARATOR,normalizePlatformAdminEmail,parsePlatformAdminEmailsparsePlatformAdminEmailsrefusal branch; pinned byplatform-admin.test.ts"one unparseable entry fails the WHOLE variable closed"matchesConfiguredPlatformAdmin→isEmailVerifiedUserRow(packages/types/src/email-verified.ts)sys_userrowmatchesConfiguredPlatformAdmin(row, config)takes a row; §6b-config passesgetUserRow(), nevergrants.emailmatchesConfiguredPlatformAdminreturnsfalseonemails.length === 0before reading the rowsinglestill promotes the first human account and writes its grant rowbootstrap-platform-admin.ts, the non-walledtail (oldestOf(humanUsers)+sys_user_permission_setinsert)group/isolated) writes no grant row, everbootstrap-platform-admin.tsif (walled) { … return 'walled_config_derived' }— no insert on that pathpackages/plugins/plugin-auth/src/auth-plugin.tsinit()throw onpostureEnforcesWall(requestedPosture) && !resolvePlatformOwnerEmail()resolvePlatformOwnerEmail()(non-blank) while standing needsresolvePlatformAdminEmails()(parsed);bootstrap-platform-admin.tslogs the fail-closed backstop instead of refusingresolvePlatformAdminEmails(live read, memo keyed on the raw string);platform-admin-service.tsis frozen and read-only by constructionplatform-admin-service.tsresolvePlatformAdminStanding/PlatformAdminServiceemail, or clearingemail_verified, is refused and points at the configurationplugin-auth/src/last-admin-guard.tsUSER_STANDING_KEYS+ the config-derived arm ofresolveAdminUserIds; pinned bylast-admin-guard.config-anchor.test.tsplatform_admin— a statement about the grant anchor onlyisGrantActivebefore derivation; §6b-config reads no windowadmin_full_accessset does not decide a configured administrator's standingpsRows/isRowActivelast-admin-guard.tsresolveAdminUserIdsconfig arm; pinned by "an environment whose only administrator is config-derived is not 'empty'"Files, and why four rather than two
The card declared
authorization.mdx,permission-sets.mdx, "plus an operatorrunbook section". Declaring the widened surface explicitly:
content/docs/permissions/authorization.mdx— the two-anchor derivation, andthree claims re-scoped to the anchor they are actually about.
content/docs/permissions/permission-sets.mdx— "who holdsadmin_full_access", under the built-in-sets table (the line the design citedas
:153).content/docs/deployment/self-hosting.mdx— the runbook. It was assumed toneed creating; it already existed as First boot: create the admin, and its
prose ("the very first account to register becomes the bootstrap admin") is
unconditionally false under walled postures. Extended in place rather than
duplicated — one runbook for this variable, not two.
content/docs/deployment/environment-variables.mdx— the canonicalOS_*table was missing
OS_PLATFORM_OWNER_EMAILentirely. Two bounded in-placefixes of the same defect class, both named here rather than left as unrecorded
drive-by edits: the new row, and
OS_DISABLE_SIGNUP, whose "the very firstuser can still sign up to bootstrap admin" is the same posture-blind claim.
Scope
title/descriptionand no heading added, removed, rewordedor re-levelled — the
content/docsslice owned by epic epic(docs-site): the site is technically un-indexable — fix robots/sitemap/canonical/OG first, then the keyword shape #12243. Provenmechanically, not asserted: a fence-aware extractor compared the heading list
and the frontmatter block of each file before and after — 15→15, 12→12, 14→14,
12→12, all identical, frontmatter identical. The one
+#line in the diff is a#comment inside a bash fence.content/docs/releases/).packages/edits..changeset/— docs-only, publishes nothing from any package. Repoprecedent, measured rather than recalled: the five most recent
docs-content-only commits (
63cf416e8,339f393c7,39d5d8a60,adf70f763,61f2ef17e— the last onedocs(permissions)) each merged withno changeset.
skip-changesetapplied.Verification — all on
47983e104, the final commitGate family derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(change set taken from git by the script, not from a hand-built path list):
27 families, harvested with
--commandsso neither invocation spelling isdropped. 26 green, 1 not measurable locally.
Quoting each gate's own verdict line rather than a shell status:
check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading— the four new cross-links included.check-doc-frontmatter: 2 content root(s) verified … content/docs 404, content/blog 3doc authoring guard: 393 files clean — no bare metadata literals.check-docs-single-h1: 395 page(s) under content/docs/ carry no body-level '# ' heading260 prose examples type-check across 3 surface(s)dispatch-gates self-test: 1017 cases pass.(check:pm-dispatch-gates, run by hand)Four gates first returned PREREQUISITE NOT MET — recorded as not measured,
never as red — and were re-run green after the builds they named
(
@objectstack/spec,@objectstack/lint,@objectstack/formula,@objectstack/client-react,@objectstack/client;gen:schemaleft the treebyte-clean).
check-test-completeness.mjsgrades a savedturbo run testlogthat only CI produces and says so itself: "the local reading for this gate is
NOT MEASURED … it is not a red".
pnpm lintis a proven-empty narrowing, not a skipped run: eslint's own flatconfig matches none of the four files (
--format jsonreports 4 files, 0errors, and 4 warnings all reading "File ignored because no matching
configuration was supplied"), and the config enables no
project/projectService, so type-aware linting is off and this diff cannot move averdict on any file it does not touch.
One difference worth a reviewer's eye
Nothing shipped contrary to the design, but the two fail-closed layers are not
equally strict, and the docs now say so. A walled deployment with the variable
unset or blank is refused at boot; a walled deployment whose value is set
but refused for an unparseable entry starts normally — the boot guard reads
resolvePlatformOwnerEmail()(non-blank) while standing needsresolvePlatformAdminEmails()(parsed) — and then has zero administrators withan error in the log. Documented as an operator instruction ("confirm from the
boot log, not from the fact that the process came up") rather than papered over.
Generated by Claude Code
Generated by Claude Code