Skip to content

[Decision] group posture is an on-premise shape — should package-authored scheduled flows run under group with the switch on, and which organization do their writes carry? (ruling G item 3, reopened by the maintainer) #18378

Description

@os-elon-musk

Filed by the director seat (session_01WCEaPsmKY4UyoivKkkaUHt) on the maintainer's instruction during the 2026-09-16 design discussion of PR #18198. Maintainer, verbatim and untranslated (live PM chat):

group 模式是本地部署的,运行 schedule 应该是可以的,但是你没有权限,可以单独开一个决策卡

⇒ Ruling G item 3 (#17396, 5642381255: 「group is off by default, like isolated」, and with the switch on 「behaves as walled」) is reopened for group only by the maintainer's own words; it stays a tenancy-boundary decision, so this seat files the card and does not rule it. ⛔ Nothing about single or isolated is reopened; ⛔ nothing here delays PR #18198.

一句话问题

一个集团版本地部署(group 姿态:多个工厂或分公司作为组织,共用一个数据库)把定时开关打开之后,包内的定时流要不要跑;跑的时候,它写出来的通知、运行记录、待办归哪个组织。

Governing text

  • Ruling G item 3 and its recorded reason: 「resolveSystemWriteOrganization refuses an organization-less system insert under any wall (walled-posture), and TenancyService.defaultOrgId() answers null under any wall by ADR-0093 D3. An organization-less scheduled run in group could read the whole group and update records in place, but the inbox, delivery and sys_automation_run rows it inserts would be refused … Which organization a group-wide sweep's inserts belong to is the part the maintainer said is not yet thought through」.
  • ADR-0105 D1: group = 「organizations share one database as data」 with membership/invitation boundaries, group-wide visibility and cross-org workflow inherent to the shape (the multi-plant MES example is the ADR's own); postureEnforcesWall('group') = true, postureStampsOrganization('group') = true (packages/spec/src/security/tenancy-posture.ts).
  • ADR-0093 D3: target-org resolution consumes declared mode — single ⇒ the bootstrap organization (slug='default'); a walled mode ⇒ none, the framework never guesses.
  • The 2026-09-08 ruling on A schedule-triggered flow's notify delivers nothing on a multi-organization install: the run carries no organization, so the tenant-scoped inbox/delivery writes are refused (#8844) while the run reads healthy #16659 (「多组织定时任务本来只能在组织内运行,应该带组织ID,不允许跨组织的定时任务」) — made for the multi-tenant shape; whether it binds a group (one legal group, cross-org visibility inherent) is exactly what this card asks.
  • After PR feat(types,triggers,service-automation,runtime,cli,spec,lint)!: package-authored scheduled work is a deployment decision, off by default #18198: resolveScheduledWorkPolicy() (packages/types/src/env.ts) answers group + on ⇒ requiresActingOrganization: true; schedule-organization.zod.ts's docblock and .describe() say the declaration is required under a walled posture. Option A changes that protocol prose and that row — 「协议不正确的应该先修改协议」 applies, so the change is in packages/spec / packages/types first.

Premises, with re-check commands

git grep -n "requiresActingOrganization" origin/main -- packages/types/src/env.ts          # the policy table (after #18198 lands)
git grep -n "defaultOrgId" origin/main -- packages/spec/src packages/core/src | head          # null under walls (ADR-0093 D3)
git grep -n "walled-posture" origin/main -- packages/spec/src/api/error-code-ledger.zod.ts  # the refusal an org-less system insert meets
git grep -n "never filled from the swept row" origin/main -- packages/triggers               # the time_relative pin option A retargets

Options × real cost

what what a customer sees
A (seat recommends) group + on binds without a declaration, like single. Writes follow the record: a time_relative run carries the swept record's organization_id; a cron schedule run that has no record carries config.organization if declared, otherwise the group's bootstrap organization (slug='default'), and the boot/audit line states which. Group-wide reads stay group-wide (inherent to the posture). Packaged schedules (contract expiry, SLA monitors, reminders) run on a multi-plant install out of the box; a record-less cron flow's artefacts land in the group's home organization unless the author declares one
B Keep ruling G: group stays walled; declaration required; packaged flows do not run; admins clone per organization (ADR-0126 §7.1) Every plant clones every packaged schedule by hand
C Automatic per-organization fan-out under group: one run per organization, reads scoped to it Unambiguous ownership, no cross-org logic possible, cost ∝ organizations, new scheduler machinery
D Bind without a declaration, but a write whose organization cannot be resolved (record-less cron, no declaration) is refused loudly (ADR-0112 envelope); record-derived writes follow the record Strictest; a cron flow that emits notifications without declaring fails at runtime, by name

业务含义直译

A = 集团总部的定时任务照常跑,单据写到它所属的工厂,没有单据的写到总部。B = 每个工厂各自复制一份任务。C = 每个工厂各跑一份,总部看不到跨厂的汇总。D = 跑可以,写不清归属就当场拒绝。

四轴(从业务立场)

  • ① 项目长远合理性:group 按 ADR-0105 就是「一家集团、多个分支、一个数据库」,集团级批处理是这个形状的固有能力(主流 ERP 多工厂、Salesforce/Dynamics 多业务部门都在一个 org 内跑全局批处理,行级归属由记录自己决定)。A 只新增一条「无记录时归总部」的兜底规则;C 新增一套扇出机制;B 把成本永久推给管理员。⇒ A。
  • ② 实际业务拉动:今天任何 group 本地部署要跑包内定时流只能逐组织克隆;维护者本人指出 group 是本地部署、应可运行。⚠️ 本席未测有多少 group 部署在线。
  • ③ 防 AI 犯错:静默风险在 A 的兜底 —— 写进总部组织的通知可能没人读;缓解是 boot/audit 行点名兜底组织、os doctor 打印。D 最响亮但把失败推到运行时。B/C 无此风险但代价见①。
  • ④ 创业阶段不扩散:A 一条规则、复用 ADR-0093 D3 已有的 bootstrap 组织概念;C 是机制;B 零新增但人工成本永久。

Prior rulings read: group,posture,schedule,organization,wall,defaultorgid,time_relative,bootstrap → 66 hits; ADR-0105 D1/D4/D5, ADR-0131 D3, ADR-0057 D9, ADR-0099 D2, ADR-0106 D6, ADR-0120 D5 — none rules the write-ownership of an organization-less scheduled run under group; ruling G item 3 recorded it as unanswered.

推荐:A(回退 D)。自检:只看①选 A;②③④ 是否翻转:否(③ 只要求兜底被响亮说出)。
置信缺口:① 09-08 「不允许跨组织的定时任务」是否有意覆盖 group(其上下文是多租户),只有维护者能答;② sys_automation_run / inbox 的其他消费者是否假定 group 下的 tenantId 必来自声明;③ 现网 group 部署数未测。

裁后执行

  • A ⇒ spec 卡(packages/spec 先:schedule-organization.zod.ts 的 docblock 与 .describe()、ADR-0087 条目 18 的 group 行),再 types/triggers/service-automation:resolveScheduledWorkPolicygroup 行改为不要求声明并带写归属规则;time_relativegroup 下把 tenantId 从被扫记录填入(退掉「never filled from the swept row」在 group 上的那半钉子);无记录的 cron 走 config.organization 否则 bootstrap 组织,boot/audit 行点名;文档四页;changeset minor + BREAKING;Clause-②: yes在 PR feat(types,triggers,service-automation,runtime,cli,spec,lint)!: package-authored scheduled work is a deployment decision, off by default #18198 落地之后再动,⛔ 不并入它。
  • B ⇒ 关卡 not planned,ruling G 原样。
  • C ⇒ 扇出设计卡(ADR 级)。
  • D ⇒ 同 A 的绑定改动,写归属改为拒绝。

Refs: #17396 (ruling G) · PR #18198 · #16659 (2026-09-08 ruling) · ADR-0105 · ADR-0093 D3 · ADR-0126 §7.1

os-decision-facets

  • ① 项目长远合理性:group 是一家集团一个库,集团级批处理是固有能力;A 一条兜底规则,C 一套机制,B 永久人工。
  • ② 实际业务拉动:group 本地部署今天只能逐组织克隆;维护者指出应可运行;部署数未测。
  • ③ 防 AI 犯错:A 的兜底须在 boot/audit 行点名;D 最响亮但失败在运行时。
  • ④ 创业阶段不扩散:A 复用 bootstrap 组织概念;C 新增扇出机制。
  • Prior rulings read: group,posture,schedule,organization,wall,defaultorgid,time_relative,bootstrap → 66 hits; ADR-0105 D1/D4/D5, ADR-0131 D3, ADR-0057 D9, ADR-0099 D2, ADR-0106 D6, ADR-0120 D5;皆不裁。
  • 推荐:A(回退 D);只看①选 A;②③④ 是否翻转:否。

维护者速读

事情。 您说 group(集团版本地部署,多个工厂或分公司共用一个库)应该可以跑定时任务。裁决 G 当时把 group 和云上多租户一样默认关、开了也要每条流声明组织,原因是那时没想清楚:一条集团级定时任务写出来的通知和运行记录归哪个组织——在围墙姿态下,没有组织的系统写入会被拒绝。

选项。 A 像单租户一样直接跑;写入跟着记录走(扫描到哪个工厂的单据就归哪个工厂),没有单据的任务归总部组织,启动日志写明。B 维持现状,每个工厂自己克隆一份。C 自动给每个组织各跑一份,互不可见。D 跑可以,但写不清归属的当场拒绝。

席位意见。 推荐 A:group 的定义就是一家集团一个库,跨分支可见是它的本意;A 只多一条兜底规则,并且在日志里说清楚。回退 D。

你要做的:回一个字母 —— A / B / C / D。


Generated by Claude Code

Activity

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