Repository navigation
docs(skills): previous is null on insert in objectstack-data and objectstack-formula, no longer unbound - #24174
Conversation
…objectstack-formula, no longer unbound `data-hooks.md`'s `previous` bullet, abort note and authoring consequence, and `objectstack-formula/SKILL.md`'s scope-table row, abort note and legacy-translation note said `previous` is unbound on insert and told authors to write insert-event conditions over `record` alone. The runtime binds it to `null` on insert in validation rules and declarative hook conditions, so the text now states that binding: `previous == null || …` judges an insert, `previous != null && …` skips one, a column read off it on insert still faults, and only a write to an existing row whose prior state is not in hand leaves `previous` unbound. Token ratchet: data-hooks.md 10066 -> 10059 (ceiling 10066), formula SKILL.md 5399 -> 5511 (ceiling 5997); catalog authored total 143907 -> 144012. Claude-Session: https://claude.ai/code/session_012PxNHU3dfov2hbpg9ycZ8q Co-authored-by: Claude <noreply@anthropic.com>
…evious` is unbound on insert Eval case 3's `expected_output` said the transition condition is 「Not usable on insert events (unbound `previous` aborts the write)」. On insert `previous` is `null`, so the sentence now says the `previous.status` read faults and aborts the write and that a condition which must also run on inserts guards it with `previous != null &&`. Assertions unchanged; the file stays at 1046/1046 tokens (4184 -> 4181 bytes), paid by tightening the same sentence. Claude-Session: https://claude.ai/code/session_012PxNHU3dfov2hbpg9ycZ8q Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read 2026-10-17T13:53Z in seat by ① Derived judgmentsThe accept-set change: published skill text in three files of
Check-runs on ② Semver level
③ Boundary flagsDev reports 6234524569 (round 1) and 6234675212 (round 2):
Reviewer's own flags: none. Implemented-by: VERDICT: PASS |
维护者速读改了什么:两个对外发布技能( 为什么改:PR #23971(#23910,10-16 合并)把 insert 上的 风险与代价(含回滚):纯文本 + changeset,不改任何键或行为; 席位意见:ACCEPT。逐句对照 #23971 的 changeset 与 你要做的:对 PR #24174 给一个 APPROVED review( 席位核对Seat Implemented-by:
|
Fixes #23983
Clause-②: no
What changed
Two published skill files told authors that
previousis unbound on insert events and that insert-event conditions must therefore be written overrecordalone. Since PR #23971 (the runtime change for card 23910, merged asb25d98a46f)@objectstack/objectqlbindsprevioustonullon 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:previousisnullin validation, hook conditions and flows:previous == null || …judges an insert,previous != null && …skips one;previous.status,previous.done) still has no value and still fails the write — guard the read withprevious != null &&, not the event;previousunbound and fails closed, soprevious == nullnever reads an update as an insert.The old advice (write insert-event conditions over
recordalone) was still safe but is no longer the rule; it is replaced by thenullguard, and the abort semantics for a typo'd key or a column read offnullare kept. A hook'sctx.previousis unchanged and its row in thectx.*table is untouched.Files (text only; nothing an author writes changes):
skills/objectstack-data/references/data-hooks.md— thepreviousbullet (was 「previousis UNBOUND where there is no prior state … insert events … write those overrecordalone」), the abort warning's example (「apreviousreference on an insert」 → a column read offpreviouson an insert), the load-bearing clause (「the unbound-previousandhas(...)bullets」 → 「theprevious-on-insert andhas(...)bullets」) and the authoring consequence (「never on an insert event」 → readpreviousonly againstnullon 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 「Referencingpreviouswhere it is unbound … So write insert-event conditions overrecordalone」) and the legacy-translation note underOLD.x/ISCHANGED(x)(was 「exists only wherepreviousis bound … On an insert event it is not」)..changeset/23983-previous-null-on-insert.md—'@objectstack/skills': patch,Clause-②: no(the catalog ships aspackages/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).skills/objectstack-data/references/data-hooks.mdlinesskills/objectstack-data/references/data-hooks.mdtokensskills/objectstack-formula/SKILL.mdlinesskills/objectstack-formula/SKILL.mdtokensskills/*/SKILL.mdlines (sum of 10)skills/**lines (65 files)No ceiling moved.
data-hooks.mdsat 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 readspreviouson an update or delete event — either phase — never on an insert event」 rule is replaced by the shorternull-guard rule. Net −7 tokens on that file;objectstack-formula/SKILL.mdspends 112 of its 598 headroom on the new row and the rewritten notes.Verification (final head
d0c7d6f13)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; change set from the merge basebd4276a91) → 44 families. Ran every one, exit code captured before any pipe;--ranreconciliation: 44 derived, 42 run (all exit 0), 2 NOT-MEASURED, 0 UNRUN.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'sLint & Repo Gatesbuilds first and runs them.pnpm --filter @objectstack/lint run check:doc-formula-expressionsfirst answered exit 3 (needs@objectstack/formula+@objectstack/lintbuilt); afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintit ran and exited 0 — that is the family that reads CEL expressions out ofskills/**.node scripts/check-skills-token-ratchet.mjsexit 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-bytesand 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) thenpnpm --filter @objectstack/spec check:generated→ 「All 14 generated artifacts are up to date」;check:skill-docs✓ andcheck:skill-refs✓ — theSKILL.mdchange is body-only, so nothing was regenerated and no generated artifact is in this PR.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 atpackages/skills/dist/skills/objectstack-data/references/data-hooks.md:247and the new table row atpackages/skills/dist/skills/objectstack-formula/SKILL.md:307.origin/mainbefore editing:packages/objectql/src/validation/rule-validator.tsruleScope()bindsextra.previous = nullwhenprevious === null, andhook-wrappers.tsevaluates a declarative hookconditionin that same scope (「previousisnullon an insert event」);content/docs/data-modeling/formulas.mdxalready states thenull-on-insert row.Acceptance notes
skills/objectstack-formula/evals/cel-predicates-formulas.jsonline 27, eval case 3'sexpected_output, now says 「On insertpreviousisnull: theprevious.statusread faults and aborts the write, so to run on inserts guard withprevious != null &&.」 — the same statement this PR corrects, in a fixture that ships in the same package. Assertions untouched; 1046/1046 tokens, paid inside the same string; head058737c52. (Round 1 had reported it as out of scope; the seat admitted it.)unboundhits inskills/**are unrelated and still true:objectstack-formula/SKILL.mdon a form-viewvisibleWhen's client-side unbound root falling back totrue;objectstack-ai/SKILL.mdandobjectstack-query/rules/pagination.mduse 「unbounded」 (numbers, queries).data-hooks.md'sctx.previousrow (「undefinedon insert → use!ctx.previous」) is still true — only the CEL binding changed — and was left as is.docs/adr/0058-expression-and-predicate-surface.mdmentionsbulk_write_previous_unboundas retired history; not published skill text, not touched.维护者速读(草稿)
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/skillspatch changeset。previousto null on insert so a rule that reads it answers with its own message #23971 已把运行时改成 insert 时绑null,这两处 skill 文本随之变假;按旧文本写的 AI 会绕开一个已不存在的错误,也写不出「只在 insert 时必填」这类规则。ctx.previous;token 棘轮 data-hooks 10066→10059(上限 10066 不动,靠删一句重复的解释句付账),formula SKILL.md 5399→5511(上限 5997)。回滚 = revert 这一个 commit。Generated by Claude Code