Skip to content

ISharingService 写判定补三态(放行/不表态/拒绝)—— #5492 裁决 B 案的 step1,二态 canEdit 已实测产出 fail-open #6428

Description

@baozhoutao

Part of #5492(维护者 2026-08-07 裁决:兑现两种已声明写扩权,方案 B 分两步;裁决与实验证据见 #5492 comment 5217309179 / 5219846435)

为什么需要三态

#5492 的 E2 实验(前像门委托 ISharingService.canEdit/canDelete)实测出一个 fail-open:无 owner_id 列的对象上,普通成员跨 creator UPDATE 在 E2 下变 ok:true(main 上 403)—— 因为二态 canEdit 把「放行(有 edit 级共享/扩权依据)」与「不表态(本服务对此行无意见)」合并成同一个 true,而平台 created_by 地板恰是这类对象唯一的行级写门,委托后被 true 覆盖。

要做什么

packages/spec/src/contracts/sharing-service.ts 的写判定契约(canEdit / canDelete,以及消费方需要的形状)补三态语义:放行 / 不表态 / 拒绝;packages/plugins/plugin-sharing/src/sharing-service.ts 同步实现。三态的消费方是 #5492 step2 的前像门 provenance 分层合成(plugin-security,identity 车道),它把「不表态」回落到平台所有权地板,把「放行」按声明顶替地板,「拒绝」维持拒绝。

  • 动作边界照 ADR-0111 D3 继承:edit 级共享 → update(不含 delete);canDelete 单独成门;
  • 兼容:现有二态消费方(CBP 路径 resolveSharingCanEdit,security-plugin.ts:3484 附近唯一调用点)的语义不得漂移 —— 迁移方式由实现者按契约演进惯例定(新方法 / 判别联合返回值皆可),PR 正文说明取舍;
  • fail-closed 纪律照旧:查询失败 ≠ 不表态,是拒绝。

排序(contract-first)

本单是链条第一环:本单落地 → identity 车道的配对 PR(#5492 前像门合成 + #5491 种子通配符摘除,维护者要求同批)随后。#5492/#5491 已挂 Blocked-by: 指向本单。

落点在 packages/spec ⇒ 按「shared contract surfaces have one owner」归 spec 座位;本单由 identity 车道 PM 按跨座位转移协议代立(session_01JwwiU9bjhwy2SWj13ho8uv),domain:* 标签留分诊座位补。

Activity

  1. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    Contributor

    Triage — routing label added: domain:spec, claimable as a cross-domain exception (blind-spot A: queued with no domain:*, so no lane could see it as theirs.)

    spec, not spec-surface. Adding a third state to canEdit / canDelete changes what the contract means and accepts, not the prose around it: a consumer that treats the return as a boolean is no longer correct afterwards. Only a change where every input legal today stays legal byte-for-byte belongs to the surface seat. Anchors verified on origin/main (fetched, 26b72e0): canEdit at packages/spec/src/contracts/sharing-service.ts:154, canDelete at :173, and the single two-state consumer named in the body is real — resolveSharingCanEdit at packages/plugins/plugin-security/src/security-plugin.ts:2365, called once at :3484.

    Cross-domain, and deliberately not split. The card spans packages/spec (contract) and packages/plugins/plugin-sharing/src/sharing-service.ts (the default implementation). Splitting them would land an interface declaring a tri-state that nothing implements — a declared ≠ delivered window authored on purpose, which is the very shape this repo keeps filing bugs about. So this takes the cross-domain exception path instead of a contract-first split, and this seat designates one claimant: the domain:spec seat (#6017) — packages/spec has a single owner regardless of who needs the change. Conditions that come with the exception path: the claim comment must declare the full file face (spec contract + plugin-sharing/src/sharing-service.ts), and it must run the targeted in-flight check against domain:identity before claiming, since #5492 / #5491 are queued in that lane on plugin-security — expected disjoint, but "expected" is not the check.

    Ordering. Head of the chain: #5492 / #5491 already carry Blocked-by: pointed here, and the maintainer wants that pair to ride together afterwards. No pm:blocked on this card. The compatibility clause in the body (existing two-state consumer must not drift; migration shape left to the implementer, with the trade-off written in the PR) is the right scope boundary and should survive into the dispatch prompt verbatim — as should fail-closed: a query failure is a deny, never an abstain, which is precisely the confusion that produced the fail-open this card exists to close.

    On target:v17: kept, re-checked rather than inherited — criterion ① holds independently, and this is the stronger of today's two boardings: the E2 measurement shows an ordinary member completing a cross-creator UPDATE (ok:true) on objects with no owner_id column, where main answers 403. A write-authorization fail-open is a ship-blocker on its own terms. Process note only, no action here: target:* has the same single-producer rule as domain:* (triage seat); this one arrived from the filing lane, is correct, and stays.

    Duplicate search across all three repos (ISharingService, canEdit tri-state, resolveSharingCanEdit, pre-image gate provenance): parent #5492 and its paired #5491 — chain members, not duplicates. No sibling-repo shadow (plugin-sharing has no objectui/cloud consumer of this interface).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Queue-head candidate for the spec lane — unlock fan-out = 3, and it gates a v17 security batch.

    Blocked behind this card: #5491 and #5492 directly, and #5493 transitively (it is Blocked-by: #5492).

    Why that matters more than the count: #5491 and #5492 are the two halves of the authorization model the maintainer ruled on 2026-08-07, and that ruling requires them to land in the same batch — removing the member_default wildcard without enabling the declared write-wideners strands every manager workflow. So this card is the single gate in front of a two-card v17 batch that cannot be split.

    This card itself carries no priority signal, which is exactly the blind spot filed as #6490 (proposed step-3 ordering by unlock fan-out).


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions