Skip to content

Commit 3d8a835

Browse files
docs(adr): amend ADR-0142 D5 — an organization authors its own assignment rules, evaluated before the declared ones; answer merge question 11 (#30828)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D56TXAmXSP2pWYUJuvZgpnd)_ domain:server#1 · os-elon-musk · session_016WV4d14C8K4gMehVb6jBvA Part of #30066 Clause-②: no Metadata-protocol: none Claim: 6672306838 (amended 6672490095, 6673262187, 6674205693, 6674382817) · Branch: `claude/project-thread-kl2al6` · Ruling-ref: 6631036940 (letter A), execution parameters 6670178869 ## 维护者速读 - 改了什么:ADR-0142 的 D5 增加一段修订:组织可以自己写分派规则(`sys_assignment_rule` 行),先于应用包声明的规则评估;同时在状态行、TL;DR 第 4 条加修订标注,并把合并问题 11(「组织自建规则推迟」)标为已答复。 - 为什么改:这是 #30066 裁决 A(你 2026-11-05 21:34Z 的「同意」)要求记进 ADR 的部分;代码已由 #30760 合入(ac890f5a),本 PR 只补文字记录,与代码 PR 不捆绑。 - 风险与回滚:极小。只改一个 ADR 文件(+38 / -3),不动代码、规范或生成物;回滚即撤 PR。 - 先看:`docs/adr/0142-ownership-queue-membership-claim-and-assignment-rules.md` D5 里「What the rule writes」之后新加的 **Amended** 段(第 524 行起),以及合并问题 11(第 805 行)末尾。 - 两分钟验证:打开 Files 标签,预期只有 1 个文件、+38 / -3;新段落的七个要点与 #30760 合入的行为一一对应(顺序、同名、保存校验、用户目标须为活跃成员)。 - 需要你做的:`docs/adr/**` 属 Tier H,请 os-zhuang 或 hotlong 审阅批准(或亲手合并)。 ## What changes One file, `docs/adr/0142-ownership-queue-membership-claim-and-assignment-rules.md`, recording ruling A on #30066 (record 6631036940, with the execution parameters of 6670178869): - **Status line**: a new **Amended** (2026-11-05) entry naming the ruling: D5 gains its second source, and merge question 11 is answered. - **TL;DR item 4**: a parenthetical **Amended** note pointing to D5. - **D5**: a new paragraph after *What the rule writes*, **a second source: an organization's own rules**, with seven points: - *Shape*: a `sys_assignment_rule` row, owned by `@objectstack/plugin-sharing` beside `sys_queue` and `sys_sharing_rule`; `organization_id` NOT NULL; one rule with one entry per row. - *One namespace*: a row may not take a declared rule's name (ADR-0131 D4); a row a later-installed package shadows is not evaluated and is reported. - *Order*: first-match stays; the organization's active rows first (by `sort_order`, then name), the declared rules as the fallback; why the other order cannot work (the activation ledger has no organization column); a failed read of the organization's rows routes by no rule. - *Validated on save*: `criteria` and `load` compiled by the two paths the seam evaluates by; the declared schema's entry refusals; a `user` target must be an active member. - *A user target at insert, from either source*: owns the row only while an active member; otherwise ownerless, logged once, never refused. The `os lint` warning on declared user targets stands. - *A lapsed queue target*: unchanged, for both sources. - *Out of this record's code*: the dedicated Setup page is the Console's. - **Merge question 11**: an **Answered** (2026-11-05) sentence pointing to D5. ## Why Ruling A asks for this record. The runtime half shipped in #30760 (merged as ac890f5); this PR follows it on the same branch, serially, and is never bundled with it. Each point above states the behaviour #30760 merged, so the record and the code read the same. ## Tests None: a docs-only change to one ADR. No code, schema, generated artifact or published file moves. ## Acceptance notes - Additions only, apart from the three lines the amendment notes extend (the Status line, TL;DR item 4's last line, question 11's last line); every existing sentence of D5 and question 11 is kept as written. - `skip-changeset`: `docs/adr/**` publishes nothing from any released package. - Out of scope, left as they are: the docs page and the skill line (card #30826); the stale "deferred" text in `packages/spec/liveness/assignment_rule.json` and `liveness/README.md` (waits for the spec seat); translations for the remaining locales (follow #30512's batch). ## Verification <details><summary>Local gates at 4b26915 (dispatch-gates --commands on this diff, 21 commands)</summary> ```text node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:adr-anchors :: exit 0 pnpm check:adr-index :: exit 0 pnpm check:adr-symbol-anchors :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-prior-rulings :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` `node scripts/pm/dispatch-gates.mjs --ran`: 21 derived, 21 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: 1 message clean. CI families outside the derivation (artifact rosters, wide-population families, the type-check lanes) are CI's; this PR's check-runs are their record. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. (none added) - [x] 3. No formatter ran; `pnpm lint` passes. (no source file touched) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (publishes nothing: `skip-changeset`) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4d14C8K4gMehVb6jBvA)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 95ba410 commit 3d8a835

1 file changed

Lines changed: 38 additions & 3 deletions

File tree

‎docs/adr/0142-ownership-queue-membership-claim-and-assignment-rules.md‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0142: An ownership queue is a platform primitive — a queue entity, membership through the existing recipient kinds, claim through the ownership-transfer gate, and declarative assignment rules evaluated on insert
22

3-
**Status**: Proposed (2026-10-25) · **Amended** (2026-10-28, ruling [`6457475657`](https://github.com/objectstack-ai/objectstack/issues/25769#issuecomment-6457475657) on #25769, letter A) — D3's `current_user.queues` producer is `@objectstack/plugin-security`, kernel-side, under the reserved key `queues`; see the note in D3 · **Amended** (2026-11-03, ruling records [`6594432222`](https://github.com/objectstack-ai/objectstack/issues/29604#issuecomment-6594432222) and [`6599731698`](https://github.com/objectstack-ai/objectstack/issues/29604#issuecomment-6599731698) on #29604, letter B in both) — D2 tells an empty expansion (a fact) from a failed read behind the queue (a fault): the admission consumers (D3, D4, D5) keep "nobody" on a failed read, the D7 grant recompute refuses it, and the D6 approver slate refuses it with the approval node refusing to open; see the notes in D2, D6 and D7. `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by the
3+
**Status**: Proposed (2026-10-25) · **Amended** (2026-10-28, ruling [`6457475657`](https://github.com/objectstack-ai/objectstack/issues/25769#issuecomment-6457475657) on #25769, letter A) — D3's `current_user.queues` producer is `@objectstack/plugin-security`, kernel-side, under the reserved key `queues`; see the note in D3 · **Amended** (2026-11-03, ruling records [`6594432222`](https://github.com/objectstack-ai/objectstack/issues/29604#issuecomment-6594432222) and [`6599731698`](https://github.com/objectstack-ai/objectstack/issues/29604#issuecomment-6599731698) on #29604, letter B in both) — D2 tells an empty expansion (a fact) from a failed read behind the queue (a fault): the admission consumers (D3, D4, D5) keep "nobody" on a failed read, the D7 grant recompute refuses it, and the D6 approver slate refuses it with the approval node refusing to open; see the notes in D2, D6 and D7 · **Amended** (2026-11-05, ruling A on [#30066](https://github.com/objectstack-ai/objectstack/issues/30066) (record [`6631036940`](https://github.com/objectstack-ai/objectstack/issues/30066#issuecomment-6631036940), with the execution parameters of [`6670178869`](https://github.com/objectstack-ai/objectstack/issues/30066#issuecomment-6670178869), agreed 「同意」)) — D5 gains its second source: an organization authors its own assignment rules as `sys_assignment_rule` rows, evaluated before the declared rules, and merge question 11 is answered; see the note in D5. `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by the
44
maintainer's merge, and ⛔ the one PR under [Execution plan](#execution-plan-after-acceptance) is cut after that merge,
55
never before it. Until a published `@objectstack/*` release carries that PR, HotCRM keeps the STOPGAP module this record
66
exists to retire. **This record declares; it implements nothing** — no schema, no code, no changeset.
@@ -87,7 +87,9 @@ HotCRM was read from a read-only clone of `objectstack-ai/hotcrm` at `8f52859`;
8787
named strategies over the queue's members.** It evaluates once, on insert, after every `beforeInsert` hook and before
8888
the write, only for a row that still has no owner; a writer-supplied, middleware-stamped or hook-set owner is respected.
8989
Its reads run under an explicit system context pinned to the row's organization. An empty pool never blocks intake: the
90-
row sits in the queue, visibly, and the platform logs once.
90+
row sits in the queue, visibly, and the platform logs once. (**Amended** 2026-11-05, ruling A on #30066: an
91+
organization also authors its own rules, as `sys_assignment_rule` rows, and they are evaluated before the declared
92+
ones; see the note in D5.)
9193
5. **The two reserved members return only with all of that in place.** The `queue` approver type resolves to the queue's
9294
members; the `queue` sharing recipient expands to them and is recomputed when membership moves. Each is authorable on
9395
the day its runtime branch is enforced and pinned — not before (ADR-0049, ADR-0078, Prime Directive #10).
@@ -519,6 +521,38 @@ reports a rule whose queue is declared with no position member at all, and canno
519521
**What the rule writes.** `owner_id` (when a member was chosen) and `queue` (always, for a queue target). Nothing else:
520522
no "assigned by" column, no stamp of the rule's name (open item 3).
521523

524+
**Amended** (2026-11-05, ruling A on [#30066](https://github.com/objectstack-ai/objectstack/issues/30066) (record [`6631036940`](https://github.com/objectstack-ai/objectstack/issues/30066#issuecomment-6631036940), with the execution parameters of [`6670178869`](https://github.com/objectstack-ai/objectstack/issues/30066#issuecomment-6670178869), agreed 「同意」)) — **a second source: an organization's own rules.** Merge question 11 deferred
525+
organization-authored routing; the ruling takes it up on the two-source model `sharing_rule` and `queue` already follow
526+
(ADR-0131 D6 regime C), and everything above holds for both sources unless a point below says otherwise.
527+
528+
- **Shape.** An organization authors a rule as a `sys_assignment_rule` row, owned by `@objectstack/plugin-sharing` beside
529+
`sys_queue` and `sys_sharing_rule`. `organization_id` is NOT NULL and the table sits behind the organization wall: a
530+
rule that reaches every organization is a declared one, never a row. One row is **one rule with one entry**, the entry
531+
named after the rule: `object_name`, `criteria`, `sort_order`, a recipient (`queue` or `user`, by name or id), an
532+
optional `strategy` and `load`, and `active`. The round-robin cursor keeps its key (rule, entry, organization).
533+
- **One namespace.** A row may not take a declared rule's name (ADR-0131 D4); the save is refused. A row that predates
534+
the declaration — a package installed later declares the name — is not evaluated (the declared rule answers for the
535+
name), and is reported at `error` at boot and once at `warn` at insert.
536+
- **Order.** The engine stays first-match. For a row of an organization, that organization's active rows are tried
537+
first, ascending by `sort_order` and then by name, and the declared rules after them, as the fallback; across both, the
538+
first entry whose `criteria` match decides, and its target stands even when it does not resolve. The other order would
539+
let an organization's rule never win: the activation ledger that switches a declared rule off is one row per
540+
deployment with no organization column, so an organization cannot switch a package rule off for itself alone. When the
541+
organization's rows cannot be read, its row is written as it stood — never routed by the fallback its own rules would
542+
have overtaken — and the engine logs at `warn`. Intake is still never refused.
543+
- **Validated on save.** `criteria` and `load` are compiled by the same two paths the seam evaluates by (the
544+
record-interpretation backend and the pushdown compiler of ADR-0058); a row either one cannot compile is refused, as is
545+
any entry shape the declared schema refuses (a strategy on a user target, `least_loaded` without `load`, …) and a row
546+
with no organization. A `user` target must be an **active member** of the rule's organization — a membership row there
547+
and no ban in force — or the save is refused.
548+
- **A user target at insert, from either source.** The user owns the row only while still an active member of the row's
549+
organization. A target that no longer resolves leaves the row ownerless, logs once at `warn` naming the rule, the user
550+
and the organization, and never refuses the insert. This tightens a declared rule's user target too: the `os lint`
551+
warning above stands, and such a rule now assigns only in an organization where that user is a member.
552+
- **A lapsed queue target** is handled exactly as the paragraph on the missing queue above says, for both sources.
553+
- **Out of this record's code.** The Setup page for these rows is the Console's (a separate card); until it lands the
554+
generic record list serves them.
555+
522556
### D6 — Re-admission of the `queue` approver type
523557

524558
The type becomes authorable in the PR that makes **all** of the following true, and not before (ADR-0049's enforce-or-
@@ -771,7 +805,8 @@ objection names the number.
771805
11. **Assignment rules are declared only; organization-authored rules are deferred.** HotCRM's pull is a package
772806
declaration; Setup-authored routing is a second door with a criteria builder and the regime-C same-name discipline.
773807
Deferred by name, with the authored `sys_queue` already in place as the data half a later `sys_assignment_rule` would
774-
target.
808+
target. **Answered** (2026-11-05, ruling A on #30066): organizations author `sys_assignment_rule` rows, evaluated
809+
before the declared rules — see the note in D5.
775810
12. **`load` is required with `least_loaded`, with no platform default.** "Open" differs per object in the first
776811
consumer; a default such as "every row" would count closed work and be exactly the silent inert instance ADR-0078
777812
forbids. Cost: one more line per rule.

0 commit comments

Comments
 (0)