Skip to content

Commit a9f654e

Browse files
feat!: ownership queues and assignment rules (ADR-0142 phase 2) (#26337)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbBvbmHqnauTSkDLEUWJthmQ)_ Part of #25769. This is phase 2 of ADR-0142: the one spec + server PR of the record's Execution plan item 1. The card stays open for what this PR does not carry: the `queues` RLS membership key (an open question below, it needs a decision), the HotCRM STOPGAP deletion (its own lane, after a published release), the Console queue picker and Claim affordance (objectui lane), and the authoring guide (Execution plan item 4). Clause-②: yes (narrowing) Metadata-protocol: narrowed Skills-docs: follow-up #26475 Contract review tier: **H**. The diff is over 3000 changed lines (168 files, +6230 / -380) and touches `skills/**`, so it lands only on a governed approver's review. ## What lands, by decision - **D1, the queue entity.** New metadata type `queue` (`QueueSchema`, `packages/spec/src/security/queue.zod.ts`) and stack key `queues`; new system object `sys_queue` in plugin-sharing for an organization's own queues. The `ownership-queue` service (`OWNERSHIP_QUEUE_SERVICE`, contract `IOwnershipQueueService`) resolves a name from the registry first, then from the organization's rows. The service is not named `queue`, which the job queue already holds (phase-2 carry 6409493120). - **D2, membership.** A declared queue's members are positions (merge question 18); an organization adds users, teams, positions and business units through `sys_queue_member`. One member expander serves every consumer, and the BU-tree recompute covers queues. - **D3, where a queued record sits.** Every `ownership: 'user'` object gets a system-managed `queue` column beside `owner_id`; declaring your own `queue` field on one is refused at registration (`INVALID_METADATA`). `queue` joins the injected system columns and `PUBLIC_FORM_SERVER_MANAGED_FIELDS`. Members reach a sitting record (`owner_id` empty, `queue` in `current_user.queues`) through the plugin-sharing owner match: read and edit, never delete. - **D4, claim, release, move.** The step-3.5 owner gate handles `queue`. A claim sets `owner_id` from empty to the caller, by id, admitted when the sharing model allows the caller the record; the engine's by-id write precondition makes one claimant win and the other get `409 CONCURRENT_UPDATE`. A release gives your own record back to its home queue. A move needs the transfer grant, and an unresolvable name is `422 QUEUE_NOT_FOUND`. An insert naming a queue needs the transfer grant or membership; a guest has neither. Explain reports `queue_membership` and `queue_claim` on the sharing layer. - **D5, assignment rules.** New metadata type `assignment_rule` (`AssignmentRuleSchema`) and stack key `assignmentRules`. The engine routes each inserted row that is still ownerless and queueless after `beforeInsert` once: park it in a queue, or pick a member by `round_robin` (cursor in `sys_assignment_cursor`, advanced under the precondition) or `least_loaded`. Assignment never refuses an insert. `kernel:ready` refuses a stack that declares queues or assignment rules when no `ownership-queue` service is registered (merge question 17). - **D6, the `queue` approver type** is authorable again: it resolves to the queue's members when the step opens, an empty queue adds nobody and logs a warning, and the `queue:NAME` placeholder is never written again. `APPROVER_VALUE_BINDINGS.queue` reads both sources (Open item 1). - **D7, the `queue` sharing recipient** is authorable: `ShareRecipientType` gains `queue`, `expandRecipient` returns the queue's members, and grants are recomputed when membership changes or a queue is switched on or off. - **Lint.** A queue-only approval node is reported `approval-approvers-may-resolve-empty`; the org-axis red line admits the queue recipient; two advisory rules with `os explain` entries, `assignment-rule-user-target` (warning) and `assignment-rule-queue-unstaffed` (info). - **Runtime.** `POST /security/_activation/:type/:name` switches a declared `queue` or `assignment_rule` on and off. - **Dogfood.** The authz conformance matrix gains the claim admission, the access path and the two re-admitted members (Open item 5). ## Edits outside the claim's declared file surface The claim names spec, plugin-sharing, plugin-approvals and the gate site in objectql / plugin-security. Measurement put producers and gate-owned ledgers elsewhere too, each edited in place: - `packages/lint`, `packages/runtime`, `packages/qa/dogfood`: the lint, activation and dogfood rows of Execution plan item 1. - `packages/metadata` (the artifact door registers `queues` and `assignmentRules`), `packages/metadata-core` (`queue` classified as an injected system column), `packages/platform-objects` (labels and the queue recipient option in zh-CN, ja-JP, es-ES), `ui/packages/app-shell` (the flow designer stops warning that a queue approver resolves to nobody). - `skills/objectstack-platform/SKILL.md`: `queues` and `assignmentRules` in the top-level key list, required by `check:skill-top-level-keys`. - Gate-owned ledgers: `scripts/platform-object-tenancy-census.json` (regenerated), `scripts/check-stack-collection-maps.mjs` (waiver rows for the two keys), `scripts/engine-double-contract.pinned.json`, `examples/app-showcase/src/coverage.ts` and `docs/qa/platform-checklist/coverage.json` (waived: no fixture declares a queue or an assignment rule until the HotCRM phase), `packages/spec/liveness/*` (two hand-authored ledgers plus an empty-state registry row), `packages/spec/llms.txt`, the strictness-ledger counts under `docs/audits/`, and prose counts in six `content/docs` pages that count gates read. `content/docs/automation/approvals.mdx` now documents the `queue` approver type. ## skills/** readings - Touched file `skills/objectstack-platform/SKILL.md`: 488 to 488 lines; 5824 to 5826 tokens (ceiling 5828). - Whole bundle, all 11 `SKILL.md`: 4525 to 4525 lines; 54450 to 54452 tokens. - The two added keys are paid by deleting two ADR citations, "(ADR-0019)" and "(ADR-0020)", from the same file. No re-wrap. ## Deviations and readings for review - The ADR writes the explain kinds hyphenated; the spec enum is snake_case (`queue_membership`, `queue_claim`), matching every other `ExplainRuleKind` member. - `sys_queue_member.queue` is a text column holding the queue name, not a lookup, because an organization staffs a declared queue under its name and a declared queue has no `sys_queue` row. - The registration refusal for a declared `queue` field applies to the base registration layer only. - Declared assignment rules are validated at load time by lint only; the runtime logs an unresolved target once and leaves the row ownerless. - Round-robin cursor: a driver with no `updateMany` advances it unconditionally, logged once (merge question 7). A cursor that keeps losing races under concurrent inserts, past its bounded retries, parks the row in the queue with a warning; this second fallback is not in the record. - An organization queue whose name clashes with a declared queue is refused with field code `invalid_value`, because `FieldErrorCode` is a closed catalog with no `duplicate` member. - Gate readings, each with a case in `ownership-queue-gate.test.ts`: an echo of the stored `queue` is not a move; a claim on a record with no queue at all is admitted when sharing positively admits it (an edit share, Modify All); clearing `owner_id` on a record with no home queue is a disown and needs the grant; a member who supplies an empty owner beside a queue on insert is still stamped owner, because parking needs the grant (merge question 13); a predicate update writing `queue` fails closed without the grant. - Two merges of `main`, both on conflict (pin counts and generated references). ## Open question (needs a decision) **The `queues` RLS membership key.** Execution plan item 1 lists "the queue service and its `queues` membership-resolver key". The resolver contract has one `IRlsMembershipResolver` slot, which an app's own resolver also fills, and the producer screens reserved keys out of a resolver's answer. So plugin-sharing cannot add `queues` without either displacing an app's resolver or being screened out. This PR does not implement it; the access path of D3 does not need it (it goes through the sharing owner match). Recommendation: reserve `queues` in `RESERVED_RLS_MEMBERSHIP_KEYS` and have plugin-security resolve it from the `ownership-queue` service, as the kernel resolves `positions`, in a follow-up on this card. ## Tests and gates (head 578e7a4, merge base 5cb1c1a) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 163 commands; all 163 run, each exit code captured before any pipe, every one 0. `--ran` reconcile: 163 derived, 163 run, 0 NOT-MEASURED, 0 UNRUN. `check:type-check-debt` ran under `os-verify-lock.sh`. - Suites at this head, under the lock: objectql 474 files / 8820 tests; plugin-security 228 / 4964 passed (45 pre-existing skips, none added); plugin-sharing 57 / 1271; plugin-approvals 98 / 1280; platform-objects 96 / 1361; lint 166 / 7393; runtime activation door and route ledger 2 / 75; dogfood authz conformance 61; app-shell targeted 2 / 34. - Spec: the touched test files plus `src/security`, `src/automation/assignment-rule`, `src/stack` and `src/data/filter.test.ts` (54 / 1955), and `src/type-alias-convention.pin.test.ts` (3). The full spec suite was not run locally: it exceeds the 560s local budget on this box; CI runs it. - Full dogfood battery: owed by the narrowing; see the landing-head section below for the reading at the landing head. ## 维护者速读 (本节按 os-dev 对受管面 PR 的要求用中文书写;仓库其余 GitHub 文本按 AGENTS.md 用英文。两条规则在此并存,已在报告中注明。) **改了什么** 平台有了「队列」。一条记录没有负责人时,可以先停在某个队列里:队列成员都能看到并编辑它(不能删除),谁先「认领」谁就成为负责人,同时认领的另一人会收到冲突提示。应用还可以声明「分配规则」:新建记录没有负责人时,按条件放进某个队列,或在队列成员中按轮询、最少负载挑一个人。审批里的「队列审批人」和共享规则里的「队列接收方」,以前是保留未开放的选项,现在真正可用。 **为什么改** Tesla 裁定 D6 = A:队列与分配是平台能力,不该由应用自己写。HotCRM 目前用 587 行临时代码给工单和线索做轮询分配,还要绕过负责人转移的权限闸门。本 PR 落地并发版后,HotCRM 可以删掉这些代码,改成声明两个队列、两条规则。ADR-0142 已被接受,本 PR 是它的第二阶段(规范 + 服务端)。 **风险与代价(含回滚)** - 所有按用户归属的对象都会多一个系统字段 `queue`。已有对象若自己声明了同名字段,注册时会被拒,需要改名。 - 新增三张系统表:`sys_queue`、`sys_queue_member`、`sys_assignment_cursor`。 - 声明了队列或分配规则、却没装 plugin-sharing 的组合,启动时会被拒,而不是静默不分配。 - 不声明队列的现有应用,行为不变。 - 改动面大(168 个文件),还动了对外发布的技能包,属于 Tier H,必须人工批准。 - 回滚:revert 本 PR 即可。新列和三张表里已有的数据会留下,但不再有代码读取。 - 还没做:RLS 策略里暂时不能直接使用 `current_user.queues`(见上方待决问题)。 **席位意见** (待席位填写) **你要做的** 1. 对待决问题拍板:`queues` 这个 RLS 成员键,是否由 plugin-security 作为平台保留键提供(推荐),在本卡后续补上。 2. 审阅并批准本 PR。 3. 合并并发版后,HotCRM 删除临时代码的那张卡才能开工。 ## Landing head — `f35f41144` (seat note) The Tests and Gates sections above were measured at `578e7a41`. At the landing head `f35f41144`: - CI ran 62 check-runs: 57 success, 5 skipped by path, 0 failed. - The full dogfood battery covered 304 files: 301 passed, 3 skipped, 0 failed (2521 tests: 2503 passed, 18 skipped). - The contract review is 6441855511, PASS at `cbd27d9a6`. It closes all three must-fix items of 6430868091. The seat's delta review carries it to `f35f41144`: in every changed file, `git diff cbd27d9 f35f411` makes exactly main's own change from `7c34e5dc2` to `fe53769a8`, except the count lines of three files. Those are `type-alias-convention.pin.test.ts` (796), `dropped-refinements.baseline.json` (251 / 887) and `references/index.mdx` (1556). The declaration lines above follow the changeset: `Clause-②: yes (narrowing)` / `Metadata-protocol: narrowed`. --- _Generated by [Claude Code](https://claude.ai/code/session_01GTntg542RUmmRRbMY6wMZD)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a485b5a commit a9f654e

184 files changed

Lines changed: 6475 additions & 403 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/objectql': minor
4+
'@objectstack/metadata-core': minor
5+
'@objectstack/metadata': minor
6+
'@objectstack/plugin-security': minor
7+
'@objectstack/plugin-sharing': minor
8+
'@objectstack/plugin-approvals': minor
9+
'@objectstack/lint': minor
10+
'@objectstack/runtime': minor
11+
'@objectstack/platform-objects': minor
12+
'@object-ui/app-shell': minor
13+
'@objectstack/skills': minor
14+
'@objectstack/cli': minor
15+
---
16+
17+
feat!: ownership queues: records wait in a queue until a member claims them, and assignment rules route new records (ADR-0142)
18+
19+
Clause-②: yes (narrowing)
20+
Metadata-protocol: narrowed
21+
22+
<!-- adr-0087: registered object-queue-field-reserved -->
23+
24+
**BREAKING** for one shape only: an object whose records have a user owner (`ownership: 'user'`, the default) can no longer declare its own field named `queue`. The platform now injects a `queue` column on every such object, and `registerObject` refuses the declaration with `422 INVALID_METADATA` (`ObjectQueueColumnRefusedError`), naming the object. An `objectExtensions` entry that declares `queue` on such an object is not refused, because an extension is folded over its object at read time: its `queue` is ignored, the platform's column stands, and a warning names the extension. The remedy: rename the business field (for example `support_queue`) and copy its data across, or, when the object's records have no user owner, declare `ownership: 'business_unit'`, `'org'` or `'none'`. The upgrade guide entry is `object-queue-field-reserved`.
25+
26+
Beside that, two enum additions: `ShareRecipientType` gains `queue` (a sharing rule can now name a queue as its recipient), and the `queue` approver type is authorable again (it leaves `NON_AUTHORABLE_APPROVER_TYPES`, so it is no longer marked deprecated). Two metadata types are new: `queue` and `assignment_rule`.
27+
28+
- **`@objectstack/spec`**: new `queue` and `assignment_rule` metadata types (`QueueSchema`, `AssignmentRuleSchema`) and the stack keys `queues` and `assignmentRules`. A declared queue's members are positions. New `IOwnershipQueueService` contract, registered under `ownership-queue`. `queue` joins the injected system columns of `ownership: 'user'` objects and `PUBLIC_FORM_SERVER_MANAGED_FIELDS`. `APPROVER_VALUE_BINDINGS.queue` reads both a declared queue and an organization's `sys_queue` row, and `APPROVER_ORG_SCOPED.queue` is `true`. The explain rule kinds `queue_membership` and `queue_claim` are new. `QUEUE_NOT_FOUND` (422) is registered under plugin-security.
29+
- **`@objectstack/objectql`**: objects with `ownership: 'user'` get a system-managed `queue` column beside `owner_id`. Declaring your own `queue` field on one is refused at registration (`INVALID_METADATA`), and an extension's own `queue` field on one is ignored with a warning. An update can carry a write precondition: when it no longer matches, the update answers `409 CONCURRENT_UPDATE`. Declared assignment rules run once for each inserted row that has no owner and no queue after the `beforeInsert` hooks. A rule can park the row in a queue, or pick a member by `round_robin` or `least_loaded`. Assignment never refuses an insert. `kernel:ready` refuses to boot a stack that declares queues or assignment rules when no `ownership-queue` service is registered.
30+
- **`@objectstack/metadata-core`**: `queue` is classified as an injected system column.
31+
- **`@objectstack/metadata`**: the artifact door registers a stack's `queues` and `assignmentRules`, so an artifact-only runtime serves them.
32+
- **`@objectstack/plugin-sharing`**: three new objects, `sys_queue`, `sys_queue_member` and `sys_assignment_cursor`, and the `ownership-queue` service. The service resolves a queue name from the registry first, then from the organization's own rows, and has one member expander. A record with no owner that sits in a queue can be read and edited by the queue's members, never deleted. The `queue` sharing recipient grants matched records to the queue's members, and its grants are recomputed when membership changes or the queue is switched on or off.
33+
- **`@objectstack/plugin-security`**: the owner gate handles `queue`. A **claim** sets `owner_id` from empty to yourself, by id. It is admitted when the sharing model allows you the record, and only one claimant wins: the loser gets `409`. A **release** gives your own record back to its home queue. A **move** to another queue needs the transfer grant, and a queue name that does not resolve is refused with `422 QUEUE_NOT_FOUND`. An insert that names a queue needs the transfer grant or membership of that queue. A guest can supply neither. Explain reports the queue reasons on the sharing layer.
34+
- **`@objectstack/plugin-approvals`**: a `queue` approver resolves to the queue's members when the step opens. An empty queue adds nobody and logs a warning. The `queue:<name>` placeholder is never written again.
35+
- **`@objectstack/lint`**: a queue-only approval node is reported `approval-approvers-may-resolve-empty`, and the org-axis red line admits the queue recipient. Two new advisory rules come with `os explain` entries: `assignment-rule-user-target` (warning) and `assignment-rule-queue-unstaffed` (info).
36+
- **`@objectstack/cli`**: the organization-ownership inventory lists `sys_queue`, `sys_queue_member` and `sys_assignment_cursor`, so `os migrate organization-ownership` plans them instead of refusing.
37+
- **`@objectstack/runtime`**: `POST /security/_activation/:type/:name` also switches a declared `queue` or `assignment_rule` on and off.
38+
- **`@objectstack/platform-objects`**: the two new metadata types' labels, and the queue recipient option, are translated in zh-CN, ja-JP and es-ES.
39+
- **`@object-ui/app-shell`**: the flow designer no longer warns that a queue approver resolves to nobody.
40+
- **`@objectstack/skills`**: the platform skill lists `queues` and `assignmentRules` among the stack's top-level keys.
41+
42+
What to do: rename any `queue` field on a user-owned object, or in an extension of one, as above. Every other existing stack behaves as before. To use queues, declare `queues` and `assignmentRules` in your stack, and make sure plugin-sharing is installed: it registers the `ownership-queue` service.

‎content/docs/automation/approvals.mdx‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -401,28 +401,28 @@ Only `approvers` is required on the node; everything else has a default
401401
(`behavior: 'first_response'`, `lockRecord: true`, `maxRevisions: 3`).
402402

403403
Approver entries resolve by kind — `position`, `user`, `field`, `manager`,
404-
`team`, `department`, `org_membership_level`, and `expression`
404+
`team`, `department`, `org_membership_level`, `queue`, and `expression`
405405
(described in the [callout above](#3-the-approval-node) and in
406406
[Dynamic approvers](#dynamic-approvers-3447)); `org_membership_level` is the
407407
one that silently resolves to nobody when it's mistaken for a business
408-
hierarchy. `queue` still parses so stored flows keep loading, but it is **not
409-
implemented** by the runtime and is no longer offered for authoring (#3508) —
410-
a queue entry resolves to nobody. Route to a `team`, `department`, or
411-
`position` instead. `field`, `manager`, and `expression` resolve against the record's
408+
hierarchy. A `queue` entry names an ownership queue by its machine name — one
409+
an installed app declares, or one of the organization's own — and resolves to
410+
the queue's members at node entry, through the same expansion that decides who
411+
may claim the queue's records (ADR-0142 D6). `field`, `manager`, and `expression` resolve against the record's
412412
**live** state at node entry (#3447); `manager` additionally depends on
413413
`sys_user.manager_id`, which a platform admin sets from **Setup → Users → ⋯ →
414414
Set Manager** — see [the `manager` callout above](#3-the-approval-node). `position`, `department`,
415-
`org_membership_level` and `expression` may additionally name which
415+
`org_membership_level`, `queue` and `expression` may additionally name which
416416
organization's directory resolves them — see
417417
[Approving across organizations](#cross-org-approvers).
418418

419419
What an entry that resolves to nobody leaves on the slate depends on its kind:
420420

421-
- **A person entry** (`manager`, `field`) adds no slot. Nor does an
422-
`expression` that yields no ids. A `user` entry never resolves to nobody: its
423-
value is the slot.
421+
- **A person entry** (`manager`, `field`) adds no slot. Nor does a `queue`
422+
with no members, or an `expression` that yields no ids. A `user` entry never
423+
resolves to nobody: its value is the slot.
424424
- **A group entry** (`position`, `team`, `department`, `org_membership_level`)
425-
keeps its `<type>:<value>` literal as a slot, and so do a `queue` entry and an
425+
keeps its `<type>:<value>` literal as a slot, and so does an
426426
`expression` value that `resolveAs` re-expands into a group with nobody in it.
427427
A `position:<name>` slot is decided by whoever is staffed into that position
428428
later. No person ever holds any other literal, so only an administrator can

‎content/docs/deployment/cli.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ os compile --json # JSON output for CI pipelines
758758
→ Normalizing stack definition...
759759
→ Lowering inline handlers...
760760
→ Validating protocol compliance...
761-
→ Running author-time rules (53)...
761+
→ Running author-time rules (54)...
762762
→ Checking that every required capability has a provider installable in this edition...
763763
→ Collecting package docs (ADR-0046)... 0 collected
764764
→ Writing artifact...

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ A clean run walks the registry and reports timing:
752752
Config: /path/to/support-desk/objectstack.config.ts
753753
Load time: 21ms
754754
→ Validating against ObjectStack Protocol...
755-
→ Running author-time rules (53)...
755+
→ Running author-time rules (54)...
756756
→ Checking that every required capability has a provider installable in this edition...
757757
→ Checking package docs (ADR-0046)...
758758

‎content/docs/getting-started/build-with-claude-code.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ visible: 'status != "resolved"'
305305
◆ Validate
306306
────────────────────────────────────────
307307
→ Validating against ObjectStack Protocol...
308-
→ Running author-time rules (53)...
308+
→ Running author-time rules (54)...
309309
310310
✗ Author-time rules failed (1 issue)
311311
• stack · action 'resolve_ticket' visible: bare reference `status` — a

‎content/docs/references/api/automation-api.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ const result = AutomationApiErrorCode.parse(data);
347347

348348
| Property | Type | Required | Description |
349349
| :--- | :--- | :--- | :--- |
350-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +346 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
350+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +347 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
351351
| **message** | `string` | ✅ | The inner refusal's own diagnostic text, verbatim, without the node and operation prefixes the run's failure text adds |
352352

353353
### Allowed Values: `FlowInnerRefusal.code`
@@ -615,6 +615,7 @@ const result = AutomationApiErrorCode.parse(data);
615615
* `PROJECT_PROVISIONING`
616616
* `PROJECT_PROVISIONING_FAILED`
617617
* `QUERY_OBJECT_MISMATCH`
618+
* `QUEUE_NOT_FOUND`
618619
* `RAW_SQL_UNSUPPORTED`
619620
* `READ_BACK_FAILED`
620621
* `READ_SCOPE_COMPILE_FAILED`

‎content/docs/references/api/contract.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const result = ApiErrorSchema.parse(data);
2828

2929
| Property | Type | Required | Description |
3030
| :--- | :--- | :--- | :--- |
31-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +346 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
31+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +347 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
3232
| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
3333
| **message** | `string` | ✅ | Readable error message |
3434
| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. |
@@ -303,6 +303,7 @@ const result = ApiErrorSchema.parse(data);
303303
* `PROJECT_PROVISIONING`
304304
* `PROJECT_PROVISIONING_FAILED`
305305
* `QUERY_OBJECT_MISMATCH`
306+
* `QUEUE_NOT_FOUND`
306307
* `RAW_SQL_UNSUPPORTED`
307308
* `READ_BACK_FAILED`
308309
* `READ_SCOPE_COMPILE_FAILED`

‎content/docs/references/api/error-code-ledger.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ const result = ErrorCode.parse(data);
470470
* `PROJECT_PROVISIONING`
471471
* `PROJECT_PROVISIONING_FAILED`
472472
* `QUERY_OBJECT_MISMATCH`
473+
* `QUEUE_NOT_FOUND`
473474
* `RAW_SQL_UNSUPPORTED`
474475
* `READ_BACK_FAILED`
475476
* `READ_SCOPE_COMPILE_FAILED`

‎content/docs/references/api/metadata.mdx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ Metadata query with filtering, sorting, and pagination
640640

641641
| Property | Type | Required | Description |
642642
| :--- | :--- | :--- | :--- |
643-
| **types** | `Enum<'object' \| 'field' \| 'hook' \| 'seed' \| 'mapping' \| 'picklist' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'api' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'position' \| 'capability' \| 'sharing_rule' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
643+
| **types** | `Enum<'object' \| 'field' \| 'hook' \| 'seed' \| 'mapping' \| 'picklist' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| 'datasource' \| 'external_catalog' \| 'translation' \| 'api' \| 'email_template' \| 'doc' \| 'book' \| 'permission' \| 'position' \| 'capability' \| 'sharing_rule' \| 'queue' \| 'assignment_rule' \| 'agent' \| 'tool' \| 'skill'>[]` | optional | Filter by metadata types |
644644
| **namespaces** | `string[]` | optional | Filter by namespaces |
645645
| **packageId** | `string` | optional | Filter by owning package |
646646
| **search** | `string` | optional | Full-text search query |
@@ -707,7 +707,7 @@ Metadata query with filtering, sorting, and pagination
707707

708708
| Property | Type | Required | Description |
709709
| :--- | :--- | :--- | :--- |
710-
| **type** | `Enum<'object' \| 'field' \| 'hook' \| 'seed' \| 'mapping' \| 'picklist' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| … +14 more>` | ✅ | Metadata type |
710+
| **type** | `Enum<'object' \| 'field' \| 'hook' \| 'seed' \| 'mapping' \| 'picklist' \| 'view' \| 'page' \| 'dashboard' \| 'app' \| 'action' \| 'report' \| 'dataset' \| 'flow' \| 'job' \| … +16 more>` | ✅ | Metadata type |
711711
| **name** | `string` | ✅ | Item name (snake_case) |
712712
| **data** | `Record<string, any>` | ✅ | Metadata payload |
713713
| **namespace** | `string` | optional | Optional namespace |
@@ -740,6 +740,8 @@ Metadata query with filtering, sorting, and pagination
740740
* `position`
741741
* `capability`
742742
* `sharing_rule`
743+
* `queue`
744+
* `assignment_rule`
743745
* `agent`
744746
* `tool`
745747
* `skill`

‎content/docs/references/api/package-api-assembled.mdx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ Installed package row whose manifest is the assembled package body
130130
| **books** | `{ name: string; label?: string; description?: string; slug?: string; … }[]` | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
131131
| **positions** | `{ name: string; label: string; description?: string; permissionSets?: string[]; … }[]` | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |
132132
| **sharingRules** | `{ name: string; label?: string; description?: string; object: string; … }[]` | optional | Record Sharing Rules |
133+
| **queues** | `{ name: string; label: string; description?: string; members?: object[]; … }[]` | optional | [ADR-0142 D1] Ownership queues — named sets of people records can sit in until a member claims them |
134+
| **assignmentRules** | `{ name: string; label?: string; description?: string; object: string; … }[]` | optional | [ADR-0142 D5] Assignment rules — route new ownerless records to a queue or a person, once, on insert |
133135
| **apis** | `{ name: string; path: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; summary?: string; … }[]` | optional | API Endpoints — declared endpoints are live from protocol 17; each is gated at publish (ADR-0121) |
134136
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'bulk_update' \| 'bulk_delete'>[]; … }[]` | optional | Outbound Webhooks |
135137
| **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents |
@@ -374,6 +376,8 @@ This schema accepts one of the following structures:
374376
| **books** | `{ name: string; label?: string; description?: string; slug?: string; … }[]` | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
375377
| **positions** | `{ name: string; label: string; description?: string; permissionSets?: string[]; … }[]` | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |
376378
| **sharingRules** | `{ name: string; label?: string; description?: string; object: string; … }[]` | optional | Record Sharing Rules |
379+
| **queues** | `{ name: string; label: string; description?: string; members?: object[]; … }[]` | optional | [ADR-0142 D1] Ownership queues — named sets of people records can sit in until a member claims them |
380+
| **assignmentRules** | `{ name: string; label?: string; description?: string; object: string; … }[]` | optional | [ADR-0142 D5] Assignment rules — route new ownerless records to a queue or a person, once, on insert |
377381
| **apis** | `{ name: string; path: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; summary?: string; … }[]` | optional | API Endpoints — declared endpoints are live from protocol 17; each is gated at publish (ADR-0121) |
378382
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'bulk_update' \| 'bulk_delete'>[]; … }[]` | optional | Outbound Webhooks |
379383
| **agents** | `{ name: string; label: string; avatar?: string; role: string; … }[]` | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents |

0 commit comments

Comments
 (0)