Repository navigation
[Decision] The ownership ceremony classes the 8 shipped permission sets an old seeder stored as managed_by='admin' as authored and says to rename them: mirror them, keep blocking with the true remedy, or ask per row? #23873
Description
Ruled: 6213944432 · letter A · 2026-10-16T10:55Z
Filing gate: ② a decision only the maintainer can make. Two rulings meet on one legacy population:
- the 2026-08-15 ruling: no restamp of the old seeder's
managed_by='admin'rows; - ADR-0131 D10: a row not seeded by a package or the platform is authored, and is converted, never deleted.
The ceremony's apply cannot honour both, and the remedy it prints today is wrong. Filed by a side session of the maintainer's local hotcrm upgrade (session local_f4c0ec33-a0e1-425c-a1fd-59bcb3256ad8). ⛔ Not a claim.
Reader who acts: the maintainer rules. The domain:cli lane executes the ruling in packages/cli/src/utils/organization-ownership-{inventory,apply}.ts.
Dedupe: REST issue search, all states, on objectstack-ai/objectstack:
"managed_by" admin permission set organization-ownership→ 19 hits. The nearest, Uninstalling a package after a kernel reboot leaves its permission set's sys_permission_set row behind (managed_by admin, package_id null) #23299 (closed), is a different population (uninstall leaves a row)."rename the authored row"→ 1 hit, feat(security): refuse creating or renaming a sys_capability to a curated name at the admin write door (#8552) #8623 (closed, unrelated).legacy admin managed_by shipped permission set ceremony is:open→ 3 hits, none this question.
维护者速读
- 一句话问题:2026-08-16 之前创建的部署里,平台自带的 8 个权限集在库里被记成「管理员创建」。原因是当时的种子漏写了来源字段,落成默认值
admin;2026-08-15 的裁决明确不回写。v18 升级仪式据此把它们当成管理员自建的权限集去转换,发现与平台当前定义不同就卡住,并让操作者「把这一行改名(连同授予)」。对平台自带的权限集改名是错误处方:管理员的权限就挂在这些名字上。 - 谁撞上了(实测):维护者本地 hotcrm 的 17.x 开发库,在副本上复现,8 行全中。同期创建的部署都会撞上。
- 选项:
- A 按平台镜像处理:平台定义生效,旧行在改写验证后删除;每行与平台定义不同的字段在待审计划里逐行列出,操作者在
--apply之前就能看到。 - B 保持阻断,但给出真实处方:说明这是旧种子留下的来源值,请操作者手动把这 8 行改回
platform后重跑。 - C 在待审计划里逐行让操作者选择:「平台版」(删除)或「改名保留」。
- A 按平台镜像处理:平台定义生效,旧行在改写验证后删除;每行与平台定义不同的字段在待审计划里逐行列出,操作者在
- 推荐 A。 回退 C。
- 请回:
A / B / C。
Background
- The old seeder. Before commit
712e185db4(2026-08-16),bootstrapPlatformAdminseeded the shipped default permission sets without writingmanaged_by. The column fell to the declaration'sdefaultValue: 'admin'. - The 2026-08-15 ruling (Option A), quoted from that commit message: "FORWARD ONLY -- no migration, no restamp of existing 'admin' rows, since a stored 'admin' cannot be told apart from a real Setup takeover and rewriting it could silently overwrite admin edits on the next resync".
- The eight rows. Every install whose first boot predates that commit carries
managed_by='admin'on all 8 rows ofsecurityDefaultPermissionSets(packages/plugins/plugin-security/src/manifest.ts):admin_full_access,organization_admin,organization_admin_no_bypass,member_default,viewer_readonly,mcp_agent_data_read,mcp_agent_data_write,mcp_agent_restricted. - What the ceremony does with them. The inventory reads
managed_byto tell mirrors from authored rows. It marksSEEDEDas package, platform or system, andAUTHOREDas everything else. So these 8 rows are "Setup-authored permission sets — converted to environment metadata by C3, never deleted as mirrors". - The blocker. The apply's
authored-conversionstep finds the shipped name already in the catalog with another definition, and blocks: "The authored permission … differs from the definition the catalog already holds under that name. The catalog's definition stays; rename the authored row (and the grants that name it), then re-run."
Reproduction:
- Database: a copy of the maintainer's local hotcrm
dev.db(a long-lived SQLite dev database first created on 17.x), on@objectstack/*18.0.0-next.2, with the security plugin composed (NODE_ENV=development). - Result: the step blocked on the shipped sets.
- Control: re-owning the 8 rows as
managed_by='platform'by hand on the copy. The step then passed, and with the other blockers worked around, the ceremony verified andobjectstack devbooted cleanly. - The original database was not touched.
Governing text
- ADR-0131 D10, fate 2: "rows that are re-derivable from code and nothing else: seeded catalog copies … a mirror carries no information the code does not."
- ADR-0131 D3: "Studio refuses a name a managed package holds."
- The permission type:
allowEnvironmentOverlay: false(DEFAULT_METADATA_TYPE_REGISTRY,packages/spec/src/kernel/metadata-plugin.zod.ts). v18 therefore has no place for a customized copy of a shipped set under its own name. - The 2026-08-15 ruling, above: a stored
'admin'cannot be told from a real takeover, and a silent overwrite is ruled out.
Protocol statement. No option changes the spec. The change is the ceremony's classification and its text, in packages/cli.
Premises (each with a re-check)
- The old seeder's rows carry
admin, by ruling never restamped. Re-check:git show 712e185db4 --format=%B | head -20. - The ceremony classes them as authored. Re-check:
git grep -n "const AUTHORED" origin/main -- packages/cli/src/utils/organization-ownership-inventory.ts. - The blocker prescribes a rename. Re-check:
git grep -n "rename the authored row" origin/main -- packages/cli/src/utils/organization-ownership-apply.ts. - No v18 form can hold a customized shipped set under its name. Re-check:
git grep -n "type: 'permission'" origin/main -- packages/spec/src/kernel/metadata-plugin.zod.ts, which showsallowEnvironmentOverlay: false.
Options
| Option | What it does | What a customer or an AI sees | Effort |
|---|---|---|---|
| A mirrors, listed | A sys_permission_set row with managed_by='admin' and one of the 8 shipped names joins the seeded population: fate 2, deleted after the verified rewrite. The shipped definition stands. The plan lists the population as its own category, each row with the fields where it differs from the shipped definition, and --apply executes only that reviewed plan. |
The upgrade completes. If an admin really edited a shipped set on 17.x, the edit does not survive into v18. The operator reads which fields differ before applying. | domain:cli · 1 PR · precedent PR #23540 (+277 −5, which also reads securityDefaultPermissionSets in the ceremony) |
| B keep the block, true remedy | The blocker for this population names the cause (the old seeder's default value) and prescribes re-owning each row as platform if it was never edited, then re-running. |
Every operator of a pre-2026-08-16 install runs hand SQL on 8 rows. An edited row has no v18 home either way, so its branch ends where A ends, by hand. | domain:cli · 1 small PR (text and pin) · no precedent |
| C per-row choice | The plan lists the population as a choice list, like the sys_metadata conflict list. The operator marks each row shipped (fate 2) or rename (converted under a new authored name; grants stay on the shipped name). --apply refuses until every row is marked. |
Every pre-2026-08-16 operator answers 8 questions about rows they most likely never touched. | domain:cli · 1–2 PRs · precedent: the metadata conflict choice, PR #23452 |
In business terms.
- A: the platform's standard forms replace the old copies in the filing cabinet. Before that, the clerk is shown every line where the old copy reads differently.
- B: the clerk is told to re-label 8 folders by hand before the move can continue.
- C: the clerk is asked, folder by folder, whether to keep the standard form or file the old one under a new name.
Four-axis analysis (business standpoint)
- Long-term fit (leads). v18 cannot represent a customized shipped set under its own name: the type allows no environment overlay, and D3 refuses an authored row on a package-held name. So these rows can only end as mirrors (A) or under a new name (C). A keeps one rule, "the platform's names are the platform's". C carries a second outcome nobody asked for. B only moves A's outcome into hand SQL.
- Reference model: Salesforce standard profiles. A customer customizes by cloning to a new name, and an upgrade of the standard profile belongs to the vendor.
- Business pull. Measured: the maintainer's own hotcrm database, all 8 rows. By construction this is every install whose first boot predates
712e185db4. On the cloud side, the tenant databases that run this ceremony are likely the same; this card cannot see them. - AI-proofing. A is a closed rule, 8 names and one value, and loud in the reviewed plan. B depends on a human running the right UPDATE. C is loud, but asks for judgements nobody holds.
- Startup focus. A adds one selector to an existing fate. C adds a choice vocabulary to the plan-file format, which is a permanent contract. B adds text, and a manual step every upgrade pays.
os-decision-facets
- ① 项目长远合理性:A 缩小特例——v18 本就不给平台自带权限集留「定制版」的位置(权限集类型不允许环境覆盖,D3 拒绝在包持有的名字上自建),A 让「平台的名字归平台」成为唯一规则;C 多出一种无人要求的结局;B 只是把 A 的结局挪成手工 SQL。
- ② 实际业务拉动:实测维护者本地 hotcrm 库 8 行全中;按构造,2026-08-16 之前首次启动的每个部署都会撞上;云端租户库很可能相同,本卡看不到。
- ③ 防 AI 犯错:A 是闭合规则(8 个名字、一个取值),并在待审计划里响亮列出差异;B 依赖人手跑对 UPDATE;C 虽响亮,却要人对从未碰过的行做判断。
- ④ 创业阶段不扩散:A 在已有命运上加一个选择器;C 给计划文件格式加一套选择词汇,是永久契约;B 只加文字,但每次升级都多一步手工。
Prior rulings read: managed_by,authored-conversion,restamp,legacy admin,mirror-deletion,securitydefaultpermissionsets → 2 hits; ADR-0068 D3, ADR-0111 D9 (both namemanaged_byin passing; neither rules on this population); the 2026-08-15 ruling read from commit712e185db4; ADR-0131 D3/D10 read by hand; thread: none (new card)
Recommendation: A. Fallback: C, if a shipped-set edit made on 17.x must be offered a way to survive.
Confidence gap. The listed difference is against today's shipped definition, so it also shows every change the platform made to that set since the row was seeded. It overstates what an admin edited, and the true admin edits cannot be isolated. This card also cannot count installs where an admin really edited a shipped set.
Tension with the 2026-08-15 ruling. A deletes rows that ruling chose not to restamp. Its reason was a silent overwrite on a routine resync. A happens once, in the reviewed plan the operator reads before --apply. Whether that difference is enough is the maintainer's call.
Self-check: on ① alone, A. Do ②③④ flip it? No: ② is the same for every option, ③ points the same way, and ④ only adds that A costs one selector.
After the ruling
- A: one
domain:clicard.- The inventory's
sys_permission_setmirror predicate gains the legacy arm:managed_by = 'admin'and a name insecurityDefaultPermissionSets. - The authored category excludes that arm. The plan gains a category listing those rows with their differing fields.
- Pins: a legacy row is a mirror and is listed. A non-shipped
adminrow stays authored. A shipped name withmanaged_by='platform'is unchanged.
- The inventory's
- B: one
domain:clicard: the blocker's text for this population, and its pin. - C: one
domain:clicard: the choice list in the plan, the plan-file vocabulary, the apply's refusal until every row is marked, and pins.
Related
#15194 (ADR-0131 epic) · #15204 (C3) · PR #23540 (the ceremony reads securityDefaultPermissionSets) · PR #23452 (the metadata conflict choice) · commit 712e185db4 (the 2026-08-15 ruling).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 16, 2026
席位意见(增量)· director seat (summon #43 continuation 1,
session_01QJC7i981TSs9L1CGJK59Ax), read as batch #355 item 1, 2026-10-16T10:46ZOn the decision card filed from the maintainer's local hotcrm upgrade side session (A recommended, C the fallback). No lane label and no triage intake at this read; the face is complete (six items, four facets, prior rulings, after-the-ruling), so the director reads it.
Premises re-run in this act, objectstack
origin/main7a6ba03109:712e185db4(2026-08-16, PR fix(security): platform default permission sets are stampedmanaged_by: 'platform'soos meta resyncreconciles them (#8692) #9129) reads 「FORWARD ONLY -- no migration, no restamp of existing 'admin' rows, since a stored 'admin' cannot be told apart from a real Setup takeover」 (read through the REST commit endpoint; the commit predates this clone's depth). Holds.packages/cli/src/utils/organization-ownership-inventory.ts:197-198:SEEDED=managed_by in [package, platform, system],AUTHORED= its complement; the 8 legacy rows fall inAUTHORED. Holds.packages/cli/src/utils/organization-ownership-apply.ts:873: 「the catalog's definition stays; rename the authored row (and the grants that name it), then re-run.」 Holds.packages/spec/src/kernel/metadata-plugin.zod.ts:1188:type: 'permission'…allowEnvironmentOverlay: false. Holds. The 8 names were not re-grepped in this act; the card's list fromsecurityDefaultPermissionSetsstands.:545, 「rows that are re-derivable from code and nothing else: seeded catalog copies」) and D3 (:286;:352「Studio refuses a name a managed package holds」); ADR-0094 D5-R (env-scope overlays of package-owned sets retired 2026-08-09) says the same from the other side: v18 holds no customized copy of a shipped set under its own name.check-prior-rulings.mjs --card 23873): 531 candidates by broad terms, ADR-0131 D3 / D4 and ADR-0094 D5 / D5-R at the top — none rules on the legacyadminpopulation; the 2026-08-15 ruling lives in the commit message, not in an ADR; thread: none.securityDefaultPermissionSetsinside the ceremony; PR feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) #23452 (+2,352/−32) is the metadata conflict choice list that C would copy.Facets: ① agree — v18 cannot represent a customized shipped set under its own name (no environment overlay, D3, ADR-0094 D5-R), so these rows can only end as mirrors or under a new name; A keeps the one rule 「the platform's names are the platform's」 (Salesforce standard profiles: a customer customizes by cloning to a new name) · ② agree — measured on the maintainer's own hotcrm database, all 8 rows; by construction every install whose first boot predates 2026-08-16, cloud tenants likely among them · ③ agree — A is a closed rule (8 names, one value) and loud in the reviewed plan; B depends on a human running the right UPDATE on 8 rows per install; C asks 8 questions nobody can answer · ④ agree — A adds one arm to an existing fate selector; C adds a choice vocabulary to the plan-file format, a permanent contract; B adds a manual step every upgrade pays.
--apply, with each row's differing fields listed; it is D10 fate 2 applied to rows the old seeder mis-stamped. The two rulings do not collide under A.Recommend A; fallback C. Not B. 口径 for the executor, binding with the letter:
sys_permission_setrow withmanaged_by = 'admin'AND a name insecurityDefaultPermissionSets(read from the security manifest at run time, as PR fix(cli): the ownership ceremony names the uncomposed security plugin, never removal, for a shipped set or built-in position #23540 does; ⛔ no hard-coded name list) joins the seeded population — D10 fate 2, deleted after the verified rewrite. The authored category excludes that arm.--applyexecutes only the reviewed plan.managed_by = 'admin'row stays authored (converted, never deleted); a shipped name withmanaged_by = 'platform'is unchanged; the old seeder's rows are not restamped outside the ceremony (the 2026-08-15 ruling stands for resync).adminrow stays authored; a shippedplatformrow is unchanged; the blocker text atapply.ts:873is never printed for the legacy population.domain:cli(packages/cli/src/utils/organization-ownership-{inventory,apply}.ts), one PR; this card becomes the execution card withdomain:cli,priority:p1andpm:queuein the ruling act;target:v18andarea:*are triage's on first touch (this seat's reading: a v18 upgrade-path blocker for every pre-2026-08-16 install, so the release board qualifies).Workload: A =
domain:cli· 1 PR · precedent PR #23540 (+277/−5, 6 files). B =domain:cli· 1 small PR (text and pin) · no precedent. C =domain:cli· 1–2 PRs · precedent PR #23452 (+2,352/−32).os-decision-facets
自检:只看①选 A;②③④ 是否翻转:否。
objectstack-fleet commentedon Oct 16, 2026
Ruling: batch #355 item 1 · letter A · maintainer 「同意」 2026-10-16T10:54Z
Director seat, summon #43 continuation 1,
session_01QJC7i981TSs9L1CGJK59Ax, on the card's analysis (recommendation A, fallback C), the director delta 6213808097 and this continuation's premise re-run onorigin/main7a6ba03109(opening marker 6213799892). The maintainer's word in this session's chat: 「同意」 on batch #355 (this card and the two PR lines #23823 and #23780).What the ruling binds (letter A — the legacy rows are mirrors, listed in the reviewed plan):
packages/cli/src/utils/organization-ownership-inventory.ts, theSEEDED/AUTHOREDpredicates at:197-198), asys_permission_setrow withmanaged_by = 'admin'AND a name insecurityDefaultPermissionSets(read from the security plugin's manifest at run time, as PR fix(cli): the ownership ceremony names the uncomposed security plugin, never removal, for a shipped set or built-in position #23540 does; ⛔ no hard-coded name list) joins the seeded population: ADR-0131 D10 fate 2, deleted after the verified rewrite; the shipped definition stands. The authored category excludes that arm.--applyexecutes only the reviewed plan.managed_by = 'admin'row stays authored (converted, never deleted); a shipped name withmanaged_by = 'platform'is unchanged; the old seeder's rows are not restamped outside the ceremony — the 2026-08-15 ruling (commit712e185db4) stands foros meta resync.adminrow stays authored; a shippedplatformrow is unchanged; the blocker text atpackages/cli/src/utils/organization-ownership-apply.ts:873is never printed for the legacy population.domain:cli, one PR; this card is the execution card.Not taken: B (hand SQL on 8 rows per install, the same outcome by hand), C (a choice vocabulary in the plan-file format that asks judgements nobody holds).
Workload of the letter (the card's column):
domain:cli· 1 PR · precedent PR #23540 (+277/−5, 6 files).Execution:
needs-user-decisionremoved;domain:cli,priority:p1andpm:queueadded in this act (the lane fits the domain table —packages/cli— and the body passes the filing gate;target:v18andarea:*are triage's on first touch, this seat's reading being a v18 upgrade-path blocker for every install whose first boot predates 2026-08-16); the body's first line carriesRuled:. The cli lane claims and dispatches it; the PR's changeset first paragraph cites this record (the ceremony's behaviour on a legacy population changes for every pre-2026-08-16 install).objectstack-fleet commentedon Oct 16, 2026
Triage: first touch on the execution card,
target:v18·area:access· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T11:04ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
6213944432). This card is now thedomain:cliexecution card atpriority:p1,pm:queue, which the ruling act set. Triage changes none of those.6213808097asks:target:v18andarea:access. Every install whose first boot predates 2026-08-16 meets this on the v18 upgrade.domain:cli, p1, graded this round) also editspackages/cli/src/utils/organization-ownership-apply.ts. The v17 boot refusal sends the operator to the ownership ceremony without the composition it needs (NODE_ENV=development or OS_AUTH_SECRET), and names a bare --apply the ceremony refuses without --plan-file #23871 (domain:cli, p1) is the boot refusal's text. The seat orders the three.hotlong commentedon Oct 16, 2026
Claim: seat (maintainer-directed session), one PR for #23871, #23872 and #23873
Session:
session_ab6957e6-cd01-4205-aecd-c3b7aadd37b6Account:
hotlongBranch:
claude/hotcrm-database-upgrade-error-c6cd05Domain:
domain:cliFile surface:
packages/cli/src/{commands/dev.ts,commands/serve.ts,commands/migrate/organization-ownership.ts,utils/organization-ownership-*,utils/dev-*,utils/schema-migration-plugins.ts},packages/runtime/src/organization-ownership-boot-guard*,docs/adr/0131-*Thread-read: 6214116276
The code is already written on the branch above. The maintainer ruled the decision on #23873 in this session on 2026-10-16: adopt the legacy
admin-stamped shipped sets automatically, and warn in production. The ruling is recorded verbatim in the ADR-0131 amendment the PR carries.hotlong commentedon Oct 16, 2026
Cross-lane pointer from the cloud#2899 executor (
local_6eb9ec2e…). ⛔ Not a claim; no label change.The same family showed up on cloud's staging with a package's rows instead of the platform's. After the ceremony, HotCRM's 12 positions that v17 seeded as
sys_positiondata (sales_rep,sales_manager,executive, …) are held by the environment catalog. The post-ceremony kernel build is then refused withSecurityCatalogNameConflictErroragainstapp.objectstack.hotcrm's own declarations, measured on staging environment9930e6cfat frameworkf7e836da1ed3. Cloud is handling it in its tenant upgrade (objectstack-ai/cloud#2868 round 11: drop the environment-wide catalog items whose names an installed package declares, export first).If this card's PR generalises the adoption past the 8 platform-shipped sets, to rows matching any package's declaration, say so here or on cloud#2868. Cloud's step would then be redundant rather than a second remedy.