Repository navigation
Read a cleared field as absent in play conditions (closes #3474) - #3477
Conversation
Pragmatic Code Review: PR #3477Review Type: Initial review (no prior reviews or comments on the PR). Reviewed commit SummaryThe change is small and placed correctly. One What I ran: the four new unit tests and the ten Requirements Check
Deviation from the issue's proposed solutionThe issue proposed making
The cost is that the two "parallel" readers now differ on purpose. Both comments say so, and the dot-path test pins the binding side ( Findings🔴 CriticalNone. 🟡 Important (non-blocking)1. A
2.
🟢 Suggestions3. Collection items are covered by the fix but not by a test
4. Comment accuracy
5. Docs
Test quality
Other readers checked, no unintended effect
Recommendation: APPROVEThe change meets every acceptance criterion I could verify, the deviation from the issue is the better design, and the tests would catch a regression. Findings 1 and 2 are worth addressing but neither blocks the merge. Commit the two |
malibio
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Review addressedAddressed (4)
Skipped (1)
Checks: Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: The only logic change is the one the review prescribed for finding 1: the same "a |
|
Merge queue: #3477 was taken out of the queue — it conflicts with main in: packages/core/src/playbook/cel.rs. Rebase onto origin/main, push, and re-run |
5218a76 to
dd690ce
Compare
A field cleared through the update pipeline is stored as JSON null in its bucket. cel-interpreter's has() tests key presence, so has(node.x) was true for a cleared field and false for one never set. - Leave null-valued properties out of the node value built for CEL, for the trigger node, nodes reached through a dot-path and collection items. The drop happens after bucket precedence runs, so a cleared field still occupies its name. - A null key nested inside an object-valued field is left out the same way, at every depth. A null list element is a value and stays. - Action bindings are unchanged: a binding to a cleared field still resolves to null. - The seeded Jira rules use has() as the one spelling of 'has no value'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dd690ce to
33fe2d6
Compare
Closes #3474
What was wrong
A field cleared through the update pipeline is stored as JSON
nullin its bucket.cel-interpreter0.10'shas()tests key presence, sohas(node.priority)was true for a cleared priority and false for one never set. Confirmed by running the new tests against the unchanged code: all of them failed.What changed
node_to_cel_value_at_scopeleavesnull-valued properties out of thenodemap (packages/core/src/playbook/cel.rs). Every condition read goes through it: the trigger node, and each node reached through a dot-path (GraphResolver::node_value→scoped_node_value). The drop runs after bucket precedence, so a cleared field still occupies its name and does not let acustom:field of the same bare name, or an ancestor bucket's, show through.has()only.!has(node.start_date) || node.start_date == nullbecomes!has(node.start_date), andhas(node.completed_date) && node.completed_date != nullbecomeshas(node.completed_date).references/jira-playbook.mdis regenerated. The spec-driven rules already usedhas()only.nullstays in the bucket.Behaviour
has(node.x)!has(node.x)node.x != nullnode.x == nullnode.x == nullused to be true for a cleared field. It is now false for both, sohas()is the one spelling for "has no value".One deviation from the issue's proposed solution
The issue proposed applying the same rule in
graph_resolver::get_node_property. I left that reader returningnull. It is not on the condition path for a field's value (conditions read throughscoped_node_value, sonode.story.priorityalready agrees), but it does feed action bindings: returningNonethere would turn{trigger.node.story.priority}on a cleared field fromnullinto a missing path, which fails the action. The issue's non-goals exclude changing hownullreads outside conditions. The doc comment onget_node_property_at_scopestates the difference, and a test pins it.Tests
cel::tests::cleared_field_reads_as_absent_in_a_condition:has,!has,== null,!= nullfor a cleared, a never-set and a set field.cel::tests::node_to_cel_leaves_out_null_properties_in_every_stored_shapeandcleared_field_does_not_unshadow_another_field_of_the_same_name.graph_resolver::tests::integration::cleared_field_through_a_dot_path_reads_as_absent: the same conditions throughnode.story.priorityagainst a real store, plus the action-binding read.methodology_jira_execution_test: starting a sprint with a clearedend_dateis rejected; creating one with anullcompleted_dateis allowed. Both fail with the fix reverted.bun run test:changedpasses (rust, skill, scripts);bun run quality:fixclean.Docs
Companion nodespace-docs edit: a "Fields with no value" section in
architecture/playbook-engine.mdand one sentence incomponents/node-types.md§1.2.🤖 Generated with Claude Code