Skip to content

docs(automation): a flow's record nodes treat a computed filter operand as a value, never as a query operator - #29925

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-vq3d3x
Nov 3, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-vq3d3x

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Requested by Irene · project thread

Fixes #29418

Clause-②: no
Metadata-protocol: none

Model: claude-fable-5-1 mandate waived by the maintainer (Fable quota exhausted); developed on Opus; contract review by triage.

维护者速读

  • 改了什么:自动化技能的「Filter operands」一节与 flows 文档的 CRUD filter 段落各加同一句规则:flow 记录节点(get_record / update_record / delete_record)把计算出的 filter 操作数当作值,永不当查询运算符;并写明作者的修法。
  • 为什么改:fix(service-automation)!: a flow's record nodes treat a computed filter operand as a value, never as a query operator #29404 已合并这条运行时规则(changeset 的 Skills-docs: 欠账),技能与文档还没讲,AI 写 flow 时会把对象或列表塞进 filter 操作数而在运行时失败。
  • 风险与回滚:只改文本,外加一个 @objectstack/skills patch changeset(skills/** 随该包发布)。技能文件的 token 上限 6449 未动,新增由同文件删除重述付账(6449 → 6449,余量 0)。回滚即 revert。
  • 先看:skills/objectstack-automation/SKILL.md 的 Filter operands 新条目与「Valid-but-silently-wrong」删掉的第 4 条;content/docs/automation/flows.mdx L434–438。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs,预期 objectstack-automation/SKILL.md 6449 / 6449 且 exit 0。
  • 需要你做的:Tier H 受管面(skills/**):契约复审 PASS 后,请审阅并批准。

What changes

  1. skills/objectstack-automation/SKILL.md, § Filter operands (config.filter). A new bullet after the null bullet states the rule from fix(service-automation)!: a flow's record nodes treat a computed filter operand as a value, never as a query operator #29404's changeset and the author's fix:

    A computed operand is a value, never a query operator: a string, number, bigint, boolean or date, or at an $in / $nin / $between comparand a list of them with no empty entry. Anything else fails the node as a guard before the query runs. Make the expression evaluate to the compared value (for a record, its id), and write conditions in the filter itself.

    Every fact is from the merged code: operandValueRefusal and LIST_COMPARAND_OPERATORS in packages/services/service-automation/src/builtin/crud-nodes.ts (a value is isAcceptedFilterComparand, i.e. string, number, bigint, boolean or Date; a list only where the operand's path ends in $in / $nin / $between, with no null entry), and refuseNode makes it a guard before the data engine is asked. No refused-value example and no "before" behaviour.

  2. content/docs/automation/flows.mdx, the CRUD-node filter paragraph ("A CRUD node's filter: each operand computes before the query…"). The same sentence, as its own paragraph after the null-operand paragraph.

  3. The lists of failures a fault edge does not route (contract review should-fixes). The skill's ROUTES table cell now reads "filter operand that evaluated to null or a non-value", and the guard list in flows.mdx § Fault edges adds "a filter operand that evaluated to something other than a value". Without these, the new bullet's word "guard" was tied to no-fault-routing nowhere in the skill.

  4. .changeset/29418-record-node-operand-value-docs.md: '@objectstack/skills': patch, with Clause-②: no and Metadata-protocol: none in its body (contract review must-fix). skills/** ships in @objectstack/skills, so the change releases that package and never takes skip-changeset (.github/workflows/pr-automation.yml route 1). The label set at creation was removed.

Token budget (ceiling row not moved, no re-wrap)

New text is paid by deleting restatement in the same file. Each deletion and where its content still lives:

deleted bytes still taught at
Valid-but-silently-wrong item 4 ("Conditions are bare CEL; only the stdlib … is callable bare", with its blank line) 241 objectstack-formula SKILL.md L28–30 (never wrap a field ref in {…}) and L219–226 (CEL_STDLIB_FUNCTIONS; PRIOR() / an unknown function fails the build); this file's opening blockquote and description already send condition CEL there
"dates and money: the legacy → CEL table in objectstack-formula" in item 1's token list 74 this file's opening blockquote: "Contract, stdlib, legacy → CEL table: objectstack-formula"
the inline example "(filter: { id: { dialect: 'cel', source: 'record.id' } })" in the first filter-operand bullet 60 the same file's update_record example under item 3, filter: { id: { dialect: 'cel', source: 'record.id' } }
"(inbox by default)" in the notify node row 19 the notify block below it: channels: ['inbox'], // RAW; default inbox
reading before (origin/main 4e0c5d3) after (HEAD)
skills/objectstack-automation/SKILL.md 452 lines, 25815 bytes, 6449 tokens (ceiling 6449) 452 lines, 25814 bytes, 6449 tokens (ceiling 6449, headroom 0)
all skills/*/SKILL.md 4543 lines, 226490 bytes 4543 lines, 226489 bytes

The ceiling row in scripts/check-skills-token-ratchet.mjs is not moved; the ROUTES cell's 16 added bytes used the 4 tokens of headroom the first round left.

The state-introspection route clause on the State Machines pointer line was a candidate and was kept: packages/rest/src/meta-state-route-doc-spelling.test.ts pins this file as a teaching site of that route.

Tests

Text only. No eval in skills/objectstack-automation/evals/ covers the deleted lines (searched for PRIOR, stdlib, bare CEL, legacy, dates and money; the one legacy hit is an ERP prompt in integrations-declared.json).

Acceptance notes

  • Contract review on 47adef2 (6606363832) was FAIL. Must-fix: the change carried skip-changeset, but a skills/** change releases @objectstack/skills. Fixed in 5ee2376 with a patch changeset, and the label removed. Both should-fixes (the ROUTES cell and the flows.mdx guard list) are fixed in the same commit. A delta review on 5ee2376 is owed.

Verification

Local gates on 5ee2376: 68 derived, 68 run, all exit 0 (`dispatch-gates --ran`)
  • On head 5ee2376, node scripts/pm/dispatch-gates.mjs --commands derived 68 commands (the changeset adds nine: check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests, check:published-files and four self-tests). All 68 ran with a 1200 s timeout each, in a tree with pnpm install --frozen-lockfile and a full pnpm build (118 tasks, exit 0). Each exit code was recorded before any pipe, and all 68 exited 0, check:skill-examples, check:dts-closure, check:dual-build-cjs-loads and check:lean-entry-closure included.
  • --ran printed: 68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED. On the first head, 47adef2, 59 derived and all 59 exited 0.
  • node scripts/check-skills-token-ratchet.mjs: exit 0, skills/objectstack-automation/SKILL.md 6449 / 6449 (+0).
  • node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD: exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAmczcwpTdN2WmzM6Mo


Generated by Claude Code

…nd as a value, never as a query operator

The automation skill's filter-operand section and the flows page's CRUD
filter paragraph state the rule from the service-automation changeset and
the author's fix: make the expression evaluate to the compared value (for a
record, its id), and write conditions in the filter itself.

The skill's token ceiling is paid in the same file by deleting restatement:
the condition-CEL item of the silently-wrong list (taught by
objectstack-formula, which the skill's opening blockquote already names), a
second legacy-to-CEL pointer that repeats the opening blockquote, the
inline filter example repeated by the update_record example further down,
and the notify row's "(inbox by default)" that the notify block's channels
default restates.

Claude-Session: https://claude.ai/code/session_01J8VVAmczcwpTdN2WmzM6Mo
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-11-03T13:07Z) · head 47adef2

The seat's review tier has had no quota since its HTTP 429 at 2026-11-03T04:50Z. The maintainer's word, 2026-11-03: 「没有fable可以用 opus,等契约复审」. So this FULL review of a Tier H skills/** PR goes to triage, as on #29781, #29912 and #29924. The PR is for card #29418.

Points for the reviewer:

  • Rule text. Check SKILL.md § Filter operands and the matching flows.mdx sentence against crud-nodes.ts (operandValueRefusal, isAcceptedFilterComparand) and the fix(service-automation)!: a flow's record nodes treat a computed filter operand as a value, never as a query operator #29404 changeset. Verify the accepted set (string, number, bigint, boolean, date; a list with no empty entry at $in/$nin/$between) and the guard-before-query failure. The card asks for no refused-value examples and no "before" behaviour.
  • Payments are four same-file deletions, 6449 → 6445 / 6449. The seat's rule is that no taught fact may lose its only home. Check each one against its claimed home:
    • silently-wrong item 4 (241 B), home objectstack-formula, a different skill: check the automation reader still finds it;
    • the duplicate legacy-to-CEL pointer (74 B), home the opening blockquote;
    • the inline filter example (60 B), home the update_record example;
    • notify "(inbox by default)" (19 B), home the notify block.
  • Re-wrap. No reflow is allowed outside the edited bullet (lanes/skills.md line 20).
  • Docs prose. content/docs/** is prose and outside the review faces. The seat checks it in its ACCEPT.
  • Gates. 59 derived, all 59 exit 0 after a full build; nothing NOT-MEASURED.

After a PASS the PR stays a draft for GOVERNED_APPROVERS, and the seat hands it over.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head 47adef2 · 2026-11-03T14:15Z

Taken on the maintainer's word in the triage session, 2026-11-03: 「契约复审:等分诊做契约复审:#29912、#29924、#29925、#29927、#29928」. This is an exception to #29667 for the domain:skills#1 handoff 6604630184, while that seat's tier answers 429.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 47adef24b8ce886c0430b059b8348d165e78fb4c
Local-runs: none
Scope: skills/objectstack-automation/SKILL.md + packages/services/service-automation/src/builtin/crud-nodes.ts, packages/services/service-automation/src/guard-refusal.ts, packages/spec/src/data/filter-comparand-type.ts, packages/spec/src/data/filter-comparand-operators.ts, packages/spec/src/automation/flow-node-expression-paths.ts, packages/services/service-automation/src/builtin/crud-filter-guard.test.ts (pins), skills/objectstack-formula/SKILL.md (relocation targets), .changeset/29050-record-node-operand-value.md (PR #29404), packages/skills/package.json, .github/workflows/pr-automation.yml, AGENTS.md; content/docs/automation/flows.mdx read for consistency only

Taken by triage on the maintainer's word 「skills#1 转来的都接」 (exception to #29667; handoff 6604630184).

① Derived judgments

  • "A computed operand is a value … a string, number, bigint, boolean or date" (SKILL.md:209-210): right. Only an envelope-shaped operand is evaluated and judged (crud-nodes.ts:110-111); operandValueRefusal passes isAcceptedFilterComparand (crud-nodes.ts:173-174), which accepts string/number/bigint/boolean/Date (filter-comparand-type.ts:177-188; its null arm is pre-empted by the nothing guard, crud-nodes.ts:114-117). Matches the fix(service-automation)!: a flow's record nodes treat a computed filter operand as a value, never as a query operator #29404 changeset's "A value" line word for word, bigint included.
  • "or at an $in / $nin / $between comparand a list of them with no empty entry" (SKILL.md:210-211): right. A list passes only when the operand path ends in a list operator (crud-nodes.ts:176-177; LIST_COMPARAND_OPERATORS, filter-comparand-operators.ts:47-57) and no entry is null or a non-value (crud-nodes.ts:178-179). An envelope that is an element of a literal list ($in: ['a', env]) has an index-terminated path (flow-node-expression-paths.ts:733-737) and must be one value, which the "at a comparand" wording correctly does not cover.
  • "Anything else fails the node as a guard before the query runs" (SKILL.md:211-212): right. notValues returns { error } (crud-nodes.ts:120-123, 141-151) and every caller wraps it in refuseNode (errorClass: 'guard', guard-refusal.ts:57-59) before the engine call: get_record crud-nodes.ts:609-613, update_record :872-876, delete_record :985-989. These are the only resolveNodeFilter callers. Pinned: crud-filter-guard.test.ts:304-460 (errorClass 'guard' at :454).
  • Remedy "Make the expression evaluate to the compared value (for a record, its id), and write conditions in the filter itself" (SKILL.md:212-213): right. It matches the refusal text (crud-nodes.ts:148-149) and the changeset's BREAKING remedy.
  • Deleted inline example in the first operand bullet (SKILL.md:197-198): right. The same envelope still appears at SKILL.md:269.
  • Deleted "(inbox by default)" in the notify row: right. It is still taught at SKILL.md:123 (// RAW; default inbox).
  • Deleted "dates and money: the legacy → CEL table": right. Covered by the blockquote at SKILL.md:31-32 ("legacy → CEL table: objectstack-formula").
  • Deleted item 4 ("Conditions are bare CEL; only the stdlib …"): right. Still taught at objectstack-formula SKILL.md:28-32 (bare CEL, no {…}) and :219-226 (CEL_STDLIB_FUNCTIONS; PRIOR() fails os build). The item failed the build, so it never fit the section's "passes build" heading. No eval or test pins the deleted lines (grep of skills/objectstack-automation/ and test files).
  • PR body: "the ceiling row is not re-locked" (check-skills-token-ratchet.mjs:493 still 6449) and "the state-route clause is kept for meta-state-route-doc-spelling.test.ts": right. Lint & Repo Gates passed.

② Semver level

  • Clause-②: no and Metadata-protocol: none in the PR body: right. The diff is text only, and no authorable key or accepted value changes.
  • must-fix: the changeset is wrong. The PR adds no .changeset/*.md and carries skip-changeset, which objectstack-fleet[bot] applied at 2026-11-03T13:06:02Z. skills/** publishes: @objectstack/skills ships the repository's skills/** tree in its dist (packages/skills/package.json description, files). The gate's own rule says a skills change "releases that package -- route 1, naming '@objectstack/skills', never the label" (.github/workflows/pr-automation.yml:837-842, :864). AGENTS.md:1019-1021 says the label "is for a diff that publishes nothing from any released package". Precedent on this same file: 504ba51 and 3eb6dfc each add a '@objectstack/skills': patch changeset. Check Changeset is green only because the label exempts it. The required level is a '@objectstack/skills': patch changeset whose body carries Clause-②: no and Metadata-protocol: none, with the label removed.

③ Boundary flags

  • PR author: objectstack-fleet[bot] (attribution huangyiirene, seat domain:skills#1).
  • Model: the claude-fable-5-1 mandate for skills/** is waived by the maintainer, quoted on the card's 2026-11-03T12:35Z claim (「没有fable可以用 opus,等契约复审」) and in the PR body. Answered.
  • Open item named by the dev (PR body § Acceptance notes): the non-value guard is missing from the non-routing lists. It was left out of scope rather than escalated, and it is taken up as a should-fix in ④.
  • Governed Tier H (skills/**): the PR is a draft awaiting GOVERNED_APPROVERS. Nothing to escalate beyond that.

④ Design judgment

skills/objectstack-automation/SKILL.md: the new bullet holds against the code. I tried six cases: a list at implicit equality, a list at $gt, a null entry in an $in list, an envelope element inside a literal list, $not: env, and a whole-string placeholder. Each one behaves as the bullet states, or falls outside its claim correctly. The bullet states the rule only, with no refused-value examples and no "before" behaviour, as the card asks. The deletions lose no teaching (see ①).

  • should-fix: the bullet says the node "fails … as a guard", but the skill never ties "guard" to "not fault-routable". The place it would land is the ROUTES table's "Does NOT route (fatal either way)" cell (SKILL.md:149-151). That cell lists "filter operand that evaluated to null" but not the new non-value refusal. An author could therefore add a fault edge expecting it to handle this failure. Add "or to a non-value" to that cell, or equivalent wording.
  • should-fix (flows.mdx, outside the review face): the paragraph at flows.mdx:434-438 matches the skill and contradicts nothing. However, § "A fault edge does not disable a guardrail" (flows.mdx:1983-1993) enumerates the guards ("a filter operand that evaluated to nothing (null)") and omits the non-value guard, so it is incomplete in the same way as the ROUTES row.

Check-runs at record time: 49 total; 32 success, 17 skipped; 0 failure. No non-success conclusions besides the skips. Check Changeset is success only through the skip-changeset exemption (see ②).

Implemented-by: claude/project-thread-vq3d3x
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: FAIL


Generated by Claude Code


Generated by Claude Code

…operand, and the skills change carries its changeset

The skill's ROUTES table and the flows page's guard list now name a filter
operand that evaluated to something other than a value beside the null
operand, so an author does not expect a fault edge to handle it. A
skills/** change releases @objectstack/skills, so the change takes a patch
changeset instead of the skip-changeset label.

Claude-Session: https://claude.ai/code/session_01J8VVAmczcwpTdN2WmzM6Mo
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot removed the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-11-03T14:45Z) · head 5ee2376

The seat's review tier still has no quota (429 at 2026-11-03T04:50Z). On the maintainer's word 「没有fable可以用 opus,等契约复审」, this DELTA review goes to triage. It answers triage's review 6606363832 on 47adef24 and covers card #29418.

What the delta should check:

  • MF, the changeset. .changeset/29418-record-node-operand-value-docs.md declares '@objectstack/skills': patch, Clause-②: no and Metadata-protocol: none. skip-changeset is removed.
  • SF, the ROUTES cell. The SKILL.md cell reads "filter operand that evaluated to null or a non-value" (+16 B). The file is at 6449 / 6449 with headroom 0, and the ceiling has not moved. Check that nothing was re-wrapped.
  • SF, flows.mdx § Fault edges. The guard list adds "a filter operand that evaluated to something other than a value". This is docs prose; the seat checks it at ACCEPT.
  • Gates. 68 derived, all 68 exit 0.

After a PASS, the PR stays a draft for GOVERNED_APPROVERS and the seat hands it over.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head 5ee2376 · 2026-11-03T14:46Z

A delta after triage's FAIL; the maintainer's standing 「skills#1 转来的都接」.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5ee23761e0dca246eb8d297cad90dd35447a0532
Local-runs: none
Scope: DELTA 47adef24 → 5ee23761: skills/objectstack-automation/SKILL.md + .changeset/29418-record-node-operand-value-docs.md, content/docs/automation/flows.mdx (prose, read for consistency). The rest carries from triage's full record on 47adef24 (FAIL: must-fix ② changeset; should-fix ④ SKILL.md L149-151, flows.mdx L1983-1993).

Taken on the maintainer's standing word 「skills#1 转来的都接」 (handoff 6606845162).

① Derived judgments

  1. The ② must-fix is fixed. The skip-changeset label is gone (labels are now documentation, size/s, tooling). .changeset/29418-record-node-operand-value-docs.md declares '@objectstack/skills': patch with Clause-②: no and Metadata-protocol: none, the route pr-automation.yml names for skills/**. Check Changeset now reads the changeset.
  2. The SKILL.md should-fix is fixed. The "Does NOT route" cell now reads "filter operand that evaluated to null or a non-value". The new guard is in the table of failures a fault edge cannot catch, so an author no longer adds a fault edge expecting to route it. This matches the full record's reading of crud-nodes.ts (a guard refusal, guard-refusal.ts:57).
  3. The flows.mdx should-fix is fixed. The guard list now adds "a filter operand that evaluated to something other than a value", beside the null operand, with the paragraph otherwise unchanged.
  4. Ratchet: no ceiling row moved (scripts/** is untouched), and Lint & Repo Gates is success on the head.

② Semver level

'@objectstack/skills': patch, which is right. Clause-②: no and Metadata-protocol: none are right.

③ Boundary flags

The PR author is objectstack-fleet[bot]. Tier H (skills/**): this record does not lift the landing; the maintainer's merge or an authorized approval does.

④ Design judgment

skills/objectstack-automation/SKILL.md: the bullet and the routing table now agree. A non-value operand fails the node as a guard and never routes, the same row as the null operand. It holds.

Check-runs on 5ee23761 at record time: 51 check-runs, 34 success, 17 skipped, 0 failure, 0 in progress.

Implemented-by: claude/project-thread-vq3d3x
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

维护者速读

席位核对 — ACCEPT · domain:skills#1 · head 5ee23761e0dca246eb8d297cad90dd35447a0532

  1. 形态: draft → main,Fixes #29418;作者 objectstack-fleet[bot];文件在认领面内,附 @objectstack/skills patch changeset(⛔ 不挂 skip-changeset)。
  2. 前提实测: 复核记录逐条对过合并后的代码;本席核对复核结论与 PR 正文一致。
  3. 门禁(钉 head 5ee23761): check-runs skipped 17 / success 34,无失败。派生门禁见 PR 正文。
  4. 接受的偏差: 开发在 Opus 上进行、契约复核由分诊代审,均依维护者原话。
  5. 范围外去向: 无。
  6. 落地条款: Tier H。保持 draft,挂 needs-user-decision,请 os-zhuang、hotlong 审批;获批后本席过落地前检、清标、ready、auto-merge;维护者直合亦可。

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review November 3, 2026 16:02
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 322b6bf Nov 3, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

3 participants