fix(example-todo): task_completion computes a real next recurrence due date instead of writing a dead DATEADD(...) string - #7227
Merged
Conversation
…ate instead of writing a dead DATEADD string (#7037) `create_next_task` wrote `DATEADD({completedTask.due_date}, ...)` into `due_date`. `DATEADD` exists nowhere in the platform, and a `create_record` node's `fields` are template-interpolated rather than evaluated, so the driver received the literal string `DATEADD(2026-08-10, 1, "daily")` and refused it with `Due Date must be a valid date (ISO-8601)`. Dead while the flow was unbound; a failed run on every recurring completion once #6882 armed it. No flow node evaluates a value-producing expression (the only expression slots are predicates and `flow-template` references -- `FLOW_NODE_EXPRESSION_PATHS`), so the date is now computed before the create by a `script` node calling `computeNextTaskDueDate`, registered via `defineStack({ functions })`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fkdTyGmMD5s8ZtEifvuGy
…p's authored metadata (#7037) Named explicitly rather than only covered by the function-call-shape guard: an invented function name in a SHIPPED example is what an AI author copies, so its absence is enforced instead of merely current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fkdTyGmMD5s8ZtEifvuGy
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-help
marked this pull request as ready for review
August 10, 2026 03:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7037
examples/app-todo'sTaskCompletionFlowspawned the next occurrence of a recurring task with a literalDATEADD(...)template increate_record'sdue_date. Two independent faults stacked:DATEADDexists nowhere in the platform, and acreate_recordnode'sfieldsvalues are TEMPLATE-interpolated rather than evaluated — the holes are filled and the surrounding text passes through verbatim. So the engine received the literal stringDATEADD(2026-08-10, 1, "daily")and the field's own coercion refused it.Premise re-verified against
origin/mainStill live at
examples/app-todo/src/flows/task.flow.ts:201, exactly as the card described.Measured BEFORE — real kernel, real record-change trigger, sqlite
One recurring task transitioned into
completed, driven the way the #6882 suite drives it:Verbatim the failure the card reported.
The route, and why the alternatives are not available
The triage ruling was to use whatever date arithmetic the platform actually ships. Measured, not assumed:
assignmentnodelogic-nodes.tscallsinterpolate()on each value. It substitutes, it does not evaluate.FLOW_NODE_EXPRESSION_PATHSin@objectstack/spec/automationlists every declared slot:screen.fields[].visibleWhenanddecision.conditions[].expressionare PREDICATES,loop.collection/map.collectionareflow-templateREFERENCES. Plus the structural predicate surfacesconfig.conditionandedge.condition. There is no value-producing expression slot in the vocabulary.scriptnode → registered functionconfig.functionresolves throughdefineStack({ functions })(#1870); pure, takesinput, returns a value, a later declarative node persists it (#4396).formulafield ontodo_taskpackages/formuladoes shipaddDays/addMonths), butsrc/objects/task.object.tsis sibling #7036's declared region this round, and it adds a permanent schema surface for one flow's use.So the value has to be computed before the create node, and the
scriptnode is the shipped way — the same shapeshowcase_task_completedalready uses. No shipped-capability gap; the ruling's STOP-and-report branch did not trigger.The change
src/functions/task.functions.ts(new) —computeNextTaskDueDate, covering all four authored cadences (daily/weekly/monthly/yearly×recurrence_interval). It clamps a monthly shift to the target month's last day exactly as@objectstack/formula'saddMonthsdoes, so the example cannot teach a recurrence semantic that disagrees with the platform's own formula function. Unknownrecurrence_type, or an intervalmin: 1forbids, refuse loudly rather than guessing a cadence; an absent interval takes the field's own declareddefaultValue: 1; a completed task with nodue_dateyieldsnullrather than an invented day.src/flows/task.flow.ts— acompute_next_due_datescriptnode on the recurring branch,due_date: '{nextDueDate}'as a whole-string token (the one forminterpolateStringreturns the raw value for), edgee3retargeted ande6added. Thecheck_recurringgate is unchanged, so the non-recurring path still skips both nodes.objectstack.config.ts—functions: todoFunctionsondefineStack.test/task-recurrence.test.ts(new — 14itdeclarations, 16 cases once the parameterized end-to-end block expands).DATEADDsurvives in no authored value anywhere; the only remaining mentions are comments and test prose describing the removed defect, and a test asserts the name's absence fromJSON.stringify(allFlows).Measured AFTER — same harness, same record
Reverse verification, committed
The suite rebuilds the pre-fix shape from the LIVE flow (computation node removed, recurring edge pointed straight at the create, a function call left in the field value) and asserts it fails inside
create_next_taskwith the date refusal — and explicitly not withno function named …, because nothing ever tried to call one. The value was never a call; it was always just text. Non-vacuous: the real flow on the same kernel and same record completes and lands2026-08-11.The invented name in that fixture is deliberately not the historical one. The failure was never specific to
DATEADD— any function-call text in acreate_recordfield value reaches the driver as a literal string — so the fixture pins the class and keeps the dead name out of the repo, which is the other half of the card. A separate class-level guard asserts no write node in any of the app's flows leaves function-call text in a field value; that is the check that would have caught the original defect at authoring time.Verification
pnpm --filter '@objectstack/example-todo^...' build— dependency closure (prefix^...= upstream deps), green.pnpm --filter @objectstack/example-todo typecheck— green.pnpm --filter @objectstack/example-todo test— 99 passed (99), 3 files. Re-run after mergingorigin/main.pnpm --filter @objectstack/example-todo build(objectstack build) — green,Bundling 1 handler. The 2 remaining author-time warnings are pre-existingflow-draft-status-ambiguouson the two schedule flows, deliberately out of scope per examples/app-todotask_completiondeclarestype: 'record_change'with notriggerType— the flow is dead, and its trigger condition is written to a key nothing reads #6882's note in the same file.npx eslinton the changed files — clean.node scripts/check-nul-bytes.mjs— OK; plus a control-byte self-scan of the changed files, clean.Changeset
Included,
@objectstack/example-todo: patch. The policy does coverexamples/**here: the package is versioned by changesets (it carries a version and aCHANGELOG.md),.changeset/config.jsonsets noignore, and the sibling example-only PR for #6882 carried.changeset/todo-task-completion-trigger-armed.mdon the same package. So noskip-changesetis needed — and Check Changeset confirms it, green.Region discipline
Sibling #7036 owns
src/objects/task.object.ts,src/hooks/task.hook.ts,src/actions/task.handlers.tsandtest/task-completion-trigger.test.tsthis round — none are touched (git diff origin/main --statcovers onlysrc/flows/task.flow.ts,objectstack.config.ts, the newsrc/functions/, the new test, and the changeset). The repro is driven the way #6882's suite drives it (completed_dateseeded on CREATE), without editing that file.origin/mainmerged before opening; happy to merge again after #7036 lands.Generated by Claude Code