Verify substrate before proposing new
Before proposing a new table, type, capability, epic, or substrate concept — grep the live codebase + the live backlog for the noun. DPF’s architecture is denser than first reads suggest; the most common reflex of “we’ll need a new X” is wrong because X already exists.
What to grep for, where
When tempted to propose a new substrate:
- Prisma schema:
grep -n "model NameOfThing" packages/db/prisma/schema.prisma. Many candidate “new tables” turn out to be columns or relations on existing models. - Type unions / enums:
grep -rn "type NameOfThing\|enum NameOfThing" packages/types/ apps/web/lib/. Some “new states” are already values in existing string-union types. - Backlog: query the live backlog via
mcp__dpf__query_backlogorpsqlforEpic.title/BacklogItem.titlekeyword matches. “New” epics often duplicate work already in flight. - Capability registry (
packages/db/data/...JSON files): new “we need a capability for X” is usually a column / flag on existingCapability/ServiceOffering/ModelProviderrows. - Spec frontmatter: search
docs/superpowers/specs/for the noun. Frequently the design decision has already been resolved in anAPPROVEDspec months ago.
Why this exists
Concrete examples:
- “Let’s add a
Backuptable” →BackupRunalready existed in the schema (added in the same PR week) → caught before the migration draft - “Let’s add a
Capability.tierenum” →capabilityCategoryalready carried the discriminator → renaming would have broken the routing-layer joins - “Let’s create an epic for restore wizard” →
EP-PLATFORM-BACKUPalready existed and had it as a backlog item
The contract
Before writing a spec / plan that proposes new substrate:
- Grep first, write second.
- Note the closest existing fit in the spec — even if you’re rejecting it, the reader needs to know why a new substrate is warranted.
- If existing substrate fits with minor extension (a new column, a new enum variant, a new association table) — propose the extension, not a parallel substrate.
- If the existing fit is misnamed for the new use, prefer renaming + extending over duplicating. Naming churn is cheap; parallel substrates are expensive forever.
Anti-pattern
- Proposing
WorkItemwhenBacklogItemalready exists - Proposing
JobStatuswhenJob.statusalready exists with the needed values - Proposing a new epic for work already tracked in an existing epic
Related principles
consult-specs-first— the design-time counterpartsweep-main-before-trusting-worktree-specs— worktree specs can be stale; check origin/main for the actual stateone-data-model— the architectural reason this matters