Repository navigation
Commit dfa179a
docs(skills): the automation skill teaches the mail option formatter for a notice's select label and the active-member rule for a user target; the docs page gains organization-authored assignment rules (#30866)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoFAQqEnan8JMBeR3XnRHXNy)_
Fixes #30731
Fixes #30826
Clause-②: no
Metadata-protocol: none
domain:skills#2 card thread (dev session
`session_01L9qibxujehvWJcGRXMYv7f`, seat session
`session_01L9qibz5RLVEYDFco93jZ7u`) · branch
`claude/project-thread-esfn9k` (the fold thread's designated branch,
named in the Claims 6678887018 on #30731 and 6678901809 on #30826) ·
base `origin/main` `677d542a`.
## 维护者速读
- 改了什么:自动化技能(`skills/objectstack-automation/SKILL.md`)新增两句:① `notify`
节——通知里要印选项标签,写在邮件模板里用 `{{ stage | option:opportunity.stage
}}`(按每个收件人的语言印),`option` 只有邮件模板认(行内 `title` / `message` 拒收),⛔ 不用
`templateData` 里的 `label()`(每次运行只算一次、在收件人扇出之前);② 「Assignment
rules」节——`assignTo: { type: 'user' }`
指定的人只在仍是该组织的在职成员时才拥有记录,否则记录无主落地。eval 15 新增通知用例。文档页
`automation/queues-and-assignment-rules` 的「Declare an assignment
rule」节新增组织自建规则(`sys_assignment_rule`):先于应用声明的规则求值、按 `sort_order`
再按名字、首个命中生效、组织规则读不到就不路由、`user` 目标的在职成员规则、保存时校验哪些字段。
- 为什么改:两个上游半边都已合入(#30727 + #30781 的邮件 `option` 格式化器;#30760
的组织自建分派规则),技能和文档还没教,照技能写通知的 agent 会去 `templateData` 里写
`label()`,要么保存被拒,要么所有收件人同一种语言;读文档的作者仍以为分派规则只有声明式、`user` 目标永远拥有记录。
- 风险与回滚:只改技能文本、eval 夹具、文档页与棘轮上限行(6449 → 6448,按实测锁定),不碰运行时;回滚 = revert 本
PR。为付账删掉的三处(见下文 Deletions)各有别处承载。
- 先看:① `skills/objectstack-automation/SKILL.md` 的 `notify`
节新段与「Assignment rules」改行(措辞是否与 `main` 上的渲染行为一致);②
`content/docs/automation/queues-and-assignment-rules.mdx` 新增三段(与
`packages/objectql/src/assignment-rules.ts` 头注释逐条对照);③ 删掉的「CRM
Automation Blueprint」节。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs | grep
objectstack-automation/SKILL.md` 预期 `6448 tokens (ceiling 6448; headroom
0)`;`pnpm check:skill-identifier-liveness` 预期退出 0。
- 需要你做的:Tier H(`skills/**`)——席位契约复审通过后,由 os-zhuang 批准/入队。
## What changes
**#30731 — `skills/objectstack-automation/SKILL.md`, `notify` section.**
One paragraph after the `notify` config block: an option label in a
notice is `{{ stage | option:opportunity.stage }}` in the mail
`template`, printed in each recipient's language; `option` is mail-only
and refused in an inline `title` / `message`; never `label()` in
`templateData`, which is evaluated once per run, before fan-out. Read
from `main`: `packages/formula/src/template-engine.ts`
(`MAIL_ONLY_FORMATTERS = ['option']`, `optionFieldReference`: bare
`object.field`), `packages/plugins/plugin-email/src/email-service.ts`
(the label table is built per send in the recipient's locale),
`packages/services/service-automation/src/builtin/notify-node.ts`
(`templateData` is a value-slot map resolved once per run; the
`template` locale is resolved per recipient after fan-out),
`packages/formula/src/stdlib.ts` (`label()` is the formula-field macro).
**#30731 —
`skills/objectstack-automation/evals/flows-triggers-approvals.json`.**
Eval 15: a stalled-opportunity e-mail whose subject shows the stage
label in each recipient's language. `must_contain`:
`option:opportunity.stage`, `template`, `once per run`;
`must_not_contain`: `source: 'label(` and `| label` (the two wrong
spellings a right answer never writes). Appended by splicing before the
closing bracket, so no other case is reformatted.
**#30826 — `skills/objectstack-automation/SKILL.md`, "Assignment
rules".** The `assignTo: { type: 'user', value: 'ID' }` sentence now
reads "names one owner, who holds the row only while an active member of
its organization (else ownerless)". Read from
`packages/objectql/src/assignment-rules.ts`
(`organizationMemberStanding`: a `sys_member` row and no ban in force;
the engine asks it of every user target at insert, declared rules
included) and
`packages/plugins/plugin-sharing/src/assignment-rule-hooks.ts` (the save
guard asks the same predicate).
**#30826 — `content/docs/automation/queues-and-assignment-rules.mdx`,
"2. Declare an assignment rule".** Three paragraphs after "The rule
writes `owner_id` …": the active-member rule for a `user` target; the
organization's own `sys_assignment_rule` records (Setup → Assignment
Rules, `manage_platform_settings`; one rule with one entry; `active`),
one namespace by name with the declared rules (mirroring the page's
`sys_queue` paragraph), evaluated **first** by `sort_order` then `name`
with the declared `assignmentRules` as the fallback (the activation
ledger has no organization column), first match wins, a failed read of
the organization's rules routes nothing; and what the save guard
refuses. One checklist bullet. Pages checked for existing coverage
before writing (maintainer rule, no duplicate sections):
`content/docs/automation/queues-and-assignment-rules.mdx` (declared
rules and `sys_queue` only), `content/docs/automation/index.mdx` (one
summary line, declared rules), `content/docs/automation/approvals.mdx`
(the `queue` approver), `content/docs/permissions/sharing-rules.mdx` and
`content/docs/permissions/rls.mdx` (queue recipients and
`current_user.queues`), `content/docs/releases/v18/18-3.mdx`
(release-owned); `content/docs/references/api/metadata.mdx` and
`references/kernel/metadata-plugin.mdx` list the `assignment_rule`
metadata type only. No page mentions `sys_assignment_rule`; the
generated references carry no page for the object.
**Changeset**
`.changeset/30731-automation-skill-notify-label-and-org-assignment-rules.md`:
`'@objectstack/skills': patch`, naming both cards.
**Ratchet** `scripts/check-skills-token-ratchet.mjs`: the SKILL.md row
moves DOWN 6449 → 6448, re-locked at the measurement (headroom 0).
### Readings (`skills/**` diff)
| Reading | before (`origin/main` 677d542) | after (this head) |
|:--|:--|:--|
| `skills/objectstack-automation/SKILL.md` lines | 452 | 448 |
| `skills/objectstack-automation/SKILL.md` tokens (`ceil((bytes − 20) /
4)`) | 6447 | 6448 |
| whole package, all 11 `skills/*/SKILL.md`, lines | 4523 | 4519 |
| whole package, all 11 `skills/*/SKILL.md`, tokens | 56716 | 56717 |
| `evals/flows-triggers-approvals.json` tokens (measured, not ratcheted)
| 3587 | 3883 |
### Deletions that pay for the two additions (same file)
1. **The `## CRM Automation Blueprint` section** (three lines + rule):
the `src/flows/*.flow.ts` → `src/flows/index.ts` → `defineStack({ flows
})` layout is carried by
`skills/objectstack-platform/references/bootstrap.md` ("Use barrel
exports", `import * as flows from './src/flows'` into `defineStack({
flows: Object.values(flows) })`), and the skill's frontmatter already
names `*.flow.ts` as the trigger to load it.
2. **"When to Use This Skill"**: the clause "that route a new record to
a queue or a person" restated the section heading `## Assignment rules —
route a new record to a queue or a person`; "scheduled automation"
restated "Flows (… scheduled)" three words earlier.
3. **"Verify your work"**: the line "In a scaffolded project: `npm run
validate`." is carried by objectstack-formula's Verify section ("`npm
run validate` in a scaffolded project"), which this skill names for CEL
validation at its top.
Each surviving sentence of the trimmed paragraphs was re-read against
`main`; nothing deleted has no other home. No line was re-wrapped to win
room.
### Neighbour overlap
Seat 1's draft PR #30831 (#30805) edits
`skills/objectstack-automation/SKILL.md` at lines 295 and 306–320 (the
assignment-rule example's names and the `approvers` line) and eval cases
3, 7, 9, 11, 12 of `flows-triggers-approvals.json`. This PR's
"Assignment rules" hunk changes the two lines directly above its
`approvers` line, so whichever lands second resolves one adjacent-line
conflict there; the `notify` paragraph, the two deletions and eval 15
(appended at the file's end) are outside its hunks. `origin/main` was
merged and the ratchet re-measured before each push.
## Tests
No package code changes. Local checks at this head:
- `node scripts/check-skills-token-ratchet.mjs` →
`skills/objectstack-automation/SKILL.md is 6448 tokens (ceiling 6448;
headroom 0)`; `--self-test` exit 0.
- Eval fixture parses (`json.load`, 15 cases, ids 1–15).
- The derived gate list (`node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`, 69 lines) — see Verification.
## Acceptance notes
- The skill line keeps the `option` argument spelling to the bare
`object.field` form `main` accepts; the quoted, marked-path and
one-sided forms are refused by `optionFieldReference` and not taught.
- `label()` stays taught where it belongs (objectstack-formula, a
formula field's display in the reader's locale); the automation skill
now only says where it is wrong.
- Out of scope, noted and not filed (class: none of a/b/c — read-only
observation): the docs page's `## See also` points at the generated
`assignment-rule` schema reference; no generated page exists for the
`sys_assignment_rule` object, so the new prose names its columns inline.
- First commit's author is spelled as the fleet bot (a local `-c
user.name` override); later commits use the container's default `Claude`
identity. The squash on landing rewrites it either way.
## Verification
Derived gates at head 61f2a99 (base `677d542a`): 87/87 families exit 0
— 69 derived first; the ratchet-row edit added 18 more; three
(`check:skill-examples`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`) first exited 3 on an unbuilt tree and exit 0
after `pnpm build --filter='./packages/**'` (80/80 tasks, 7m58s under
the verify lock). `origin/main` (five commits, none touching this PR's
files) was then merged as 18600c3 and the skill-text families re-run
there: `check-skills-token-ratchet` (6448 / 6448, headroom 0),
`check:skill-identifier-liveness`, `check:pm-skill-id-lint`,
`check:doc-authoring`, `check:role-word`, spec `check:skill-docs`,
`check-empty-changeset`, `check:docs-single-h1` — all exit 0. The
per-command exit lists are
`/mnt/project-files/skills-seat-2/r2026-11-06/out/30731-gates-ran-61f2a99e.list`
and `…-18600c34-merged.list`, reconciled with `dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran`.
Relay writes: `pr_create` (this PR); `post-stamped.mjs` for the
os-dev-report on #30731. MCP GitHub calls: reads only (`issue_read` ×6,
`pull_request_read` ×1 for #30831's file list); 0 MCP writes. The
harness's assignee/reviewer step is skipped (no MCP writes; the seat
sets the assignee at hand-over).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibxujehvWJcGRXMYv7f)_
---------
Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0b15730 commit dfa179a
5 files changed
Lines changed: 55 additions & 16 deletions
File tree
- .changeset
- content/docs/automation
- scripts
- skills/objectstack-automation
- evals
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
96 | 102 | | |
97 | 103 | | |
98 | 104 | | |
| |||
153 | 159 | | |
154 | 160 | | |
155 | 161 | | |
| 162 | + | |
156 | 163 | | |
157 | 164 | | |
158 | 165 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
490 | 490 | | |
491 | 491 | | |
492 | 492 | | |
493 | | - | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
494 | 505 | | |
495 | 506 | | |
496 | 507 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
27 | | - | |
| 26 | + | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
39 | | - | |
40 | 38 | | |
41 | 39 | | |
42 | 40 | | |
| |||
127 | 125 | | |
128 | 126 | | |
129 | 127 | | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
130 | 133 | | |
131 | 134 | | |
132 | 135 | | |
| |||
315 | 318 | | |
316 | 319 | | |
317 | 320 | | |
318 | | - | |
| 321 | + | |
| 322 | + | |
319 | 323 | | |
320 | 324 | | |
321 | 325 | | |
| |||
421 | 425 | | |
422 | 426 | | |
423 | 427 | | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | 428 | | |
433 | 429 | | |
434 | 430 | | |
| |||
440 | 436 | | |
441 | 437 | | |
442 | 438 | | |
443 | | - | |
444 | | - | |
445 | 439 | | |
446 | 440 | | |
447 | 441 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
263 | 280 | | |
264 | 281 | | |
265 | 282 | | |
0 commit comments