Skip to content

Commit 83fc231

Browse files
docs(skills): teach that a notify with no link opens the run's trigger record (#30682)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D8rNJVDBGUc9LuK6RAGm4BT)_ Fixes #30254 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:自动化技能的 notify 示例、flows 文档的 notify 段落、一条新 eval,改教「不写链接时,通知点开就是触发这次运行的那条记录」。 - 为什么改:PR #30252 已让运行时这样做,技能和文档还在教旧规则「不写成对的 sourceObject/sourceId 就没有链接」,AI 会白写一对多余的键。 - 风险与回滚:只改教学文字和 eval,不改任何代码或 schema;回滚即整体 revert 本 PR。 - 先看:`skills/objectstack-automation/SKILL.md` 的 notify 示例三行(L124–126),其次 `content/docs/automation/flows.mdx` 约 L351 的段落。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,应看到 objectstack-automation/SKILL.md 为 6447 tokens(上限 6449)。 - 需要你做的:受管面(`skills/**`,Tier H),请审阅后批准或合并。 ## What changes The runtime default that PR #30252 landed, read off `origin/main` `b77c160a` (`packages/services/service-automation/src/builtin/notify-node.ts`, `defaultClickThroughOf` and the `sourceObject` / `sourceId` / `actionUrl` config-schema descriptions): a `notify` node that writes none of `sourceObject`, `sourceId` and `actionUrl` links to the run's trigger record when the run has one (a record change other than a delete, a time-relative sweep's row, an action started on a record). An explicit pair or `actionUrl` wins; a half-written pair is dropped, never defaulted; a run with no trigger record links nowhere. - `skills/objectstack-automation/SKILL.md`: the notify example's link comments now state the default and its three exceptions (`link: PAIR, else dropped; none of the 3 ⇒ the trigger record (not on delete), if any`; `actionUrl` overrides the pair). The old `// click-through: a PAIR, else dropped` is gone. - `content/docs/automation/flows.mdx`: the notify `sourceId` paragraph states the default, its three cases, and the three exceptions, with the no-trigger-record examples the code comment names (a schedule tick, an inbound API post, a manual start with no record). - `skills/objectstack-automation/evals/flows-triggers-approvals.json`: eval 14, a record-triggered notify that should open its case. It expects no `sourceObject:` / `actionUrl:` written and the words `trigger record` (taught in SKILL.md). - `.changeset/30254-notify-default-link-skill.md`: `'@objectstack/skills': patch`. ### Token readings (`skills/**`) | Reading | Before (`b77c160a`) | After (`HEAD`) | |:--|--:|--:| | `skills/objectstack-automation/SKILL.md` lines | 452 | 452 | | same file, ratchet tokens (stamp line excluded) | 6449 (ceiling 6449, headroom 0) | 6447 (headroom 2) | | all `skills/*/SKILL.md` lines | 4534 | 4534 | | all `skills/*/SKILL.md` bytes | 226877 | 226867 | The new notify comment costs 13 bytes net inside the example (it also drops the `(a value slot)` and `text slot;` asides, which Valid-but-silently-wrong item 1 teaches for `sourceId` and `actionUrl`). It is paid inside the same file by deleting a duplicated aside from the Flow Types `api` row, `or a literal secret;`: the Inbound webhook config table still teaches `secretRef` / `secret`. No ceiling moved. ## Tests Teaching text and an eval only; no code changed, so no unit suite and no ablation apply. The gate readings are under Verification. ## Acceptance notes - Card item 3 is not in this PR (spec lane, out of this file surface). On `origin/main` `b77c160a`, `packages/spec/src/automation/io-node-config.zod.ts` still carries the old rule: the `sourceObject` (L750) and `sourceId` (L756) `.describe()` texts say only that a half pair is dropped, and the `actionUrl` text says only that it overrides the synthesized link. None of the three mentions the trigger-record default. The generated `content/docs/references/automation/io-node-config.mdx` (L106, L107, L109) repeats them. The seat carries this to `domain:spec`. - The SKILL.md ceiling is left at 6449 (headroom 2): lowering it is a `scripts/` edit outside this file surface. ## Verification All readings at head `d9415882`, after `turbo run build` of `@objectstack/spec`, `@objectstack/lint...`, `@objectstack/client-react...` and `@objectstack/formula` (`BUILD :: exit 0`). - `node scripts/pm/dispatch-gates.mjs --ran FILE --repo objectstack-ai/objectstack`: 68 derived families accounted for, 67 run, 1 NOT MEASURED, 0 unrun. - NOT MEASURED: `pnpm check:dual-build-cjs-loads`, reason: exit 3, PREREQUISITE NOT MET (it needs a whole-tree build; CI runs it). - `pnpm lint :: exit 0`. - `check-skills-token-ratchet`: `57 authored bundle file(s) within their ceilings`; `skills/objectstack-automation/SKILL.md is 6447 tokens (ceiling 6449; headroom 2)`. - `check:skill-examples`: `283 prose examples type-check across 3 surface(s)`. - `check:dts-closure` is green over the 41 built packages only; CI sweeps the whole tree. <details><summary>Every local family and its exit code</summary> ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: 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/check-doc-route-spelling.mjs --advisory :: exit 0 node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 node scripts/check-docs-section-name.mjs :: exit 0 node scripts/check-docs-section-name.mjs --self-test :: exit 0 node scripts/check-dts-emitted.mjs --self-test :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-section-landing-index.mjs :: exit 0 node scripts/check-section-landing-index.mjs --self-test :: exit 0 node scripts/check-skills-token-ratchet.mjs :: exit 0 node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 pnpm --filter @objectstack/spec run check:docs :: exit 0 pnpm --filter @objectstack/spec run check:empty-state :: exit 0 pnpm --filter @objectstack/spec run check:liveness :: exit 0 pnpm --filter @objectstack/spec run check:skill-docs :: exit 0 pnpm --filter @objectstack/spec run check:skill-examples :: exit 0 pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:corpus-claim-drift :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-anchors :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:doc-frontmatter :: exit 0 pnpm check:docs-audit-scope :: exit 0 pnpm check:docs-redirects :: exit 0 pnpm check:docs-single-h1 :: exit 0 pnpm check:docs-spec-enumerations :: exit 0 pnpm check:docs-transcript-drift :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:dts-closure :: exit 0 pnpm check:dual-build-cjs-loads :: exit 3 pnpm check:gitlink-declared :: exit 0 pnpm check:lean-entry-closure :: exit 0 pnpm check:merge-driver :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-skill-id-lint :: exit 0 pnpm check:published-files :: exit 0 pnpm check:published-readme-links :: exit 0 pnpm check:react-page-adapter-contract :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:role-word :: exit 0 pnpm check:skill-compatibility :: exit 0 pnpm check:skill-frame-sync :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:sourcemap-no-sources-content :: exit 0 pnpm check:vendor-version-stamps :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm lint :: exit 0 ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cyou7XDWHnizyxR5MQ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent fcefe4e commit 83fc231

4 files changed

Lines changed: 42 additions & 6 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): a `notify` node that writes no link opens the record that started the run
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The `objectstack-automation` skill's `notify` example commented its click-through keys as "a PAIR, else dropped", so a record-triggered flow's notification looked like it linked nowhere unless the author wrote `sourceObject` + `sourceId: record.id`.
11+
- **Now.** The example says what the runtime does: a node that writes none of `sourceObject`, `sourceId` and `actionUrl` links to the run's trigger record when it has one (a record change other than a delete, a time-relative sweep's row, an action started on a record). An explicit pair or `actionUrl` wins; half a pair is still dropped; a run with no trigger record links nowhere. A new eval asks for a record-triggered notification that opens its record and expects no source keys written.
12+
- No schema, key or behaviour changes.

‎content/docs/automation/flows.mdx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,14 @@ entry (`['{owners}']`), on as **one** recipient joined with commas (`'u1,u2'`),
349349
which reached nobody. Guard a recipient field that may be unset —
350350
`has(record.assignee) ? record.assignee : null` — so it is dropped rather than
351351
failing the run. `sourceId` takes a string or an envelope evaluating to one:
352-
`null` leaves the key unset, and a half-specified click-through pair is
353-
dropped, as before. The node's `sourceObject`, `actorId` and `payload` are
352+
`null` leaves the key unset. A node that writes none of `sourceObject`,
353+
`sourceId` and `actionUrl` links to the run's trigger record when it has one —
354+
a record change other than a delete, a time-relative sweep's row, an action
355+
started on a record — so a record-triggered `notify` opens its record without a
356+
pair. An explicit pair or `actionUrl` wins; a half-specified pair is dropped,
357+
never defaulted; a run with no trigger record (a schedule tick, an inbound API
358+
post, a manual start with no record) links nowhere.
359+
The node's `sourceObject`, `actorId` and `payload` are
354360
**literals**, read as written: no flow computed one, so nothing is evaluated
355361
there — write the value itself (`sourceObject: 'crm_case'`). A `{…}` token
356362
there is refused, since it would reach the messaging service with its braces,

‎skills/objectstack-automation/SKILL.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ A **Flow** is a directed graph of nodes — the primary automation building bloc
4545
| `screen` | Interactive — presents UI screens to the user (wizards, forms) |
4646
| `schedule` | Runs on a cron/interval cadence declared on the **start node's `config.schedule`** — or a **per-record date sweep** via `config.timeRelative`, see *Time-relative triggers* |
4747
| `record_change` | Fires automatically on record create/update/delete. `autolaunched` + the same `record-*` binding behaves identically; `record_change` also opts into the trigger-readiness lint |
48-
| `api` | Inbound **webhook** — needs a start-node `secretRef` (or a literal `secret`; see *Inbound webhook triggers* below); a flow only ever started explicitly is `autolaunched` |
48+
| `api` | Inbound **webhook** — needs a start-node `secretRef` (see *Inbound webhook triggers* below); a flow only ever started explicitly is `autolaunched` |
4949

5050
**A flow an installed package ships** is read-only. To change what it does, save
5151
a same-named flow in the base's paired `.custom` package declaring
@@ -121,9 +121,9 @@ error. **RAW** keys are read as written: a
121121
topic: 'task', // RAW; default 'notify'
122122
severity: 'warning', // RAW; CLOSED enum info|warning|critical
123123
channels: ['inbox'], // RAW; default inbox
124-
sourceObject: 'task', // click-through: a PAIR, else dropped
125-
sourceId: { dialect: 'cel', source: 'record.id' }, // (a value slot)
126-
actionUrl: '/tasks/{{ record.id }}', // text slot; overrides the synthesized link
124+
sourceObject: 'task', // link: PAIR, else dropped; none of the 3 ⇒ the
125+
sourceId: { dialect: 'cel', source: 'record.id' }, // trigger record (not on delete), if any
126+
actionUrl: '/tasks/{{ record.id }}', // overrides the pair
127127
} }
128128
```
129129

‎skills/objectstack-automation/evals/flows-triggers-approvals.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,24 @@
242242
"beforeUpdate"
243243
]
244244
}
245+
},
246+
{
247+
"id": 14,
248+
"prompt": "When a `crm_case`'s `priority` becomes `urgent`, send its owner an inbox notification; clicking the notification should open that case.",
249+
"expected_output": "A `record_change` flow bound on the `start` node `config` (`objectName: 'crm_case'`, `triggerType: 'record-after-update'`, condition `previous.priority != 'urgent' && record.priority == 'urgent'`) and a `notify` node with `recipients: { dialect: 'cel', source: 'record.owner_id' }`, an inline `title`, `channels: ['inbox']`, and no `sourceObject` / `sourceId` / `actionUrl`: a notify that writes none of the three links to the run's trigger record, here the case. An explicit pair or `actionUrl` is only for linking elsewhere and wins when written; half a pair is dropped, never defaulted; a run with no trigger record (a schedule tick) links nowhere. `requires` lists `automation`, `triggers`, `messaging`.",
250+
"files": [],
251+
"assertions": {
252+
"must_contain": [
253+
"record-after-update",
254+
"notify",
255+
"recipients",
256+
"trigger record"
257+
],
258+
"must_not_contain": [
259+
"sourceObject:",
260+
"actionUrl:"
261+
]
262+
}
245263
}
246264
]
247265
}

0 commit comments

Comments
 (0)