Repository navigation
Commit f1c0b57
docs(skills):
Fixes #23983
Clause-②: no
## What changed
Two published skill files told authors that `previous` is **unbound** on
insert events and that insert-event conditions must therefore be written
over `record` alone. Since PR #23971 (the runtime change for card 23910,
merged as `b25d98a46f`) `@objectstack/objectql` binds `previous` to
`null` on insert in validation rules and in declarative hook conditions
— the binding a flow's start condition already had — so that text was
false. Both files now state the binding and the guard:
- **on insert, `previous` is `null`** in validation, hook conditions and
flows: `previous == null || …` judges an insert, `previous != null && …`
skips one;
- **a column read off it on insert** (`previous.status`,
`previous.done`) still has no value and still fails the write — guard
the read with `previous != null &&`, not the event;
- **a write to an existing row whose prior state is not in hand** keeps
`previous` unbound and fails closed, so `previous == null` never reads
an update as an insert.
The old advice (write insert-event conditions over `record` alone) was
still safe but is no longer the rule; it is replaced by the `null`
guard, and the abort semantics for a typo'd key or a column read off
`null` are kept. A hook's `ctx.previous` is unchanged and its row in the
`ctx.*` table is untouched.
Files (text only; nothing an author writes changes):
- `skills/objectstack-data/references/data-hooks.md` — the `previous`
bullet (was 「`previous` is UNBOUND where there is no prior state …
insert events … write those over `record` alone」), the abort warning's
example (「a `previous` reference on an insert」 → a column read off
`previous` on an insert), the load-bearing clause (「the
unbound-`previous` and `has(...)` bullets」 → 「the `previous`-on-insert
and `has(...)` bullets」) and the authoring consequence (「never on an
insert event」 → read `previous` only against `null` on insert).
- `skills/objectstack-formula/SKILL.md` — the §5 scope-table insert row
(was 「**unbound** — there is no prior state」), a new row for the
existing-row-not-in-hand case, the abort note under the table (was
「Referencing `previous` where it is unbound … So write insert-event
conditions over `record` alone」) and the legacy-translation note under
`OLD.x` / `ISCHANGED(x)` (was 「exists only where `previous` is **bound**
… On an insert event it is not」).
- `.changeset/23983-previous-null-on-insert.md` —
`'@objectstack/skills': patch`, `Clause-②: no` (the catalog ships as
`packages/skills/dist`).
## `skills/**` readings (lines and tokens, `bd4276a91` → `d0c7d6f13`)
Token counts are the ratchet's own (`node
scripts/check-skills-token-ratchet.mjs`, `ceil(utf8 bytes / 4)`,
release-stamp line excluded).
| Reading | before | after | ceiling |
|:---|---:|---:|---:|
| `skills/objectstack-data/references/data-hooks.md` lines | 997 | 998 |
— |
| `skills/objectstack-data/references/data-hooks.md` tokens | 10066
(headroom 0) | 10059 (headroom 7) | 10066 |
| `skills/objectstack-formula/SKILL.md` lines | 462 | 465 | — |
| `skills/objectstack-formula/SKILL.md` tokens | 5399 (headroom 598) |
5511 (headroom 486) | 5997 |
| all `skills/*/SKILL.md` lines (sum of 10) | 4389 | 4392 | — |
| whole `skills/**` lines (65 files) | 13332 | 13336 | — |
| catalog authored tokens (ratcheted) | 143907 | 144012 | 154888 |
| bundle total tokens (whole shipped tree) | 155093 | 155198 | — |
No ceiling moved. `data-hooks.md` sat at zero headroom, so the new
sentences were paid for inside the file by deleting content, not by
re-wrapping: the sentence 「Those are opposite failures, so "the
condition said no" and "the platform could not work out what the
condition says" are now different outcomes and the second one is loud」
restated what the paragraph's first sentence (does **not** degrade … it
**fails the write**) and its protocol-17 history already say, and the
four-line 「put a condition that reads `previous` on an update or delete
event — either phase — never on an insert event」 rule is replaced by the
shorter `null`-guard rule. Net −7 tokens on that file;
`objectstack-formula/SKILL.md` spends 112 of its 598 headroom on the new
row and the rewritten notes.
## Verification (final head `d0c7d6f13`)
- Derivation: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; change set from the merge base
`bd4276a91`) → 44 families. Ran every one, exit code captured before any
pipe; `--ran` reconciliation: **44 derived, 42 run (all exit 0), 2
NOT-MEASURED, 0 UNRUN**.
- NOT MEASURED locally (recorded exit 3, PREREQUISITE NOT MET — they
load the built `dist/` of 92 packages / `@objectstack/objectql`, which a
three-text-file worktree does not build): `pnpm
check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure`. Both are
whole-tree families named on every card; CI's `Lint & Repo Gates` builds
first and runs them.
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions`
first answered exit 3 (needs `@objectstack/formula` +
`@objectstack/lint` built); after `turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint` it ran and
exited 0 — that is the family that reads CEL expressions out of
`skills/**`.
- `node scripts/check-skills-token-ratchet.mjs` exit 0: 「54 authored
bundle file(s) within their ceilings」 (the readings above).
- `pnpm check:role-word`, `pnpm check:corpus-claim-drift`, `pnpm
check:skill-identifier-liveness`, `pnpm check:skill-compatibility`,
`pnpm check:skill-frame-sync`, `pnpm check:doc-authoring`, `pnpm
check:nul-bytes` and the changeset gates (`check-adr-0087-registration
--base origin/main`, `check-changeset-no-major --base origin/main`,
`check-empty-changeset --base origin/main`, `check:published-files`,
`check:objectui-changeset`, `check:pm-changeset-deadline-census`) all
exit 0.
- `pnpm --filter @objectstack/spec build` (under the shared verify lock,
command-exit 0) then `pnpm --filter @objectstack/spec check:generated` →
「All 14 generated artifacts are up to date」; `check:skill-docs` ✓ and
`check:skill-refs` ✓ — the `SKILL.md` change is body-only, so nothing
was regenerated and no generated artifact is in this PR.
- Ship-proof: `pnpm --filter @objectstack/skills build` → 「65 file(s)
across 10 skill(s) copied from skills/** into dist/skills and read back
byte-identical」; `grep -rc "UNBOUND where there is no prior state"
packages/skills/dist/` → 0 in every file; the new bullet is at
`packages/skills/dist/skills/objectstack-data/references/data-hooks.md:247`
and the new table row at
`packages/skills/dist/skills/objectstack-formula/SKILL.md:307`.
- Control-character scan of the three changed files: none.
- Premise re-checked on `origin/main` before editing:
`packages/objectql/src/validation/rule-validator.ts` `ruleScope()` binds
`extra.previous = null` when `previous === null`, and `hook-wrappers.ts`
evaluates a declarative hook `condition` in that same scope (「`previous`
is `null` on an insert event」);
`content/docs/data-modeling/formulas.mdx` already states the
`null`-on-insert row.
## Acceptance notes
- Out of scope by the dispatch order (⛔ no other `skills/**` file):
`skills/objectstack-formula/evals/cel-predicates-formulas.json` line 27,
eval case 3's `expected_output`, still says 「Not usable on insert events
(unbound `previous` aborts the write)」 — the same statement this PR
corrects, in a fixture that ships in the same package. Reported in the
dev report for the `domain:skills` seat; the fixture's `assertions` do
not pin that sentence, so no eval fails on it today.
- Other `unbound` hits in `skills/**` are unrelated and still true:
`objectstack-formula/SKILL.md` on a form-view `visibleWhen`'s
client-side unbound root falling back to `true`;
`objectstack-ai/SKILL.md` and `objectstack-query/rules/pagination.md`
use 「unbounded」 (numbers, queries).
- `data-hooks.md`'s `ctx.previous` row (「`undefined` on insert → use
`!ctx.previous`」) is still true — only the CEL binding changed — and was
left as is.
- `docs/adr/0058-expression-and-predicate-surface.md` mentions
`bulk_write_previous_unbound` as retired history; not published skill
text, not touched.
## 维护者速读(草稿)
- **改了什么**:两份对外发布的 skill 文本(`objectstack-data` 的 data-hooks
参考页、`objectstack-formula` 的 SKILL.md)里「insert 时 `previous` 未绑定,只能用
`record` 写条件」的说法,改为运行时的真实行为:insert 时 `previous` 为 `null`,`previous ==
null` 判定 insert、`previous != null && …` 跳过 insert;insert 时读 `previous.x`
仍然报错拒写,要用 `previous != null &&` 守住;只有「已存在的行但没拿到旧状态」才是未绑定。另附一份
`@objectstack/skills` patch changeset。
- **为什么改**:PR #23971 已把运行时改成 insert 时绑 `null`,这两处 skill 文本随之变假;按旧文本写的 AI
会绕开一个已不存在的错误,也写不出「只在 insert 时必填」这类规则。
- **风险与代价(含回滚)**:纯文本,不改任何作者可写的键,也不改 hook 的 `ctx.previous`;token 棘轮
data-hooks 10066→10059(上限 10066 不动,靠删一句重复的解释句付账),formula SKILL.md
5399→5511(上限 5997)。回滚 = revert 这一个 commit。
- **席位意见**:
- **你要做的**:在此 PR 上给一个 APPROVED review;席位负责落地。
---
_Generated by [Claude
Code](https://claude.ai/code/session_012PxNHU3dfov2hbpg9ycZ8q)_
---------
Co-authored-by: Claude <noreply@anthropic.com>previous is null on insert in objectstack-data and objectstack-formula, no longer unbound (#24174)1 parent b0fe9fa commit f1c0b57
4 files changed
Lines changed: 47 additions & 34 deletions
File tree
- .changeset
- skills
- objectstack-data/references
- objectstack-formula
- evals
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
244 | 244 | | |
245 | 245 | | |
246 | 246 | | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
256 | 259 | | |
257 | 260 | | |
258 | 261 | | |
| |||
274 | 277 | | |
275 | 278 | | |
276 | 279 | | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
286 | 287 | | |
287 | 288 | | |
288 | | - | |
289 | | - | |
290 | | - | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
291 | 292 | | |
292 | 293 | | |
293 | 294 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
303 | 303 | | |
304 | 304 | | |
305 | 305 | | |
306 | | - | |
| 306 | + | |
| 307 | + | |
307 | 308 | | |
308 | 309 | | |
309 | 310 | | |
310 | 311 | | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
319 | 321 | | |
320 | 322 | | |
321 | 323 | | |
| |||
371 | 373 | | |
372 | 374 | | |
373 | 375 | | |
374 | | - | |
375 | | - | |
376 | | - | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
377 | 380 | | |
378 | 381 | | |
379 | 382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
0 commit comments