- FEAT β Task app CrudGen-first refactor map
- Purpose
- Key observation
- Resource-by-resource map
- 1) Projects
- 2) Tasks
- 3) Events
- 4) External refs
- 5) Sync states
- Cross-cutting refactor rules
- Rule 1 β Keep existing task-system DTO intent where it is already good
- Rule 2 β Move custom behavior downward
- Rule 3 β Separate standard CRUD from semantic operations
- Rule 4 β Preserve Omni-only persistence
- Likely implementation sequence for task-app
- Current status
FEAT β Task app CrudGen-first refactor map
Status: draft / in progress
Related plan: docs/todo/FEAT-crudgen-first-omnikernel-task-app-plan.md
Purpose
This document breaks the task-app refactor down resource by resource so the migration back to a CrudGen-first architecture stays reviewable.
The idea is to avoid a vague βrewrite task-app with CrudGenβ effort and instead define, for each resource:
- what the current legacy/task-system surface already models well
- what can stay generated by CrudGen
- what should move into Omni-backed service/repository overrides
- what should remain custom only if strictly necessary
Key observation
The old task-system-module is actually very useful as a surface model reference.
Its DTOs already express most of the app-facing contract we want to preserve:
TaskProjectTypeTaskItemTypeTaskEventTypeTaskExternalRefTypeTaskSyncStateType
And several of those DTOs already use CrudGen relation metadata correctly, for example:
TaskProjectType.tasksTaskProjectType.eventsTaskItemType.projectTaskEventType.project
This means the future task-app should probably reuse the DTO modeling patterns much more aggressively, instead of rebuilding the whole surface manually.
Resource-by-resource map
1) Projects
Desired public meaning
Project/container resource.
Omni mapping
- storage target:
OmniCollectionEntity - semantic kind: collection/container
- membership relation to tasks/events:
contains
CrudGen-first target
Prefer:
- generated GraphQL CRUD via
resolverFactory - generated REST CRUD via
crudRestControllerFactory - DTO keeps relation metadata for:
tasksevents
Custom logic should live in
- service override:
- normalize/create/update as collection semantics
- enforce
kind/collectionKind - event-manager logging/hooks
- repository override only if relation/grid behavior cannot be expressed with existing repo path
Avoid
- handwritten CRUD controller for basic project CRUD
- handwritten get/update/delete GraphQL surface
2) Tasks
Desired public meaning
Task resource inside a project/container.
Omni mapping
- storage target:
OmniRecordEntity - semantic kind:
task - project membership:
contains - optional semantic cross-links:
referencesrelated_to
CrudGen-first target
Prefer:
- generated GraphQL CRUD/grid
- generated REST CRUD
- DTO keeps relation metadata for:
project- potentially future derived/semantic fields
Custom logic should live in
- service override:
- map task DTO/input -> Omni record payload
- synchronize
contains - synchronize
references/related_to - event-manager logs/errors/events
- optional API strategy calls for downstream sync/orchestration
- repository override only when generic grid/filter/join support needs semantic adaptation
Likely custom query need
Potentially a semantic query for relation-heavy views if generic grid is not expressive enough yet.
Avoid
- handwritten CRUD mutation/query surface unless relation semantics truly require it
3) Events
Desired public meaning
Calendar-like event resource associated to a project.
Omni mapping
- storage target:
OmniRecordEntity - semantic kind:
event - project membership:
contains
CrudGen-first target
Prefer:
- generated GraphQL CRUD/grid
- generated REST CRUD
- DTO keeps relation metadata for
project
Custom logic should live in
- service override:
- event payload mapping
containsmembership sync- event-manager hooks
Avoid
- manual CRUD surface for standard event lifecycle
4) External refs
Desired public meaning
External system identity binding.
Omni mapping
- storage target:
OmniExternalRefEntity - semantic uniqueness:
(provider, externalId, account, container) - internal target type maps to collection/document/record as needed
CrudGen-first target
Prefer:
- generated GraphQL CRUD/grid
- generated REST CRUD
Custom logic should live in
- service override:
- semantic upsert/deduping
- internal target validation
- provider/account/container identity handling
- event-manager errors for invalid refs
Avoid
- hand-rolled endpoints just to support upsert semantics; start from generated CRUD and extend service behavior first
5) Sync states
Desired public meaning
Synchronization state resource tied to an external ref.
Omni mapping
- storage target:
OmniRecordEntity - semantic kind:
sync-state - relation to ref currently modeled by
externalRefIdin payload/DTO surface
CrudGen-first target
Prefer:
- generated GraphQL CRUD/grid
- generated REST CRUD
Custom logic should live in
- service override:
- payload mapping
- external-ref existence validation
- event-manager logging/errors
- optional API strategy orchestration for sync lifecycle later
Open design question
Decide whether externalRefId should stay as a direct scalar surface only, or whether OmniKernel should eventually expose a stronger relation pattern for it.
Cross-cutting refactor rules
Rule 1 β Keep existing task-system DTO intent where it is already good
The old DTO layer already captures important relation semantics. Reuse/adapt that before inventing a fresh manual surface.
Rule 2 β Move custom behavior downward
When a task-app resource needs special behavior, prefer this order:
- DTO metadata
- service override
- repository override
- custom query/mutation attached to generated surface
- only then a full handwritten resolver/controller method
Rule 3 β Separate standard CRUD from semantic operations
Examples of standard CRUD:
- create/update/delete/get/list/grid on projects/tasks/events/external-refs/sync-states
Examples of semantic operations that may justify custom surfaces later:
- βlink this task to that task as
referencesβ - βmove this task between collections with semantic event emissionβ
- βsync this resource with provider X nowβ
Do not let semantic operations force the entire resource back into a handwritten CRUD stack.
Rule 4 β Preserve Omni-only persistence
Restoring CrudGen-first architecture must not mean restoring legacy task-system storage.
Generated surfaces should sit on top of Omni-backed services/repositories.
Likely implementation sequence for task-app
Phase 1 β DTO/contract alignment
- decide which current task-system DTOs can be reused directly
- decide which must become Omni-aware DTOs with
copyFrom/ metadata reuse - normalize relation metadata so CrudGen can do more automatically
Phase 2 β Service/repository alignment
- make Omni-backed service overrides the primary semantic layer
- decide which queries need repository specialization
- remove behavior from handwritten controllers/resolvers that belongs in services
Phase 3 β Restore generated surfaces
- projects generated via CrudGen
- tasks generated via CrudGen
- events generated via CrudGen
- external refs generated via CrudGen
- sync states generated via CrudGen
Current note:
tasksnow uses dedicated CrudGen DTOs plus generated GraphQL/REST surfaces, while the Omni-backed service override keepscontains,references, andrelated_tosemantics below the API layer.- The generated REST path now relies on a framework-level flat-query equality mapping, so collection filters such as
GET /tasks?projectId=...can stay generated-first instead of forcing a handwritten list controller. projectsandeventsnow use dedicated CrudGen DTOs plus generated GraphQL/REST surfaces, while Omni-specific collection/record semantics remain in the service overrides.- The mapper/service split is now explicit:
- base CRUD DTOs stay scalar-first and CrudGen-compatible
- relation fields such as
project.tasks,project.events, andevent.projectare resolved in the GraphQL relation layer instead of being eagerly materialized in the mapper
external-refsnow uses a generated GraphQL surface pluscrudRestControllerFactory, while the Omni-specific dedupe/validation logic stays in the service override.- The slice also flushed out two framework composition issues that are now explicit:
- app-local GraphQL enum names must not collide with CrudGen enum names (
SortDirection,JoinTypes) - example-local dataloader wiring is safer with an explicit provider override than with an implicit event-emitter token that can vary across duplicated package installs
- app-local GraphQL enum names must not collide with CrudGen enum names (
Phase 4 β Custom semantic operations only where needed
- identify remaining true custom operations
- attach them narrowly via custom queries/mutations or dedicated semantic endpoints
Phase 5 β Tests and docs
- move specs into conventional separated folders
- add tests around generated + overridden behavior
- update task-app docs to explain composition clearly
Current status
- initial per-resource map drafted
- align map with exact current task-app code paths
- turn each resource section into concrete implementation tasks