Repository navigation
feat(plugin-sharing): list the objects a sharing rule can take effect on - #31207
Conversation
GET /api/v1/sharing/shareable-objects answers the objects the save doors admit a sharing rule on (SharingService.sharingRuleObjectRefusal), business objects first, for Setup's sharing-rule object picker. Admitted as the sharing-rule surface admits (manage_sharing, ADR-0111 D6). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
…s ledger The list is compared with defineRule's own verdict for every registered object; the platform's credential and session objects are pinned absent and refused; the door is driven through the platform resolver. The ledger is wired into the live-mount parity gate and the envelope guard. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
effectiveSharingModel's isSystem-or-sys_ test becomes isPlatformObjectSchema, which the shareable-objects order reads too, so the object-schema isSystem read stays where the system-context census already classifies it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4
📓 Docs Drift CheckThis PR changes 1 package(s): 35 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b893919ac673848622942d246657c4cf4e4fb856 && git checkout b893919ac673848622942d246657c4cf4e4fb856
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin de3996bd55e3394c60da31d346df70d0b7465e14 59c76e787e7b21dd493272fdd133042ed846a902 && git checkout -B drift-repro de3996bd55e3394c60da31d346df70d0b7465e14 && git merge --no-ff 59c76e787e7b21dd493272fdd133042ed846a902
node scripts/docs-audit/affected-docs.mjs --json de3996bd55e3394c60da31d346df70d0b7465e14
|
|
domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B 维护者速读
席位核对ACCEPT · Head-sha:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 42399338579 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B Queue red triaged · 2026-11-07T18:27Z Queue build 42399338579 failed in |
Requested by 田騏 · project thread
Refs #30209 (row 5 B, server half; rows 1 and 2 measured below). The card stays open for row 1 and the console picker.
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
GET /api/v1/sharing/shareable-objects,返回「共享规则能生效的对象」清单(业务对象在前、系统对象在后,各组按标签排序);判定直接调用保存时拒绝用的同一个判断(A sharing rule on an object without record sharing saves and silently does nothing #30621 的sharingRuleObjectRefusal),不另写一份;会话、账号、API 密钥、密钥等凭据类对象都不在清单里。sys_*,业务对象被埋住,还能选到存凭据和会话的对象(保存才报错)。按裁决 6670358877 的第 5 行 B,服务端先给出清单,控制台下一张卡据此只列这些对象。manage_sharing,或旧的manage_platform_settings,且有当前组织)能调用,其他人 403、匿名 401、带查询参数 400。回滚即撤销本 PR。packages/plugins/plugin-sharing/src/shareable-objects.ts(清单与排序,判定由调用方传入)、src/sharing-rule-service.ts的listShareableObjects(准入与判定接线)、src/shareable-objects-routes.ts(拒绝顺序:身份→受限登录→匿名→查询参数→管理员)。pnpm --filter @objectstack/plugin-sharing exec vitest run src/shareable-objects.test.ts,预期 16 个用例全过,其中「每个不在清单里的对象,defineRule 都以 SHARING_NOT_ENABLED 拒绝」逐个对象核对。What changes
registerShareableObjectsRoutes(src/shareable-objects-routes.ts) mountsGET /api/v1/sharing/shareable-objectsonhttp.server(aliashttp-server) atkernel:ready, the way the share-link routes are mounted. The rule administration routes (/api/v1/sharing/rules*) belong to@objectstack/restover thesharingRulesspec contract; a list there would need a new contract member, so this door reads the plugin's ownSharingRuleServiceinstead and touches neitherpackages/restnorpackages/spec. No HTTP server: onewarnnaming the route.src/shareable-objects.ts): every object in the engine's registry (getConfigs()) for whichSharingService.sharingRuleObjectRefusal(object)answersnull, the check both save doors (A sharing rule on an object without record sharing saves and silently does nothing #30621) already ask, handed in as a function and never restated. Each entry is{ name, label, system };systemisisSystemor asys_name. Business objects first, then platform objects, each group by label, then name. Response:{ success: true, data: { objects } }.SharingRuleService.listShareableObjects(context), new): the sharing-rule surface's own admission,assertCanManageRules(ADR-0111 D6:manage_sharingor the legacymanage_platform_settings, plus an active organization unless platform authority). The route's refusal order, each before the list is read: caller resolution throughresolveRequestExecutionContext(session or API key; a permission-store outage and an API key lifecycle refusal relayed as declared), held sign-in403with the policy code, anonymous401 UNAUTHENTICATED, any query parameter400 VALIDATION_ERRORnaming it (closed set: empty), then a non-administrator403 PERMISSION_DENIEDwith the surface's sentence.enforce: false) answers501 NOT_IMPLEMENTED, as the REST rule routes do; an engine that cannot enumerate its objects answers500 INTERNAL_ERRORwith the error in the fault log.src/sharing-route-ledger.ts(oneserver-onlyrow) with its package-local conformance test; the live-mount parity dogfood test imports it, andscripts/cross-package-test-inputs.mjs/turbo.jsondeclare it as that test's input;scripts/check-route-envelope.mjsdeclares the new door inMODULESat{ responses: 0, ok: 0, err: 0 }(every body goes throughsendOk/sendError/sendServerFault). These four one-line wirings are the ones feat(service-messaging): serve the notification topics and channels a preference or template can name #31004's amendment named.isPlatformObjectSchema(isSystemor asys_name) is extracted insrc/sharing-service.tsfromeffectiveSharingModel's own test, and the list reuses it for itssystemflag (no behaviour change; the system-context census keys that read to this file)..changeset/30209-sharing-shareable-objects.md,@objectstack/plugin-sharing: patch, both declaration lines at line start.Row 5's security note: credential and session objects
Measured, then pinned. The platform's credential and session objects as their packages declare them (
sys_session,sys_account,sys_verification,sys_api_key,sys_two_factor,sys_oauth_application,sys_oauth_access_token,sys_oauth_consent,sys_jwks,sys_secret) are none of them listed, anddefineRulerefuses a rule on each withSHARING_NOT_ENABLED/422(each is owner-less or public). So, after #30621, a rule on one of them is refused at save and never shares anything; none remains listable. A wider probe registered every object definition@objectstack/platform-objects(root, identity, security, audit) and plugin-sharing export, 65 objects: none was listable.Rows 1 and 2: readings (no code)
Row 2 is done on
main8ef4d309(PR #30496), both halves:describeFilterGroupinui/packages/fields/src/widgets/FilterConditionField.tsx, 2 hits; its pinFilterConditionField.readWords-30209.test.tsx(3 cases) is on main.dependentValues={data}inui/packages/plugin-detail/src/DetailSection.tsx, 2 sites), sorecipient-pickerreadsrecipient_type; pinned byrecordFace.siblingValues-30209.test.tsx(2 cases) on main.Row 1: the cheapest honest shape, and which arm needs spec.
record:related_listoversys_user_permission_set(packages/platform-objects/src/pages/sys-user.page.ts), which can only list stored rows: sets from the default (everyone) binding and from positions are never rows, so no related-list source can show them. No existing page component shows effective access.typeis an open string outside the namespaces its enum claims (ai,app,element,global,nav,page,record), andmcp:authorized-assistantson the same page is the precedent: a console widget whose zod arm lives inui/packages/types, placed by the page with nopackages/specedit. Asecurity:effective-accesswidget (any unclaimed namespace) follows it. The ui seat's reading (6659268469) that row 1 needs a spec vocabulary entry holds only for a type inside a claimed namespace such asrecord:.POST /api/v1/security/explainanswers one object and one operation; itsprincipal.permissionSetslists the resolved names without their origin, and only its per-layercontributors[].viasays how a set arrived (baseline,everyone,position:NAME), per object.resolveUserAuthzGrants(@objectstack/core) carriespermissionsanddirectPermissionsbut not which position or default binding brought each other set. So "effective sets with their origin (default set, position, direct)" needs a per-user read, for exampleGET /api/v1/security/users/:id/effective-access, built on the resolver plus the catalog's position bindings. If it is served by@objectstack/restover a spec service contract, that contract member is the one spec arm; served by plugin-security onIHttpServer(this PR's shape), none.sys-user.page.ts(platform-objects). No spec card unless the read goes through the REST contract.Tests
src/shareable-objects.test.ts(new, 16 cases) on realObjectQL+SqlDriver(better-sqlite3), with the realSharingService/SharingRuleServiceand the A sharing rule on an object without record sharing saves and silently does nothing #30621 save guard bound:sys_user,controlled_by_parent,sys_sharing_ruleitself);defineRulerefuses withSHARING_NOT_ENABLED422";defineRule;manage_sharingand an organization-lessmanage_sharingholder are refusedPERMISSION_DENIED; CONTROL the legacymanage_platform_settingsis admitted;manage_sharing): an administrator is served the list and the service receives the resolved context; a member is refused403 PERMISSION_DENIEDby the real service; anonymous401, held sign-in403 PASSWORD_EXPIRED, a query parameter400naming it, each asserting the envelope and that the list was never read;501when no rule service runs and500when the list cannot be read, neither with data.src/sharing-route-ledger.conformance.test.ts(new): registrar enumeration against the ledger, both directions.Acceptance notes
sys_sharing_rule.object_namein this PR. The field already declareswidget: 'object-ref', which draws today's object picker.FieldSchema.widgetis one free string and an unregistered value degrades to the text input, so naming a new widget here before the console registers it would turn the picker into a text box until the ui card lands. feat(service-messaging): serve the notification topics and channels a preference or template can name #31004's fields had no widget, so its hint degraded to what they already were; this field does not. The hint and the registration should land together, in the ui card (one line inplugin-sharing's object file beside theui/widget). Raised for the seat as an open question.label, not localized: the door withholds locale like its feat(service-messaging): serve the notification topics and channels a preference or template can name #31004 sibling. The console localizes object names by key as it does elsewhere./api/v1/sharing/shareable-objects), like the share-link and messaging doors onIHttpServer; the REST rule routes also have an environment-scoped twin, this one does not.Verification
All readings on head ``59c76e787e
(59c76e7`)` unless a line says otherwise.Package suite, typecheck, lint
(The package has no
lintscript; the root eslint ran over the touched files.)Negative-pin ablations (each through scripts/ablation-replace.mjs; anchor hit once; restored blob == HEAD, git diff HEAD empty)
if (refusal(name) !== null) continue;): 4 failed / 12 passed: the list case, the one-verdict case, the credential case and the door's administrator case.listShareableObjects(this.assertCanManageRules(context)replaced byvoid context): 3 failed / 13 passed: member refused, organization-less refused, and the door's member403. (A first attempt replaced the call with text the anchor already contained; the tool refused it as a no-op (replacement count moved 1 -> 1) and restored; rerun with the replacement above.)if (false as boolean) {: 1 failed (refuses an anonymous caller 401 UNAUTHENTICATED).if (false as boolean) {: 1 failed (refuses a query parameter 400 VALIDATION_ERROR naming it).Derived gates
Reconciliation:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran <the list above> :: exit 0(89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN).Not derived, run as a check on the merged tree:
pnpm --filter @objectstack/spec run check:generatedexits 1 under the default 2 GB heap becausetsc -p tsconfig.test.jsonaborts out of memory ("failed without diagnostics", exit 134), and exits 0 withNODE_OPTIONS=--max-old-space-size=8192(CI sets 4096). This diff touches nopackages/specfile.🤖 Generated with Claude Code
https://claude.ai/code/session_017ihL2f62c1GYk6XzbdtCa4