Skip to content

docs(spec): ISharingService.canEdit 补上 modifyAllRecords 旁路分支 (#5125) - #5818

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5125-canedit-bypass-doc
Aug 6, 2026
Merged

docs(spec): ISharingService.canEdit 补上 modifyAllRecords 旁路分支 (#5125)#5818
baozhoutao merged 1 commit into
mainfrom
claude/issue-5125-canedit-bypass-doc

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes#5125

前提复核(先证伪,再动手)

origin/main @ 5ab0842 逐条核过 issue 的两个前提,都成立:

(a) 实现确实有第三条 modifyAllRecords 写旁路。packages/plugins/plugin-sharing/src/sharing-service.ts:

  • canEdit 在 366-401 行,分三步:1) ownership(write DEPTH 放宽,matchesOwnerScope)→ 2) WRITE_ACCESS_LEVELS 的 share 行 → 3) 398-400 行return this.hasModifyAllBypass(object, context);
  • hasModifyAllBypass 在 341-351 行,走 late-bound 的 probe.hasWriteBypass(object, context),并且 fail CLOSED(无 security service / probe 抛错 → false)。

(b) 契约文档确实还没这一条。 改前 packages/spec/src/contracts/sharing-service.tsmodifyAllRecords 只出现 2 次(canDeletecanManageShares),canEdit 的 doc comment 只写到 ownership + share 为止。

权限名逐层确认过,没有分叉(任务书要求不能盲抄 canDelete 的措辞):ISecurityService.hasWriteBypass 的契约就写明是 modifyAllRecords(packages/spec/src/contracts/security-service.ts:220-237);实现 packages/plugins/plugin-security/src/security-plugin.ts:676-692permissionEvaluator.hasSuperuserWriteBypass;再往下 permission-evaluator.ts:296-311superuserBypassSetsbit === 'modify' 分支上读的正是 op.modifyAllRecords。所以 EDIT 路径和 canDelete同一个 bit、同一个谓词,措辞可以直接对齐。

改动

一句话级的 doc comment 修正,措辞与 canDelete 对齐(the modifyAllRecords super-user bypass),另补两句说明这条旁路与 canDelete / canManageShares 是同一个 ISecurityService.hasWriteBypass 谓词、放在最后问、fail closed —— 这三点都是实现里已有的事实,不是新约定。

外加一条文档对偶 pin(packages/spec/src/contracts/sharing-service.test.ts):三道写门(canEdit / canDelete / canManageShares)的 JSDoc 必须各自点名 modifyAllRecords。没有任何东西会对 doc comment 做类型检查,而这次漂移的恰恰是 prose,所以用 TS AST 读接口成员的 leading trivia 来钉。两道反空转保护:成员枚举整体断言(改名不能把断言掏空),外加 buildReadFilter 必须含该词作为判别性负例 —— 读路径确实没有 hasWriteBypass 分支(View/Modify All Data 持有者读到全部行,是因为 security 层把 read DEPTH 解析成 org 而在 sharing 之前短路,buildReadFilter 202 行 if (readScope === 'org') return null;),所以在那里写上写旁路本身就会是新的漂移。

生成物投影:没有

contracts/ 这一目录不产任何生成物,证据三条:

  1. packages/spec/scripts/build-docs.ts:106 只收 *.zod.ts(if (!entry.name.endsWith('.zod.ts')) continue;),而 packages/spec/src/contracts/ 下一个 .zod.ts 都没有;
  2. content/docs/references/contracts/ 里只有 meta.json,内容是 "pages": [];check:docs 跑起来自己也这么说 —— Skipping clean of contracts/ — no JSON schemas found in .../json-schema/contracts;
  3. api-surface.json./contracts 条目只记导出名与种类("ISharingService (interface)"),不含任何文档正文;本 PR 也没有增删任何导出,所以 check:api-surface 结构上不可能动。

跑过的门:check:docs ✅(240 generated files in sync with packages/spec)、check:authorable-surface ✅。⚠️ 两者都会顺手重写 packages/spec/authorable-surface.base.json(已知 #5358),已 git checkout -- 还原,PR 里没有这个文件 —— 本 PR 只含上述两个文件。

Changeset:选 skip-changeset

packages/spec 发布的 filesdist + src/**/*.zod.ts,本文件是普通 .ts,源码不随包走;上面已证没有生成物投影;导出面、类型、schema、运行时行为全部零变化。按 pr-automation.yml changeset 门自己的处方,这属于"releases nothing → 打 skip-changeset 标签(PREFERRED)"。同形先例:.changeset/auth-database-hooks-middleware-claim-corrected.md(同样是已发布包里的 comment-only 更正,结论 "Comments only — no runtime behaviour changes, nothing released")。诚实起见记一句:更正后的文字仍会经 dist/*.d.ts 到达 consumer 的 IDE 悬浮提示 —— 但那不改变本 PR 不发布任何东西这一事实,所以走标签而不是 changeset。标签已由我在建 PR 后立即打上。

验证

pnpm --filter @objectstack/spec test → Test Files 319 passed (319) | Tests 8145 passed (8145)
vitest run src/contracts/sharing-service.test.ts → Test Files 1 passed (1) | Tests 3 passed (3)
pnpm --filter @objectstack/spec typecheck → tsc --noEmit ✅ + check:test-typecheck OK
node scripts/check-nul-bytes.mjs → OK (5684 files, no raw ASCII control bytes)

反向验证(方向是事先预判的:红)。canEdit 的 doc comment 还原成改前那段,新 pin 应该转红、两条既有断言应该保持绿 —— 实测正是如此:

FAIL src/contracts/sharing-service.test.ts > [#5125] ... > canEdit / canDelete / canManageShares each name the `modifyAllRecords` bypass
→ canEdit must document the modifyAllRecords bypass
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)

还原修正后重跑回到 3 passed。

越界发现(已另开 issue,未在本 PR 修)


Generated by Claude Code

…pass (#5125)
`canEdit`'s contract doc listed ownership and an `edit`-level share and
stopped there, while the implementation has carried a third branch since
#4647: the `modifyAllRecords` super-user write bypass, probed through
`ISecurityService.hasWriteBypass` after ownership and shares both fail
(`packages/plugins/plugin-sharing/src/sharing-service.ts:398-400`, via
`hasModifyAllBypass` at :341-351).
The omission was worse than silence because `canDelete` sits four lines
below naming the same bypass ("ownership (widened by write DEPTH) or the
`modifyAllRecords` super-user bypass ONLY"), so the pair read as a
deliberate exclusion on the update gate -- the exact opposite of the code.
The wording now matches `canDelete`'s, and names the same permission the
implementation actually checks (`bit: 'modify'` -> `op.modifyAllRecords`,
`permission-evaluator.ts:superuserBypassSets`).
Adds a parity pin over the interface's own JSDoc: the three write gates
(`canEdit` / `canDelete` / `canManageShares`) must each name the bypass,
`buildReadFilter` must not (the read path has no `hasWriteBypass` branch),
and the member enumeration is asserted whole so a rename cannot empty it.
Nothing type-checks a doc comment, and prose is what drifted here.
Comment + test only: no schema, type, export or behaviour change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
@vercel

vercelBot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 6, 2026 6:26am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 契约文档漂移:ISharingService.canEdit 未提 modifyAllRecords 旁路(canDelete 提了)

2 participants

@baozhoutao@claude