Summary
Establish a parent-child relationship between Clients and Projects so every Project can be organizationally anchored to a Client.
Scope
- Extend
ProjectCreateCommand schema with optional(ClientId) field
- Add new commands:
project.client.assign, project.client.reassign, project.client.unassign
- Add new events:
project.client.assigned, project.client.reassigned (carries previousClientId), project.client.unassigned
- Add
clientId column to projection_projects table (nullable, new migration)
- Update
OrchestrationProject schema with Schema.optional(Schema.NullOr(ClientId)) and Schema.withDecodingDefault(() => null) for backward compatibility
- Update decider to validate target Client exists and is active on assign/reassign
- Update projector to set
clientId on project projection
- Handle cascade on
client.deleted: unassign all Projects belonging to that Client
Constraints
- A Project may have at most one
clientId at any time
- Assigning to an archived or deleted Client is rejected
- Reassign to same Client is a no-op (no event emitted)
- Creating a Project without
clientId is still valid (appears in "Unassigned")
Key files to modify
packages/contracts/src/orchestration.ts — extend ProjectCreateCommand, add new command/event types
apps/server/src/orchestration/decider.ts — new command cases
apps/server/src/orchestration/projector.ts — new event cases + cascade logic
apps/server/src/persistence/Migrations/ — new migration adding client_id column
apps/server/src/persistence/Layers/ProjectionProjects.ts — update SQL queries
Dependencies
- F1 (Client entity must exist first)
Acceptance criteria
Summary
Establish a parent-child relationship between Clients and Projects so every Project can be organizationally anchored to a Client.
Scope
ProjectCreateCommandschema withoptional(ClientId)fieldproject.client.assign,project.client.reassign,project.client.unassignproject.client.assigned,project.client.reassigned(carriespreviousClientId),project.client.unassignedclientIdcolumn toprojection_projectstable (nullable, new migration)OrchestrationProjectschema withSchema.optional(Schema.NullOr(ClientId))andSchema.withDecodingDefault(() => null)for backward compatibilityclientIdon project projectionclient.deleted: unassign all Projects belonging to that ClientConstraints
clientIdat any timeclientIdis still valid (appears in "Unassigned")Key files to modify
packages/contracts/src/orchestration.ts— extendProjectCreateCommand, add new command/event typesapps/server/src/orchestration/decider.ts— new command casesapps/server/src/orchestration/projector.ts— new event cases + cascade logicapps/server/src/persistence/Migrations/— new migration addingclient_idcolumnapps/server/src/persistence/Layers/ProjectionProjects.ts— update SQL queriesDependencies
Acceptance criteria
clientIdclientIdcontinue to work (backward compat)