You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(plugin-security): an app permission set's readable:false on a sys_* field also masks platform admins who hold that set #29106
An app permission set cannot hide a platform object's fields from its own users without hiding them from platform admins who also hold that set.
Repro: an app set (duly_member) grants sys_business_unit: { allowRead: true } and FLS { readable: false } on every field but name. A platform admin also holds duly_member (alongside admin_full_access).
Observed:
The admin's sys_business_unit list returns name only.
sort=code is refused with 403 "references field(s) not readable by the caller".
Setup → Business Units shows Kind, Business Unit Head and Active empty.
The Org Chart renders flat.
Cause:getFieldPermissions merges most-permissively among the sets that declare the field. admin_full_access grants the object but declares no field entries, so it casts no vote, and the app set's readable: false wins.
Expected: a set that grants full object access (for example admin_full_access, or any viewAllRecords/modifyAllRecords set) counts as readable: true / editable: true on every field of that object for the merge. An app set's mask then narrows only callers who hold no wider set.
Impact: an app has to choose between disclosing every column of a platform object to its members, or breaking platform Setup screens for admins. duly#180 ships the object-level read without a field map for this reason.
Read fresh. An app permission set's field-level readable: false on a sys_* field also hides that field from a platform admin who holds the same set, so Setup pages show it empty and sorts on it are refused. This over-restricts; it discloses nothing, so it is not a security leak. The lane first reads the permission-merge rule its sets follow, in plugin-security and the ADR that governs field-level security. If the rule is union across sets, this is a defect and goes straight to a fix. If it is deny-wins by design, the lane says so here and the card becomes a decision. domain:server, p2.
Director seat (第 56 场). The merge-rule question (6572555344) is presented to the maintainer as batch #453 item 3 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. ⛔ Not a claim.
Ruling: batch #453 item 3 · letter A · maintainer 「同意」 2026-11-02T09:22Z
Director seat (第 56 场; brief-claimed above in 6573082985 and presented in the 总监决裁 thread as batch #453 item 3, the merge-rule question of 6572555344, recommendation A). The maintainer's word is 「同意」 for the whole batch, so this card is ruled A.
What the ruling binds (A):
In the field-permission merge (PermissionEvaluator.getFieldPermissions, and the row-blind merge /auth/me/permissions shares), a permission set that holds viewAllRecords on an object votes readable: true on every field of that object, and one that holds modifyAllRecords votes editable: true on every field, reusing the super-user reading superuserBypassSets already applies at row level. Union without deny is otherwise unchanged: an app set's readable: false still narrows a caller who holds no wider set.
ADR-0066 D2 ⑧ gains a note recording this (Tier H path, its own PR, the maintainer's click).
Precondition: before the runtime PR opens, the dev greps templates and examples for a set that declares both viewAllRecords and a readable: false entry on the same object and reports the hits on this card (none found by this seat in examples/app-showcase).
Workload of the letter: one runtime PR in plugin-security (red-first, ablation, full dogfood) plus one ADR-note PR; about 100–200 lines.
Execution:domain:server seat 2 dispatches as its analysis says; the lane seat moves the label. The Ruled: body line is written by this act.
Claim: PM loop round 1 · card thread · 2026-11-02T12:16Z
Session: session_01ADHkmQxwoGb5fSnrFkpxmt (the seat); the card thread is session_01ADHkmSgjH5Vi4jT3KVJpvt
Account: os-steve (the seat's linked user; the card's assignee)
Branch: claude/project-thread-q91xrq (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: 6573563853 — letter A, the maintainer's 「同意」 2026-11-02T09:22Z (director batch #453 item 3) on the seat's decision analysis 6572555344.
File surface: packages/plugins/plugin-security/src/permission-evaluator.ts (getFieldPermissions), packages/plugins/plugin-hono-server/src/current-user-endpoints.ts (only if its merge is separate); tests; dogfood; changeset. A second PR, governed Tier H and draft until GOVERNED_APPROVERS approve: docs/adr/0066-unified-authorization-model.md (an append-only note at D2 ⑧). ⛔ No packages/spec/**, no ui/**.
Container & model: M, mode:thread, model: opus (TIER_DEFAULT; --tier names no path mandate: PM's call), effort: high
Clause-②: no (expected: runtime merge semantics per the ruling; no spec accept set moves)
Metadata-protocol: none (expected)
Responsibility: the code is the field-permission merge in @objectstack/plugin-security · the platform path is an admin who also holds an app set that masks sys_* fields, seeing those fields empty in Setup and refused on sort · reached by every app that masks a platform object's fields for its members
Thread-read: 6573563853
Premise: holds (the merge rule read on origin/main by the seat's analysis; the dev re-measures red first, and first greps for any set declaring viewAllRecords beside a readable: false entry on one object, as the ruling requires)
Serial constraints cleared: none — no open PR touches permission-evaluator.ts, current-user-endpoints.ts or ADR-0066 (read this act)
Resume-from: none — fresh from origin/main
State: needs-user-decision → pm:dispatched, assigned to os-steve, recorded by this claim (the ruling's exit stroke).
Precondition grep (ruling 6573563853) · read 2026-11-02T12:19Z against origin/main 5780fa4
Question: does any permission set declare viewAllRecords (or modifyAllRecords) on an object AND a readable: false (or, under modifyAllRecords, editable: false) entry on a field of that same object? The super-user bit counts whether it is on the explicit entry or on a '*' wildcard the object has no explicit entry to shadow (the resolveObjectPermission rule).
Declared sets: 0 hits.
packages/plugins/plugin-security/src/objects/default-permission-sets.ts: the only readable: false entries are the identity Admin group withheld by member_default and viewer_readonly, and neither holds a super-user bit. admin_full_access and organization_admin (and its derived no-bypass variant) carry the keeping entry { readable: true, editable: true }.
examples/app-showcase/src/security/permission-sets.ts: the one readable: false set, showcase_client_liaison, holds plain allowRead on showcase_project. The VAMA sets (showcase_auditor, showcase_ops) declare no fields.
packages/metadata/src/__fixtures__/hotcrm-17.1-built-permissions.artifact.json (4 sets with 17 field entries): 0 hits by a structural scan.
Every other non-test file with both words (git grep -l over examples/, packages/, skills/, docs/, content/docs/; ui/ and tests excluded) was read: each readable: false there is prose, a merge/gate implementation, or a set with no super-user bit (skills/objectstack-data/rules/security.md, content/docs/permissions/permission-metadata.mdx, docs/notes/crm-development-standards.mdx, docs/design/permission-model.md, content/docs/protocol/objectql/security.mdx).
One documentation near-hit, not a declared set:content/docs/permissions/permissions-matrix.mdx section 3 is a prose matrix. Its Sales Manager column shows contact.ssn as Hidden and opportunity.margin as Visible read-only. Section 2's example gives the same sales_manager_accessviewAllRecords on contact and modifyAllRecords on opportunity. The page declares no fields for that set, so nothing at runtime changes. Under A, though, the matrix describes a combination that a single set can no longer express. Reported as an out-of-scope docs finding, not fixed here.
{
"issue": 29106,
"status": "done",
"branch": "claude/project-thread-q91xrq",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29387",
"session": "session_01ADHkmSgjH5Vi4jT3KVJpvt",
"premise_still_valid": true,
"summary": "Ruling 6573563853 letter A implemented. A set holding viewAllRecords on an object votes readable on every field of it in the field merge, and one holding modifyAllRecords votes readable and editable. The vote is applied by superuserFieldVote in both merges: PermissionEvaluator.getFieldPermissions and core's buildEffectiveFieldPermissions, which serves /auth/me/permissions. Who holds the bypass is one function, core's holdsSuperuserBypass, which superuserBypassSets now also reads. Union without deny is otherwise unchanged and the member stays masked. The PR is 'Part of #29106'. The ADR-0066 D2 ⑧ note is the second PR: seat 2 ruled path (a), which opens it from this branch once #29387 merges. The patch is ready at /mnt/project-files/server-seat2/29106-adr-0066-note.patch.",
"tests": "Red first on 5780fa49 plus tests only: plugin-security fls-superuser-vote.test.ts 7 failed / 18 passed (every admin vote case); dogfood fls-superuser-vote 6 failed / 2 passed (admin sort 403 'references field(s) not readable by the caller: code'; explain masked [code, kind, manager_user_id, active] for the admin); member controls green. Green after the fix: unit 25/25, core effective-object-permissions 43/43, dogfood 11/11. Package suites: core 129 files passed; plugin-security 240 files, with 4 red in explain-principal-person.test.ts (forced fixture edit), then that file 16/16; plugin-hono-server 38 files / 472 tests. typecheck (tsc + check:test-typecheck) passed for core, plugin-security and hono-server after a DTS build. Ablation: superuserFieldVote returns null (marker ABLATION_29106_NO_SUPERUSER_VOTE in core dist index.js/index.cjs per ablation-dist-preflight). Unit 6 failed / 19 passed; dogfood 5 failed (admin me-permissions, list, record, sort+filter, explain) / 6 passed (premise and 5 member controls); core 1 failed / 42 passed. Restore: blob equals HEAD 09e5a75c, core rebuilt, preflight --absent exit 0, git status clean.",
"gates": "head 718ba1b2: dispatch-gates --commands derived 94; all run; 93 exit 0 first pass; check-plugin-teardown-shape --self-test exit 3 (shallow clone missing positive control) then exit 0 after git fetch origin 621a4876; --ran: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config over packages/core/src, plugin-security/src and the new dogfood file: exit 0. PR check-runs: in_progress at report time (not waited on).",
"dogfood_battery": "718ba1b2 · os-verify-lock VERDICT command-exit 0 (41m18s) · Test Files 394 passed / 3 skipped (397) · Tests 3254 passed / 28 skipped (3282) · no failures",
"mcp_calls": "3 — mcp__github__issue_read x3 (get, get_comments x2); no MCP writes",
"api_writes": "3 — POST issues/29106/comments (precondition grep, issuecomment-6576959092, via post-stamped relay); pr_create pulls (#29387, via with-fleet dispatch relay); POST issues/29106/comments (this report, via post-stamped relay)",
"files_changed": [
".changeset/29106-superuser-field-vote.md",
"content/docs/permissions/field-level-security.mdx",
"content/docs/permissions/permissions-matrix.mdx",
"packages/core/src/security/effective-object-permissions.ts",
"packages/core/src/security/effective-object-permissions.test.ts",
"packages/core/src/security/index.ts",
"packages/plugins/plugin-security/src/permission-evaluator.ts",
"packages/plugins/plugin-security/src/fls-superuser-vote.test.ts",
"packages/plugins/plugin-security/src/explain-principal-person.test.ts",
"packages/qa/dogfood/test/fls-superuser-vote.dogfood.test.ts"
],
"line_budget": "vs 47718699: 10 files, +587 / -12 (runtime 118 incl. core export lines and docblocks; tests 441; docs 7 net; changeset 21)",
"deviations": [
"File surface: the /auth/me/permissions merge is packages/core (buildEffectiveFieldPermissions), not plugin-hono-server/current-user-endpoints.ts, which is untouched. Seat 2 accepted this; the claim amendment is the seat's.",
"Docs: permissions-matrix.mdx §3 is fixed at seat 2's request. field-level-security.mdx gets one sentence too, because its union paragraph made the same claim the runtime no longer holds.",
"Forced test edit: plugin-security explain-principal-person.test.ts. The qa_pp_directory_masked fixture held viewAllRecords plus readable:false on sys_user.email. It now reaches rows by a select row policy, keeping its intent.",
"Main was merged once (47718699) before gates and dogfood. 23 main commits landed after, touching none of this diff's packages or files, so they were not merged again before pr_create.",
"First ablation attempt was a no-op: a comment marker was stripped from dist, and preflight caught it as absent. Retried with a string-literal marker; that reading is the one reported.",
"PR has no assignee or reviewer, per the standing order (no MCP writes; os-dev forbids PR assignees). The session-context assignee step was skipped. Not subscribed to PR activity, per os-dev's dispatch contract."
],
"open_questions": [
{
"question": "Should ADR-0066's Status line gain an '· **Amended** (2026-11-02, ruling 6573563853)' entry beside the D2 ⑧ note, like its 2026-10-14 and 2026-10-29 entries?",
"options": [
"A: note only, Status line untouched (as the delta says: add one note)",
"B: also append an Amended entry to the Status line"
],
"recommendation": "A, because the delta binds 'add one note' and earlier ⑧ notes (2026-10-26, 2026-10-28) left the Status line alone"
}
],
"security_findings": "none",
"out_of_scope_findings": []
}
The card thread's report 6578856250 (PR #29387, head 718ba1b2) put the superuser field vote in @objectstack/core, beside the object-level answer it reads, and kept permission-evaluator.ts as the caller. current-user-endpoints.ts is untouched. The seat accepts:
File surface adds: packages/core/src/security/effective-object-permissions.ts and packages/core/src/security/index.ts (one export); packages/plugins/plugin-security/src/explain-principal-person.test.ts (one forced fixture edit); content/docs/permissions/permissions-matrix.mdx and content/docs/permissions/field-level-security.mdx.
PR #29387, new head 252a02f4 (one commit on top of 718ba1b2). It changes one test file and no runtime code.
1. The red, measured. On 718ba1b2, packages/services/service-automation/src/resume-decider-identity.integration.test.ts gives 1 failed / 20 passed. The failing case is a post-decision node the decider may not perform is refused loudly, and the run reports the node, the same case as the CI job (Test Core 8/8, line 495). The cause is the ruling. The deal_manager set held viewAllRecords + modifyAllRecords on deal beside 'deal.restricted_note': { editable: false }, so under A the set's own Modify All out-votes the lock and the decider's write lands. Base 47718699 is green, as the reviewer measured.
2. The forced edit.dealManagerSet now grants deal with allowRead / allowEdit and readScope: 'org' / writeScope: 'org' instead of the super-user bits. The manager still reaches a deal the rep owns on the private deal object, and still may not edit restricted_note, without holding a bypass. Every assertion is unchanged. The GUARD case still shows that the manager's write rests on the manager's own grants. Result: 21/21 green. The PR body names it as forced test edit 2 of 2.
3. Repo-wide sweep. I searched packages, examples, the showcase, app metadata and tests for a set that declares viewAllRecords / modifyAllRecords beside readable: false / editable: false on the same object.
ald_withhold_set / ald_withhold_only_set hold View All on sys_user beside email withheld. Its assertions are about ledger rows served (none), not about the field, so it stays green. Note only, not edited.
core, lint, spec, ui tests
schema, lint and client-side editor fixtures
none of these reaches the server field merge
Shipped default sets, showcase, hotcrm fixture, other app metadata
—
0 hits. No behaviour change for shipped apps, so the changeset stays as written.
4. Every hit package, run on the new head.
service-automation, full suite: 260 files / 3338 tests passed.
objectql (hook-run-as plus 3 protocol tests): 44/44.
rest (4 files): 55/55.
runtime (data-conflict-caller-visibility): 16/16.
cli (organization-ownership-apply): 112/112.
dogfood: the full battery on 718ba1b2 (394 files / 3254 tests) still stands, because the round-1 commit touches no dogfood file and no runtime code.
5. CI on 252a02f4. Test Core 1/8 to 8/8 all passed, and the aggregate Test Core check is green (run 41458968131). Shard 8/8, which was red, now passes. Test UI 1–16/16, Build Core, Build Docs, Live E2E UI, TypeScript Type Check, ESLint and Lint & Repo Gates are all green too.
mcp_calls: 7 (issue_read ×3, pull_request_read get_check_runs ×4), no MCP writes. api_writes: 5, adding the PR body issue_patch via the relay and this comment via post-stamped.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:server·area:access·pm:queue· 2026-11-02T07:55Z⛔ Not a claim.
readable: falseon asys_*field also hides that field from a platform admin who holds the same set, so Setup pages show it empty and sorts on it are refused. This over-restricts; it discloses nothing, so it is not a security leak. The lane first reads the permission-merge rule its sets follow, inplugin-securityand the ADR that governs field-level security. If the rule is union across sets, this is a defect and goes straight to a fix. If it is deny-wins by design, the lane says so here and the card becomes a decision.domain:server, p2.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
决策分析(席位 domain:server#2)· 2026-11-02T08:27Z
一句话问题: 应用给自己成员遮掉平台对象(
sys_*)某些字段时,同时持有该应用权限集的平台管理员也被遮住,Setup 页面显示为空、按该字段排序被拒。背景: 分诊 6572001650 要求先读合并规则:若规则是「全集合并集」则是缺陷直接修;若按设计如此则转决策。席位读数:按设计如此。
Governing text: ADR-0066 D2 ⑧(
docs/adr/0066-unified-authorization-model.md,含 2026-10-26 附注):"field grants still union without deny … and only declaring entries vote." 代码packages/plugins/plugin-security/src/permission-evaluator.tsgetFieldPermissions同句注释 "only DECLARING entries vote, as before"。admin_full_access授对象但不声明字段条目 ⇒ 不投票 ⇒ 应用集的readable: false胜出。检索:grep -rn "only declaring\|union without deny" docs/adr→ ADR-0066 两处。协议声明: 不改 spec;改的是 ADR-0066 ⑧ 的合并口径(受管 Tier H 修订)与 plugin-security 运行时。属于权限边界的放宽(管理员看到更多字段)⇒ 人工地板,席位不代裁。
前提(带复查命令): 管理员持
admin_full_access时该集合对sys_business_unit有viewAllRecords/modifyAllRecords但无字段条目 —grep -n "admin_full_access" -A40 packages/plugins/plugin-security/src/objects/default-permission-sets.ts;合并只看声明条目 —grep -n "only DECLARING" packages/plugins/plugin-security/src/permission-evaluator.ts。viewAllRecords)的权限集,在字段合并时对该对象每个字段投「可读」;持「修改全部」(modifyAllRecords)的投「可编辑」。复用已有的超级用户判定(superuserBypassSets,RLS 已在用)。ADR-0066 ⑧ 加一条附注admin_full_access等)补声明所有sys_*字段可读可写sys_*字段上设readable:false业务含义: A = 「查看全部数据」的人就能看到全部字段(Salesforce 管理员档案的常见做法);B = 只给平台自带的管理员开后门;C = 应用不能对平台对象做字段级隐藏。
四维(业务立场):
os-decision-facets
① 项目长远合理性: A 把行级与字段级的超级用户判定合一,缩小特例;B 增加随对象增长的清单;C 留能力缺口。
② 实际业务拉动: duly#180 实测撞上,今天就有。
③ 防 AI 犯错: A 规则可一句话陈述、不靠隐性约定;C 出错时管理员页面静默变空。
④ 创业阶段不扩散: A 不新增键或配置面;B 增加维护面。
Prior rulings read: "only declaring", "union without deny", "viewAllRecords field" → 2 hits; ADR-0066 D2 ⑧ (+2026-10-26 note, ruling 6408558941 on
when); thread: none推荐:A。 只看①选 A;②③④ 是否翻转:否。置信缺口:未实测有没有应用依赖「持查看全部但字段仍被遮」来隐藏敏感字段(例如工资字段对有查看全部权限的运营人员隐藏)—— 若有,A 会让它们可见;执行前 dev 先全仓 grep 示例与模板中同时声明
viewAllRecords与readable: false的权限集并报告。裁后执行: A ⇒ 席 2 派一个 dev:改
getFieldPermissions(及/auth/me/permissions共用的合并)、红先 + 消融、全量 dogfood;ADR-0066 ⑧ 附注另开 Tier H PR 等审批人批。B ⇒ 改默认权限集声明。C ⇒ 文档一句并关卡 not planned。State:
pm:queue→needs-user-decision, recorded by this comment.zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #453 · 2026-11-02T08:57Z
Director seat (第 56 场). The merge-rule question (6572555344) is presented to the maintainer as batch #453 item 3 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. ⛔ Not a claim.
Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Ruling: batch #453 item 3 · letter A · maintainer 「同意」 2026-11-02T09:22Z
Director seat (第 56 场; brief-claimed above in 6573082985 and presented in the 总监决裁 thread as batch #453 item 3, the merge-rule question of 6572555344, recommendation A). The maintainer's word is 「同意」 for the whole batch, so this card is ruled A.
What the ruling binds (A):
PermissionEvaluator.getFieldPermissions, and the row-blind merge/auth/me/permissionsshares), a permission set that holdsviewAllRecordson an object votesreadable: trueon every field of that object, and one that holdsmodifyAllRecordsvoteseditable: trueon every field, reusing the super-user readingsuperuserBypassSetsalready applies at row level. Union without deny is otherwise unchanged: an app set'sreadable: falsestill narrows a caller who holds no wider set.viewAllRecordsand areadable: falseentry on the same object and reports the hits on this card (none found by this seat inexamples/app-showcase).Workload of the letter: one runtime PR in
plugin-security(red-first, ablation, full dogfood) plus one ADR-note PR; about 100–200 lines.Execution:
domain:serverseat 2 dispatches as its analysis says; the lane seat moves the label. TheRuled:body line is written by this act.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Claim: PM loop round 1 · card thread · 2026-11-02T12:16Z
Session:
session_01ADHkmQxwoGb5fSnrFkpxmt(the seat); the card thread issession_01ADHkmSgjH5Vi4jT3KVJpvtAccount:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-q91xrq(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: 6573563853 — letter A, the maintainer's 「同意」 2026-11-02T09:22Z (director batch #453 item 3) on the seat's decision analysis 6572555344.
File surface:
packages/plugins/plugin-security/src/permission-evaluator.ts(getFieldPermissions),packages/plugins/plugin-hono-server/src/current-user-endpoints.ts(only if its merge is separate); tests; dogfood; changeset. A second PR, governed Tier H and draft until GOVERNED_APPROVERS approve:docs/adr/0066-unified-authorization-model.md(an append-only note at D2 ⑧). ⛔ Nopackages/spec/**, noui/**.Container & model:
M,mode:thread,model: opus(TIER_DEFAULT;--tiernames no path mandate: PM's call),effort: highClause-②: no (expected: runtime merge semantics per the ruling; no spec accept set moves)
Metadata-protocol: none (expected)
Responsibility: the code is the field-permission merge in
@objectstack/plugin-security· the platform path is an admin who also holds an app set that maskssys_*fields, seeing those fields empty in Setup and refused on sort · reached by every app that masks a platform object's fields for its membersThread-read: 6573563853
Premise: holds (the merge rule read on
origin/mainby the seat's analysis; the dev re-measures red first, and first greps for any set declaringviewAllRecordsbeside areadable: falseentry on one object, as the ruling requires)Serial constraints cleared: none — no open PR touches
permission-evaluator.ts,current-user-endpoints.tsor ADR-0066 (read this act)Resume-from: none — fresh from
origin/mainState:
needs-user-decision→pm:dispatched, assigned toos-steve, recorded by this claim (the ruling's exit stroke).Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmSgjH5Vi4jT3KVJpvt
Precondition grep (ruling 6573563853) · read 2026-11-02T12:19Z against origin/main 5780fa4
Question: does any permission set declare
viewAllRecords(ormodifyAllRecords) on an object AND areadable: false(or, undermodifyAllRecords,editable: false) entry on a field of that same object? The super-user bit counts whether it is on the explicit entry or on a'*'wildcard the object has no explicit entry to shadow (theresolveObjectPermissionrule).Declared sets: 0 hits.
packages/plugins/plugin-security/src/objects/default-permission-sets.ts: the onlyreadable: falseentries are the identityAdmingroup withheld bymember_defaultandviewer_readonly, and neither holds a super-user bit.admin_full_accessandorganization_admin(and its derived no-bypass variant) carry the keeping entry{ readable: true, editable: true }.examples/app-showcase/src/security/permission-sets.ts: the onereadable: falseset,showcase_client_liaison, holds plainallowReadonshowcase_project. The VAMA sets (showcase_auditor,showcase_ops) declare nofields.packages/metadata/src/__fixtures__/hotcrm-17.1-built-permissions.artifact.json(4 sets with 17 field entries): 0 hits by a structural scan.git grep -loverexamples/,packages/,skills/,docs/,content/docs/;ui/and tests excluded) was read: eachreadable: falsethere is prose, a merge/gate implementation, or a set with no super-user bit (skills/objectstack-data/rules/security.md,content/docs/permissions/permission-metadata.mdx,docs/notes/crm-development-standards.mdx,docs/design/permission-model.md,content/docs/protocol/objectql/security.mdx).One documentation near-hit, not a declared set:
content/docs/permissions/permissions-matrix.mdxsection 3 is a prose matrix. Its Sales Manager column showscontact.ssnas Hidden andopportunity.marginas Visible read-only. Section 2's example gives the samesales_manager_accessviewAllRecordsoncontactandmodifyAllRecordsonopportunity. The page declares nofieldsfor that set, so nothing at runtime changes. Under A, though, the matrix describes a combination that a single set can no longer express. Reported as an out-of-scope docs finding, not fixed here.Generated by Claude Code
fix(plugin-security): a View All / Modify All set votes on every fiel…
test(plugin-security): the masked directory fixture reaches user rows…
test(dogfood): split each door's admin pin from its member control (#…
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmSgjH5Vi4jT3KVJpvt
os-dev-report
{ "issue": 29106, "status": "done", "branch": "claude/project-thread-q91xrq", "pr": "https://github.com/objectstack-ai/objectstack/pull/29387", "session": "session_01ADHkmSgjH5Vi4jT3KVJpvt", "premise_still_valid": true, "summary": "Ruling 6573563853 letter A implemented. A set holding viewAllRecords on an object votes readable on every field of it in the field merge, and one holding modifyAllRecords votes readable and editable. The vote is applied by superuserFieldVote in both merges: PermissionEvaluator.getFieldPermissions and core's buildEffectiveFieldPermissions, which serves /auth/me/permissions. Who holds the bypass is one function, core's holdsSuperuserBypass, which superuserBypassSets now also reads. Union without deny is otherwise unchanged and the member stays masked. The PR is 'Part of #29106'. The ADR-0066 D2 ⑧ note is the second PR: seat 2 ruled path (a), which opens it from this branch once #29387 merges. The patch is ready at /mnt/project-files/server-seat2/29106-adr-0066-note.patch.", "tests": "Red first on 5780fa49 plus tests only: plugin-security fls-superuser-vote.test.ts 7 failed / 18 passed (every admin vote case); dogfood fls-superuser-vote 6 failed / 2 passed (admin sort 403 'references field(s) not readable by the caller: code'; explain masked [code, kind, manager_user_id, active] for the admin); member controls green. Green after the fix: unit 25/25, core effective-object-permissions 43/43, dogfood 11/11. Package suites: core 129 files passed; plugin-security 240 files, with 4 red in explain-principal-person.test.ts (forced fixture edit), then that file 16/16; plugin-hono-server 38 files / 472 tests. typecheck (tsc + check:test-typecheck) passed for core, plugin-security and hono-server after a DTS build. Ablation: superuserFieldVote returns null (marker ABLATION_29106_NO_SUPERUSER_VOTE in core dist index.js/index.cjs per ablation-dist-preflight). Unit 6 failed / 19 passed; dogfood 5 failed (admin me-permissions, list, record, sort+filter, explain) / 6 passed (premise and 5 member controls); core 1 failed / 42 passed. Restore: blob equals HEAD 09e5a75c, core rebuilt, preflight --absent exit 0, git status clean.", "gates": "head 718ba1b2: dispatch-gates --commands derived 94; all run; 93 exit 0 first pass; check-plugin-teardown-shape --self-test exit 3 (shallow clone missing positive control) then exit 0 after git fetch origin 621a4876; --ran: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config over packages/core/src, plugin-security/src and the new dogfood file: exit 0. PR check-runs: in_progress at report time (not waited on).", "dogfood_battery": "718ba1b2 · os-verify-lock VERDICT command-exit 0 (41m18s) · Test Files 394 passed / 3 skipped (397) · Tests 3254 passed / 28 skipped (3282) · no failures", "mcp_calls": "3 — mcp__github__issue_read x3 (get, get_comments x2); no MCP writes", "api_writes": "3 — POST issues/29106/comments (precondition grep, issuecomment-6576959092, via post-stamped relay); pr_create pulls (#29387, via with-fleet dispatch relay); POST issues/29106/comments (this report, via post-stamped relay)", "files_changed": [ ".changeset/29106-superuser-field-vote.md", "content/docs/permissions/field-level-security.mdx", "content/docs/permissions/permissions-matrix.mdx", "packages/core/src/security/effective-object-permissions.ts", "packages/core/src/security/effective-object-permissions.test.ts", "packages/core/src/security/index.ts", "packages/plugins/plugin-security/src/permission-evaluator.ts", "packages/plugins/plugin-security/src/fls-superuser-vote.test.ts", "packages/plugins/plugin-security/src/explain-principal-person.test.ts", "packages/qa/dogfood/test/fls-superuser-vote.dogfood.test.ts" ], "line_budget": "vs 47718699: 10 files, +587 / -12 (runtime 118 incl. core export lines and docblocks; tests 441; docs 7 net; changeset 21)", "deviations": [ "File surface: the /auth/me/permissions merge is packages/core (buildEffectiveFieldPermissions), not plugin-hono-server/current-user-endpoints.ts, which is untouched. Seat 2 accepted this; the claim amendment is the seat's.", "Docs: permissions-matrix.mdx §3 is fixed at seat 2's request. field-level-security.mdx gets one sentence too, because its union paragraph made the same claim the runtime no longer holds.", "Forced test edit: plugin-security explain-principal-person.test.ts. The qa_pp_directory_masked fixture held viewAllRecords plus readable:false on sys_user.email. It now reaches rows by a select row policy, keeping its intent.", "Main was merged once (47718699) before gates and dogfood. 23 main commits landed after, touching none of this diff's packages or files, so they were not merged again before pr_create.", "First ablation attempt was a no-op: a comment marker was stripped from dist, and preflight caught it as absent. Retried with a string-literal marker; that reading is the one reported.", "PR has no assignee or reviewer, per the standing order (no MCP writes; os-dev forbids PR assignees). The session-context assignee step was skipped. Not subscribed to PR activity, per os-dev's dispatch contract." ], "open_questions": [ { "question": "Should ADR-0066's Status line gain an '· **Amended** (2026-11-02, ruling 6573563853)' entry beside the D2 ⑧ note, like its 2026-10-14 and 2026-10-29 entries?", "options": [ "A: note only, Status line untouched (as the delta says: add one note)", "B: also append an Amended entry to the Status line" ], "recommendation": "A, because the delta binds 'add one note' and earlier ⑧ notes (2026-10-26, 2026-10-28) left the Status line alone" } ], "security_findings": "none", "out_of_scope_findings": [] }Stamped 2026-11-02T13:50Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Claim amendment to
6576911116· 2026-11-02T13:52Z · file surface increment, declaredThe card thread's report
6578856250(PR #29387, head718ba1b2) put the superuser field vote in@objectstack/core, beside the object-level answer it reads, and keptpermission-evaluator.tsas the caller.current-user-endpoints.tsis untouched. The seat accepts:packages/core/src/security/effective-object-permissions.tsandpackages/core/src/security/index.ts(one export);packages/plugins/plugin-security/src/explain-principal-person.test.ts(one forced fixture edit);content/docs/permissions/permissions-matrix.mdxandcontent/docs/permissions/field-level-security.mdx.packages/plugins/plugin-hono-server/src/current-user-endpoints.ts.Clause-②: nois unchanged. The changeset shipspatchfor core and plugin-security.Generated by Claude Code
test(service-automation): the decider fixture reaches every deal by w…
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmSgjH5Vi4jT3KVJpvt
os-dev-report · delta (FAIL round 1)
PR #29387, new head
252a02f4(one commit on top of718ba1b2). It changes one test file and no runtime code.1. The red, measured. On
718ba1b2,packages/services/service-automation/src/resume-decider-identity.integration.test.tsgives 1 failed / 20 passed. The failing case isa post-decision node the decider may not perform is refused loudly, and the run reports the node, the same case as the CI job (Test Core 8/8, line 495). The cause is the ruling. Thedeal_managerset heldviewAllRecords+modifyAllRecordsondealbeside'deal.restricted_note': { editable: false }, so under A the set's own Modify All out-votes the lock and the decider's write lands. Base47718699is green, as the reviewer measured.2. The forced edit.
dealManagerSetnow grantsdealwithallowRead/allowEditandreadScope: 'org'/writeScope: 'org'instead of the super-user bits. The manager still reaches a deal the rep owns on the privatedealobject, and still may not editrestricted_note, without holding a bypass. Every assertion is unchanged. The GUARD case still shows that the manager's write rests on the manager's own grants. Result: 21/21 green. The PR body names it as forced test edit 2 of 2.3. Repo-wide sweep. I searched packages, examples, the showcase, app metadata and tests for a set that declares
viewAllRecords/modifyAllRecordsbesidereadable: false/editable: falseon the same object.service-automation/src/resume-decider-identity.integration.test.tsplugin-security/src/explain-principal-person.test.tsqa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts159–179ald_withhold_set/ald_withhold_only_sethold View All onsys_userbesideemailwithheld. Its assertions are about ledger rows served (none), not about the field, so it stays green. Note only, not edited.4. Every hit package, run on the new head.
718ba1b2(394 files / 3254 tests) still stands, because the round-1 commit touches no dogfood file and no runtime code.5. CI on
252a02f4. Test Core 1/8 to 8/8 all passed, and the aggregateTest Corecheck is green (run 41458968131). Shard 8/8, which was red, now passes. Test UI 1–16/16, Build Core, Build Docs, Live E2E UI, TypeScript Type Check, ESLint and Lint & Repo Gates are all green too.mcp_calls: 7 (
issue_read×3,pull_request_readget_check_runs ×4), no MCP writes. api_writes: 5, adding the PR bodyissue_patchvia the relay and this comment via post-stamped.Stamped 2026-11-02T14:37Z.