Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
35 KiB
SCRATCHPAD — Workflow Project Actions
- Slug:
workflow-project-actions - Date:
2026-04-27 - Status: Discovery / PRD convergence
Initial request
Add workflow actions for project-management entities:
- FIND task / phase / project
- MOVE task (to different status + phase, or project + phase + status)
- RENAME task / phase / project
- ASSIGN task
- DUPLICATE task
- DELETE task / phase / project
- LINK TICKET to task
- ADD TAG to task or project
- EDIT task / phase / project description
Context from request
Workflow actions are server-registered through shared/workflow/runtime/registries/actionRegistry.ts, commonly in shared/workflow/runtime/actions/registerBusinessOperationsActions.ts and domain files under shared/workflow/runtime/actions/businessOperations/.
Action schemas are Zod schemas converted to JSON Schema and consumed by the workflow designer. Picker/editor metadata can be attached via schema descriptions and normalized into ActionInputField.editor. Picker-backed fields should use x-workflow-editor where possible, with legacy picker metadata still supported.
Repository discoveries
- Existing project workflow action file:
shared/workflow/runtime/actions/businessOperations/projects.ts. - Existing registered project action:
projects.create_task. - It already validates project, optional phase, optional project_task status, optional user/team assignee, optional ticket link, writes audit, and returns task URL/status metadata.
- Existing ticket action code has a generic ticket-to-entity pattern in
shared/workflow/runtime/actions/businessOperations/tickets.ts, includingprojectandproject_taskentity types. - Plans live under
ee/docs/plans/YYYY-MM-DD-<slug>/withPRD.md,features.json,tests.json, andSCRATCHPAD.md.
Open questions
- Need decide whether FIND actions are exact-ID lookup actions, criteria/search actions, or both.
- Need define destructive delete semantics for phases/projects/tasks.
- Need define duplicate semantics for tasks (comments/checklists/tags/ticket links/time entries/subtasks?).
- Need confirm picker support needed for project/phase/task/status/tag resources in workflow fixed-value inputs.
Decisions
-
FIND scope: use both exact lookup and search/list-style actions, following existing workflow action patterns.
-
Pattern fidelity is a primary design constraint. Existing examples:
clients.find: exact lookup by id/name/external ref withon_not_foundand nullable output.clients.search: query + filters + pagination, returns array, first result, page metadata, total.contacts.find/contacts.search: same split.tickets.find: exact lookup by id/number/external ref with optional include flags.
-
Proposed PRD direction: project entities should mirror that split rather than inventing separate
getnaming unless the registry already uses it elsewhere. -
Delete semantics decision: target soft/archive semantics where the project domain supports them; use hard delete only where the domain's existing behavior is genuinely hard delete. Preserve existing domain guards and avoid workflow-specific cascade behavior.
Delete/move/duplicate implementation fidelity discoveries
- User decision: delete semantics should mirror existing project UI/domain behavior exactly.
- Existing task delete UI action (
packages/projects/src/actions/projectTaskActions.ts) checksproject:delete, validates project read access, refuses delete when associatedtime_entriesexist, deletes ticket links, deletes checklist items, then deletesproject_tasks. - Existing phase delete UI action (
packages/projects/src/actions/projectActions.ts) checksproject:delete, validates project read access, then callsProjectModel.deletePhase. - Existing project delete UI action (
packages/projects/src/actions/projectActions.ts) usesdeleteEntityWithValidation('project', ...), cleans up project tags, task tags under phases, project ticket links, email reply tokens, then deletes throughProjectModel.delete. - Existing API service (
server/src/lib/api/services/ProjectService.ts) has simpler hard-delete methods, but workflow plan should prefer UI/domain action semantics when they are richer. - Existing task move UI action:
moveTaskToPhasesupports target phase, optional target status mapping, optional target project, optional before/after positioning. It remaps status mappings on same-project phase moves and cross-project moves if no explicit target status is provided, regenerates WBS/order key, and updates ticket link project/phase references. - Existing task duplicate UI action:
duplicateTaskToPhasesupports target phase, optional new status mapping, and options for primary assignee, additional assignees, checklist, and ticket links. It appends(Copy), resets actual hours to 0, creates task, optionally copies related records, and emits workflow events.
Update action decision
- User decision: use generic update actions, not separate narrow rename/edit-description actions.
- PRD direction:
projects.updatecovers project name and description edits, plus only other fields intentionally included by this plan.projects.update_phasecovers phase name and description edits, plus only other fields intentionally included by this plan.projects.update_taskcovers task title/name, description, assignment, status, phase/project move-adjacent fields only if they do not conflict with the dedicated move action. Prefer dedicatedprojects.move_taskfor phase/project/status relocation because existing domain logic is non-trivial.
- Avoid palette/action sprawl for
rename_*andedit_*_descriptionvariants.
Assignment action decision
- User decision: use a dedicated
projects.assign_taskaction, matchingtickets.assignfidelity. - PRD direction:
- Keep task assignment out of generic
projects.update_taskexcept possibly preserving current assignment when other fields update. projects.assign_taskshould followtickets.assignshape where practical: required assignment target, optional reason/comment if useful,no_op_if_already_assigneddefault true, output task id/assigned target/updated timestamp.- Existing project task model supports
assigned_toprimary user and additional task resources. Need decide if workflow assignment supports only primary user in v1 or primary + additional users.
- Keep task assignment out of generic
Task assignment model decision
- User decision:
projects.assign_taskshould support primary user + additional users. - PRD direction:
- Primary assignee maps to
project_tasks.assigned_to. - Additional users map to task resource/additional assignment records, matching existing project task UI/domain behavior.
- Do not invent team assignment unless existing project task resource semantics support it; the first-class workflow task assignment model is user-centric.
- Provide no-op behavior when primary and additional users already match, mirroring
tickets.assign.
- Primary assignee maps to
Tag behavior discovery/decision
-
User instruction: look at contact workflow/tag behavior and match it.
-
There is no dedicated
contacts.add_tagworkflow action today; contact/client workflow search actions filter by existing tag text throughtag_mappings+tag_definitionsjoins. -
Existing ticket workflow create/update tag handling creates missing
tag_definitions, inserts mappings, treats duplicate mappings as no-op, and normalizes/uniquifies trimmed tag text. -
Existing generic tag domain action
createTagalso get-or-creates tag definitions, requires entity update permission, requirestag:createonly when the definition is new, creates the mapping, and publishes tag events. -
createTagsForEntityWithTransactionget-or-creates definitions and continues on per-tag failures; this is used by imports including project task import. -
PRD direction:
projects.add_tagshould match existing tag application semantics: trim/validate tag text, get-or-create tag definition for tagged type (projectorproject_task), insert mapping idempotently/no-op on duplicate, use existing color generation/definition colors, and respect tag permissions when practical. -
Need be careful that workflow runtime direct DB implementation should either reproduce permission requirements or call shared tag-domain helper if callable from workflow runtime.
-
Follow-up search for user-mentioned
Add Tag to Contact: current worktree search did not find a dedicated workflow contact add-tag action inshared/workflow/runtime/actions/businessOperations/contacts.tsor elsewhere under workflow runtime. It may be in another branch/module or not present in this worktree. -
Concrete tag behavior found in
packages/tags/src/actions/tagActions.ts#createTag: validates non-empty tag text, trims, max 50 chars, character whitelist, requires target entity update permission, requirestag:createonly if creating a new definition, get-or-createstag_definitions, insertstag_mappings, publishesTAG_DEFINITION_CREATEDandTAG_APPLIED, and returns the mapping id astag_idfor compatibility.
Tag action behavior decision/discovery
- User direction: match the existing workflow contact module's
contacts.add_tagbehavior. - Existing
contacts.add_tagbehavior:- Action id:
contacts.add_tag. - Input:
contact_id,tags: string[], optionalidempotency_key. - Side-effectful with
idempotency: { mode: 'actionProvided', key: actionProvidedKey }. - Requires
{ resource: 'contact', action: 'update' }. - Ensures the contact exists.
- Normalizes/uniques tag text.
- Creates missing tag definitions for
tagged_type: 'contact'with generated colors. - Inserts tag mappings idempotently via conflict ignore.
- Returns
added,existing,added_count,existing_count. - Writes run audit.
- Action id:
- PRD direction:
- Add project/task tag actions should create missing tag definitions and idempotently attach mappings, not fail on missing tags.
- Match output shape and idempotency behavior from
contacts.add_tag. - Use resource permissions appropriate to the target entity (
project:update; task likely project update after project/task read scoping, matching existing project task actions).
Tag action shape decision
- User decision: two resource-specific actions.
- PRD direction:
projects.add_tagattaches tags to a project.projects.add_task_tagattaches tags to a project task.- Both mirror
contacts.add_tagbehavior and output shape.
Ticket-to-task link decision
- User decision: workflow task-ticket link should write both
project_ticket_linksandticket_entity_links. - PRD direction:
- Add action likely named
projects.link_ticket_to_task. - Validate task, its phase/project, and ticket exist.
- Require appropriate project update and ticket read permissions, matching existing UI behavior plus workflow business action conventions.
- Insert
project_ticket_linksidempotently/best-effort where duplicate constraints exist. - Insert/ensure
ticket_entity_linkswithentity_type: 'project_task',entity_id: task_id,link_type: 'project_task', and metadata containing project/phase IDs, matchingprojects.create_taskprecedent. - Return IDs and counts/created-vs-existing indicators if practical.
- Add action likely named
Picker/editor support decision
- User decision: hybrid picker support.
- PRD direction:
- Add workflow fixed-value picker/editor support for project, phase, project task, and project task status/status mapping where needed.
- Keep tag entry as free-text string array, matching
contacts.add_tag; no tag picker in this plan. - Use dependency metadata so phase/task/status pickers can depend on selected project/phase as applicable.
PRD approval
- User approved the PRD scope on 2026-04-27.
- Estimated feature list size: medium/large workflow action expansion, roughly 60-75 atomic features because it spans handler schemas, action behavior, permissions/audit, tag/link helpers, and designer picker support.
- Test plan should be Pareto-focused: DB-backed action handler coverage for representative read/update/mutation/destructive paths, plus picker metadata/designer resource coverage.
Implementation checkpoint — 2026-04-27 (Find/Search foundation)
Completed features
-
F001: Added shared workflow schemas inshared/workflow/runtime/actions/businessOperations/projects.tsfor:projectSummarySchemaphaseSummarySchemataskSummarySchemastatusMappingSummarySchematagResultSchemaassignmentResultSchemalinkResultSchemaRationale: establishes canonical output schema contracts for new action family.
-
F002: Added reusable entity/context loaders with standardizedNOT_FOUNDerrors:ensureProjectExistsensurePhaseExistsensureTaskContextensureTicketExistsensureStatusMappingExistsRationale: avoid repeated ad-hoc lookup/error logic across action handlers.
-
F003: Added reusable permission + narrowing helpers:requireProjectReadPermissionrequireProjectUpdatePermissionrequireProjectDeletePermissioncanReadProject/assertProjectReadableRationale: centralizes project read/auth narrowing behavior and future-proofs update/delete action implementation.
-
F004: Added project picker metadata helpers and kinds:- picker kinds:
project,project-phase,project-task,project-task-status - helper:
withWorkflowPickerRationale: fixed-value mode can render dependency-aware project entity pickers.
- picker kinds:
-
F005+F006: Implementedprojects.findschema + handler:- supports
project_id, exactname, optionalexternal_ref,on_not_found - enforces tenant scoping + project read permission + auth narrowing
- returns nullable
projectsummary output
- supports
-
F007+F008: Implementedprojects.searchschema + handler:- query + filters + pagination
- deterministic ordering +
first_project+total - authorization-filtered results
-
F009+F010: Implementedprojects.find_phaseschema + handler:- supports
phase_idor project-scoped exactname - supports
on_not_found - enforces project read auth via phase->project context validation
- supports
-
F011+F012: Implementedprojects.search_phasesschema + handler:- supports project scope, query, filters, pagination
- deterministic ordering +
first_phase+total - auth-filtered by parent project visibility
-
F013+F014: Implementedprojects.find_taskschema + handler:- supports
task_idor scoped exact taskname - supports
on_not_found - validates task->phase->project context and project read authorization
- supports
-
F015+F016: Implementedprojects.search_tasksschema + handler:- supports query + project/phase/status mapping/status/assignee/tag filters
- returns
tasks,first_task, page metadata, total - tenant-scoped + project-auth filtered
Completed tests
-
T001: Addedshared/workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts- validates registration, labels, side-effect/idempotency metadata, category
- validates picker metadata presence and dependencies for project/phase/task/status fields
-
T002+T003+T004: Added DB-backed tests inshared/workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts- covers
projects.find+projects.searchhappy paths, not-found behavior, pagination/tenant scoping - covers
projects.find_phase+projects.search_phasesproject scoping and deterministic ordering - covers
projects.find_task+projects.search_tasksscoped lookups, filters, first/total/page metadata
- covers
Commands / runbook used
- Targeted test run:
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts
Gotchas discovered
- Shared runtime package does not resolve
@alga-psa/authorization/kernelin this test runtime; switched to local schema-aware read narrowing helper. - Test DB schema differs from legacy assumptions (
project_numberrequired; status column variants; user/client columns vary); test fixtures were made column-aware viainformation_schemachecks. - Throwing standardized action objects inside try/catch requires pass-through handling; otherwise generic rethrow path can incorrectly map to
INTERNAL_ERROR.
Implementation checkpoint — 2026-04-27 (Generic update actions)
Completed features
-
F017+F018: Implementedprojects.updateschema+handler:- non-empty patch validation (
project_name,description) - project update permission + project read auth check
- changed-field diffing +
no_opsupport - output includes updated project summary + changed metadata
- workflow run audit writes include
changed_fieldsandno_op
- non-empty patch validation (
-
F019+F020: Implementedprojects.update_phaseschema+handler:- non-empty patch validation (
phase_name,description) - phase->project context validation
- project update permission + read auth check
- changed-field output +
no_op+ run audit
- non-empty patch validation (
-
F021+F022: Implementedprojects.update_taskschema+handler:- non-empty patch validation for task
task_nameanddescription - excludes move/status relocation fields by schema design
- task->phase->project context validation
- project update permission + read auth check
- changed-field output +
no_op+ run audit
- non-empty patch validation for task
Completed tests
-
T006: Added DB-backed coverage for update happy paths and audit writes:- validates name/description updates for project, phase, and task
- checks returned changed fields/no-op flags
- asserts audit log operation records exist for all three update actions
-
T007: Added DB-backed validation/permission guard coverage:- empty patch rejects at schema parse time
- missing project/phase/task returns
NOT_FOUND - denied
project:updatereturnsPERMISSION_DENIED - verifies denied update does not mutate persisted project data
Checklist correction
T001was initially marked complete during the first checkpoint but that was premature because it requires registration coverage for the full final project action surface. Reset toimplemented:falseuntil the full action set is present.
Implementation checkpoint — 2026-04-27 (Task move action)
Completed features
-
F023: Addedprojects.move_taskinput schema with:task_idtarget_phase_id- optional
target_project_status_mapping_id - optional
target_project_id - optional
before_task_id/after_task_id(mutually exclusive) - picker metadata and dependency wiring for project/phase/task/status fields
-
F024+F025: Implemented status remap/default resolution when explicit status mapping is omitted:- attempts same-project mapping reuse
- attempts same underlying status id mapping in target project
- falls back to first visible target project mapping by display order
- cross-project path uses target-project mappings only
-
F026: Implemented move-time WBS/order metadata updates:- regenerates
wbs_codebased on target phase WBS + next ordinal - updates
order_keywhen supported by schema
- regenerates
-
F027: Implementedproject_ticket_linkscontext rewrite on move:- updates
project_id+phase_idfor all links tied to movedtask_id
- updates
-
F028: Added move output schema/result payload with previous/current project/phase/status mapping/status ids plus updated WBS/order/updated_at. -
F029: Added permission checks, validation errors, and workflow run audit logging forprojects.move_task.
Completed tests
-
T008: DB-backed same-project move test validates:- phase relocation
- status mapping/status resolution non-null path
- WBS/order metadata change
- move audit record creation
-
T009: DB-backed cross-project move test validates:- target project/phase assignment
- status mapping resolution in target project
project_ticket_linksproject/phase context updates
-
T010: DB-backed validation guard test validates:- missing task =>
NOT_FOUND - missing target phase =>
NOT_FOUND - invalid explicit target mapping =>
VALIDATION_ERROR
- missing task =>
Gotchas discovered
- Ticket fixture schema for this branch requires adaptive field population (e.g.,
client_idconstraints and varying timestamp columns), so the test helper was made schema-aware viainformation_schema. - Same-project status remap behavior can legitimately keep source mapping in some fixture shapes; tests were adjusted to verify remap outcomes without over-constraining mapping identity.
Implementation checkpoint — 2026-04-27 (Task assignment action)
Completed features
-
F030: Addedprojects.assign_taskinput schema:task_idprimary_user_idadditional_user_ids(array; deduped)- optional
reason no_op_if_already_assigneddefaulttrue- optional
idempotency_key - schema guard that rejects
additional_user_idscontaining the primary user
-
F031: Added assignment user resolution helper for active tenant users:- validates primary user exists and is active
- validates all additional users exist and are active
- enforces internal-user filtering where user schema supports
user_type - returns deterministic deduped/sorted additional user ids
-
F032: Implemented no-op comparison for assignment requests:- compares current
project_tasks.assigned_to - compares current
task_resources.additional_user_idset - honors
no_op_if_already_assigned(default true)
- compares current
-
F033: Implemented assignment mutation and additional-user reconciliation:- updates
project_tasks.assigned_to - clears
assigned_team_idwhen present - replaces
task_resourcesrows for the task with the resolved additional users
- updates
-
F034: Implemented assign action output shape:task_id,assigned_to,additional_user_ids,no_op,updated_at
-
F035: Added permission checks and run audit logging forprojects.assign_task:- requires
project:update - validates project readability through task->phase->project context
- writes
workflow_action:projects.assign_taskaudit rows for both no-op and mutation paths
- requires
Completed tests
-
T011: Added DB-backed happy-path assignment test validating:- primary assignee change
- additional-user reconciliation in
task_resources - deterministic output
- audit record creation
-
T012: Added DB-backed no-op test validating:- identical requested assignment returns
no_op: true - task
updated_atunchanged when no-op short-circuits
- identical requested assignment returns
-
T013: Added DB-backed validation failure test validating:- inactive primary user rejected
- missing additional user rejected
- no partial mutation of
project_tasks.assigned_toor existingtask_resources
Commands/runbook used
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts
Gotchas discovered
task_resourceshas no uniqueness constraint on (task_id,additional_user_id), so reconciliation uses delete+reinsert to keep assignment state canonical.- UUID lexical ordering is non-semantic; tests were adjusted to compare additional-user sets order-insensitively where needed.
Implementation checkpoint — 2026-04-27 (Task duplicate action)
Completed features
-
F036: Addedprojects.duplicate_taskinput schema with:source_task_idtarget_phase_id- optional
target_project_status_mapping_id - copy toggles for primary assignee, additional assignees, checklist, and ticket links
-
F037: Implemented core duplicate behavior:- clones source task into target phase
- appends
(Copy)suffix to task name - preserves estimated hours when schema supports it
- resets actual hours to
0when schema supports it - resolves target status mapping/status similarly to move semantics
-
F038: Implemented optional checklist copy:- copies
task_checklist_itemsrows to new task with new ids/timestamps - returns copied checklist count
- copies
-
F039: Implemented optional assignment copy:- optional primary assignment copy to
project_tasks.assigned_to - optional additional assignment copy via
task_resources - clears
assigned_team_idon duplicates when that column exists
- optional primary assignment copy to
-
F040: Implemented optional ticket link copy with permission-aware filtering:- checks
ticket:readpermission before copying links - copies
project_ticket_linksinto target project/phase/task context - writes matching
ticket_entity_linksrecords for duplicated task links
- checks
-
F041: Added duplicate action output schema:- source/new task ids
- target project/phase/status mapping/status ids
- copied relation counts
created_at
-
F042: Added permission checks and run audit logging:- requires
project:createand project read checks for source/target context - writes
workflow_action:projects.duplicate_taskaudit with target ids and copied counts
- requires
Completed tests
-
T014: DB-backed duplicate core behavior test validates:- new task creation in target phase/project
(Copy)suffix- description copy
- estimated-hours preservation + actual-hours reset when columns exist
- status mapping/status target metadata in output
-
T015: DB-backed optional relation copy test validates:- checklist copy count and persisted copied checklist rows
- primary/additional assignment copy count and
task_resourcesrows - ticket-link copy count and copied
project_ticket_linkstarget context
Commands/runbook used
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts
Gotchas discovered
task_checklist_items/task_resources/project_tasksschemas vary slightly across migrations in this branch; test fixtures and assertions were kept column-aware to avoid false failures.task_resourcesassignment rows are easiest to keep coherent via full-row clone with refreshed ids/timestamps and target task id, while overridingassigned_toaccording to copy options.
Implementation checkpoint — 2026-04-27 (Delete task/phase/project actions)
Completed features
-
F043: Addedprojects.delete_taskinput/output schemas with cleanup count fields:- input:
task_id - output:
task_id,deleted,deleted_ticket_link_count,deleted_checklist_item_count
- input:
-
F044: Implemented delete-task guard for associated project task time entries:- checks
time_entriesbywork_item_type='project_task'andwork_item_id=task_id - rejects with
VALIDATION_ERRORwhen entries exist
- checks
-
F045: Implemented delete-task cleanup flow mirroring UI semantics:- deletes task ticket links
- deletes task checklist items
- removes task resources and task-side ticket entity links before deleting task row
-
F046: Added delete-task permission checks and audit logging:- requires
project:delete - validates task->project context readability
- writes
workflow_action:projects.delete_taskrun audit
- requires
-
F047: Addedprojects.delete_phaseinput/output and handler:- input:
phase_id - output:
phase_id,project_id,deleted - deletes phase with project context validation
- input:
-
F048: Added delete-phase validation, permissions, standardized errors, and audit logging:- requires
project:delete - validates phase + project context and readability
- writes
workflow_action:projects.delete_phaserun audit
- requires
-
F049: Addedprojects.deleteinput/output schemas exposing validation/result fields:- input:
project_id - output includes
success,deleted,can_delete,code,message,dependencies,alternatives
- input:
-
F050: Implemented project delete handler with cleanup behavior:- collects phase/task descendants
- blocks delete when descendant task time entries exist
- cleans up project tags + task tags (
tag_mappings) - cleans up project/task ticket links and project email reply tokens
- removes descendant tasks/phases then project row
-
F051: Added delete-project permission checks, failure shape, and run audit logging:- requires
project:delete - returns structured validation failure result when blocked
- writes
workflow_action:projects.deleterun audit on mutation path
- requires
Completed tests
-
T016: DB-backed destructive task-delete happy path validates:- ticket-link + checklist cleanup counts
- task row removed
- related checklist/link rows removed
-
T017: DB-backed destructive guard test validates:- task delete rejected when project_task time entries exist
- task/checklist/link rows remain intact
-
T018: DB-backed destructive phase/project delete coverage validates:- phase delete happy path
- project delete happy path including cleanup of project/task tags, project ticket links, and email reply tokens
- project delete validation-failure path when descendant task time entries exist
Commands/runbook used
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts
Gotchas discovered
time_entriesschema in this branch requires additional non-null columns (e.g.,work_date,work_timezone), so destructive guard fixtures needed column-aware inserts.- Project delete cleanup needs to tolerate optional tables (
email_reply_tokens,task_resources,ticket_entity_links) across schema variants; helper deletion logic was made table-existence-aware.
Implementation checkpoint — 2026-04-27 (Ticket-link + tag actions)
Completed features
-
F052: Addedprojects.link_ticket_to_taskinput schema:task_id,ticket_id, optionalproject_id, optionalphase_id, optionalidempotency_key- picker metadata and dependency paths for task/project/phase context fields
-
F053: Added task/ticket/project/phase validation for link action:- validates task context exists
- validates ticket exists
- validates optional project/phase inputs match task context
-
F054: Implemented ensure/insert behavior forproject_ticket_links:- idempotent existing-link detection
- insert-on-miss with created vs existing signaling
-
F055: Implemented ensure/insert behavior forticket_entity_links:entity_type='project_task',entity_id=task_id,link_type='project_task'- metadata includes project/phase ids
- created vs existing signaling
-
F056: Added link output schema with idempotency indicators and resolved ids:project_ticket_link_id,ticket_entity_link_idproject_ticket_link_created,ticket_entity_link_created
-
F057: Added permission checks + run audit logging for link action:- requires
project:update - writes
workflow_action:projects.link_ticket_to_task
- requires
-
F058: Added shared project/project-task tag helper behavior mirroringcontacts.add_tag:- tag normalization + dedupe
- generated colors
- tag definition upsert (conflict ignore)
- mapping insert conflict ignore
- returns
added+existing
-
F059: Implementedprojects.add_tag:- schema with
project_id,tags[], optionalidempotency_key - action-provided idempotency metadata
- project validation +
project:updatepermission - added/existing counts + audit logging
- schema with
-
F060: Implementedprojects.add_task_tag:- schema with
task_id,tags[], optionalidempotency_key - action-provided idempotency metadata
- task/project context validation +
project:updatepermission - added/existing counts + audit logging
- schema with
Completed tests
T019: DB-backed link action happy path validates both link tables and project/phase metadata.T020: DB-backed link idempotency test validates repeat calls do not duplicate effective links and report existing state.T021: DB-backed project tag action test validates definition creation, idempotent mappings, and added/existing counts.T022: DB-backed project-task tag action test validates definition creation, idempotent mappings, and added/existing counts.
Commands/runbook used
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts
Gotchas discovered
project_ticket_links/ticket_entity_linksidempotency needed explicit existing-row fallback because unique constraints vary by schema/migration state.- Tag helper had to be column-aware for
tag_definitions/tag_mappingsto remain robust across branch schema variants.
Implementation checkpoint — 2026-04-27 (Picker resources + compatibility closeout)
Completed features
-
F061: Registered project fixed-picker resource support in workflow designer pickers:- added
projectto supported fixed-picker resources - picker now routes through fixed-value control path alongside existing resources
- added
-
F062: Registered project-phase picker resource support with dependency handling:- added
project-phasesupport - dependency hint defaults include
project_id,target_project_id, andfilters.project_id
- added
-
F063: Registered project-task picker resource support with dependency handling:- added
project-tasksupport - dependency hint defaults include project + phase scoped paths (
project_id,phase_id,target_project_id,target_phase_id, filter variants)
- added
-
F064: Registered project-task-status picker support with dependency handling:- added
project-task-statussupport - dependency hint defaults include project + phase scoped paths and filter variants
- added
-
F065: Ensured dependency-aware picker metadata is honored end-to-end in fixed-value mode:- new project/phase/task/status kinds surface dependency-disabled explanations until upstream fixed values are provided
- preserved no-tag-picker behavior (tags remain free-text arrays)
-
F066: Confirmed new project actions are present in workflow registry metadata with labels/descriptions/categories/schema/side-effect-idempotency metadata. -
F067: Confirmed side-effectful project actions continue using standardized action errors, tenant-scoped mutations, and workflow run audit records. -
F068: Verifiedprojects.create_taskcompatibility remains intact after shared helper/picker expansion.
Completed tests
T001: Registry metadata test verifies new project actions are registered with expected flags/schemas/idempotency metadata.T005: DB-backed authorization/tenant-scope test verifies project find/search/task search behavior is tenant scoped and authorization aware (with schema-aware branch handling whenusers.client_idis unavailable).T023: Schema/picker metadata assertions validate project/phase/task/status picker metadata + dependency paths and absence of tag picker metadata.T024: Workflow designer fixed-picker test validates project-related pickers render and dependency-disabled behavior is shown until upstream fixed values exist.T025: Compatibility regression test verifiesprojects.create_taskremains registered and functional.
Commands/runbook used
cd shared && npx vitest run workflow/runtime/actions/__tests__/registerProjectActionsMetadata.test.ts workflow/runtime/actions/__tests__/businessOperations.projects.db.test.tscd ee/server && npx vitest run src/components/workflow-designer/__tests__/InputMappingEditorPickerFields.test.tsx -t "T024"
Gotchas discovered
- Project fixed pickers use async option loading in fallback
CustomSelectmode (non-dedicated picker kinds), so immediate enabled assertions can be flaky; T024 now useswaitForon project picker readiness. - Authorization fixture assumptions can differ across schema variants (
users.client_idoptional); T005 now guards for column presence to keep assertions meaningful in both variants.