Skip to content

Decision: an imported duty is invisible to the person it names — which way to make a record visible to its owner? #143

Open

Description

@objectstack-fleet

② Maintainer decision (filing gate category ②). Part of #127 (A — a permission model that is correct in the open edition). Found by the #132 card thread on a fresh clone (PR #141, docs/template-checklist.md item 7).

维护者速读

模板最核心的一步是"把你自己的职责清单导进来"。实测(17.3.0,全新克隆):管理员把职责导入、指给张三之后,张三自己登录看不到这条职责,它生成的任务也看不到;张三自己建的职责他能看到。原因是平台的"只看自己的"按记录的创建人(owner_id)判断,而 Duly 的"负责人"是另一个字段(owner)。导入的人是管理员,所以记录归管理员。这是安全边界上的选择,所以呈你裁:A 等平台支持"声明哪个字段是归属字段";B 在 Duly 里写 hook 把两个字段保持一致;C 只写文档;D 用平台已有的共享规则按 owner 字段共享(要先测平台是否已支持)。推荐 D,前提不成立则 A,不做 B。一字即可:A / B / C / D。

The problem in one sentence

A duty or task that someone else created for you — by import, or by an admin — never appears in your own lists, so the import path the template is built around produces a tenant in which every member's week is empty.

Measured (17.3.0, pnpm dev, fresh clone; from the #132 report)

  • GET /api/v1/data/duly_duty as an imported duty's owner, duly_member bound: total 0. The same person's self-created duty: total 1.
  • The imported row's owner_id is the importing admin; its owner field is the member.
  • duly_duty and duly_task are sharingModel: 'private'; duly_member grants readScope: 'own' (src/security/permission-sets.ts). For a private object the sharing layer's read filter is the owner match OR the caller's sys_record_share grants, and an RLS policy can only narrow it (src/security/sharing-rules.ts header).

Governing text: AGENTS.md rule 9 (metadata first; a platform gap is filed, not worked around) and the product invariant "one task has exactly one owner"; src/security/sharing-rules.ts (fail closed; a grant that reaches the wrong people is the worse failure); ADR-0057 (depth scopes); epic #127 decision rule (a security boundary goes to the maintainer).

Premises, each with its re-check

  1. Record-relative sharing recipients exist on the installed platform. Sharing rules cannot name a RECORD-RELATIVE recipient (the owner's manager; the value of a user field on the matched record), and no other declarative surface can widen a private object either objectstack#14103 ("Sharing rules cannot name a RECORD-RELATIVE recipient … the value of a user field") closed completed 2026-09-03. Re-check: grep -rn "ShareRecipientType\|recipientField\|record_field" node_modules/@objectstack/spec/src/security/sharing.zod.ts (positive control: business_unit, which must hit). Not verified here.
  2. Dispatched tasks inherit the same problem only when their owner_id is not the assignee. Re-check: on pnpm demo, read one member's duly_task rows and compare owner_id with owner. Not verified here.

Options

Option What the customer sees Work
A Platform: an object declares which user field is its ownership field for own scope; Duly names owner. Filed upstream, Duly waits. Correct and declarative once it ships; until then imports stay invisible to their owners. Duly: one line after the platform ships; platform card: no precedent measured.
B Duly hook keeps owner_id equal to owner on duly_duty and duly_task. Works today on any edition. ~1 day; new security code in the app.
C Document only; every owner creates their own duties. The import path is useless for its main purpose. half a day
D Declarative criteria sharing rule: share each duly_duty / duly_task with the user in its owner field (premise 1). Works today if the platform carries it; fails closed if not. ~1 day incl. measurement; no precedent in this repo.

四维分析

  • 实际业务需求:导入是 M4 的核心路径("装上、导入自己的清单、不用我们也能跑")。今天导入后每个成员的"我的本周"都是空的,这是确定会撞上的,不是推测。
  • 项目长远合理性:归属与可见性应由平台声明并强制。D 用平台现成的共享规则(若 #14103 已交付),A 是更根本的平台形态;B 把一条安全规则写进应用 hook,长期要还,而且和平台的归属模型并行存在两份事实。
  • 防 AI 写代码犯错:D 和 A 都是声明式,写错会被 validate 或运行时拒绝、并且失败时关闭;B 的 hook 要处理批量写、空值、系统上下文写入,是 AI 最容易写出漏洞的地方,漏了就是静默越权或静默不可见。
  • 创业阶段不扩散需求:D 不新增能力面;A 在平台新增一个声明键;B 新增应用侧安全代码;C 最省但等于放弃导入。

推荐 D;D 的前提 1 不成立则 A(上游立卡,Duly 写明缺口并等待),⛔ 不做 B。 只看①选 D;②③④ 是否翻转:否。置信缺口:没有实测 #14103 交付的具体形状,也没有核实派发出来的任务是否同样受影响(前提 2)。

裁后执行

D:派一张 duly 卡,先验前提 1、2,再写两条共享规则和对应测试(导入的职责与派发的任务对负责人可见、对他人不可见);前提不成立即报 fork,不得退回 B。A:席位在 objectstack 立平台卡,本卡转 pm:blocked 挂上它,getting-started 第 8 步保留缺口说明。

os-decision-facets

  • ① 项目长远合理性:D/A 收窄特例(归属由平台声明),B 增加应用侧特例。
  • ② 实际业务拉动:今天导入后的每个成员都撞上。
  • ③ 防 AI 犯错:声明式共享规则失败时关闭,优于 hook 的静默容错。
  • ④ 创业阶段不扩散:D 零新增能力面,优于 B 的新安全代码。
    Prior rulings read: owner visibility, readScope own, sharing rule recipient → 0 hits in duly issues; ADR none; thread: none

Dedupe: duly issues (all states) with owner / visib / import / readscope in the title, 2026-10-26 → 3 hits: #110 (the import feature itself, closed), #96 and #70 (unrelated); none on visibility.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note (2026-11-02T07:30Z), measured in duly#180 Step 0 on main 358c2ca / 18.2.0. This is input for the decision on this card; it changes no ruling.

    On today's demo, seeded tasks carry owner_id = Dev Admin. As a result, Owen Pryce (duly_member) reads 0 of his seeded tasks: 1 task has owner = Owen and 0 have owner_id = Owen. Whatever this card decides about which field own scopes on, the seed has to stamp that field with the assignee.


    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