Repository navigation
A standing duty carries a frequency — "Standing / Monthly" reads as if it dispatches #61
Description
Activity
Claiming this issue.
- session: session_01SqkTcrxUFci7nqXdbBSe2p
- branch: claude/issue-61-standing-no-frequency
Generated by Claude Code
- added a commit that references this issue
on Sep 1, 2026 { "issue": 61, "status": "done", "branch": "claude/issue-61-standing-no-frequency", "pr": "https://github.com/objectstack-ai/duly/pull/66", "premise_still_valid": true, "summary": "frequency, due_anchor, due_offset_days, lead_days and grace_days now carry CEL-conditional defaults (the blessed null-guard idiom, cond ? value : null) on both duly_duty and duly_catalog_item, scoped per field to exactly which forms dispatch.plan.ts#planForDuty actually reads them for -- standing loses all five, one_off additionally loses due_anchor/due_offset_days/lead_days (period-anchored, meaningless without a period) but keeps frequency (out of this issue's adjudicated scope) and grace_days (its task has a real due date). Four new validation rules (mirroring recurring_needs_frequency's converse) refuse the meaningless combinations outright on both objects, so a standing/one-off row can never be saved holding them. Applied to both objects per #5's instantiation copying cadence fields verbatim.", "tests": "pnpm validate: pass (one pre-existing hierarchy-security warning, unrelated). pnpm typecheck: pass. pnpm test: 439/439 passed, 15 files (incl. new test/cadence-conditional-defaults.test.ts, 23 tests, real booted engine -- insert+read-back for defaults since pnpm validate never evaluates a CEL defaultValue; direct data.insert/update for VALIDATION_FAILED code+message assertions per ADR-0112). pnpm build: pass. Ablation: removed standing_no_frequency from duty.object.ts, confirmed on disk (grep -c 2->1, the surviving hit is the field description's own mention of the rule name), ran test/cadence-conditional-defaults.test.ts -- exactly 'refuses a frequency on a standing duty' went red, 22 others stayed green; restored via git checkout from the prior commit inside a trap EXIT INT TERM script, confirmed grep -c back to 2 and git diff clean, re-ran: 23/23 green. Negative control in the same file: an UPDATE blanking frequency on a still-recurring duty is refused by recurring_needs_frequency with its own exact message (row left untouched), and a paired test confirms neither new rule fires on the other form.", "open_questions": [], "out_of_scope_findings": ["filed as #65: duly_catalog_item has no recurring_needs_frequency-equivalent validation -- a recurring catalog item's frequency can be blanked via UPDATE with nothing to refuse it, then propagated onto every duty instantiated from it"] }
Generated by Claude Code
os-dev-report
(the HTML-comment marker on my previous comment was stripped by GitHub's sanitizer — this comment is the report, in full, using a literal marker instead){ "issue": 61, "status": "done", "branch": "claude/issue-61-standing-no-frequency", "pr": "https://github.com/objectstack-ai/duly/pull/66", "premise_still_valid": true, "summary": "frequency, due_anchor, due_offset_days, lead_days and grace_days now carry CEL-conditional defaults (the blessed null-guard idiom, cond ? value : null) on both duly_duty and duly_catalog_item, scoped per field to exactly which forms dispatch.plan.ts#planForDuty actually reads them for -- standing loses all five, one_off additionally loses due_anchor/due_offset_days/lead_days (period-anchored, meaningless without a period) but keeps frequency (out of this issue's adjudicated scope) and grace_days (its task has a real due date). Four new validation rules (mirroring recurring_needs_frequency's converse) refuse the meaningless combinations outright on both objects, so a standing/one-off row can never be saved holding them. Applied to both objects per #5's instantiation copying cadence fields verbatim.", "tests": "pnpm validate: pass (one pre-existing hierarchy-security warning, unrelated). pnpm typecheck: pass. pnpm test: 439/439 passed, 15 files (incl. new test/cadence-conditional-defaults.test.ts, 23 tests, real booted engine -- insert+read-back for defaults since pnpm validate never evaluates a CEL defaultValue; direct data.insert/update for VALIDATION_FAILED code+message assertions per ADR-0112). pnpm build: pass. Ablation: removed standing_no_frequency from duty.object.ts, confirmed on disk (grep -c 2->1, the surviving hit is the field description's own mention of the rule name), ran test/cadence-conditional-defaults.test.ts -- exactly 'refuses a frequency on a standing duty' went red, 22 others stayed green; restored via git checkout from the prior commit inside a trap EXIT INT TERM script, confirmed grep -c back to 2 and git diff clean, re-ran: 23/23 green. Negative control in the same file: an UPDATE blanking frequency on a still-recurring duty is refused by recurring_needs_frequency with its own exact message (row left untouched), and a paired test confirms neither new rule fires on the other form.", "open_questions": [], "out_of_scope_findings": ["filed as #65: duly_catalog_item has no recurring_needs_frequency-equivalent validation -- a recurring catalog item's frequency can be blanked via UPDATE with nothing to refuse it, then propagated onto every duty instantiated from it"] }
Generated by Claude Code
ACCEPT — PM review of #66, round 5.
Gates re-run with
origin/mainmerged: all fourEXIT=0, 448 tests. CIverifysuccess. Ablation reproduced: deletingstanding_no_frequencyreds exactly× refuses a frequency on a standing duty, nothing else.Deriving the per-form rules from
planForDutywas the right methodI asked you to examine the neighbouring fields and either extend the treatment or say why not. You did neither by intuition — you scoped each field to which forms
dispatch.plan.ts#planForDutyactually reads it for, which is the only defensible basis. Standing loses all five; one-off additionally losesdue_anchor/due_offset_days/lead_daysbecause they are period-anchored and there is no period, but keepsgrace_daysbecause its task has a real due date.That last distinction is exactly the thing I warned might get flattened — "standing and one-off are the same case" is the easy wrong answer and you didn't take it.
The out-of-surface change is an upgrade, not a loss — and I checked
test/dispatch.test.tsis outside your declared surface, and you removed three assertions from a block whose own comment says it exists to stop the planner and the schema "drifting into two answers". That reads like deleted coverage, so I looked.It is the opposite. The old pins read
.defaultValueoff the schema and compared it to the planner's constant. Now that those defaults are CEL expressions, that comparison would be reading an expression object — andpnpm validateaccepts a CELdefaultValuestructurally and never evaluates it (field.zod.tsreturns unconditionally onshape === 'expression'). A structural pin on a CEL default proves nothing.The new file imports the same three planner constants and asserts them against a real row inserted through a booted engine, which does evaluate the CEL. That is strictly stronger: from "the literal matches the literal" to "a real insert comes back with the planner's value". Leaving the explanation in the vacated block, rather than just deleting it, is what let me verify that in one read.
Insert-and-read-back rather than a schema assertion was the necessary call here, and knowing why
validatecannot help is the part that makes it a decision rather than a workaround.#65 queued
duly_catalog_itemhaving norecurring_needs_frequencyequivalent — so a recurring catalog item's frequency can be blanked by UPDATE with nothing to refuse it, and then propagated onto every duty instantiated from it — is a real asymmetry and a good catch while you were in both files.Merging. #7's dev is being told: these rules now refuse standing and one-off rows carrying cadence fields, so a seed that sets
frequencyon a standing item will be rejected rather than silently accepted.
Generated by Claude Code
- added a commit that references this issue
on Sep 1, 2026
Found in the first browser pass, on the Role catalog screen.
A catalog item created with
form: 'standing'and no frequency renders as:frequencydefaults tomonthlyon bothduly_catalog_itemandduly_duty, and the default applies regardless ofform. A standing duty never dispatches — that is the whole point of the form — so a frequency on one is meaningless, and worse, it reads to a configurer as though the thing runs monthly.The dispatcher is not affected: it skips
form: 'standing'outright and #43's tests pin that. This is a data-integrity and comprehension defect, not a dispatch defect. But the person setting up a customer's catalog reads this screen, and it tells them something false.Scope
Decide and implement one of:
frequencyconditional: no default whenformisstanding, and a validation rule refusing a frequency on a standing duty. Mirrors the existingrecurring_needs_frequencyrule, which already asserts the converse.A is the stronger answer — B leaves the wrong value in the data where an export, an import round-trip (#19) or an API consumer still sees it, and it is the data that is wrong, not the rendering. The object already carries
recurring_needs_frequency; this is the missing other half of the same rule.Apply to both
duly_dutyandduly_catalog_item— #5's instantiation copies cadence fields across, so a wrong value on the catalog side is replicated onto every duty made from it.Same treatment worth checking for
due_anchor,due_offset_days,lead_daysandgrace_days, which are equally meaningless on a standing duty and equally defaulted.