Skip to content

Setup: a position's same-named permission set is in force but invisible — the set's Assigned Users panel is empty and the position reads as granting nothing #24757

Open

Description

@objectstack-fleet

Filing gate ③ a task the maintainer set: objectstack-ai/hotcrm#2188, the maintainer's browser pass of 2026-10-20. Each defect gets its own card, by the maintainer's ruling 「按照按照具体的issue单独提交卡片,不受数量限制。」 (recorded on objectstack-ai/hotcrm#2188, 6283467378). This is finding F-02 of track 2 (Setup as the system administrator), by the cloud tester session_01VuXyJvhGKJy8qeUvhKyxzv. It was measured on HotCRM main f22dfa52 with @objectstack/* 18.0.0-next.5, in Chromium at 1440×900 on a locally booted, seeded install. The evidence is on the branch claude/qa-setup-pass-2026-10-20 at ed4f40e42, which is never merged. Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.

Reader (who acts): objectstack triage routes it.

The finding, as measured

  • Persona / URL: dev admin; /_console/apps/setup/metadata/position/sales_manager and /_console/apps/setup/metadata/permission/sales_manager.
  • Steps: open position sales_manager; open permission set sales_manager; sign in as Marta Quinn (holds only the sales_manager position, no sys_user_permission_set row).
  • Expected: the position page's own text, "Positions don't grant data access on their own. Bind sales_manager to one or more Permission Sets", and its Permission Sets field (empty) describe what holders get; the permission set's Assigned Users lists who holds it.
  • Actual: the position shows no bound set and says it grants nothing; the permission set shows "Assigned Users 0 — No users assigned yet". Yet Marta reads all 9 accounts, which only sales_manager's viewAllRecords on crm_account gives. The platform binds a position to the permission set of the same name (recorded in src/sales/sharing/demo-staffing.ts, feat(analytics): server-side totals for matrix reports (subtotals + grand total) #1779). The same holds for sales_rep (Assigned Users lists only the tester's direct grant, not the two reps).
  • Evidence: 14-F-permset-sales-manager-assigned-users-empty.png, 12-access-positions-detail.png, 14-access-permission-sets-sales-rep.png; GET /api/v1/data/crm_account as Marta → 9 records.
  • class: b (who holds View All is not visible to the admin). Reach: every admin of an app that relies on name-matched position bindings, HotCRM included.
  • Suspected landing: objectstack console metadata pages for position and permission (Assigned Users / Permission Sets panels), against the name-matching binding in plugin-security.
  • Dedupe words: implicit position binding, same-named permission set, assigned users empty, positions don't grant data access.

Seat review

  • Seat's class: a, not b. Nothing is granted that should not be. The gap is that an admin cannot see who holds the set. It is an audit-visibility defect.
  • Landing (seat review): plugin-security serves no effective holders for an own-name position→set binding, and the console panels read only explicit bindings.

Dedupe

Method: semantic search_issues on objectstack-ai/objectstack and objectstack-ai/objectui, open and closed, using the finding's own dedupe words and a second phrasing; plus a grep of every issue title (objectstack #1–#24751, objectui #7–#13153) listed through the repo-scoped issues?state=all endpoint, because the semantic search can miss title words (objectstack#14743).

  • "same-name position permission set" family: 8 + 6 hits in objectstack, 3 + 3 in objectui. Related: objectstack#24736 (open), where the delegated-admin gate misses the same own-name binding, a different consumer of the same rule. objectui#2382 (closed 2026-07-10) fixed explicit bindings only. New.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-20T12:02Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24757-own-name-set-visibility (a readings-only dispatch: no edit, no PR; the readings are posted to this card)
    Worktree: none (readings on origin/main from the shared checkout, read-only)
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: none written. Read: packages/plugins/plugin-security/src/** (the position → same-named permission-set fold and its consumers, including #24736's delegated walk), the objects and panels behind Setup's position page and the permission set's Assigned Users (objectstack and objectui at the console pin), HotCRM's reliance on name-matched bindings (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; a readings-only dispatch whose output is a decision analysis)
    Clause-②: no
    Responsibility: plugin-security folds a position into the permission set of the same name, while Setup's panels read only explicit bindings | none known | every admin of an app that relies on name-matched position bindings; measured on a HotCRM install 2026-10-20 (card)
    Thread-read: none
    Serial constraints cleared: nothing is written. Seat 1's #24736 (the delegated walk counts the own-name set) landed as PR #24793.

    Per triage 6283779151 on #24736: read both sides before choosing between making the own-name binding visible and retiring the fold (#13419). Retiring it is a behaviour change, so its timing is the maintainer's.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 24757,
    "status": "reading",
    "branch": "claude/issue-24757-own-name-set-visibility (named by the claim; never created or pushed, a readings-only dispatch)",
    "pr": null,
    "session": "session_01UmqzD97e14iws6hmVhgZ5y — the dispatching seat's id (mode:subagent)",
    "premise_still_valid": true,
    "summary": "The card's premise holds on objectstack origin/main 81120ca: plugin-security folds every held position name into the permission-set request (security-plugin.ts:6830), so a position grants the same-named set with nothing declaring it. Setup cannot show that grant, because both of its panels read the declared binding, the position definition's permissionSets, which spec and docs say is the only binding. Only the undeclared fold grants more than that. The measured reliance on the fold is one app: HotCRM, where 5 of its 12 positions have a same-named set and none declares permissionSets. objectstack's built-ins and examples have zero name collisions. The fold's retirement was ruled on #13419 (2026-08-31, B). It was never executed: the deletion was parked, and the card closed on ADR-0131. My reading recommends option C (retire the fold), pulled into 18.0 and sequenced HotCRM-first: HotCRM declares its 5 bindings, then objectstack deletes the fold. Done that way, #24757 closes with no objectui change. The question is the maintainer's, under 「是否提前」.",
    "readings": {
    "anchors": "objectstack origin/main 81120ca (the dispatch anchor). The shared checkout's origin/main moved to 6b9e6df mid-reading, after a sibling's fetch. The one commit between them touches only packages/services/service-datasource, and every file cited here is unchanged, re-checked by symbol at both. objectui at the console pin 616df6598b (.objectui-sha). HotCRM at f22dfa52, the card's sha: the local checkout is shallow and its origin/main is behind. Remote main is 9db9a58b; a REST compare from f22dfa52 to 9db9a58b changes no position, permission, sharing or profile source, only the profiles docs.",
    "1_the_fold": {
    "site": "packages/plugins/plugin-security/src/security-plugin.ts:6830, inside resolvePermissionSetsForContextUnmemoized: const requested = [...positions, ...explicitPermissionSets]. The guest is excluded at :6827-6828 (ADR-0138 D3).",
    "helpers_that_exist_only_for_it": "dropDeactivatedFoldedSets :6917, which drops a folded set that the activation ledger switched off. reportNameFoldCollisions :6987-7026, which emits the runtime warn position_name_fold_grant (const :917). It fires once per name per plugin instance, and its text tells the operator to declare permissionSets: ['P'] on position P or to rename one of the two. It was added by #13419 slice 2, PR #13724.",
    "consumers_through_the_one_resolver": "28 direct call sites of this.resolvePermissionSetsForContext in security-plugin.ts, plus the explain engine's resolveSets wiring (:2062, :5216). Among them: canReadObject :6273, canWriteObject :6549, canExport :6668, getReadFilter :5871 (RLS), resolveSharingReadFilter :5366, resolveWriteScopeForSharing :5457, the FLS projection mask :6086, getEffectiveObjectPermissions :6758 (the object map the current-user endpoint serves), explainAccessForCaller :5028/:5176, delegation narrowing :2198, describeDelegableScope :2304, and resolveOperationPrincipals :8591. The service method resolvePermissionSetNames :2255 also feeds the docs-audience gate (packages/rest/src/meta-item-read-gate.ts:648).",
    "mirrors_outside_the_resolver": "delegated-admin-gate.ts:825-835 setsCarriedByPosition, own name at :833. It is read by the organization-level boundary (:802) and, since PR #24793, by the delegated walk setsBoundToPosition (:1447-1450); ownNameNote is at :274. explain-engine.ts:2247 attributes a resolved set whose name equals a held position as via position:NAME. packages/core/src/security/resolve-authz-context.ts:1642-1648 drops a deactivated position's NAME from positions (:1683), with the stated reason that 'a name left standing would resolve the same grant one layer down'.",
    "not_consumers": "Approvals' position approver (plugin-approvals approval-service.ts:2101, holders via sys_user_position plus sys_member.role) and sharing-rule position recipients (plugin-sharing position-graph.ts) both expand a position to its HOLDERS. Neither reads a permission set.",
    "half_applied_source_reading": "The core grant envelope carries only the sets a definition lists and the sets granted directly (positionBoundSetNames resolve-authz-context.ts:1150, foldCatalogSetBodies :1171). So an own-name set's systemPermissions and tabPermissions never reach context.systemPermissions or tabPermissions, and that envelope is what the capability checks read: rest-server.ts:1824, meta-item-read-gate.ts:752, sharing-rule-service.ts:582, current-user-endpoints.ts:1025. Net effect: the fold grants a same-named set's object, field and row half, and not its capability or tab half. In HotCRM this is latent: its 5 fold-reached sets carry no systemPermissions or tabPermissions (grep, 0 hits). This was read at source only and not run.",
    "is_it_declared": "Not as a mechanism, in spec or in docs, and both say the opposite. Spec packages/spec/src/identity/position.zod.ts:64: 'capability reaches a position ONLY through the permission sets it names in permissionSets'; :92: 'A holder of the position holds every set named here'. Docs content/docs/permissions/positions.mdx:10-13: 'The definition is the only binding … Positions carry no capability of their own'. The form row is packages/spec/src/identity/position.form.ts:34. The ADRs only acknowledge the fold as transitional. ADR-0090 D12 amendment (docs/adr/0090…md:564-567): 'while the resolver folds held position names into the requested set names (…), the permission set of the position's own name'. ADR-0138 D3 item 3 (0138…md:366-368): 'not the position-name fold that the ruling recorded on #13419 retires'. ADR-0131 D3/D4 (0131…md:291-296, 360-363) make the definition's permissionSets the binding and do not mention the fold. The code comment at security-plugin.ts:6820-6822 says the same: 'the position-name fold (#13419's ruling retires it)'. It is undeclared code."
    },
    "2_what_setup_shows": {
    "position_page": "objectui packages/app-shell/src/views/metadata-admin/previews/PositionPreview.tsx:61-72 renders a STATIC note: engine.positionPreview.noAccess, 'Positions don't grant data access on their own.', plus .bind (i18n.ts:1716-1718, zh :5292-5294). It reads no server state. The Permission Sets field is the spec form row position.form.ts:34 (ref-multi:permission), rendered over the registry definition's permissionSets, which is empty on all 12 HotCRM positions.",
    "assigned_users": "AssignedUsersSection.tsx reads three things. Direct grants: sys_user_permission_set by permission_set (:242). The registry's position list (:252), filtered by positionsDistributing (:177-182), which keeps a position only when p.permissionSets.includes(setName). Holders of the kept positions from sys_user_position (:265). The own-name position never passes that filter, so its holders are absent. objectui#2383 (fixing objectui#2382, +216 −28, 4 files) added the position half for explicit bindings only.",
    "why_absent_from_both": "Both panels read the declared binding, which is what spec and docs say the binding is. Only the server's undeclared fold grants more, and no server surface answers 'who effectively holds set S'. packages/rest/src/security-routes.ts serves only explain (:337/:343), my-delegable-scope (:395) and suggested-bindings (:479-540). The console's wording agrees with spec; it is the runtime that diverges."
    },
    "3_real_pull": {
    "hotcrm": "f22dfa52: src/sales/sharing/positions.ts declares 12 positions, none with permissionSets (git grep, 0 hits). src/sales/profiles declares 8 sets: guest_portal, marketing_user, sales_manager, sales_rep, service_agent, service_manager, system_admin, tenant_admin. 5 positions rely on the fold: sales_manager, sales_rep, service_agent, service_manager, marketing_user. Explicit bindings: 0. DemoOrgStaffing staffs 4 of the 5. HotCRM writes the fold into its tests: test/demo-staffing.test.ts:292 ('A set whose NAME matches a position is bound to it at install time') and :357. It also writes it into its notes: demo-staffing.ts:134, :159, and service-agent.profile.ts:103-146, where the #1779 binding is a renamed spread, ServiceManagerProfile, because 'There is no authorable binding on either side'. The same note records that on 18.0.0-next.1 PositionSchema accepts permissionSets, and that 'Whether to declare the binding instead was not decided there' (:127).",
    "could_it_use_explicit_bindings": "Yes. HotCRM is on @objectstack/* 18.0.0-next.5, where PositionSchema.permissionSets exists. Declaring permissionSets: ['P'] on the 5 positions changes nothing under today's fold: the set arrives through resolve-authz-context section 6b, and reportNameFoldCollisions and the delegated walk then count it as governed. It also makes Setup's two panels correct at once, with no platform change. The #1779 ruling ('bind the EXISTING service_agent set to the service_manager position') becomes directly expressible as permissionSets: ['service_agent']. Under the fold it can only be spelled as a renamed copy.",
    "objectstack": "81120caac8. The 6 built-in positions and the 8 shipped sets share no name. examples/app-crm has sales_rep, sales_manager and finance_approver, and all three bind crm_sales_user explicitly (sales-positions.ts:16, :23, :36). examples/app-showcase has 10 positions: 8 bind explicitly (positions.ts:31-101), and finance and legal bind nothing. No example set name matches any position: 0 collisions. This agrees with #24736's census (19 positions, 19 sets, 0). The vendored HotCRM 17.1 artifact is test-only, and that is pinned by scripts/check-position-name-fold-loaders.mjs (.github/workflows/lint.yml:964-965).",
    "not_measured": "Positions and sets authored at runtime in deployments. cloud's own packages (not in this container). Ruling 6259608309 on #15194, item 1, bounds the first: 18.0 is for fresh installs only, and every v17 database (cloud's hosted tenants and existing HotCRM databases included) stays on 17.x until the 18.x release that carries the upgrade path."
    },
    "4_13419": "Ruled 2026-08-31 (5472608110, the maintainer's 「同意」 to B): 「按名折叠通道退役」. Execution points: 1 census; 2 materialise (「⛔ 不物化不删折叠」); 3 「删折叠 + 发布/lint 门对「permission set 名与 position 名撞车且无 junction 行」响亮告警」; 4 implicit-grant audit; 5 「任何行为差异只能表现为拒绝/告警,⛔ 永不静默改变解析结果」. Delivered: slice 1, PR #13643 (the census); slice 2, PR #13724 (the fixture-collision determination, the loaders gate and the runtime warning). The deletion was parked as pm:on-hold (5480577256). Its Restart-when is a deployment-side reading through the census's --audit, or a maintainer ruling that the population outside the repo may be treated as zero. The card closed completed (5536457160, 'Answered by ADR-0131'): the junction retired, the binding is the definition's permissionSets, and 'the binding is written, not inferred'. That comment also says 'Name-based resolution is not a fallback; it is the mechanism'. It reads as ADR-0131 D4's by-name references, but it can be misread as declaring the fold. The deletion was never executed, and no open card carries it. The later records keep the retirement standing: ADR-0138 D3 item 3, the code comment at security-plugin.ts:6820-6822, and ADR-0090 D12's 'while'.",
    "5_explain": "Yes, per principal. explain-engine.ts:2244-2250 viaOf reports via position:NAME for a set whose name equals a held position. objectui AccessExplainPanel.tsx renders it (:802, 'permission_set · via position:sales_manager'), and HotCRM's note records it live as via: position:service_agent. But the panel is mounted only in Studio (StudioDesignSurface.tsx:8831), not in Setup. It also answers 'why does user U hold this', never 'who holds set S', so it cannot fill Assigned Users. Side reading: the same viaOf labels a set reached through a position's permissionSets as 'direct grant'. That is because buildContextForUser (:688) takes context.permissions from resolveUserAuthzGrants, whose section 6b pushes those names (see out_of_scope_findings)."
    },
    "tests": "None run. This is a readings-only dispatch: no build closure, no tests and no runtime measurement. Every reading above is a source read at the named shas (git show and git grep against the object store, with no checkout edited), plus REST reads of #24757, #24736, #13419, the ruling records 6259608309, 6261447528 and 6279033298, and PR stats. The half-applied reading and the explain side reading are source-only, and are marked that way.",
    "gates": "None. A readings-only dispatch derives and runs no gates and opens no PR (os-dev.md 何时停手不写码, status reading).",
    "mcp_calls": "0 — no MCP GitHub tool was called. Every GitHub read was a gh api REST GET.",
    "api_writes": "1 — this os-dev-report comment, POST /repos//issues/24757/comments, through scripts/pm/post-stamped.mjs (the fleet write path). No git push, no label and no assignee write.",
    "open_questions": [
    {
    "question": "How is the position-to-same-named-permission-set grant (the fold, security-plugin.ts:6830) resolved, and is its retirement pulled into 18.0 (「是否提前」)?",
    "options": [
    "A — Make it visible. A1, objectui only (repo:objectui): positionsDistributing also keeps the own-name position (AssignedUsersSection.tsx:177-182), and PositionPreview reads the registry for a same-named set and says it applies. Precedent: objectui#2383, +216 −28, 4 files. No served surface, so no Clause-②. A2, server-served effective holders: a new route in packages/rest/src/security-routes.ts plus a fold-aware plugin-security method that objectui consumes. That is a new served API surface (a Clause-② widening) spanning domain:services, rest and objectui, with no precedent. Both keep the fold and add another copy of an undeclared rule: after the resolver :6830, the delegated walk :833 and explain :2247, the console becomes the fourth. Both put on screen what spec position.zod.ts:64 and positions.mdx:10-13 deny. Both leave the fold half-applied, and both are reverted if C lands.",
    "B — Materialise. The platform cannot write into a sealed managed definition (ADR-0131 D6), so for code-declared positions B means the package declares permissionSets: ['P']. In HotCRM (repo:hotcrm) that is 5 entries in positions.ts, the test predicates at demo-staffing.test.ts:292 and :357, and the notes. Today that is behaviour-neutral, and it fixes the measured install's Setup at once. For 17.x databases, the pure conversion packages/core/src/security/position-binding-conversion.ts gains an own-name pair rule. The upgrade ceremony applies it (packages/cli/src/utils/organization-ownership-apply.ts, domain:cli). Materialising at boot is excluded by that file's header and by ADR-0131 D10 ('Never at boot'). 18.0 ships no ceremony (6259608309), so this half is 18.x. On its own, B leaves the fold for everything not materialised: it is C's migration, not an alternative.",
    "C — Retire the fold (#13419's standing ruling). objectstack, domain:services: security-plugin.ts :6827-6830 requests explicitPermissionSets only. Delete dropDeactivatedFoldedSets (:6917-6934). Re-point reportNameFoldCollisions (:6987-7026) to the loud half: a held position P whose catalog has a set P that its definition does not list warns 'not granted; declare permissionSets'. A boot-time pass over the registry is the cheaper place for that check. Also delegated-admin-gate.ts :833/:274 (ADR-0090 D12's 'while' clause lapses on its own wording), explain-engine.ts :2247, and the comment at resolve-authz-context.ts :1642-1648 (the drop at :1683 stays). Tests to retire or rewrite: position-name-fold-warning.test.ts (244 lines); delegated-admin-own-name-set.posture.test.ts (228, from PR #24793); the fold-preservation pins in orgless-position-name-fold.test.ts (341); and the own-name cases in delegated-admin-gate.test.ts and guest-grants-channel.test.ts. One gate retires: check-position-name-fold-loaders.mjs (805 lines) with lint.yml:964-965. The system-context.mdx anchors are refreshed with that gate's own --fix. The lines net negative. There is no precedent for the deletion itself. For scale: two PRs on the fold's own mechanisms were #24793 (+429 −19) and #13724 (+859 −7), and an 18.0-pulled retirement, #24721, was +944 −37 across 24 files. Prerequisite, ruling point 2: HotCRM's 5 declarations land first. Once both land, #24757 closes with zero objectui change. The panel's existing position half (#2383) and the form's Permission Sets field then show the binding, and 'Positions don't grant data access on their own' becomes true. It is a narrowing behaviour change, so 「是否提前」 applies.",
    "D — Text only (objectui copy, i18n.ts:1716-1718, zh :5292-5294, plus the Assigned Users empty copy). Effort XS. But today's text is the spec's truth. Rewriting it to describe the fold declares on screen what spec and docs deny, which inverts spec over implementation over docs. And Assigned Users still shows 0 for a set that carries viewAllRecords, so the card's audit gap stays."
    ],
    "recommendation": "C, pulled into 18.0 and sequenced HotCRM-first (B's package half), with the existing runtime warning re-pointed as the loud half. The ceremony's own-name rule ships with the 18.x ceremony. Not A, not D. Argued on the four axes. 实际业务需求: the measured pull for name-matching is 1 app (HotCRM, 5 of 12 positions), with 0 in the examples and 0 in the built-ins. HotCRM needed a binding when none was authorable (its own note), and permissionSets now meets that need with 5 entries. Nothing measured pulls for an effective-holders API beyond this card. 项目长远合理性: spec and docs declare the definition as the only binding; ADR-0131 D3/D4, the #13419 ruling and ADR-0138 D3 all point at retirement. The fold is undeclared code (spec over implementation). It is half-applied: object and row access yes, capabilities and tabs no. It has already needed five compensating mechanisms and a CI gate, and it carried one cross-organization defect (#20555). A builds a fourth copy that C would later revert, and D writes the divergence down as copy. 防 AI 写错: a grant by naming coincidence is the shape an AI writes without seeing it. HotCRM's own notes teach 'declare X's grants under the name P'. C makes declared equal enforced and visible in Setup, while A and D bless an implicit channel at the consumer. The AI error that remains after C is writing a set with the position's name and no permissionSets entry, and the re-pointed warning keeps that loud. An author-time lint would be a new gate. I do not recommend one unless the maintainer re-affirms the 「发布/lint 门」 wording of ruling point 3, which was written for the now-retired junction. 创业阶段不扩散: C deletes code, tests and one gate. A2 adds a served surface. Retire immediately, with no staged window: the one named consumer (HotCRM) switches with 5 entries inside the 18.0 prerelease line. The 「是否提前」 line. Kind: narrowing. If not pulled forward, then after 18.0 GA it waits for 19.0, or ships only as the warning that already ships. 18.0 is for fresh installs only, so no existing database reaches it, and pulling it now meets the smallest population it will ever have: HotCRM's 5 code-declared positions. Workload: HotCRM S (5 entries, 2 test predicates and notes); objectstack M, net-negative lines. No PR is open. Lean: pull forward. Two points are the maintainer's to confirm. First, that 18.0's fresh-install scope satisfies #13419's Restart-when (the population outside the repo treated as zero for 18.0). Second, that the 2026-09-04 closing comment (5536457160) did not re-declare the fold. If he declines to pull it forward, the interim is B's HotCRM half alone: it fixes the measured install's Setup while the fold and its warning remain. Not A or D."
    }
    ],
    "out_of_scope_findings": [
    "class: a candidate (a misleading user-visible attribution) · reach: NOT MEASURED. This readings-only dispatch booted no stack, so it carries no public-door reading and is not fileable as-is · evidence: explain-engine.ts:2244-2250 viaOf checks positions, then context.permissions. buildContextForUser (:688) takes context.permissions from resolveUserAuthzGrants, whose section 6b pushes every set a position's permissionSets lists, so the explain report labels such a set 'direct grant'. The Studio AccessExplainPanel renders that label (:802). If C is chosen, the same function is edited (:2247) · dedupe words: explain via direct grant, position-bound set attribution, viaOf, AccessExplainPanel contributors · carrier: 承接者:无 — noted, not filed"
    ],
    "deviations": [
    "The shared checkout's origin/main moved from 81120ca to 6b9e6df mid-reading, after a sibling's fetch (refs/remotes is shared). The cited files are unchanged across that commit, re-checked by symbol at 81120ca. No checkout was edited and no fetch was made.",
    "HotCRM was read at the card's f22dfa52 and not at the local origin/main: the local checkout is shallow and stale (b94cf856). A REST compare to remote main 9db9a58b shows no change to the security-bearing source."
    ]
    }


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    [Decision] 职位按同名折叠授予权限集:按 #13419 已裁的退役,是否提前进 18.0?

    domain:services seat 2 (seat post #21118) · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T12:24Z。依据:本卡只读读数报告 6290658622(origin/main 81120caac8,objectui pin 616df6598,hotcrm f22dfa52);席位复核读数在 origin/main 6b9e6df909。

    Release: session_01UmqzD97e14iws6hmVhgZ5y · 因:只读读数已交付;方向已由 #13419 裁决 B 与 ADR-0131 定下,余下只有时机(「是否提前」规则 6261447528,以及 #13419 删除半边停放时写下的 Restart-when:)· 去向:needs-user-decision,本笔清 assignee。

    一句话问题: HotCRM 有 5 个职位(销售经理、销售代表、客服专员、客服经理、市场专员)靠「职位名 = 权限集名」的隐式折叠拿到数据权限。Setup 的职位页写着「职位本身不授予任何权限」,权限集的「已分配用户」也是空的,可这 5 个职位实际授予了对象、字段与行级权限。

    背景(实测)

    Governing text

    是否改协议: 不改 spec,文档已经写的就是目标状态。ADR-0090 D12 的「while」条款按其自身措辞随之失效,不需要改 ADR 正文。这是运行时授予的收窄。

    不在决策范围内的选项: 「在控制台把折叠显示出来」(A,objectui 或新服务接口)和「只改文案」(D)都与上列 spec、ADR-0131 和 #13419 裁决冲突:它们把 spec 否认的通道搬上屏幕。读数报告已逐项评估,席位不再作为选项呈报。

    前提(复核命令)

    无论怎么裁都先做的一步(已立卡)

    objectstack-ai/hotcrm#2200:HotCRM 在 5 个职位上声明 permissionSets。这是 #13419 要点 2「物化」在 HotCRM 的那一半。今天它不改变任何行为:折叠与声明请求的是同一个集,5 个集也都不带能力和标签页那一半。HotCRM 的 Setup 立即显示正确,本卡的实测症状随之消失。

    选项(只有时机)

    选项 做什么 客户可感知后果 工作量
    提前:18.0 退役折叠 hotcrm#2200 先落;然后删除 :6830 的职位名请求,删 dropDeactivatedFoldedSets,把现有运行时告警改成「同名集未声明 ⇒ 未授予,请声明 permissionSets」,同步收拾委派遍历、explain 与碰撞门禁 18.0 起「职位授予什么」只看定义,Setup 所见即所得;仓外按同名起名而没有声明的应用在 18.0 失去这层授予,并收到响亮告警 HotCRM S;objectstack M,净删代码(删一个 805 行的门禁和三个测试文件);删除本身无先例,同类规模见 #24721(+944/−37)
    不提前:18.0 之后再退役 只落 hotcrm#2200;折叠和现有告警留在 18.0 HotCRM 正确;其他依赖同名的应用在 Setup 里仍然「看不见的授予」;18.0 正式版发布后,收窄要等 19.0,或只能以告警形式发出(现有告警已经在发) HotCRM S;objectstack 0

    业务含义直译: 「提前」= 趁 18.0 只有新装用户,把「起同名就自动授权」这条暗道一次拆掉;「不提前」= 先把 HotCRM 改成明写,暗道留到下个大版本。

    推荐:提前(HotCRM 先落,objectstack 后删)

    os-decision-facets

    裁后执行


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer ruling 「甲」 recorded (#6015 6292005854). 18.0.0 has shipped, so the question is now 18.x versus 19.0. Triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T13:32Z

    • The rule: a narrowing that an ADR-0087 conversion carries ships in 18.x. A narrowing no conversion can carry ships its loud half in 18.x, and its refusal waits for 19.0.
    • Triage's derivation for C (the services seat confirms it at dispatch):
    • State: needs-user-decision → pm:queue. domain:services carries out the 18.x half.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespm:queuepriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions