Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Schema Design

ASecurity

Turn what a system must guarantee into tables, keys and constraints — normalization judgement, nullability, foreign key behaviour, and naming that survives. Use when designing new tables, reviewing ORM models or a migration's DDL, or when a bug reduces to "the database allowed a row that should be impossible". Not for tuning a slow query, not for engine-specific syntax and features, and not for writing or running the migration that ships the change.

46 stars
0 votes
0 copies
0 views
Added 9/22/2026
ai-agentssqlexpressdatabase

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add nahid-sparktales/agent-dispatcher --skill schema-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Schema Design?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Schema Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nahid-sparktales-schema-design/badge)](https://www.skillsdirectory.com/skills/nahid-sparktales-schema-design)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: schema-design
description: Turn what a system must guarantee into tables, keys and constraints — normalization judgement, nullability, foreign key behaviour, and naming that survives. Use when designing new tables, reviewing ORM models or a migration's DDL, or when a bug reduces to "the database allowed a row that should be impossible". Not for tuning a slow query, not for engine-specific syntax and features, and not for writing or running the migration that ships the change.
---

# Schema design

The schema is the last place a rule is still enforced. Application code, a background job, a psql
session and next year's import script all write to the same tables; only the constraints apply to
all of them.

## When this fires

Designing new tables, adding columns to existing ones, reviewing ORM models or DDL in a migration,
or when a defect traces back to the data model permitting an impossible state. It does not fire
for a query that is merely slow — that is a plan problem, not a modelling one.

## Procedure

1. **Read the existing schema before proposing anything.** Get the real DDL, constraints and
   indexes for the tables involved — `\d+` in psql, `information_schema`, or the migration files
   if that is all the access you have. Note the conventions already in use: plural or singular
   table names, key style, timestamp columns. You are joining a schema, not starting one.
2. **Write each invariant as one sentence.** "An order belongs to exactly one customer." "A
   subscription has at most one active period at a time." "An email identifies at most one
   account." Derive these from behaviour and from what breaks when they are violated, not from the
   nouns in the request. Each will become a constraint or fail to; the ones that fail are your
   risk list.
3. **Separate entities from attributes.** A thing that is only ever reached through its parent and
   is never queried, counted or referenced on its own is a column, not a table. A thing with its
   own lifecycle, identity or history is a table even when there is currently one of them.
4. **Normalize to third normal form by default** — one fact, one place. Denormalize only with a
   measured read problem and a written mechanism that keeps the copy honest (generated column,
   trigger, materialized view, or an application invariant plus a reconciliation job that can
   detect drift). "It will be faster" with no measurement is not a reason, and the measured answer
   is usually an index, not a duplicated column.
5. **Choose keys deliberately.** Every table gets a primary key. A surrogate key is the default
   because natural keys change — but adding one does not excuse you from declaring the natural
   uniqueness as its own UNIQUE constraint, or duplicates become legal and will appear. For a pure
   join table the composite of the two foreign keys is usually the right primary key.
6. **Encode invariants as constraints, not intentions.** NOT NULL for anything the system cannot
   operate without; UNIQUE, including a partial unique index for "at most one active X per Y";
   CHECK for ranges, allowed values and cross-column rules; FOREIGN KEY with an ON DELETE action
   chosen on purpose, since RESTRICT, CASCADE and SET NULL are three different operational
   promises. Any invariant you cannot express declaratively gets named in the design along with
   where it is enforced instead.
7. **Decide what NULL means for each nullable column.** "Not yet known", "not applicable" and
   "none" are three different facts and at most one of them should be NULL; the others want a
   sentinel, a separate flag, or a separate table. Remember NULL compares equal to nothing,
   including itself, so uniqueness and CHECK behave differently around it than readers expect.
8. **Pick types for meaning, not convenience.** Instants as timezone-aware timestamps, dates as
   dates, money as exact decimal and never float, identifiers as the type they actually are. For
   what a specific engine offers, read the engine's own skill rather than guessing.
9. **Name so the name survives.** Follow the existing convention over your preference. snake_case,
   foreign keys as `<referenced_table>_id`, no type in the name, no abbreviation only you
   understand, no reserved words, no column called `data` or `info`. Renaming later is a
   coordinated change across every reader in every deployed version.
10. **Check the model against the queries it will serve.** List the three to five real access
    patterns. If a common one needs a join no key supports, or a scan of an unbounded table, or a
    column that does not exist yet, the model is wrong — fix it now, not with an index later.
11. **Make the change shippable.** Expand then contract: add the new nullable or defaulted column,
    backfill, start writing both, move reads, then drop the old one — with each step deployable
    while the previous application version is still running. Applying any of it to a shared or
    production database is a separate authorized step: hand over the DDL and stop there.

## Checklist

- [ ] Current DDL, constraints and indexes read, not inferred from models
- [ ] Every invariant written down, and each mapped to the constraint that enforces it
- [ ] Invariants that cannot be enforced declaratively are named, with where they are enforced
- [ ] Primary key on every table; natural uniqueness declared even where a surrogate key exists
- [ ] Every foreign key has a deliberate ON DELETE action
- [ ] Every nullable column has a stated meaning for NULL
- [ ] Naming matches the conventions already in the schema
- [ ] The real access patterns were checked against the model
- [ ] The rollout is expand/contract and compatible with the running application version
- [ ] Nothing was applied to a shared database

## Failure handling

- **Only the ORM models are visible.** Design against them, and say plainly that the live schema
  was not inspected — ORM definitions drift from the database, and indexes and CHECK constraints
  often exist in only one of the two.
- **Existing rows violate a constraint you want to add.** Count them first. The count decides the
  plan: clean the data, scope the constraint with a partial index, or enforce it only for new rows.
  Adding a constraint that the current data fails is a failed deployment, not a design.
- **An invariant needs cross-row or cross-table logic.** Say so rather than pretending a CHECK can
  do it. Name the alternative — a partial unique index, an exclusion constraint, a trigger, or a
  serializable transaction — and its cost.
- **Pressure to add a column "for later".** Do not. An unused nullable column is a claim nobody
  maintains; add it when the behaviour arrives.
- **Disagreement about normalization.** Resolve it with the invariant, not with taste: if two
  copies of a fact can disagree and nothing detects it, that settles it.

## Evidence to report

The proposed DDL; the invariant list with the constraint enforcing each; the invariants left
unenforced and where they are enforced instead; row counts for anything an existing table would
have to satisfy; the access patterns checked. Be exact about status — a schema that has been
*designed* or *reviewed* is not one that has been *applied*, and neither is one that has been
*tested* against representative data.

Attribution

nahid-sparktalesnahid-sparktales
View sourceMore from nahid-sparktales →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1066601 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

651 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →