Skip to content

docs(skills): the formula skill says the build refuses date subtraction (end - start) - #29122

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-hx2n84
Nov 2, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-hx2n84

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread

Fixes #29061
Clause-②: no
Metadata-protocol: none

Blocked-by: #29058

维护者速读

What changes

Text only, inside the claim's file surface. The wording follows PR #29058 at head e029ea772b1555ea33b2946e0978ff4175dbdd3e (its gate in packages/formula/src/validate.ts, its refusal messages, its changeset and its new row in content/docs/data-modeling/formulas.mdx).

  • skills/objectstack-formula/SKILL.md, the "No date arithmetic" note. It said end - start "does not fault — it yields a duration stored as {}". It now says the build refuses both date + n and end - start: a date or date-time mixed with a number, or minus another, reads null, and today() / now() minus a date field reads {}. It prescribes daysBetween(a, b). The Want / Write table is unchanged.
  • skills/objectstack-ui/evals/analytics-inline-vs-dataset.json, eval 6. The stored days_requested formula is restated from end_date - start_date to daysBetween(record.start_date, record.end_date). Its only must_contain token, number, is unchanged and still taught by the skill.
  • Item 3 sweep (skills/**, then content/docs/**), two more hits, both fixed here:
    • skills/objectstack-ui/rules/dashboards.md line 100 described the same leave-days formula as `days_requested` (`end - start`). It now reads (`daysBetween(start, end)`). The rule it teaches (never sum a formula) is unchanged.
    • skills/objectstack-formula/evals/cel-predicates-formulas.json eval 4 explained record.close_date - today() as "a duration stored as {}", which reads as a shape that builds. It now says "the build refuses it". Assertions unchanged.
    • Outside skills/**: content/docs/data-modeling/formulas.mdx is updated by fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 itself; content/docs/releases/v16.mdx line 726 is release history and stays as written. No other content/docs/** text subtracts dates in a formula.
  • .changeset/29061-formula-skill-date-subtraction-refused.md: @objectstack/skills patch.

Skills readings (lines, then tokens from check-skills-token-ratchet):

before after
skills/objectstack-formula/SKILL.md lines 473 473
skills/objectstack-ui/rules/dashboards.md lines 463 463
all skills/*/SKILL.md lines 4550 4550
skills/objectstack-formula/SKILL.md tokens (ceiling 5997) 5668 5686
skills/objectstack-ui/rules/dashboards.md tokens (ceiling 6262) 6248 6251
eval fixtures tokens (not ratcheted) 29160 29165
bundle total tokens 222567 222593

No ceiling moved and no line was re-wrapped.

Tests

No code changes. The claims in the new text were checked against the gate itself, by calling validateExpression('value', …) from a built @objectstack/formula, once at #29058's head (with this commit cherry-picked on top) and once at origin/main a4015ab4:

#29058 head refuses every shape the note names and accepts every daysBetween shape; origin/main accepts the subtractions
-- #29058 head e029ea77
REFUSED date-arithmetic          record.d + 1
REFUSED date-text-arithmetic     record.end_date - record.start_date
REFUSED date-text-arithmetic     record.end_at - record.start_at
REFUSED date-duration-value      today() - record.start_date
REFUSED date-duration-value      now() - record.start_at
REFUSED date-duration-value      record.close_date - today()
accepted                         daysBetween(record.start_date, record.end_date)
accepted                         daysBetween(record.start_date, record.end_date) + 1
accepted                         daysBetween(today(), record.close_date)
-- origin/main a4015ab4
REFUSED date-arithmetic          record.d + 1
accepted                         record.end_date - record.start_date
accepted                         record.end_at - record.start_at
accepted                         today() - record.start_date
accepted                         now() - record.start_at
accepted                         record.close_date - today()
accepted                         daysBetween(record.start_date, record.end_date)
accepted                         daysBetween(record.start_date, record.end_date) + 1
accepted                         daysBetween(today(), record.close_date)

The origin/main half is why this PR is Blocked-by: #29058: before that lands, the note would describe a refusal main does not make.

check:doc-formula-expressions (it judges the formula examples in skills/** with the built @objectstack/formula) is green on both trees: on this branch at a36bf3ab, and on #29058's head with this commit cherry-picked (25 record-scoped examples across 548 files judged clean).

Acceptance notes

Verification

Gates derived with node scripts/pm/dispatch-gates.mjs --commands (merge base a4015ab47, 5 paths, +18/−6) and run one by one on head a36bf3ab, the exit code captured before any pipe.

45 derived, 41 exit 0, 4 NOT MEASURED (exit 3, whole-tree build prerequisite); --ran: 0 UNRUN
41 commands :: exit 0, including
node scripts/check-skills-token-ratchet.mjs :: exit 0
node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0   (after building formula + lint)
pnpm --filter @objectstack/spec run check:skill-docs :: exit 0
pnpm check:skill-compatibility :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0

NOT MEASURED, exit 3 (PREREQUISITE NOT MET: every package's dist), left to CI:
pnpm check:dts-closure
pnpm check:dual-build-cjs-loads
pnpm check:lean-entry-closure
pnpm check:sourcemap-no-sources-content

dispatch-gates --ran: 45 derived famil(ies) accounted for — 41 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3).

The four not measured are whole-tree families that read every package's dist/; this diff touches no package source, so a full local build was declared to CI rather than run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAjkjQLh4kZsuRAM4iV


Generated by Claude Code

The "No date arithmetic" note in objectstack-formula said `end - start`
does not fault and yields a duration stored as `{}`. With the formula
gate refusing bare date subtraction, the note now says the build refuses
both `date + n` and `end - start` and prescribes `daysBetween(a, b)`.
The dashboards rule, the analytics eval prompt and a formula eval stop
describing a stored formula that subtracts dates.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAjkjQLh4kZsuRAM4iV
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-claim: session_01J8VVAnMUWA9rufXzFaEh4J · head a36bf3a · 2026-11-02T08:03Z

The seat review runs at CONTRACT_REVIEW_TIER in an isolated subagent (the seat session is not at that tier).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a36bf3ab1b99671f74fff76719ab50669e11fc7a
Local-runs: probe — node scripts/check-skills-token-ratchet.mjs on a detached worktree of the head (exit 0; the only local run, since the ratchet is the one reading the blobs cannot give)
Scope: skills/objectstack-formula/SKILL.md, skills/objectstack-formula/evals/cel-predicates-formulas.json, skills/objectstack-ui/evals/analytics-inline-vs-dataset.json, skills/objectstack-ui/rules/dashboards.md + packages/formula/src/validate.ts (dateTextIssue), packages/formula/src/cel-engine.ts (registerDateTextOverloads, firstDateTextVerdict), packages/formula/src/validate-date-text-arithmetic.test.ts, packages/lint/src/validate-expressions.test.ts, content/docs/data-modeling/formulas.mdx, all read at PR #29058 head e029ea7; packages/formula/src/stdlib.ts (daysBetween) on origin/main; .changeset/29061-formula-skill-date-subtraction-refused.md judged sentence by sentence under ②

Reviewer: the diff (+18/−6 against merge base a4015ab, five files, the same net diff against origin/main 6ada509), card #29061 with triage 6571419284, claim 6571850949 and dev report 6572112933 (read as input), PR #29058's gate code at e029ea7, and the head's check-runs were read; nothing was posted. The PR is Blocked-by: #29058, so every sentence below is judged against #29058's head, not against origin/main, where the refusal does not yet exist.

① Derived judgments

Each changed sentence against the gate at e029ea7:

  • SKILL.md line 151, "The build refuses date + n and end - start". True. date + n is the pre-existing date-arithmetic refusal (firstTypeMismatch, Timestamp + int), untouched by fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058. end - start is the new date-text-arithmetic error: registerDateTextOverloads declares no DateText - DateText overload, so firstDateTextVerdict returns kind: 'fault' and dateTextIssue emits an error in any role; the test table pins record.end_date - record.start_date and record.end_at - record.start_at (both date and datetime) as refused, and packages/lint/src/validate-expressions.test.ts pins the same verdict at validateStackExpressions, the pass behind os validate, os build and os lint. Correct.
  • SKILL.md lines 151–153, "a date or date-time mixed with a number, or minus another, reads null". True. The test table's runtimeReading reads null for both subtraction rows and for (end - start) + 1; formulas.mdx line 246 says the number shape always faulted to null. Correct.
  • SKILL.md line 153, "today() / now() minus a date field reads {}". True for a formula: Timestamp - DateText is declared as Duration, and dateTextIssue refuses a value-role Duration as date-duration-value; the table pins today() - record.start_date and now() - record.start_at as refused and reading {}. The gate does not refuse the same subtraction inside a predicate comparison (today() - record.start_date compared against duration("72h") is a clean control row); the note is about what a field reads and names no predicate, so it claims no refusal the gate does not make. The simplification is conservative (judged under ③). Correct.
  • SKILL.md line 153, "Use daysBetween(a, b)". True. Both new refusal messages end in Use \daysBetween(a, b)` for the span in whole days, and the D3 entry's replacement` names the same. Correct.
  • The Want / Write table (lines 155–161) is unchanged and still matches: daysBetween(start, end) and the inclusive daysBetween(record.start_date, record.end_date) + 1 are clean rows (daysBetween(dyn, dyn): int, int + int); daysFromNow(n) / addDays(d, n) / addMonths(d, n) are the shift helpers the refusal messages prescribe; daysBetween(record.hire_date, today()) / 365 is int arithmetic; now() + duration("3h") computes a Timestamp, not a Duration, so date-duration-value does not fire and no date field is named for the text pass. The header's "never end - start, date + n, today() + 30" names three shapes the gate refuses. Correct.
  • Eval 4 (cel-predicates-formulas.json), "never record.close_date - today() (the build refuses it)". True: DateText - Timestamp is a Duration, refused in the value role as date-duration-value (the table's record.start_date - today() row). daysBetween(today(), record.close_date) is the days-remaining shape: daysBetween(a, b) rounds (b − a) / day, so a future close date reads positive; the SKILL table row and the stdlib comment say the same. "never date arithmetic on today() (it faults)" is the pre-existing date-arithmetic refusal of today() + n. Assertions unchanged. Correct.
  • Eval 6 (analytics-inline-vs-dataset.json), the prompt's daysBetween(record.start_date, record.end_date) with returnType 'number': a clean control row, type int. The eval still asks for the thing it grades (never sum a formula). Correct.
  • dashboards.md line 100, daysBetween(start, end): the same shorthand as the SKILL table row; the aside "(date fields: no duration)" still reads as intended, since the bullet above it reserves a duration measure for two datetime fields. The rule taught (never sum a formula) is unchanged. Correct.
  • Nothing claims a refusal fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 does not make. No changed sentence names os validate, the save door or a status code, so none can be wrong about them.

Evals and their gate: the four must_contain tokens of eval 4 (cel\``, daysFromNow(, daysBetween() each appear as whole tokens in skills/objectstack-formula/SKILL.md at the head, and eval 6's numberappears in four.mdfiles of skills/objectstack-ui (dashboards.md, pages.md, list-views.md, references/react-blocks.md).ui/scripts/check-skill-eval-tokens.mjsis the gate for both: itsSCAN_ROOTS = ['skills']and a bundle is any directory under it with anevals/` folder, so objectstack-formula is covered as well as objectstack-ui (its header's "exactly one bundle" sentence is stale prose, outside this PR). Read, not run.

Token ratchet: run on a detached worktree of a36bf3a, exit 0. skills/objectstack-formula/SKILL.md 5686 / 5997 (headroom 311), skills/objectstack-ui/rules/dashboards.md 6251 / 6262 (headroom 11), eval fixtures 29165 measured not ratcheted, bundle total 222593: the dev report's figures. The ceilings live in CEILINGS inside scripts/check-skills-token-ratchet.mjs, which the diff does not touch, so no ceiling moved. Line counts 473 and 463 are unchanged.

Sweep (card item 3): git grep over skills/ at the head for end - start, end_date - start_date, close_date - today, today() - record, now() - record, a _date/_at field minus a record field, stored as {} and does not fault finds only the four intended survivors: the SKILL note naming the refused shapes, the table header's "never ...", eval 4's "never record.close_date - today()", and dashboards.md's aside. Over content/docs/ at #29058's head the only hits are formulas.mdx rows 246–247 (the gate's own table) and content/docs/releases/v16.mdx (release history, correctly left). The sweep is complete.

② Semver level

@objectstack/skills patch is right: packages/skills ships dist (its files[]), and scripts/sync-catalog.mjs copies the whole skills/** tree into it, evals and rules included, so all four skill files are published bytes; the change is text with no key, command or behaviour change. The changeset's sentences are true against the diff and the gate: the Before quotes the removed note verbatim, the Now restates the three new clauses and the daysBetween(a, b) prescription, the leave-days formula is daysBetween(start, end) in the rule and daysBetween(record.start_date, record.end_date) in the eval, and formula-date-subtraction-refused is the D3 entry id #29058 registers. Clause-②: no is right in both carriers: this PR changes no accept set; the narrowing is #29058's own Clause-②: yes (narrowing). Metadata-protocol: none is right, so no Skills-docs: line is owed by this PR (that line is owed by a non-none kind or a narrowing arm, and it is #29058 that carries Skills-docs: follow-up #29061). The PR body carries Fixes #29061 (all three card items are delivered, so a close on merge is right), Clause-②: no, Metadata-protocol: none and Blocked-by: #29058; Blocked-by is not a closing keyword and sits on its own line. No content/docs/releases/ change; no file unrelated to the card.

③ Boundary flags

④ Design judgment

  • skills/objectstack-formula/SKILL.md: the note now matches the gate that enforces it. It names the refused shapes with the words the gate's messages use (null, {}, daysBetween(a, b)), keeps the Want / Write table, and stays 311 tokens under its ceiling. Against the card's item 1 and fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058's test table, the change holds.
  • skills/objectstack-formula/evals/cel-predicates-formulas.json eval 4: the expected output explains the refusal with the gate's verdict instead of a runtime detail that no longer applies; the prescribed shape is a clean control row; the assertions still grade what the skill teaches. Holds.
  • skills/objectstack-ui/evals/analytics-inline-vs-dataset.json eval 6: the prompt now describes a stored formula that builds after fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058, so the eval exercises the sum-refusal rule without teaching a refused shape; number stays taught. Against the card's item 2, holds.
  • skills/objectstack-ui/rules/dashboards.md line 100: the same formula spelled as the skill prescribes, 11 tokens under the ceiling; the rule and its aside still read as intended. Holds.

Must-fix: none. Should-fix: none that blocks; the predicate carve-out is a deliberate omission, recorded above.

Check-runs at record time: 2026-11-02T08:08Z · 41 check-runs on a36bf3a · 26 success · 13 skipped · 2 in_progress (Test Core 1/8, Test Core 2/8) · 0 failure · Lint · repo & tooling gates, Lint · docs & code guards, Lint · CI, release & engine gates, Type Check · workspace, Type Check · consumer gates, Governed Surface Queue Guard, Check Changeset, PR-body guards all success.

Implemented-by: claude/project-thread-hx2n84
Reviewed-by: session_01J8VVAnMUWA9rufXzFaEh4J

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

维护者速读

  • 改了什么: 公式技能不再说 end - start「不报错、存成 {}」,改为:构建会拒绝 date + n 与 end - start,today() / now() 减日期字段读出 {},一律用 daysBetween(a, b)。另把两处 eval(公式 eval 4、UI 分析 eval 的 days_requested)和 objectstack-ui/rules/dashboards.md 第 100 行里的日期相减改成 daysBetween(...)。changeset @objectstack/skills patch。
  • 为什么改: 公式门禁 PR fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058(已合入 51058efc0d)开始拒绝日期相减;旧技能会教 AI 写出构建不过的公式。
  • 风险与回滚: 只改对外技能文本与 eval,不动代码;token 棘轮在上限内(SKILL.md 5686/5997,dashboards.md 6251/6262),上限未动。回滚即 revert。
  • 先看: ① skills/objectstack-formula/SKILL.md 第 151–153 行;② 两个 eval 改动行;③ dashboards.md 第 100 行。
  • 两分钟验证: node scripts/check-skills-token-ratchet.mjs 绿;git grep -n "end_date - start_date\|does not fault" skills/ 无命中。
  • 需要你做的: 审批(APPROVE)本 PR(skills/** 是 Tier H)。达档契约复核 PASS 在案:评论 6572282700,Head-sha: a36bf3ab1b99671f74fff76719ab50669e11fc7a。阻塞项 fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 已合入。批准后由本席落地。
  • 席位意见: ACCEPT。每句新话都由复核对照 fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 的门禁代码与测试表逐条核过;四处残留说法全部清掉。

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

  1. 形态: draft → main,Fixes #29061,Blocked-by: #29058 独占一行;无他卡关键词;作者 objectstack-fleet[bot](中继)。
  2. 前提实测: origin/main 上 SKILL.md 第 152 行仍写 "end - start does not fault"。复核所对照的 fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 head e029ea772b 正是其合入时的 head(合入于 2026-11-02T08:10Z,merge 51058efc0d),所以复核结论在合入后的 main 上成立,不需要重读。
  3. 门禁 check-runs(钉 head a36bf3ab): success 29 / skipped 13 / 无红无在跑。check-governed-merges --pr 29122:Tier H(skills/**),+18/−6。Lint UI 在本 head 为 skipped,eval token 已由复核逐个对照技能正文核过。
  4. 接受的偏差: 第 3 项清扫多找出两处(dashboards.md:100、公式 eval 4),都在申报面内修掉。SKILL 注释没有写出谓词里的例外(today() - record.start_date 与 duration("72h") 比较时仍然合法):注释只讲公式字段读出什么,对公式字段永远成立;这个例外由 formulas.mdx 第 247 行承载。不改,也不另立卡。
  5. 范围外去向: ui/scripts/check-skill-eval-tokens.mjs 文件头仍称「只覆盖一个 bundle」,已过时(实际覆盖所有带 evals 的 bundle)。这是复核顺带看到的,不在本 PR 的面内,本席会记在座位贴上。Skills-docs: 不适用(Metadata-protocol: none)。fix(formula)!: the formula gate refuses bare date subtraction, which reads null or {} at runtime #29058 已带 Skills-docs: follow-up #29061。
  6. 落地条款: Tier H。等 GOVERNED_APPROVERS 授权批准;批准后本席过落地前检,清 needs-user-decision,再 pr_ready + automerge_enable。

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review November 2, 2026 09:42
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 74a19dd Nov 2, 2026
48 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-hx2n84 branch November 2, 2026 10:41
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

Development

Successfully merging this pull request may close these issues.

skills: the formula skill and an analytics eval still teach date subtraction (end - start), which the formula gate now refuses (follow-up of #28944)

2 participants