11.2.5 Attribute API-created work items — the `api` planning source and the totality sweep of its consumers
A prerequisite the create endpoint cannot ship without, found by checking the enum rather than the service: WorkItemPlanningSource is native | mcp | manual (prisma/schema.prisma) — there is no value for an item created over the public API. Every write surface Motir has records which surface made the row (create_work_item stamps planningSource: 'mcp' server-side; the web app stamps native), so a v1 POST has exactly three options and two of them are wrong: claim mcp (a false attribution that pollutes every provenance read) or leave it null.
Null is the tempting one and it is the worse one, because it is not inert: lib/workItems/provenanceBackfill.ts exists to CLASSIFY null-provenance rows by heuristic, so v1-created items would eventually be labelled as something they are not — and a row's origin cannot be reconstructed after the fact. Rows created between the endpoint shipping and this being fixed are unattributable permanently, which is what makes this a prerequisite rather than a follow-up.
What to build
- The migration — add
apito thework_item_planning_sourceenum. One value, no data change, no column. Generate it through the repo's documented migration flow (do NOT pointprisma migrate devat the shared development database); verify the generated SQL is a bareALTER TYPE … ADD VALUE. - The totality sweep — 23 files reference
planningSource. Walk them and handle the new value everywhere a lookup keyed off the enum must be TOTAL (the decision ladder's rung-2 rule: a lookup keyed off an enum must be total over every value it can hold):lib/dto/workItems.ts—WorkItemPlanningSourceDto;lib/mappers/workItemMappers.ts— the mapping.app/(authed)/items/[key]/_components/ProvenanceSection.tsx— the human label for an API-planned item, with itsen.jsonkey AND itszh.jsontwin (catalog parity is a gate; a key present in one catalog and absent in the other fails it).lib/workItems/provenanceBackfill.ts— the backfill fills NULLs only, so a row already stampedapimust be left alone; assert it, since this is the mechanism that would otherwise re-label them.- Prefer a shape where a future enum value is a compile error (an exhaustive map /
satisfies Record<Source, …>), not a runtime fallback — the patternlib/mcp/scopes.tsuses.
- No write path changes here. The v1 create route stamps the value; this card only makes a correct value exist and every consumer total over it.
Scope BOUNDARY
Ends at the enum, its consumers and their tests. It ships no route (the create endpoint is 11.2.6), changes no other column, and does not touch plannedWithHarness / plannedWithModel (self-reported provenance, which the API surface may accept later as an additive field under ADR §8). It does not re-classify any existing row.
Acceptance criteria
work_item_planning_sourcecarriesapi, applied by a migration that changes no row.- Every consumer of the enum handles
api: the DTO, the mapper, the provenance renderer and the backfill classifier — with a test that a value added to the enum and left unhandled fails typecheck or a totality assertion, so the next value cannot land silently. - The provenance section renders a distinct label for an API-planned item, and the label's key exists in BOTH
en.jsonandzh.json. - The backfill leaves a row stamped
apiuntouched — asserted directly against real Postgres, since silently re-classifying it is the failure this card exists to prevent. - No existing row's
planningSourcechanges; the migration is additive-only. - The per-file coverage floor (≥90%) holds on every changed file.
Context refs
prisma/schema.prisma—enum WorkItemPlanningSource(line ~1745) and thework_item_planning_sourcemapping.lib/dto/workItems.ts·lib/mappers/workItemMappers.ts— the DTO value and its mapping.app/(authed)/items/[key]/_components/ProvenanceSection.tsx— the renderer and its label keys.lib/workItems/provenanceBackfill.ts—classifyPlanningSource,BackfillablePlanningSource, and the null-only rule.lib/mcp/tools/createWorkItem.ts— the shipped precedent for a surface stamping its own source server-side.lib/mcp/scopes.ts— the typed-total pattern to copy.- Consumer: 11.2.6. Parent story: 11.2.