Skip to content

Commit 79b9421

Browse files
committed
Merge origin/main (PR #28814 landed) into the #28829 branch
The two passages this card extends now come from main; the conflict on them is resolved to this branch's text, which is main's plus this card's sentences (delta against origin/main re-read: this card's changes only). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc
2 parents 5f2e4a8 + 7f4cbc9 commit 79b9421

31 files changed

Lines changed: 1000 additions & 292 deletions
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): objectstack-data rules/lifecycle.md teaches the save notice
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** A hook can tell the person who made a create or update what the save set in motion (`ctx.notice(message)`, answered as the create/update `notice`), and a flow a record create/update trigger starts can set the same message with a `save_notice` node, but no published skill taught it, so an AI author writing a save hook from `objectstack-data` kept reaching for a `record:alert` banner.
11+
- **Now.** `objectstack-data/rules/lifecycle.md` gains *Save Notice — what the save set in motion*: the `ctx.notice` spelling for inline `handler` and sandboxed `body` alike (no capability), info severity and the distinction from validation `warnings` and from a `record:alert` banner, one message per write with the last call winning, the dispatches that carry it, the non-string refusal, and the flow face (`save_notice`, refused outside a create/update record trigger and after an `approval` / `wait`).
12+
- The file stays under its token ceiling: the new text is paid for by deleting the Decision Tree (which restated the class table and the syntax examples), the telemetry-growth anecdote and a Safety Semantics restatement. No ceiling moves; no schema, key or behaviour changes.

‎.changeset/28560-field-patch-slot.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ objectExtensions: [{
1616
industry: {
1717
label: 'Sector',
1818
inlineHelpText: 'The market the account sells into',
19-
options: { tech: { label: 'Technology' } },
19+
options: { technology: { label: 'Tech' } },
2020
},
2121
},
2222
}]

‎.changeset/28565-field-patch-applied.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ADR-0146 D10's second rule, live. The per-field display patch an object extensio
1717
objectExtensions: [{
1818
extend: 'crm_account',
1919
fieldPatches: {
20-
industry: { label: 'Sector', inlineHelpText: 'The market the account sells into', options: { tech: { label: 'Technology' } } },
20+
industry: { label: 'Sector', inlineHelpText: 'The market the account sells into', options: { technology: { label: 'Tech' } } },
2121
},
2222
}]
2323
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(objectql): a refused first install leaves nothing of itself, even over a tenant's object
9+
10+
A first install of a package that `ObjectQL.registerApp` refuses part-way now leaves nothing of itself registered in every case, including when the package layers over an object a tenant already holds (with or without the tenant's own overlay), over an overlay with no owner, or over another package's extension of one of its objects. Afterwards there is no package record, object, extension, app, item, picklist extension, namespace, navigation contribution, contributed kind or datasource definition of the package, and the tenant's object, its overlay and the other package's extension read exactly as before the attempt. A persisted "disabled" state for that id still applies to its next install. What is refused, and every refusal's code, status and words, is unchanged.
11+
12+
Every registration is now put back by the registry itself when it is refused, the way a refused upgrade already was. This supersedes the line of the pending `28697-refused-registration-leaves-nothing` changeset saying the engine passes `{ keepBootSeed: true }` to `SchemaRegistry.uninstallPackage`: the engine no longer uninstalls a refused first install. The option itself stays available and unchanged.
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): objectstack-data teaches an object extension's `fieldPatches` — relabel a vendor field without redeclaring it in `fields`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** `ObjectExtensionSchema.fieldPatches` is applied by the registry merge and refused at install and by `os validate` when it names nothing, but the published `objectstack-data` skill's *Object Extension Model* taught only `fields` / `priority`, so an agent that wanted a vendor field to read "Sector" instead of "Industry" redeclared the field in `fields` and took it over.
11+
- **Now.** The section carries one paragraph on `fieldPatches`: display keys only (`BASE_FIELD_DISPLAY_WHITELIST` — `label`, `description`, `inlineHelpText`, `placeholder`, and an option's `label` keyed by its stored `value`), type / values / rules stay the owner's, a tenant's edit wins, the higher `priority` wins between extensions, a patch naming a missing field or option is refused at install and by `os validate`, a `picklist`-bound field's options are not patchable — with the `crm_account.industry` example.
12+
- The file stays under its token ceiling: the new text is paid for by deleting restatement in the same file (the search section's second refusal paragraph, folded into its first; the index blockquote and the validation type list, each already carried by `rules/indexing.md` / `rules/validation.md`). No ceiling moves, no frontmatter, schema, key or behaviour changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics)!: two granularities of one time dimension are refused on every face, not collapsed to the last one
6+
7+
Clause-②: no (narrowing) — `timeDimensions` naming one dimension, spelled the same way, at two granularities (`[{ dimension: 'closed_at', granularity: 'month' }, { dimension: 'closed_at', granularity: 'year' }]`) is now refused `INVALID_QUERY` / 400 on the native strategy, the ObjectQL strategy and the draft preview, with the refusal each already gives one column asked twice under one name (#28765); every face served it as the last granularity's buckets and dropped the other entry. Nothing is widened: no export, public member, answer member, error code or status is added.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type of the package root changes: a selection that was half served is refused with a refusal already on the wire. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on `queryDataset` (`@objectstack/service-analytics`), on every face.
13+
14+
- **Before.** Measured on real SQLite: the pair above answered one `2026` row holding the whole total on all three faces (the `month` entry dropped silently); `year` then `month` answered the months (the `year` entry dropped). Beside the raw dimension `closed_at`, both live faces answered one `1970-01` bucket holding the whole total.
15+
- **Now.** The ObjectQL strategy's group-key builders keep every granularity a dimension is asked at, so the engine sees both buckets of the one column and answers its duplicate-column refusal, which the native strategy relays for a bucketed selection; the draft preview judges the same keys and gives the same refusal, field for field. This holds beside other dimensions and beside a cross-object dimension.
16+
- **Unchanged.** One dimension asked twice at the same granularity is one bucket and is served. One bucket per column, and buckets of different columns, are served. No new refusal is added; the existing one now sees the pair.
17+
- **Who sees it.** A caller sending two granularities for one time dimension: a refusal saying the column is asked twice, instead of a chart missing one of them.
18+
19+
**The remedy.** Ask each time dimension at one granularity.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the documented `fieldPatches` example keys its option by a value `crm_account.industry` really stores
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `fieldPatches` description, its TSDoc example and the refusal text for an option list under `options` showed `options: { tech: { label: 'Technology' } }`. `crm_account.industry` stores `technology`, not `tech`, so a copy of that example was refused at `os validate` and at install as naming an option the field does not have. The example now reads `options: { technology: { label: 'Tech' } }`. Prose only: no schema accepts or refuses anything differently.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: a `save_notice` node that a suspending node reaches is refused at the flow parse (#28806)
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: narrowed
9+
10+
**BREAKING** for a flow that places a `save_notice` node after an `approval`, `wait`, `screen` or `approval_revise` node on a path from its start.
11+
12+
- **Before.** The placement check (#28251) read only the start trigger, so a record-triggered flow with `start → approval → save_notice` parsed and saved. At run time the leg after the suspension holds no write to answer, so the node was refused by name. After an approval the decision was already recorded when the resumed run failed, so every decision on such a flow stranded its run (`RESUME_FAILED`).
13+
- **Now.** The `FlowSchema` parse also refuses a `save_notice` node reachable along the flow's edges from a node that always suspends the run (`FLOW_PAUSE_CAPABLE_NODE_TYPES`: `screen`, `wait`, `approval`, `approval_revise`). Any one such path counts, loops included, even when another path reaches the node before any suspension, because a static graph cannot rule the suspended path out. A `save_notice` inside a region body is judged by its top-level container. The refusal is the same `INVALID_METADATA` 422 at `nodes.<i>.type` as the trigger case. It names the suspending node and tells the author to move the `save_notice` ahead of it, or to tell the person after it with a `notify` node. `objectstack validate`, the metadata save door and `registerFlow` refuse it alike.
14+
- **Not refused.** A `save_notice` reachable only before any suspension. A `subflow` or `map` ahead of the node: those pause only when the child flow they name pauses, which the parse cannot see, so a `save_notice` after one still meets the run-time refusal by name.
15+
- **The fix.** Move the `save_notice` ahead of the suspending node the refusal names. To tell someone after the approval, use a `notify` node there.
16+
- **Who moves.** No shipped flow has the refused shape: the showcase's one `save_notice` (`showcase_task_completed`) has no suspending node, and HotCRM's `main` carries no `save_notice`.
17+
18+
<!-- adr-0087: not-required (no-migration-prescription) no key of any schema is added, removed, renamed or re-shaped and no export or type changes: one more graph shape is refused at the parse with its own prescription in the error (move the node ahead of the suspending node, or use a notify node after it), which is a judgment about where the node stands, not a mechanical rewrite a migration could apply -->

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
7171
- 席位两形:Routine 席与项目席(会话是车队项目的线程);细则见 `references/seat-lifecycle.md`。
7272
- 项目席唤醒 = PR 订阅 + 每席一个自建自绑巡查 routine(总监席除外);⛔ 无 `send_later` 链。
7373
- 探针先行:fire 的第一动作是一次最便宜的车道盘点查询,⛔ 在它之前不读任何长文本。
74-
- 队列空 + 无在飞 + 无待复核 + 决策箱无新答复 ⇒ 自退。
74+
- 队列空(逐卡表见 `references/seat-lifecycle.md`)+ 无在飞 + 无待复核 + 决策箱无新答复 ⇒ 自退。
7575
- 分诊席与车道席常驻,断粮即换账号重开该席与信箱;分诊席默认判断档;裁决归总监席。
7676
- 子代理裁决逐份过转录核验采信。
7777

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,30 +39,30 @@
3939
- 阻塞解除后延后单重定价:前一单派发词必答 #X 变简单、变难、不必要或无影响。
4040
- 派发后一单前用该回答重读它的选项与成本,⛔ 不沿用立单时那份。
4141
- 在飞卡衍生三分:不修它验收过不过;in-scope ⇒ 父卡 sub-issue,认领席自有、优先级继承。
42-
- 该 sub-issue 与 main-red 锚卡各带域与级直接派发,绕过分诊;sweep 事后扫,重复关 `duplicate`。
4342
- 排队卡含他车道项 ⇒ 发现席自拆:新卡无 `domain:*`,首行 `carries item k of #N; #N keeps items …`。
4443
- 分诊首触定域可改判;拆出卡 ⛔ 不承父卡域(承域只及本车道 sub-issue),父卡余项照 `Fixes`。
44+
- 余项落 `ui/**` 的排队卡照常派:`ui/` 在本仓,同 worktree、门禁、合并队列;⛔ 不作 UI 跳过。
45+
- `ui/**` 余项不是他车道项:本席自派不拆;自拆只对整落 `ui/**` 外他车道树的卡自列独立项。
46+
- 派 `ui/**` 余项前,在飞求交加读 `domain:ui` 的 `pm:dispatched` 认领文件面;相交 ⇒ 串行或让行。
4547
- 自在飞派发升级出的卡首行一句:本卡承哪一半、父卡留哪一半;缺此行不入队不入箱。
4648
- 分诊预见的后续工作是一张卡,触发条件落地时立卡;⛔ 分诊一句话永不授权无卡编辑。
4749
- 阻塞项无主 ⇒ 被挡席认领做掉,不限大小;在该卡走完整认领、尊重其热文件串行队。
4850
- 阻塞项在飞 ⇒ 等:`pm:blocking` 在其车道排最前、等待者写该卡;p0/p1 优先级沿链传递。
4951
- 取卡前置 = `docs/NORTH-STAR.md`「优先级」第 3 条:本车道队列开放 P0/P1 每次取卡现读。
5052
- 开放 = 未派发(无 `pm:dispatched`、无 assignee);在飞的 P0/P1 不挡任何候选,只余取卡全序。
51-
- 每轮每次唤醒首读本车道开着的 `priority:p0`,无人认领即同轮认领派发,⛔ 不等下轮。
5253
- 取卡全序:维护者直派插队卡(出处三件)> 契约面卡(判据见 `references/lanes/spec.md`)> 标签序。
5354
- 标签序:`priority:p0` > `pm:blocking` > 功能点位次 > `target:` 板上项 > p1 > p2 > p3 > 无级。
5455
- `pm:blocking` 级内先按解锁扇出(从 `Blocked-by:` 反向索引现算,⛔ 扇出数不落标签)。
5556
- 全序每级现读/现算,零逐卡维护;同级先 `Bug` 再卡龄;无级/缺 `Path:` 轮报记分诊缺口。
5657
- 一车道多席按全序交替取卡:席 1 奇位、席 2 偶位;只为破交替才发 `split`;认领戳仲裁。
5758
- 解锁那一刻 PM 自己的判断最不可信:裁决收窄或关掉了那张卡是假设不是前提。
58-
- 该假设以机制假设身份进派发令,被证伪就在同一张卡公开更正。
5959

6060
## 认领(先认领后动工)
6161

6262
- 归属 = session ID,⛔ 不认作者字段;`Account:` 派生自 assignee,不等即半态、持卡席下笔补正。
6363
- 卡 assignee 归 PM,dev 恒不写;PR ⛔ 不派席位账号;在飞 PR 读 `head:claude/` 开 PR 正文点名的卡。
6464
- 部分落地(PR 带 `Refs #N (item k)`,⛔ 不 `Fixes`)即释放:合入同笔回 `pm:queue` + 清 assignee。
65-
- 同笔 `Release:` 行点名已落项与余项;余项整树归他席 ⇒ 点名该席改标,拆分加 `pm:retriage`。
65+
- `Release:` 行点名已落与余项;余项整落 `ui/**` 外他席树 ⇒ 点名其改标,拆分加 `pm:retriage`。
6666
- 派发前按序执行原子对:① Assign @me,并把 `pm:dispatched` 与摘 `pm:queue` 放进同一次标签写入。
6767
- step ① 之后获得 assignee 的直接弃出本批。
6868
- ② 认领评论照抄 SKILL.md 模板:`Branch:`、`Clause-②:`、`Metadata-protocol:` 各占一行且行首。

0 commit comments

Comments
 (0)