2.1.2 Service-layer type-parent validation
Estimate: 15m · Depends on: 2.1.1
Add an issuesService (and a thin workItemRepository if Story 1.4 didn't already ship one) enforcing the type-parent rules at the application layer before any write, so the API returns a clean typed error rather than relying on the DB constraint to reject with a raw Postgres error. The DB constraint from 1.4 stays as the structural backstop (defense in depth); this layer is the friendly gate.
Why both layers: the 1.4 DB constraint guarantees integrity even against direct writes, but a 500 from a constraint violation is a poor API contract. The service validates first and throws InvalidParentTypeError (→ 422) with a message naming the offending pair. Same pattern as the workspace invite errors from 1.2.5.
Acceptance criteria
issuesService.assertValidParent(parentType, childType)throws a typedInvalidParentTypeError(codeINVALID_PARENT_TYPE) on illegal pairs.- The create/move paths (built in 2.1.3 and later Stories) call it before writing.
- Depth rule honored: a subtask/bug at the leaf cannot parent anything (delegates to
canParentfrom 2.1.1). - Vitest (real Postgres) proving the service rejects an illegal pair AND that the DB constraint still rejects a direct illegal write (both layers verified).
Context refs
lib/issues/issueTypes.tsfrom 2.1.1lib/services/*+lib/repositories/*patterns +lib/workspaces/errors.ts(typed-error precedent from 1.2.5)- Story 1.4's DB constraint definition