# 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`, including `project` and `project_task` entity types.
- Plans live under `ee/docs/plans/YYYY-MM-DD-<slug>/` with `PRD.md`, `features.json`, `tests.json`, and `SCRATCHPAD.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 with `on_not_found` and 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 `get` naming 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`) checks `project:delete`, validates project read access, refuses delete when associated `time_entries` exist, deletes ticket links, deletes checklist items, then deletes `project_tasks`.
- Existing phase delete UI action (`packages/projects/src/actions/projectActions.ts`) checks `project:delete`, validates project read access, then calls `ProjectModel.deletePhase`.
- Existing project delete UI action (`packages/projects/src/actions/projectActions.ts`) uses `deleteEntityWithValidation('project', ...)`, cleans up project tags, task tags under phases, project ticket links, email reply tokens, then deletes through `ProjectModel.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: `moveTaskToPhase` supports 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: `duplicateTaskToPhase` supports 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.update` covers project name and description edits, plus only other fields intentionally included by this plan.
  - `projects.update_phase` covers phase name and description edits, plus only other fields intentionally included by this plan.
  - `projects.update_task` covers task title/name, description, assignment, status, phase/project move-adjacent fields only if they do not conflict with the dedicated move action. Prefer dedicated `projects.move_task` for phase/project/status relocation because existing domain logic is non-trivial.
- Avoid palette/action sprawl for `rename_*` and `edit_*_description` variants.

## Assignment action decision

- User decision: use a dedicated `projects.assign_task` action, matching `tickets.assign` fidelity.
- PRD direction:
  - Keep task assignment out of generic `projects.update_task` except possibly preserving current assignment when other fields update.
  - `projects.assign_task` should follow `tickets.assign` shape where practical: required assignment target, optional reason/comment if useful, `no_op_if_already_assigned` default true, output task id/assigned target/updated timestamp.
  - Existing project task model supports `assigned_to` primary user and additional task resources. Need decide if workflow assignment supports only primary user in v1 or primary + additional users.

## Task assignment model decision

- User decision: `projects.assign_task` should 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`.

## Tag behavior discovery/decision

- User instruction: look at contact workflow/tag behavior and match it.
- There is no dedicated `contacts.add_tag` workflow action today; contact/client workflow search actions filter by existing tag text through `tag_mappings` + `tag_definitions` joins.
- 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 `createTag` also get-or-creates tag definitions, requires entity update permission, requires `tag:create` only when the definition is new, creates the mapping, and publishes tag events.
- `createTagsForEntityWithTransaction` get-or-creates definitions and continues on per-tag failures; this is used by imports including project task import.
- PRD direction: `projects.add_tag` should match existing tag application semantics: trim/validate tag text, get-or-create tag definition for tagged type (`project` or `project_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 in `shared/workflow/runtime/actions/businessOperations/contacts.ts` or 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, requires `tag:create` only if creating a new definition, get-or-creates `tag_definitions`, inserts `tag_mappings`, publishes `TAG_DEFINITION_CREATED` and `TAG_APPLIED`, and returns the mapping id as `tag_id` for compatibility.

## Tag action behavior decision/discovery

- User direction: match the existing workflow contact module's `contacts.add_tag` behavior.
- Existing `contacts.add_tag` behavior:
  - Action id: `contacts.add_tag`.
  - Input: `contact_id`, `tags: string[]`, optional `idempotency_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.
- 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_tag` attaches tags to a project.
  - `projects.add_task_tag` attaches tags to a project task.
  - Both mirror `contacts.add_tag` behavior and output shape.

## Ticket-to-task link decision

- User decision: workflow task-ticket link should write both `project_ticket_links` and `ticket_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_links` idempotently/best-effort where duplicate constraints exist.
  - Insert/ensure `ticket_entity_links` with `entity_type: 'project_task'`, `entity_id: task_id`, `link_type: 'project_task'`, and metadata containing project/phase IDs, matching `projects.create_task` precedent.
  - Return IDs and counts/created-vs-existing indicators if practical.

## 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 in `shared/workflow/runtime/actions/businessOperations/projects.ts` for:
  - `projectSummarySchema`
  - `phaseSummarySchema`
  - `taskSummarySchema`
  - `statusMappingSummarySchema`
  - `tagResultSchema`
  - `assignmentResultSchema`
  - `linkResultSchema`
  Rationale: establishes canonical output schema contracts for new action family.

- `F002`: Added reusable entity/context loaders with standardized `NOT_FOUND` errors:
  - `ensureProjectExists`
  - `ensurePhaseExists`
  - `ensureTaskContext`
  - `ensureTicketExists`
  - `ensureStatusMappingExists`
  Rationale: avoid repeated ad-hoc lookup/error logic across action handlers.

- `F003`: Added reusable permission + narrowing helpers:
  - `requireProjectReadPermission`
  - `requireProjectUpdatePermission`
  - `requireProjectDeletePermission`
  - `canReadProject` / `assertProjectReadable`
  Rationale: 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: `withWorkflowPicker`
  Rationale: fixed-value mode can render dependency-aware project entity pickers.

- `F005` + `F006`: Implemented `projects.find` schema + handler:
  - supports `project_id`, exact `name`, optional `external_ref`, `on_not_found`
  - enforces tenant scoping + project read permission + auth narrowing
  - returns nullable `project` summary output

- `F007` + `F008`: Implemented `projects.search` schema + handler:
  - query + filters + pagination
  - deterministic ordering + `first_project` + `total`
  - authorization-filtered results

- `F009` + `F010`: Implemented `projects.find_phase` schema + handler:
  - supports `phase_id` or project-scoped exact `name`
  - supports `on_not_found`
  - enforces project read auth via phase->project context validation

- `F011` + `F012`: Implemented `projects.search_phases` schema + handler:
  - supports project scope, query, filters, pagination
  - deterministic ordering + `first_phase` + `total`
  - auth-filtered by parent project visibility

- `F013` + `F014`: Implemented `projects.find_task` schema + handler:
  - supports `task_id` or scoped exact task `name`
  - supports `on_not_found`
  - validates task->phase->project context and project read authorization

- `F015` + `F016`: Implemented `projects.search_tasks` schema + 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`: Added `shared/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 in
  `shared/workflow/runtime/actions/__tests__/businessOperations.projects.db.test.ts`
  - covers `projects.find` + `projects.search` happy paths, not-found behavior, pagination/tenant scoping
  - covers `projects.find_phase` + `projects.search_phases` project scoping and deterministic ordering
  - covers `projects.find_task` + `projects.search_tasks` scoped lookups, filters, first/total/page metadata

### 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/kernel` in this test runtime; switched to local schema-aware read narrowing helper.
- Test DB schema differs from legacy assumptions (`project_number` required; status column variants; user/client columns vary); test fixtures were made column-aware via `information_schema` checks.
- 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`: Implemented `projects.update` schema+handler:
  - non-empty patch validation (`project_name`, `description`)
  - project update permission + project read auth check
  - changed-field diffing + `no_op` support
  - output includes updated project summary + changed metadata
  - workflow run audit writes include `changed_fields` and `no_op`

- `F019` + `F020`: Implemented `projects.update_phase` schema+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

- `F021` + `F022`: Implemented `projects.update_task` schema+handler:
  - non-empty patch validation for task `task_name` and `description`
  - 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

### 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:update` returns `PERMISSION_DENIED`
  - verifies denied update does not mutate persisted project data

### Checklist correction

- `T001` was initially marked complete during the first checkpoint but that was premature because it requires registration coverage for the full final project action surface. Reset to `implemented:false` until the full action set is present.

## Implementation checkpoint — 2026-04-27 (Task move action)

### Completed features

- `F023`: Added `projects.move_task` input schema with:
  - `task_id`
  - `target_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_code` based on target phase WBS + next ordinal
  - updates `order_key` when supported by schema

- `F027`: Implemented `project_ticket_links` context rewrite on move:
  - updates `project_id` + `phase_id` for all links tied to moved `task_id`

- `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 for `projects.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_links` project/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`

### Gotchas discovered

- Ticket fixture schema for this branch requires adaptive field population (e.g., `client_id` constraints and varying timestamp columns), so the test helper was made schema-aware via `information_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`: Added `projects.assign_task` input schema:
  - `task_id`
  - `primary_user_id`
  - `additional_user_ids` (array; deduped)
  - optional `reason`
  - `no_op_if_already_assigned` default `true`
  - optional `idempotency_key`
  - schema guard that rejects `additional_user_ids` containing 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_id` set
  - honors `no_op_if_already_assigned` (default true)

- `F033`: Implemented assignment mutation and additional-user reconciliation:
  - updates `project_tasks.assigned_to`
  - clears `assigned_team_id` when present
  - replaces `task_resources` rows for the task with the resolved additional users

- `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 for `projects.assign_task`:
  - requires `project:update`
  - validates project readability through task->phase->project context
  - writes `workflow_action:projects.assign_task` audit rows for both no-op and mutation paths

### 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_at` unchanged when no-op short-circuits

- `T013`: Added DB-backed validation failure test validating:
  - inactive primary user rejected
  - missing additional user rejected
  - no partial mutation of `project_tasks.assigned_to` or existing `task_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_resources` has 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`: Added `projects.duplicate_task` input schema with:
  - `source_task_id`
  - `target_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 `0` when schema supports it
  - resolves target status mapping/status similarly to move semantics

- `F038`: Implemented optional checklist copy:
  - copies `task_checklist_items` rows to new task with new ids/timestamps
  - returns copied checklist count

- `F039`: Implemented optional assignment copy:
  - optional primary assignment copy to `project_tasks.assigned_to`
  - optional additional assignment copy via `task_resources`
  - clears `assigned_team_id` on duplicates when that column exists

- `F040`: Implemented optional ticket link copy with permission-aware filtering:
  - checks `ticket:read` permission before copying links
  - copies `project_ticket_links` into target project/phase/task context
  - writes matching `ticket_entity_links` records for duplicated task links

- `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:create` and project read checks for source/target context
  - writes `workflow_action:projects.duplicate_task` audit with target ids and copied counts

### 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_resources` rows
  - ticket-link copy count and copied `project_ticket_links` target 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_tasks` schemas vary slightly across migrations in this branch; test fixtures and assertions were kept column-aware to avoid false failures.
- `task_resources` assignment rows are easiest to keep coherent via full-row clone with refreshed ids/timestamps and target task id, while overriding `assigned_to` according to copy options.

## Implementation checkpoint — 2026-04-27 (Delete task/phase/project actions)

### Completed features

- `F043`: Added `projects.delete_task` input/output schemas with cleanup count fields:
  - input: `task_id`
  - output: `task_id`, `deleted`, `deleted_ticket_link_count`, `deleted_checklist_item_count`

- `F044`: Implemented delete-task guard for associated project task time entries:
  - checks `time_entries` by `work_item_type='project_task'` and `work_item_id=task_id`
  - rejects with `VALIDATION_ERROR` when entries exist

- `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_task` run audit

- `F047`: Added `projects.delete_phase` input/output and handler:
  - input: `phase_id`
  - output: `phase_id`, `project_id`, `deleted`
  - deletes phase with project context validation

- `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_phase` run audit

- `F049`: Added `projects.delete` input/output schemas exposing validation/result fields:
  - input: `project_id`
  - output includes `success`, `deleted`, `can_delete`, `code`, `message`, `dependencies`, `alternatives`

- `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.delete` run audit on mutation path

### 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_entries` schema 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`: Added `projects.link_ticket_to_task` input schema:
  - `task_id`, `ticket_id`, optional `project_id`, optional `phase_id`, optional `idempotency_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 for `project_ticket_links`:
  - idempotent existing-link detection
  - insert-on-miss with created vs existing signaling

- `F055`: Implemented ensure/insert behavior for `ticket_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_id`
  - `project_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`

- `F058`: Added shared project/project-task tag helper behavior mirroring `contacts.add_tag`:
  - tag normalization + dedupe
  - generated colors
  - tag definition upsert (conflict ignore)
  - mapping insert conflict ignore
  - returns `added` + `existing`

- `F059`: Implemented `projects.add_tag`:
  - schema with `project_id`, `tags[]`, optional `idempotency_key`
  - action-provided idempotency metadata
  - project validation + `project:update` permission
  - added/existing counts + audit logging

- `F060`: Implemented `projects.add_task_tag`:
  - schema with `task_id`, `tags[]`, optional `idempotency_key`
  - action-provided idempotency metadata
  - task/project context validation + `project:update` permission
  - added/existing counts + audit logging

### 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_links` idempotency needed explicit existing-row fallback because unique constraints vary by schema/migration state.
- Tag helper had to be column-aware for `tag_definitions`/`tag_mappings` to 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 `project` to supported fixed-picker resources
  - picker now routes through fixed-value control path alongside existing resources

- `F062`: Registered project-phase picker resource support with dependency handling:
  - added `project-phase` support
  - dependency hint defaults include `project_id`, `target_project_id`, and `filters.project_id`

- `F063`: Registered project-task picker resource support with dependency handling:
  - added `project-task` support
  - dependency hint defaults include project + phase scoped paths (`project_id`, `phase_id`, `target_project_id`, `target_phase_id`, filter variants)

- `F064`: Registered project-task-status picker support with dependency handling:
  - added `project-task-status` support
  - dependency hint defaults include project + phase scoped paths and filter variants

- `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`: Verified `projects.create_task` compatibility 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 when `users.client_id` is 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 verifies `projects.create_task` remains 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.ts`
- `cd 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 `CustomSelect` mode (non-dedicated picker kinds), so immediate enabled assertions can be flaky; T024 now uses `waitFor` on project picker readiness.
- Authorization fixture assumptions can differ across schema variants (`users.client_id` optional); T005 now guards for column presence to keep assertions meaningful in both variants.
