Skip to content

[finding] AdminScope.manageBindings and authorEnvironmentSets are honoured by no write path after C3; the ADR-0090 D9/D12 junction gates are unmeasured; permissions docs describe removed gates #23488

Open

Description

@objectstack-fleet

Filing gate ①, class (b), declared ≠ enforced: the enforce-or-remove channel. Filed by domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX at #22975's landing (8-post, PR #23428 → 0d3ea295d1), as the seat review 6124054616 (finding 1) and the contract record 6162274013 (item 5) provide. Domain and area are triage's call: the positions span packages/spec, plugin-security and content/docs.

Positions (read on main at 0d3ea295d1)

  1. AdminScope.manageBindings and AdminScope.authorEnvironmentSets are declared and honoured by no write path.
    • The keys are declared in packages/spec/src/contracts/security-service.ts:92-93 and authorable in a permission set's adminScope (ADR-0090 D12; the form at packages/spec/src/security/permission.form.ts:62). The metadata form's help text (packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts:2326) tells authors that they grant binding and environment-set authoring.
    • delegated-admin-gate.ts still parses them (:213, :519, :883) and compares them in strict containment (:893).
    • But C3 retired the two objects they governed: sys_position_permission_set (the binding) and sys_permission_set (env-set authoring). Their gates left with them (assertBindingWrite, assertSetAuthoring). A position's sets are now part of its definition (ADR-0131 D3). Authoring a permission set is an environment metadata write, judged by manage_metadata, not by a delegate's scope.
    • So an author can declare a delegate who "may bind" or "may author env sets", and no door honours it.
  2. The ADR-0090 D9 (guest) and D12 junction gates are not measured on the new model.
    • The D5/D7 door counterparts are pinned (suggested-binding-acceptance.dogfood.test.ts: the risky accept answers 403, and PUT /meta/position/everyone answers 403).
    • D9's guest binding and D12's delegate gates had their junction-row enforcement removed with the junction, and no carrier re-states them against the definition write (record 6162274013, ③).
  3. Text that still describes the removed gates.

Wanted

  • Positions 1 and 2: enforce or remove, under ADR-0049. Either the definition write judges the delegate's manageBindings (a delegate edits a position's permissionSets only inside its subtree and allowlist) and authorEnvironmentSets, or the two keys retire through the ADR-0087 route with the forms, help text and pins that a removal needs (spec-property-retirement). Which one is a product question when the measurement shows no user. Per the four axes, the default with zero pull is to retire.
  • Position 3: correct the text to the model on main. This is docs-accuracy class, with no contract change.
  • One pin per position kept, or the ADR-0087 marker per key retired.

Not here

Dedupe: no open card names these keys or pages. The search covered manageBindings, authorEnvironmentSets and the permissions pages; #23323 and #22379 are closed and cover other sentences.

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · priority:p1 + target:v18 · domain:spec · area:access · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T13:56Z

    Read fresh. Declared ≠ enforced: AdminScope.manageBindings and authorEnvironmentSets (packages/spec/src/contracts/security-service.ts:92-93) are authorable, but no write path honours them after C3.

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-14T15:47Z · measure-first: a reading-only dispatch
    Session: session_0121ooBYRmS8ziFXm9ai5YHm
    Account: sunhaolin (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: none (reading-only: no closure, no test run, no PR)
    Worktree: none (reads origin/main through git show / git grep and REST GETs)
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: none written. Reads cover packages/spec/src/contracts/security-service.ts (AdminScope), packages/spec/src/security/permission.form.ts, packages/plugins/plugin-security/src/delegated-admin-gate.ts, the position definition write path, the three content/docs/permissions/*.mdx pages, examples/app-showcase/src/security/permission-sets.ts, and objectstack-ai/objectui (Studio's adminScope authoring).
    Container & model: S, mode:subagent, model: default tier (reading-only; no build).
    Clause-②: no
    Responsibility: delegated-admin-gate.ts still parses AdminScope.manageBindings / authorEnvironmentSets after C3 retired the two objects they governed, so no write path honours them | none: the definition write is judged by manage_metadata, not by a delegate's scope (to be measured) | an author who declares a delegate that "may bind" or "may author env sets"; whether anyone does is the reading this round takes
    Thread-read: 6170064364
    Ruling-ref: none. Triage 6170064364 gives the card's default under the four axes: retire on zero pull; a measured user turns it into a product question. Per the measure-first rule, this round only measures. The building dispatch waits on a ruling recorded on this card.
    Serial constraints cleared: reading-only, no write surface. The building round will re-run the overlap scan against open PRs in packages/spec/src/contracts/ and plugin-security.

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report (reading)
    Reading-only round on #23488 · dispatched by the domain:spec seat 4 PM · session session_0121ooBYRmS8ziFXm9ai5YHm · identity = Claim 6172320924 (Branch: none) · read at 2026-10-14T16:03Z

    Trees read. objectstack origin/main = 1418de4c7e (git show / git grep, read-only). objectstack-ai/objectui main = df05784ae4 (one REST tarball GET, grepped locally) plus a contents GET of the facet at the pin .objectui-sha = b9814b334d. /search/* is denied, so no code search was used anywhere. No branch, no worktree, no build, no test, no PR, no file edited. The card read 0d3ea295d1; git merge-base --is-ancestor 0d3ea295d1 origin/main exits 0, and git log 0d3ea295d1..origin/main over the gate, the spec files, the three pages and the showcase file is empty, so the card's line numbers still hold.

    Summary

    • Writers (reading 1): no production author sets either key to true. The only production author, the showcase, writes the default false. The true writers are two test fixtures. Control leg: the live sibling manageAssignments: true has 3 production authoring sites, so the census can see a writer. Two authoring doors make the keys settable: Setup's nested adminScope form and Studio's two switches.
    • Readers (reading 2): no door that writes what the keys name (a position's permissionSets, or a permission set) consults them. That door's judge is manage_metadata plus the wall, and no delegate-scope check runs there. Correction to the card: the keys do change one decision, the strict-containment comparison on the two assignment doors, and in one direction an inert flag satisfies the "equal scope refused" rule (traced from source, not run).
    • D9 / D12 (reading 3): D9's guest binding is carried by ADR-0138 D3 items 4-5. No door binds guest, and that absence is pinned (403 NOT_OVERRIDABLE). D12's binding and authoring rights have no judge and no pin, and the new model gives a position definition no business-unit dimension to judge them against.
    • False text (reading 4): the three pages no longer name assertBindingWrite, assertSetAuthoring or a walled data door (0 hits tree-wide). Docs-side false text narrows to two "or authoring" sentences. The showcase carries seven false sentences: five in permission-sets.ts and two in its tour doc. Same-class text also sits in the spec JSDoc and .describe(), the liveness ledger, two plugin-security comments, metadata-core's wall docstring and objectui's help string.
    • Recommendation: A (retire both keys now).

    Reading 1: who writes manageBindings / authorEnvironmentSets

    Spelling: git grep -n -E 'manageBindings|authorEnvironmentSets' 1418de4c7e returns 73 lines in 36 files. 13 are release history (8 CHANGELOG.md files, .changeset/pre/15204-walled-catalog-refusal.md:24, content/docs/releases/v13.mdx:91). The rest are declarations, readers and fixtures.

    Spelling for value-carrying writers: git grep -n -E '(manageBindings|authorEnvironmentSets)"?\s*:\s*(true|false)' 1418de4c7e returns 18 lines.

    surface production authors tests / fixtures
    examples/** app-showcase/src/security/permission-sets.ts:400-401, both false, which is the schema default (packages/spec/src/security/permission.zod.ts:692,694) none
    apps/** 0 0
    packages/** sources 0 (readers only, reading 2) true: plugin-security/src/delegated-admin-gate.test.ts:27-28, plugin-security/src/can-write-object-admission.test.ts:256-257. false: delegated-admin-gate-catalog.test.ts:33-34, delegated-admin-gate-cross-organization.test.ts:76-77, delegated-admin-gate.test.ts:752-753,864-865, granted-by-writer-provenance.test.ts:89-90, objects/reserved-identity-names.test.ts:216-217
    dogfood stack 0 packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts:98 reads the showcase's false
    content/docs/** examples 0. Both adminScope samples (permissions/delegated-administration.mdx:25-30, permissions/permission-sets.mdx:323-327) set manageAssignments only. The keys appear in the table row delegated-administration.mdx:59 and in the generated reference references/security/permission.mdx:34-35,128,185-186 none
    skills/** 0 (skills/objectstack-data/rules/security.md:53 teaches adminScope with manageAssignments only) none
    • Control leg: git grep -n -E 'manageAssignments"?\s*:\s*true' 1418de4c7e finds 3 production authoring sites (showcase permission-sets.ts:399, delegated-administration.mdx:28, permission-sets.mdx:325) and tests in 10 files. The page's other hit, delegated-administration.mdx:113, is a response sample, not an author. The same census that sees those writers finds none for the two keys.
    • objectui df05784ae4: grep -rn -I -E 'manageBindings|authorEnvironmentSets' over the tarball returns 10 lines in 5 files.
      • Studio's permission editor renders the keys as author-settable switches: packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx:659-674. The manageAssignments, manageBindings and authorEnvironmentSets switches each write { [key]: !!v } into the draft adminScope (:670), typed from the spec AdminScope (:31, :123).
      • Labels: i18n.ts:2477-2478 ("Manage bindings", "Author env sets"), zh :5761-5762. Help: :2467-2468 and zh :5754. Pin: PermissionAdvancedFacets.adminScopeAnchor.test.tsx:106-117. objectui ADR: docs/adr/0056-permission-editing-studio.md:142.
      • At the pin b9814b334d the same two switches stand at :662-663.
      • This is an authoring door, not an author: objectui ships no set with either key set.
      • No objectui code reads the two fields from my-delegable-scope: console/organizations/manage/InviteMemberDialog.tsx:92-107,301,316 reads placeableBusinessUnitIds and assignablePositions only.
    • Setup (objectstack): packages/spec/src/security/permission.form.ts:55-62 gives adminScope a json widget that renders its six keys as a nested form.
    • NOT MEASURED: objectstack-ai/cloud, every other consumer repo, and any deployment's stored rows are unreadable from this fleet. The stored rows are sys_metadata permission items and legacy sys_permission_set.admin_scope.

    Reading 2: readers, traced from the door to the judge

    Readers on 1418de4c7e:

    1. Schema. AdminScopeSchema (permission.zod.ts:654-702, strictObject) parses both keys at :692,694 with default false.
    2. Contract. DelegableAdminScope (packages/spec/src/contracts/security-service.ts:85-98, fields :92-93) is the response shape of describeDelegableScope (:1014).
      • Served by GET /api/v1/security/my-delegable-scope (packages/rest/src/rest-server.ts:10836-10880; ledger packages/rest/src/rest-route-ledger.ts:399).
      • Reached through the SDK (packages/client/src/index.ts:6096-6097) and objectui's auth client (packages/auth/src/createAuthClient.ts:719-731).
      • It is a report only, and no consumer reads the two fields.
    3. Gate normalisation. resolveHeldScopes (packages/plugins/plugin-security/src/delegated-admin-gate.ts:913-935) forces both keys to booleans at :925-926. Two consumers:
      • 3a. describeDelegableScope copies them into the report (:519-520). Placement uses manageAssignments only (:525-528).
      • 3b. definitionContainsStrictly (:879-909): the rights axis at :892-894 and strictness at :900-908. It is reached through assertScopeGrantContainment (:861-872) from three callers: assertAssignmentWrite (:789, a delegate's sys_user_position write), assertDirectGrantWrite (:825, sys_user_permission_set) and the picker (:546).
    4. Help text. permission.form.ts:62 and the generated bundles packages/platform-objects/src/apps/translations/{en,es-ES,ja-JP,zh-CN}.metadata-forms.generated.ts:2326 name the keys and their defaults only. The grant claim lives in the .describe() strings (permission.zod.ts:692,694), which feed the generated reference content/docs/references/security/permission.mdx:34-35,185-186. objectui's help text also carries it (reading 4).
    5. Tests. packages/spec/src/security/permission.test.ts:24-25 checks the defaults; the dogfood zoo :98 checks the showcase value.
    6. Lint. git grep -n -E 'manageBindings|authorEnvironmentSets' 1418de4c7e -- packages/lint/src returns 0. No rule reads either key.

    scopesCoverUser (:420-433) reads no flag: any held scope authorises explaining a user in its subtree. Its JSDoc (:411-413) still says "assignments, bindings, authoring".

    The door that writes what the keys name, and its judge.

    • The binding. A position's permissionSets is written by PUT /api/v1/meta/position/:name (REST rest-server.ts:7115 through metaSaveVerdict :3739-3745; dispatcher packages/runtime/src/domains/meta.ts:911-916), and by its publish, rollback and reset twins.
    • The judges, in order:
      • metaWriteCapabilityVerdict (packages/metadata-core/src/meta-write-capability.ts:88-102) admits isSystem or manage_metadata and nothing else. In the code's own words, "the verdict reads the caller alone: no type and no organization enters it" (rest-server.ts:3734-3737).
      • walledCatalogWriteVerdict (:202-232) admits only the platform operator under group / isolated.
      • saveMetaItem's item refusals follow, for example the packaged-base NOT_OVERRIDABLE (packages/metadata-protocol/src/protocol.ts:17291-17320, 17411-17417).
    • Permission-set authoring goes through the same door (PUT /api/v1/meta/permission/:name) under the same judges. The clone door (packages/runtime/src/domains/permission-set-clone.ts:238, 252) adds nothing and does not copy adminScope (:109, :119).
    • No delegate-scope check runs at any of these doors. DelegatedAdminGate.assert returns at once unless the object is in GOVERNED_OBJECTS, which is sys_user_position, sys_user_permission_set and sys_member (delegated-admin-gate.ts:133-149, :284). No /meta door calls it. Its one other caller is the data-door preview canWriteObject (plugin-security/src/security-plugin.ts:6189-6200).
    • Outcome: a delegate without manage_metadata cannot write any definition whatever its flags, and a delegate with manage_metadata can write any definition whatever its flags. Neither key changes either outcome.

    A write path the keys do change (refines "honoured by no write path"). Traced from source, NOT MEASURED by a run:

    • (i) Refusal. The granted set's scope has manageBindings: true (or authorEnvironmentSets: true) and the delegate's scope does not. Result: "not strictly contained" (:893-894), refused on sys_user_position / sys_user_permission_set.
    • (ii) Admission. The delegate's scope has manageBindings: true, and the granted set's scope is identical except for false. :900-908 counts that as strictly smaller and admits it. Since the flag confers nothing, the delegate hands out an effectively identical scope. That is the lateral propagation the equal-scope refusal exists to stop (delegated-admin-gate.test.ts:311-318).
    • No test pins (i) or (ii): the gate fixture sets both flags true on both sides (:23-30, :48). This sits inside the card's own class (b) and is not a new card.

    Reading 3: Position 2, D9 and D12 on the new model (source only)

    D9 (guest binding).

    • No door writes a set into guest's definition. ADR-0138 D3 item 5 (docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md:379-396) records it: a save of the packaged guest position answers 403 NOT_OVERRIDABLE, the package door refuses a built-in name, and confirmAudienceBindingSuggestion refuses a guest row.
    • The tier predicate describeAnchorForbiddenBits(set, 'guest') has no caller on main (ADR-0138 :170-173; packages/lint/src/validate-security-posture.ts:29-37).
    • ADR-0138 D3 item 4 (:370-378) assigns the tier check to the bind door when that door is built.
    • So no junction-row gate is owed today. The card's "D9 unmeasured" is superseded: the absence is measured.

    D12 (a delegate's subtree and allowlist at the definition write).

    • No judge exists (reading 2).
    • PositionSchema has no business-unit key (packages/spec/src/identity/position.zod.ts: name :82, label, description, permissionSets :106, delegatable :132). A position is one environment definition shared by every organization (ADR-0131 D3). So "position↔set bindings within that subtree" (ADR-0090 D12, docs/adr/0090-permission-model-v2-concept-convergence.md:521-522) has nothing on the definition to anchor to.
    • Under a wall, ADR-0131 D12 items 7-8 refuse any non-operator definition write outright (meta-write-capability.ts:156-201).

    Pins that exist, by ADR-0090 row:

    row pin what it covers
    D5 acceptance by manage_metadata only packages/qa/dogfood/test/suggested-binding-acceptance.dogfood.test.ts:108-124, :136-150, :161-167 accept turns a member's 403 into 200; a tenant admin's accept is refused 403; revoke
    D5 anchor sealed same file :152-156 PUT /meta/position/everyone answers 403
    D7 high-risk set same file :126-134 the risky accept answers 403
    D9 guest packages/qa/dogfood/test/guest-grants-channel.dogfood.test.ts:479-491 (no door: PUT /meta/position/guest answers 403 NOT_OVERRIDABLE), :509-523 (reference-app census: no guest binding in showcase, crm or multi-package), :290-365 (grants channel over a test-installed binding); packages/plugins/plugin-security/src/audience-anchors.test.ts:93-106 (the predicate only) the absence of a bind door, and the predicate
    D12 assignments delegated-admin-gate.test.ts:225-318 (subtree, allowlist, anchors, equal-scope refusal) and :950 (judged by the definition's permissionSets, not a junction row); delegated-admin-gate-catalog, -cross-organization and -holding-organization tests; showcase-permission-zoo.dogfood.test.ts; delegated-admin-invite.dogfood.test.ts the assignment half of D12 on the new model
    D12 bindings and env-set authoring none there is no judge to pin
    who writes definitions packages/qa/dogfood/test/wall-catalog-refusal.dogfood.test.ts:131-245 (walled refusal plus the single control); packages/metadata-core/src/meta-write-capability.test.ts the generic capability gate; no pin names a delegate scope at that door

    Reading 4: Position 3, the false text

    Spellings and counts:

    • git grep -n -E 'assertBindingWrite|assertSetAuthoring' 1418de4c7e returns 0 hits tree-wide, changelogs included.
    • git grep -n sys_position_permission_set 1418de4c7e over the three pages returns 1 hit per page (delegated-administration.mdx:59, permission-sets.mdx:241-242, positions.mdx:11). Each states the junction is retired, which is true.
    • git grep -n -i 'data door' over content/docs/permissions finds only "metadata door".

    So the docs half of Position 3 is narrower than the card states. In the list below, FALSE means the sentence is wrong on main; PROSPECTIVE means it describes a door that does not exist yet.

    Docs

    • FALSE content/docs/permissions/delegated-administration.mdx:60: "Granting or authoring a set that itself carries an adminScope requires a held scope that strictly contains it". Authoring goes through the metadata door, which reads no adminScope; the row above, :59, already says so.
    • FALSE content/docs/permissions/permission-sets.mdx:338-339: "strict containment when granting or authoring a set that itself carries an adminScope". Same reason.
    • PROSPECTIVE permission-sets.mdx:268: "the wildcard ban is the stricter guest tier's rule". True of the predicate, but no door runs it for guest.
    • Accurate on main: delegated-administration.mdx:59 (the flags admit no write and still count in containment). positions.mdx: no false sentence found (:11-12 and :111-120 read correctly).

    Showcase

    • FALSE examples/app-showcase/src/security/permission-sets.ts:357-359: "Guest bindings face the STRICTEST lint tier". The lint reads isDefault sets against everyone only (validate-security-posture.ts:29-37, :905).
    • FALSE :361-362: "Binding is an ADMIN action (Setup → Access Control → bind guest → showcase_guest_portal)". No door binds a set to guest (reading 3).
    • FALSE :384-385: "manageBindings stays false: this delegate re-staffs positions, they do not re-compose what a position means". This implies true would let a delegate re-compose a position; no door honours that.
    • FALSE :26-27 "(lint-blocked on everyone/guest bindings)" and :166-167 "the D7 lint blocks binding this set to the everyone/guest anchors". showcase_auditor is not isDefault, so no lint rule reads it, and the guest half has no door.
    • FALSE examples/app-showcase/src/docs/showcase_tour_security.md:41-42 ("the publish linter blocks it from everyone/guest bindings") and :51-52 ("binding it is an admin action in Setup"). Same reasons as above.

    Contract and code text, same class, beyond the card's list

    • FALSE packages/spec/src/security/permission.zod.ts:
      • :643-644: the D12 WHAT includes "position↔set bindings, author environment-owned sets".
      • :648-650: enforcement "on sys_user_position / sys_position_permission_set / sys_user_permission_set / sys_permission_set".
      • :691: "May create/delete sys_position_permission_set bindings…".
      • :946-948: "may manage assignments/bindings (and optionally author environment sets)".
      • .describe() at :692, :694 ships into the generated reference content/docs/references/security/permission.mdx:34-35, :185-186.
    • FALSE packages/spec/liveness/permission.json:39: the adminScope evidence names gates on sys_position_permission_set / sys_permission_set.
    • FALSE packages/plugins/plugin-security/src/objects/sys-user-permission-set.object.ts:30: "delegated manageBindings direct grants write this". Direct grants are judged on manageAssignments (delegated-admin-gate.ts:814).
    • FALSE delegated-admin-gate.ts:411-413: "Any scope kind qualifies (assignments, bindings, authoring)".
    • FALSE packages/metadata-core/src/meta-write-capability.ts:184-185: lists "the security plugin's data door on the catalog objects" among the doors that ask the walled verdict. 0d3ea295d1 removed that door: git grep -n 'walledCatalogWriteVerdict(' 1418de4c7e -- packages finds callers in cloud-connection, rest and runtime only. This is the walled-data-door sentence the card looked for, found here rather than in the docs.
    • PROSPECTIVE packages/plugins/plugin-security/src/builtin-positions.ts:92 (the served guest description, "Bindings face the strictest checks…") and packages/core/src/security/resolve-authz-context.ts:1099-1100 ("A set bound to guest passed the strictest anchor tier where it was bound"). Both hold only once a bind door exists (ADR-0138 D3 item 4).
    • FALSE docs/design/permission-model.md:306-310: the authoring sample names both keys and CRUD on sys_position_permission_set.
    • Governed (Tier H): docs/adr/0086-…:446 ("authorEnvironmentSets keeps package-managed rows publish-only"); ADR-0090 D12 :521-522, not amended for ADR-0131.
    • objectui: packages/app-shell/src/views/metadata-admin/i18n.ts:2467-2468, help "(assign users, manage bindings)", and zh :5754.
    • Release-owned, not touched by a code PR: content/docs/releases/v13.mdx:91.

    Reading 5: what each outcome would touch

    RETIRE (both keys, tombstone route under spec-property-retirement)

    spec (about 12 files, generated ones included)

    • security/permission.zod.ts:691-694: two retiredKey() tombstones with prescriptions. JSDoc :643-652, :696-700, :946-948.
    • contracts/security-service.ts:92-93: drop both fields from DelegableAdminScope.
    • security/permission.form.ts:62: the helpText drops both names.
    • security/permission.test.ts:24-25: becomes tombstone pins (the prescription and the parse refusal), plus a tree-scoped absence pin with its CROSS_PACKAGE_TEST_INPUTS radius.
    • ADR-0087:
      • one conversion ending in -removed in conversions/registry.ts MAJOR_18_CONVERSIONS (spec is 18.0.0-next.1), stripping permissions[].adminScope.{manageBindings,authorEnvironmentSets}, with a fixture carrying 2 notices;
      • migrations/entries/retired-keys/18.security__AdminScope__manageBindings.ts and …__authorEnvironmentSets.ts, spelled security/AdminScope:manageBindings / security/AdminScope:authorEnvironmentSets as authorable-surface/security.json:19,23 spells them;
      • one D3 semantic entry that states the containment change in (i) and (ii);
      • a STEP18_RATIONALE fragment in registry.ts.template, then gen:migration-registry.
      • Template for the semantic entry: migrations/entries/semantic/18.admin-scope-business-unit-blank-refused.ts.
    • Generated:
      • authorable-surface/security.json:19,23 (the rows turn RETIRED), authorable-defaults/security.json:8,11, the reference page (gen:docs);
      • authorable-surface.base.json:4936,4940 only through gen:authorable-surface-base;
      • the manifest and api-surface should not move (the AdminScope def survives); the build confirms it.
    • liveness/permission.json:36-41: rewrite the evidence. The adminScope row is a leaf and stays live.
    • Changeset for @objectstack/spec and @objectstack/plugin-security: minor, BREAKING, FROM → TO plus a one-line fix, Clause-②: yes (narrowing), and ADR-0087 disposition registered naming the new conversion id.

    plugin-security (about 10 files)

    • delegated-admin-gate.ts:204-222, :411-413, :519-520, :525-526, :879-909, :925-926. Containment then compares includeSubtree, manageAssignments and the allowlist; case (ii) turns into a refusal.
    • objects/sys-user-permission-set.object.ts:30.
    • Tests: delegated-admin-gate.test.ts:27-28, :752-753, :864-865; delegated-admin-gate-catalog.test.ts:33-34; delegated-admin-gate-cross-organization.test.ts:76-77; granted-by-writer-provenance.test.ts:89-90; objects/reserved-identity-names.test.ts:216-217; can-write-object-admission.test.ts:256-257; __tests__/legacy-catalog-objects.testkit.ts:333-336 (text only).
    • Plus one pin for the containment change.

    Other objectstack files

    • platform-objects (4 generated): translations/{en,es-ES,ja-JP,zh-CN}.metadata-forms.generated.ts:2326.
      • Regenerate with node scripts/check-i18n-bundles.mjs --write; pnpm i18n:extract regenerates platform-objects alone.
      • en is rewritten from the form source. The three translated helpText values are hand-written, and merge mode keeps them, so each needs a hand edit.
      • pnpm check:i18n gates the result.
    • dogfood: showcase-permission-zoo.dogfood.test.ts:98.
    • cli: the legacy-row ceremony packages/cli/src/utils/organization-ownership-apply.ts:685 saves sys_permission_set rows through the metadata door via packages/core/src/security/permission-set-row-definition.ts:101-111, carrying admin_scope verbatim.
      • Whether that save replays the conversion is NOT MEASURED. If it does not, the row mapper must drop the retired keys.
    • examples: app-showcase/src/security/permission-sets.ts:384-385, :400-401 (tsc finds them under the tombstone), plus the reading-4 sentences.
    • docs: delegated-administration.mdx:59-60, permission-sets.mdx:338-339, docs/design/permission-model.md:306-310.
    • ADR (Tier H): an ADR-0090 status-line amendment retiring D12's bindings and env-set authoring rights (the definition write is manage_metadata's, per ADR-0131 D3), and the ADR-0086 row :446.

    objectui (same landing plus the .objectui-sha bump, per AGENTS.md Definition of done item 4)

    • PermissionAdvancedFacets.tsx:49, :659-674 (drop the two switches); i18n.ts:2467-2468, :2477-2478, :5754, :5761-5762; PermissionAdvancedFacets.adminScopeAnchor.test.tsx:106-117; objectui docs/adr/0056-…:142.
    • Whether the pinned objectui build reds against the tombstoned type is NOT MEASURED (no build in a reading round). The facet change is owed either way, because the switches would write a value the spec refuses.

    Not touched: skills/** (0 hits), apps/** (0), content/docs/releases/** (release-owned).

    Size. About 30 objectstack files, roughly 12 of them hand-written, and 4-5 objectui files plus the pin. Measured precedents:

    Expect one objectstack PR and one objectui PR, plus the ADR amendment split off as its own Tier H piece.

    Stored rows: can sys_metadata permission-set rows carry the keys? Yes, from source.

    • PermissionSetSchema.adminScope (permission.zod.ts:953) admits both keys.
    • permission items are environment-authorable (packages/objectql/src/security-catalog-namespace.ts:128-139).
    • saveMetaItem stores the submitted body verbatim (permission.zod.ts:669-671).
    • Setup's nested form and Studio's switches both write the keys explicitly, and legacy admin_scope rows reach the door through the ceremony.
    • So a registered strip conversion is owed; rehydration replays it through applyConversionsToStoredItem.
    • How many deployments hold such rows: NOT MEASURED.

    ENFORCE

    • Decision first. A new ADR, or an ADR-0090 D12 amendment (Tier H), has to settle two things:
      • what "within the subtree" means for an environment-wide position definition that has no business unit;
      • what bounds a delegate-authored set body.
      • Without that second bound, authorEnvironmentSets plus an allowlisted set the delegate may self-assign is a self-escalation path: edit the set's body, then assign it to yourself.
      • The ADR would apply to single only, because ADR-0131 D12 items 7-8 forbid this under a wall.
    • Door. meta-write-capability.ts:88-102 admits manage_metadata only. Enforcing means admitting a non-manage_metadata delegate for position and permission items, which makes the verdict type- and item-aware:
      • on both transports (rest-server.ts:3739-3745, :7115; runtime/src/domains/meta.ts:911-916);
      • in the read exemption that mirrors it (rest-server.ts:3503 mayWriteItem);
      • in the publish, rollback and reset twins, and in the clone door.
    • Judge. A new plugin-security judge reached from the metadata door; DelegatedAdminGate governs data-door objects only.
      • For position: diff the old and new permissionSets; require every changed set to be allowlisted; apply containment to sets that carry adminScope; refuse anchors and built-ins; and add a blast-radius rule over every holder's sys_user_position.business_unit_id, across organizations.
      • For permission: a body-containment judge (the set grants no more than the delegate's own effective grants) plus the allowlist on the name.
    • Pins.
      • Unit pins for each rule, with controls.
      • Dogfood pins: an allowlisted in-subtree save answers 200; out-of-allowlist, anchor, walled and authoring-escalation saves answer 403.
      • objectui would need a delegate-reachable editor (Studio is manage_metadata-gated).
    • Effort: no precedent. At least 3 PRs across domain:spec, plugin-security, the metadata doors and objectui, plus a Tier H ADR.

    Decision: retire or enforce AdminScope.manageBindings and AdminScope.authorEnvironmentSets

    os-decision-facets

    • ① 项目长远合理性 (long-term soundness): A shrinks the contract to what the model on main carries (assignments, allowlist, containment). B regrows a binding and authoring surface on environment-wide definitions that have no business-unit dimension and are closed under a wall by ADR-0131, which is a new special case.
    • ② 实际业务拉动 (business pull): zero. No production author sets either key true; the control key manageAssignments: true has 3. Cloud and deployments are NOT MEASURED.
    • ③ 防 AI 犯错 (AI-authoring safety): today an AI that writes manageBindings: true through Setup, Studio or source gets a right that is silently never granted, and that quietly loosens strict containment. A turns each such write into a tsc error and a parse-time prescription.
    • ④ 创业阶段不扩散 (startup focus): removing beats declaring and maintaining. B adds a gate nobody named. Retire immediately; no named external user justifies a staged window.

    Prior rulings read: manageBindings, authorEnvironmentSets over docs/adr, AGENTS.md and .claude on 1418de4c7e → 1 hit (ADR-0086 :446). ADR-0090 D12 states the two rights in prose (:521-522). ADR-0049, ADR-0131 D3 and D12 items 7-8, and ADR-0138 D3 items 4-5 also govern. Thread: 6170064364 (triage: retire on zero pull).

    The problem in one sentence. Setup and Studio let an administrator tick "Manage bindings" and "Author env sets" on a delegate's scope, and the platform never grants either right, while the ticked box still changes which scopes that delegate may hand out.

    Option What it does What a customer sees Effort
    A: retire both keys now tombstones, a registered strip conversion, the containment axes dropped, D12 amended, the two Studio switches and help removed, false text corrected a delegate's scope offers what it actually grants: placement and assignment inside a subtree from an allowlist. A source that writes either key fails tsc and parse with the fix. A stored row is stripped on load. measured precedent: a two-key retirement, 40 files, +1096 / −187 (#22990); 1 objectstack PR + 1 objectui PR + a Tier H ADR line
    B: enforce both at the definition write widens the metadata door to delegates for position and permission, adds a new binding judge and a set-body containment judge, single only a subsidiary admin could edit what a company-wide position grants and author sets, with new refusals to learn, and nothing under a wall no precedent; 3 or more PRs across 4 surfaces plus a new ADR

    What each option means for the business.

    • A: a branch manager may staff the branch's people into company-defined jobs. This is how Salesforce Delegated Administration (assign allowlisted permission sets to users in a role subtree) and Microsoft Entra administrative units (scoped role assignment; role definitions stay tenant-level) model it. Those references come from product knowledge, not from an in-repo measurement.
    • B: a branch manager may also redefine what a company-wide job title grants everywhere.

    Two years out. Delegated administration on ObjectStack is assignment-scoped (subtree, allowlist, containment), and definitions are authored centrally per environment. That is the mainstream shape named above and the shape ADR-0131 D3 already gives the catalog.

    Recommendation: A.

    • Fallback: B, only if a named deployment shows a delegate who must recompose positions, and then with the ADR first.
    • Confidence gaps (NOT MEASURED): cloud and deployment stored rows; whether the pinned objectui build reds; whether the legacy-row ceremony replays conversions.
    • Self-check: 只看①选 A;②③④ 是否翻转:否.

    After the ruling (if A).

    • One building dispatch on this card (domain:spec): one objectstack PR covering spec, plugin-security, the containment pin, docs, showcase, the reading-4 text and the changeset.
    • One objectui PR covering the facet, i18n and test, landed with the pin bump.
    • The ADR-0090 and ADR-0086 lines go as a separate Tier H docs PR.
    • Position 2: D9 needs no new carrier (ADR-0138 D3 item 5 and the pinned absence). D12's unjudged rights retire, so the card's "marker per key retired" is the two RETIRED_KEYS_BY_MAJOR entries under the registered conversion.
    • Position 3 rides the same PR as docs accuracy with no contract change, including the non-key sentences (showcase guest text, meta-write-capability.ts:184-185, the tour doc).

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    待裁:AdminScope 的两个「委派管理」开关,退役还是补执行 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T16:06Z

    读数来源:只读轮报告 6172654646(objectstack origin/main 1418de4c7e,objectui main df05784ae4)。本条把它呈为决定;本席不裁。

    一句话问题

    权限集的「委派管理范围」里有两个开关:「可管理绑定」(manageBindings)和「可编写环境级权限集」(authorEnvironmentSets)。Setup 和 Studio 都能把它们打开,但打开之后没有任何地方兑现它们承诺的权限。它们唯一的实际作用是:在一个方向上放松了「不许授出与自己同等范围」这条防自我提权规则。所以要二选一:把这两个开关退役,还是补齐执行。

    Governing text

    • ADR-0049 Decision:安全属性「must be in exactly one of three states」,即 Enforced / experimental / Absent。「parsed, unmarked, unenforced — is prohibited」。两个开关今天处在被禁的第四态。
    • ADR-0090 D12 声明了它们:「manage user↔position assignments and position↔set bindings within that subtree; optionally author environment-owned sets there」。
    • ADR-0131 D3 收回了它们的执行面:C3 退役了绑定行与权限集行,集合成了岗位定义的一部分。D12 第 7–8 条在隔离姿态下禁止这类委派。
    • Prior rulings:工具点名的 ADR-0060 D2、ADR-0124 D3 只是关键词「delegate」撞上,没有一条裁过这两个键(见下方四棱块)。

    协议声明,以及是否改协议

    两个选项都改协议:

    • A 是收窄。两个键按 ADR-0087 退役,并需要一条 registered 剥离转换,因为存量 sys_metadata 权限集行可能带着它们。
    • B 是新增判定。在岗位定义写入门上新加委派范围的裁判。

    前提(各附 re-check 命令)

    1. 没有生产作者把任一键设为 true。re-check:git grep -n -E '(manageBindings|authorEnvironmentSets)"?\s*:\s*true' origin/main -- ':!**/*.test.ts',应为 0。对照:manageAssignments: true 有 3 处生产作者。
    2. 写岗位 permissionSets 或权限集的门(PUT /meta/position|permission/:name),裁判是 manage_metadata 加隔离墙,不跑委派范围检查。re-check:git show origin/main:packages/metadata-core/src/meta-write-capability.ts | sed -n 88,102p。
    3. 两键只被严格包含比较读到(delegated-admin-gate.ts:892-908)。按源码推演(未实跑):一个空转的 true 可以让「与自己同等的范围」通过「同等范围拒绝」。re-check:git show origin/main:packages/plugins/plugin-security/src/delegated-admin-gate.ts | sed -n 879,909p。
    4. 看不见的部分:cloud 仓与各部署的存量行,未实测。

    选项 × 真实代价 × 工作量

    做什么 客户可感知后果 工作量
    A 退役 两键加墓碑。走 ADR-0087 剥离转换。包含比较去掉这两轴。表单、帮助文字和译文同步。Studio 移除两个开关并跟 pin。ADR-0090 D12 一行 Tier H 修订。 委派管理员的界面少两个从不生效的开关。授予范围的包含判断回到只看真实权力。已存的 true 被迁移剥掉,并有说明。 spec 车道 · objectstack 1 PR(约 30 个文件)+ objectui 1 PR(4–5 个文件)+ 1 次 pin hop + 1 行 Tier H。先例 #22990:40 个文件,+1096/−187。
    B 补执行 先立新 ADR,定义「子树内」对一个没有业务单元维度的全环境岗位意味着什么,并限定权限集正文的范围,否则「可编写权限集 + 可自分配白名单」就是一条自我提权路径。再把元数据写入门改成按类型与条目判定(两条传输都改,含发布、回滚、重置、克隆各门),新增两个裁判,并给委派管理员做编辑界面。 委派管理员真能在子树内管绑定、写权限集。只在 single 姿态下成立,隔离姿态按 ADR-0131 禁止。 ADR + 至少 3 个 PR,无先例。

    业务含义直译

    • A:门禁系统里有两个「可开某类门」的勾选项,从来没接到任何门上,把它们从表单上拿掉。
    • B:为了让这两个勾选项生效,先重新设计「分公司管理员能改总部岗位到什么程度」的规则,再给每扇相关的门装读卡器。

    四轴(业务立场)

    • ① 项目长远合理性(权重 ≥50%):A 让合同回到 ADR-0131 D3 的模型,委派只管人员到岗位的分配、白名单和包含关系,特例减少。B 要在一个全环境、无业务单元维度的定义上新开特例,且隔离姿态下 ADR-0131 已把它关掉。两年后的平台样子:参照 Salesforce 的委派管理员:它委派的是「从指定清单里分配档案与权限集」,权限集本身的编写留给全局管理员。
    • ② 实际业务拉动:实测为零。没有生产作者打开过任一键,对照键有 3 个作者。cloud 与部署未测。
    • ③ 防 AI 犯错:今天 AI 通过 Setup、Studio 或源码写 manageBindings: true,得到的是一个静默不兑现的权限,还顺带放松了包含规则,没有任何人看到报错。A 之后同样的写法会得到编译错误和解析期处方,是响亮拒绝。
    • ④ 创业阶段不扩散:移除优于「声明并维护」。没有具名外部用户支撑分阶段窗口,所以立即退役。B 是一个没人点名要的新门禁,新门禁默认否。

    推荐 A,回退 B,置信缺口

    • 只看①选 A;②③④ 是否翻转:否。
    • 回退 B 的条件:出现一个具名部署,真的需要委派管理员重组岗位。那时先立 ADR,再动代码。
    • 置信缺口:cloud 与部署的存量行;objectui 跟 pin 时类型墓碑会不会让构建变红;os migrate organization-ownership 的仪式是否会对旧 admin_scope 行重放转换。

    裁后执行段

    • 裁 A:本卡回 pm:queue,由 spec 车道认领。一个 objectstack PR 做墓碑、剥离转换、包含比较改两轴、表单、译文、测试、文档和展示应用。卡上 Position 3 列出的失真文字随同修正。objectui Studio 的两个开关另开 objectui 卡(消费方首发卡)。ADR-0090 D12 一行走 Tier H,等维护者批准。附带关掉第 3 条前提里「空转开关放松包含规则」的口子。
    • 裁 B:本卡挂 pm:blocked,Blocked-by: 指向新立的 ADR 卡。

    四棱块

    os-decision-facets

    • ① 项目长远合理性:A 缩小特例,与 ADR-0131 D3 的分配模型一致;B 在全环境定义上新增特例。
    • ② 实际业务拉动:零实测作者,对照键有 3 个;cloud 与部署未测。
    • ③ 防 AI 犯错:A 把静默不兑现、且放松包含的开关变成编译期加解析期的响亮拒绝。
    • ④ 创业阶段不扩散:remove 优于 declare-and-maintain;B 是无人点名的新门禁。
      Prior rulings read: managebindings,authorenvironmentsets,adminscope,delegate → 3 hits; ADR-0060 D2, ADR-0124 D3 (keyword matches only, neither rules on these keys); thread: none; repo: objectstack-ai/objectstack

    状态

    Release: session_0121ooBYRmS8ziFXm9ai5YHm releases the reading-only claim 6172320924; that round ended as dispatched, with readings in 6172654646. Destination: needs-user-decision, assignee cleared, out of the active queue. Resume-from: none (no branch was created).

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Decision-box entry: passes, with a speed read added · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T17:06Z

    Entry check on 6172722135, the domain:spec seat 4 presentation from reading 6172654646. The question, governing text (ADR-0049, ADR-0090 D12, ADR-0131 D3), the protocol declaration (both options change it), premises with re-check commands, options × consequence × workload, the four facets (with the os-decision-facets token), prior rulings, recommendation (A), confidence gap and post-ruling steps are all present. It passes.

    Premises re-read today on main 222293f60a:

    1. Holds: 0 production writers of either key at true. The control manageAssignments: true now has 4 production sites; the reading said 3, and main has moved since.
    2. Holds: metaWriteCapabilityVerdict (packages/metadata-core/src/meta-write-capability.ts:88-102) judges manage_metadata only, with no delegate-scope check.
    3. Holds (traced, not run): definitionContainsStrictly (delegated-admin-gate.ts:879-909) still reads both keys on the rights and strictness axes.

    Why the box, at zero pull: retiring the keys is a protocol narrowing on a permission boundary, with an ADR-0090 D12 amendment. All three are on the human floor (triage-duties.md 〈代裁人工地板〉). This corrects this seat's grading note (6170064364), which implied that zero pull alone settles it.

    • Option B adds a new judgment at the definition write. The maintainer's standing word 「新增门禁默认否」 applies to it.

    Speed read (the one this card lacked): a permission set's "delegated admin" scope has two switches, "may manage bindings" and "may author environment sets". Setup and Studio can turn them on, but nothing honours them, and in one direction they loosen the rule that stops a delegate granting their own full scope.

    • A retires both switches inside the v18 major: about one objectstack PR, one objectui PR and a pin hop.
    • B makes them work: a new ADR first, then at least three PRs, and only in the single posture.
    • The seat recommends A. A or B?

    After the answer:

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #37 continuation 2, session_01LqNgdPZB2Jnr5HpGbaqLeB), read as batch #347 item 2, 2026-10-14T23:47Z

    Premises re-run on origin/main 086fa4640 (the card's commands): 0 production writers of either key at true (the control manageAssignments: true: 4); metaWriteCapabilityVerdict (packages/metadata-core/src/meta-write-capability.ts:88-102) admits isSystem or manage_metadata and nothing else; definitionContainsStrictly still reads both keys on the rights axis (delegated-admin-gate.ts:893-894) and the strictness axis (:902-903); the keys stand at security-service.ts:92-93 and permission.zod.ts:692,694 with default(false); the governed lines are ADR-0090 D12 (0090:521) and ADR-0086 (0086:446), Tier H. Prior rulings (check-prior-rulings --card 23488): 180 candidates, none on these keys; the seat's and triage's readings hold.

    Facets: ① agree (the definition write is manage_metadata's, ADR-0131 D3; a delegate recomposing an environment-wide position is a special case the walled posture already forbids) · ② agree · ③ agree, with one sharpening: case (ii) of reading 6172654646 (an inert true on the delegate's side lets an otherwise equal scope through the equal-scope refusal) is a live loosening of a permission check today, so A is a fix as well as a retirement · ④ agree.

    Recommend A, fallback B, with 口径 for the executor:

    1. Clause-②: yes (narrowing); one registered strip conversion in the 18 window (MAJOR_18_CONVERSIONS), two retiredKey tombstones naming the fix ("delete the key; a delegate's scope is placement, assignment and the allowlist"), the two RETIRED_KEYS_BY_MAJOR entries, and a D3 semantic entry stating the containment change.
    2. The containment comparison drops the two axes; case (ii) becomes a refusal and is pinned with a control (the gate fixture sets both flags true on both sides today, so the pin is new).
    3. The legacy-row ceremony (packages/cli/src/utils/organization-ownership-apply.ts:685 through permission-set-row-definition.ts:101-111, admin_scope verbatim): the dev measures whether that save replays the conversion; if it does not, the row mapper drops the two keys in the same PR; otherwise the report names it NOT MEASURED no longer.
    4. objectui: Studio's two switches, the help strings and the pin test are an objectui p1 follow-up on objectui#12334 (Studio would otherwise write a key the spec refuses); it lands before or with the .objectui-sha move that carries the tombstoned type.
    5. ADR-0090 D12 (one status line: bindings and env-set authoring retired; the definition write is manage_metadata's per ADR-0131 D3) and ADR-0086 :446 go as one Tier H docs PR; Position 3's text (two docs sentences, the showcase's seven, the spec JSDoc and .describe() strings, the ledger note, two plugin-security comments, meta-write-capability.ts:184-185) rides the main PR as docs accuracy; D9 needs no carrier (ADR-0138 D3 item 5 and its pinned absence).

    Workload of the letter: domain:spec · 1 objectstack PR (about 30 files, 12 hand-written) + 1 objectui PR (4–5 files) + 1 pin hop + 1 Tier H docs PR · precedent #22990 (40 files, +1096/−187). B: a new ADR first, then 3 or more PRs across spec, plugin-security, the metadata doors and objectui, no precedent.

    自检:只看①选 A;②③④ 是否翻转:否。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions