The onboarding path, and the single biggest adoption risk in the product. Customers arrive with their catalog already written — usually a spreadsheet. If taking a position means hand-typing 26 duties, the rollout dies in week one.
Files you own
src/actions/catalog.actions.ts (new), added to dulyActions in src/actions/index.tssrc/actions/catalog.handlers.ts (new)src/actions/register-handlers.ts — add your registration call inside the existing functiontest/catalog-instantiate.test.ts (new)
duly_catalog_apply — global action
Input: a position_code and one or more sys_user ids.
For each active duly_catalog_item with that position_code, create a duly_duty for each selected user:
| Duty field | From |
|---|
name, description, form, frequency | the catalog item |
due_anchor, due_offset_days, lead_days, grace_days | the catalog item |
owner | the selected user |
business_unit | the user's sys_user_position.business_unit_id anchor |
timezone | the user's zone if resolvable, else the org default, else UTC |
source | 'catalog' |
catalog_item | the item, so edits can be replayed |
status | 'active' |
Idempotent. Applying twice creates nothing the second time — skip any item that already has a duty for that (catalog_item, owner) pair. Report counts: created, skipped.
duly_catalog_sync — global action
Replays cadence edits from the catalog onto duties already instantiated from it. Updates frequency, due_anchor, due_offset_days, lead_days, grace_days on every duly_duty where source = 'catalog' and catalog_item points at a changed item.
Does not touch owner, status, timezone or effective_* — those are local decisions the catalog has no business overwriting. Does not delete duties for retired catalog items; report them instead so a human decides.
Return a summary of what changed, per duty. Sync is destructive to authored cadence, so it must be legible after the fact.
Notes
position_code is free text on purpose: a customer can load their catalog on day one, before positions are modelled in the platform. Do not require a sys_user_position row to exist.role is a reserved word in the platform vocabulary and the author-time linter rejects it — the field is position_code, and any new field you add must avoid the word too.
Acceptance
- applying a 26-item catalog to 3 users creates 78 duties, all
source: 'catalog' with catalog_item set - applying the same input again creates 0 and reports 78 skipped
- editing an item's
due_offset_days then syncing updates every derived duty and leaves owner/status untouched - a duty whose
source is 'self' is never touched by sync, even if its catalog_item is somehow set - deactivating a catalog item and syncing reports it and changes nothing
Gates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.
The onboarding path, and the single biggest adoption risk in the product. Customers arrive with their catalog already written — usually a spreadsheet. If taking a position means hand-typing 26 duties, the rollout dies in week one.
Files you own
src/actions/catalog.actions.ts(new), added todulyActionsinsrc/actions/index.tssrc/actions/catalog.handlers.ts(new)src/actions/register-handlers.ts— add your registration call inside the existing functiontest/catalog-instantiate.test.ts(new)duly_catalog_apply— global actionInput: a
position_codeand one or moresys_userids.For each active
duly_catalog_itemwith thatposition_code, create aduly_dutyfor each selected user:name,description,form,frequencydue_anchor,due_offset_days,lead_days,grace_daysownerbusiness_unitsys_user_position.business_unit_idanchortimezoneUTCsource'catalog'catalog_itemstatus'active'Idempotent. Applying twice creates nothing the second time — skip any item that already has a duty for that
(catalog_item, owner)pair. Report counts: created, skipped.duly_catalog_sync— global actionReplays cadence edits from the catalog onto duties already instantiated from it. Updates
frequency,due_anchor,due_offset_days,lead_days,grace_dayson everyduly_dutywheresource = 'catalog'andcatalog_itempoints at a changed item.Does not touch
owner,status,timezoneoreffective_*— those are local decisions the catalog has no business overwriting. Does not delete duties for retired catalog items; report them instead so a human decides.Return a summary of what changed, per duty. Sync is destructive to authored cadence, so it must be legible after the fact.
Notes
position_codeis free text on purpose: a customer can load their catalog on day one, before positions are modelled in the platform. Do not require asys_user_positionrow to exist.roleis a reserved word in the platform vocabulary and the author-time linter rejects it — the field isposition_code, and any new field you add must avoid the word too.Acceptance
source: 'catalog'withcatalog_itemsetdue_offset_daysthen syncing updates every derived duty and leavesowner/statusuntouchedsourceis'self'is never touched by sync, even if itscatalog_itemis somehow setGates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.