Data Model Radar
SkillAI & modelsAudits SwiftData/Core Data model layer for field completeness, serialization gaps, relationship integrity, semantic ambiguity, dead fields, and migration safety. Finds model-layer bugs that manifest as workflow bugs. Triggers: "audit models", "model radar", "/data-model-radar".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Data Model Radar skill
What this skill tells your AI
The instructions your AI receives, as published by terryc21/radar-suite in skills/data-model-radar/SKILL.md and read by ahel’s review.
Audits the @Model layer for completeness, consistency, and round-trip integrity. Finds model-layer bugs before they manifest as workflow bugs.
Anti-shortcut rule: Do not claim a domain is "clean" without evidence. Every domain grade must cite specific files read and patterns checked. "No dead fields detected from structural analysis" without grepping is a failing grade for the auditor, not a passing grade for the model.
Quick Commands
| Command | Description |
|---|---|
/data-model-radar | Full audit across all model-layer domains (9 numbered + 2 sub-domains under Domain 3 + Domain 8 delegated to time-bomb-radar) |
/data-model-radar [ModelName] | Audit a single model in depth |
/data-model-radar models | Show all models with risk ranking (no audit, discovery only) |
/data-model-radar serialization | Domain 2 only — backup/export coverage |
/data-model-radar relationships | Domain 3 only — cascade rules, orphan risk |
/data-model-radar migration | Domain 6 only — schema version safety |
/data-model-radar dead-fields | Domain 5 only — unused model fields |
/data-model-radar time-bombs | Domain 8 only — deferred operations on aged data |
/data-model-radar status | Show audit progress |
--show-suppressed | Show findings suppressed by known-intentional entries |
--accept-intentional | Mark current finding as known-intentional (not a bug) |
Overview
Data Model Radar audits the foundation your app is built on — the data models. Every UI bug, every sync failure, every round-trip data loss traces back to a model-layer decision. This skill finds those issues at the source instead of waiting for them to surface in workflows.
| Domain | What It Finds | Est. Time |
|---|---|---|
| 1. Field Completeness | Missing fields, enum gaps, semantic holes | ~3-5 min |
| 1.5 Computed Properties | Business logic bugs in computed properties (nil chains, fallback defaults, currency math) | ~5-10 min |
| 2. Serialization Coverage | Backup/export fields that don't round-trip | ~10-20 min |
| 3. Relationship Integrity | Cascade rules, inverse relationships, orphan risk | ~3-5 min |
| 3a. Cross-Context Mutation | @Model param mutated in a manager's own context = crash | ~3-5 min |
| 3b. Stale Object After Cross-Context Save | Manager saves in own context; caller's @Model reference goes stale | ~3-5 min |
| 4. Semantic Clarity | nil vs zero ambiguity, missing type distinctions | ~2-3 min |
| 5. Field Usage Mapping | Dead fields (no UI reads), phantom fields (UI shows, model doesn't store) | ~10-20 min |
| 6. Migration Safety | Schema versions, VersionedSchema coverage, migration plan gaps | ~5-10 min |
| 7. Cross-Model Consistency | Identifier strategy, naming conventions, shared pattern violations | ~3-5 min |
| 7.5 Near-Duplicate Detection | Models sharing 70%+ fields that should be consolidated | ~3-5 min |
| 8. Time Bombs (delegated to time-bomb-radar) | Deferred operations on aged data — cascade deletes, cache expiry, scheduled side effects | ~5-10 min |
Skill Introduction (MANDATORY — run before anything else)
This section replaces radar-suite-core.md § Session Setup for the data-model-radar entry point. Do NOT also run core's 4-question Session Setup — its questions are consolidated below. Other radar-suite skills entered via their own SKILL.md files have their own equivalent sections.
On first invocation, ask the user all four setup questions in a single AskUserQuestion call:
Question 1: "What's your experience level with Swift/SwiftUI?"
- Beginner — New to Swift. Plain language, analogies, define terms on first use.
- Intermediate — Comfortable with SwiftUI basics. Standard terms, explain non-obvious patterns.
- Experienced (Recommended) — Fluent with SwiftUI. Concise findings, no definitions.
- Senior/Expert — Deep expertise. Terse, file:line only, skip explanations.
Question 2: "Table format?"
- Full tables (Recommended) — 8-column Issue Rating Tables
- Compact tables — 3-column with details below
Question 3: "Fix handling?"
- Auto-fix safe items (Recommended) — Apply isolated, low-blast-radius fixes automatically. Present cross-cutting fixes and design decisions for approval first.
- Review first — Present all findings with ratings, then ask before making any changes. Fixes still happen — you just approve each wave first.
- Batch mode — Approve all fixes in each wave at once.
IMPORTANT: All three fix modes lead to fixes. "Review first" means the user sees the plan before code changes — it does NOT mean "skip fixes and jump to handoff." After presenting findings, ALWAYS offer to fix them regardless of which mode was selected.
Question 4: "Would you like a brief explanation of what this skill does?"
- No, let's go (Recommended) — Skip explanation, proceed to audit.
- Yes, explain it — Show a 3-5 sentence explanation adapted to the user's experience level (see below), then proceed.
Store as: USER_EXPERIENCE, TABLE_FORMAT, FIX_MODE. Apply to ALL output for session, per radar-suite-core.md § Experience-Level Output Rules. Also persist these to .radar-suite/session-prefs.yaml per radar-suite-core.md § Session Persistence.
Experience-adapted explanations for Data Model Radar:
-
Beginner: "Data Model Radar checks the blueprints your app is built on — the data models that define what an 'item' or 'warranty' or 'photo' looks like in the database. Think of it like inspecting a building's foundation before checking the rooms. If the blueprint says a house has 10 rooms but only 8 have doors, people can't reach 2 rooms. Similarly, if your Item model has 47 fields but your backup only saves 40, those 7 fields are lost forever when someone restores from backup. This skill finds those gaps."
-
Intermediate: "Data Model Radar audits your @Model classes for field completeness (missing enums, semantic holes), serialization coverage (backup/export round-trip gaps), relationship integrity (cascade rules, orphan risk), nil-vs-zero ambiguity, dead fields (defined but never read), migration safety (schema versions), and cross-model consistency. It finds model-layer bugs that would otherwise surface as workflow bugs across multiple features."
-
Experienced: "Audits @Model layer across 9 numbered domains (1, 1.5, 2, 3, 4, 5, 6, 7, 7.5) plus 2 sub-domains (3a cross-context mutation, 3b stale object) plus Domain 8 (time bombs, delegated to time-bomb-radar). Covers field completeness, computed properties, serialization coverage, relationship integrity, cross-context safety, semantic clarity, usage mapping, migration safety, cross-model consistency, near-duplicate detection. Outputs issue rating tables with fix plans. Findings feed roundtrip-radar as suspects."
-
Senior/Expert: "Model audit: fields → computed → serialization → relationships (+3a/3b cross-context) → semantics → usage → migration → consistency → near-dupes → time-bombs (delegated). Rating tables + fix plans."
Store the experience level as USER_EXPERIENCE and apply to ALL output for the session.
User impact explanations: Can be toggled at any time with --explain / --no-explain. When enabled, each finding gets a 3-line companion explanation (what's wrong, fix, user experience before/after). See the shared rating system doc for format and rules. Store as EXPLAIN_FINDINGS (default: false).
Subsequent models (if auditing multiple): Show one-line reminder:
Using: [Beginner] mode, [Auto-fix] or [Review first], [Display only]. Type "adjust" to change, or press Enter to continue.
Shared Patterns
See radar-suite-core.md for: Rules Summary, Tier System, Pipeline UX Enhancements, Table Format, Rating Table Gate, Plain Language Communication, Work Receipts, Contradiction Detection, Finding Classification, Audit Methodology, Context Exhaustion, Progress Banner, Issue Rating Tables, Handoff YAML schema, Known-Intentional Suppression, Pattern Reintroduction Detection, Experience-Level Output Rules, Implementation Sort Algorithm, short_title requirement.
Pre-Scan Startup (MANDATORY — before any domain scan)
-
Known-intentional suppression: Run the protocol in
radar-suite-core.md § Known-Intentional Suppression. Core owns this — do not restate the steps here. -
Pattern reintroduction detection: Run the protocol in
radar-suite-core.md § Pattern Reintroduction Detection. Core owns this. -
Experience-level auto-apply (data-model-radar local): If
USER_EXPERIENCE= Beginner, auto-setEXPLAIN_FINDINGS = trueand default sort toimpact. If Senior/Expert, default sort toeffort. Apply output rules fromradar-suite-core.md § Experience-Level Output Rules.
Audit Depth
Each domain can be run at two depths:
| Depth | When to Use | What It Does |
|---|---|---|
| Quick | Triage, low-risk models, <10 fields | Structural analysis — read the model, check patterns, report what's visible |
| Deep | High-risk models, >20 fields, multiple serialization targets | Grep every field, read every serialization target, verify every claim |
Default: Deep for models with Risk = High. Quick for Low risk. Ask for Medium risk.
The difference matters. A quick audit of Domain 5 says "no obvious dead fields." A deep audit greps each of the 55 fields and proves it. Quick audits must label their grades with (quick) so the handoff distinguishes verified from unverified.
Risk-Ranking (MANDATORY — before any verification)
Do not start verifying until you have ranked where to go deep. The default behavior is to verify whatever is easiest to read (usually backup code) and skip what's harder (CSV import, CloudKit mapping). Risk-ranking inverts this: verify riskiest first, not easiest first.
Six Signals (Check in Order)
Signal 1: Prior findings exist. Before choosing depth, check:
- Memory files for prior audit results mentioning this model
.agents/ui-audit/*-handoff.yamlfor companion skill findings.agents/research/*-audit.mdfor previous capstone/codebase audit notes
If prior sessions found CSV import loses fields, go deep on CSV — not backup. This is the strongest signal and the cheapest to collect.
Signal 2: Asymmetry between input and output. Count fields on both sides of any serialization target. If export writes 27 columns but import reads 11, the 16-field gap is where data loss hides. Go deep on the smaller side (the reader, not the writer).
Signal 3: Recently changed code.
git log --since="3 months ago" -p -- Sources/Models/{Model}.swift | grep "^+.*var " | head -20
New fields are most likely to be missing from serialization structs. Cross-reference these against backup/CSV/CloudKit code.
Signal 4: Multiple systems touch the same data. Count how many systems read/write each field. A field serialized across backup + CSV + CloudKit + UI = 4 consumers = high risk. A field only in one view = low risk.
Signal 5: Money, identity, and relationships.
Fields with InCents/Price/Value/Cost, identity fields (cloudSyncID, ownerUserRecordID), and @Relationship properties have higher consequences when gaps exist.
Signal 6: The "looks clean" feeling (meta-signal). When you feel confident about a domain without having produced its required artifact — that IS the signal to stop and do the work. Confidence without evidence is the #1 predictor of shallow work.
Risk-Ranking Output
Before starting Domain 1, produce a risk-ranking table:
Risk Ranking for [Model]:
GO DEEP:
1. CSV import (Signal 2: 27 export vs 11 import — 16-field asymmetry)
2. Price fields (Signal 5: 6 currency fields across 3 serialization targets)
3. Fields added since Build 24 (Signal 3: 4 new fields in last 3 months)
QUICK OK:
4. Backup (Signal 1: prior audit found full coverage)
5. Relationship integrity (low change rate, all cascade)
NOT APPLICABLE:
6. CloudKit (no CKRecord mapping code found)
This table determines where time is spent. Domains that touch "GO DEEP" items get deep verification. Domains that only touch "QUICK OK" items can use quick verification (labeled accordingly).
Step 0: Model Discovery
Scan the codebase and identify all data models. This step runs automatically before any audit and is also available standalone via /data-model-radar models.
How to Find Them
IMPORTANT: Models can live anywhere under Sources/, not just Sources/Models/. Scan the full tree.
- Search for
@Modelclasses across ALL of Sources/:Grep pattern="@Model" glob="**/*.swift" path="Sources/" - Search for Core Data entities if applicable:
Glob pattern="**/*.xcdatamodeld" - Search for Codable structs used in serialization: backup structs, export structs
- Search for relationship models:
Grep pattern="@Relationship" glob="**/*.swift" path="Sources/" - Check for recently changed models:
git log --since="3 months ago" --name-only --diff-filter=M -- "Sources/**/*.swift"(note:**not justSources/Models/)
Common locations missed if only checking Sources/Models/:
Sources/Features/*/(feature-specific models like ScoutBookmark, ScoutRecentScan)Sources/Data/orSources/Database/- Root of
Sources/itself
Risk Criteria
| Risk | Criteria |
|---|---|
| High | Many fields (>20) + multiple serialization targets + external sync or externalStorage |
| Medium | Moderate fields (10-20) + some serialization |
| Low | Simple model (<10 fields), local only, cache/ephemeral |
Risk boosters (promote a model one level):
- Has
@Attribute(.externalStorage)on any field (delete/sync crash risk) - Changed in the last 3 months (new fields may be missing from serialization)
- Has prior findings in
.radar-suite/ledger.yaml - Is referenced by 3+ other models (hub model, high blast radius)
Output: Risk-Ranked Model Inventory
Present models grouped by risk level, sorted within each group by field count descending. Include recently-changed indicator and prior finding count.
Found [N] @Model classes. Ranked by audit priority:
HIGH RISK (recommend auditing first)
1. Item — 88 fields, 10 rels, Backup+CSV+CloudKit, changed 3d ago
2. ExtendedWarranty — 43 fields, 1 rel, Backup+CloudKit
3. RMARecord — 41 fields, 1 rel, Backup, changed 2w ago
MEDIUM RISK
4. DonationRecord — 30 fields, 0 rels, Backup+CSV, externalStorage ⚠
5. MaintenanceTask — 28 fields, 1 rel, Backup
6. ScoutBookmark — 25 fields, 0 rels, Backup, externalStorage ⚠
...
LOW RISK (cache/ephemeral, audit only if time permits)
18. AIResponseCache — 14 fields, cache only, externalStorage ⚠
...
[N] models with prior findings: [list RS-NNN IDs if any]
Mark externalStorage models with ⚠ since they have elevated delete/sync crash risk.
Standalone Command: /data-model-radar models
When invoked with the models argument, run ONLY Step 0 (discovery) and present the risk-ranked inventory. Do not start any audit. Do not ask setup questions. This is a read-only browse command.
After presenting the inventory, offer:
Options:
1. Audit a specific model — pick from the list above
2. Audit all High Risk models — deep audit of [N] models (~[time])
3. Full audit — all [N] models across 7 domains (~[time])
4. Done — exit without auditing
Model Selection Menu (when user chooses "Audit a specific model")
When the user selects a single-model audit (either from the interactive menu or via /data-model-radar [ModelName]), always run Step 0 first to present the risk-ranked inventory before asking which model. Do NOT ask the user to name a model blind.
Exception: If the user provided a model name directly (e.g., /data-model-radar Item), skip the selection menu and audit that model immediately. The user already knows what they want.
After presenting the inventory, ask:
Which model to deep-audit? (enter number, model name, or "all high")
The AskUserQuestion options should list the top 4 models by risk as selectable options, with "Other" allowing any model name.
Recently Changed Fields (Deep mode)
For high-risk models, identify fields most likely to have serialization gaps:
git log --since="3 months ago" -p -- Sources/Models/Item.swift | grep "^+.*var " | head -20
Fields added recently are highest risk -- they may not have been added to backup/CSV/CloudKit structs yet. Audit these first in Domain 2.
Step 1: Per-Model Audit
Audit one model at a time across all 9 numbered domains (1, 1.5, 2, 3, 4, 5, 6, 7, 7.5) plus the 2 sub-domains under Domain 3 (3a, 3b) plus Domain 8 (delegated to time-bomb-radar).
Before Starting (First Model Only)
FIX_MODE was already captured during the Skill Introduction setup call. Confirm the remaining per-session preferences in one AskUserQuestion:
- Delivery: Display only / Report / Both
- Presence: Normal / Hands-free / Pre-approved
If FIX_MODE has not been set for some reason (e.g. session-prefs file deleted mid-session), re-ask Question 3 from § Skill Introduction before proceeding.
Domain 1: Field Completeness enumerate-required
What to check:
- Are there fields that should exist based on how the model is used?
- Grep for hardcoded strings/enums that could be model fields (e.g.,
"inherited"string where anacquisitionTypeenum should exist) - Check if sibling models have fields this model lacks (e.g., all models have
cloudSyncIDexcept one)
- Grep for hardcoded strings/enums that could be model fields (e.g.,
- Are enums complete? Check every
switchstatement — are theredefaultcases hiding missing enum values? - Are optional fields appropriately optional? (Should
warrantyMonthsreally be non-optional with default 12?)
Output per finding: What field is missing, why it should exist, which code paths would use it.
Domain 1.5: Computed Property Correctness mixed
Models often embed business logic in computed properties (e.g., effectiveReplacementCostInCents, isNearingEndOfLife, warrantyStatus). A wrong computation is a model-layer bug that affects every view consuming it.
What to check:
- Nil propagation chains: Computed properties that chain optional fields. Does
effectiveReplacementCostInCentscorrectly fall back fromreplacementCostInCentstopriceInCentsto0? Does the fallback chain match business intent? - Fallback default correctness: When a computed property returns a default for nil input, is the default appropriate? Returning
0for a missing price may be wrong if downstream code interprets0as "free" rather than "unknown." - Stale input assumptions: Properties like
isReplacementCostStalethat useassetAge > 2.0embed a threshold. Is the threshold reasonable? Is it documented? Could it be a user-configurable setting? - Circular dependencies: Property A reads property B which reads property A. SwiftData models can create subtle cycles through relationship traversal.
- Calendar/date correctness: Properties computing durations (
assetAge,daysRemaining) that use hardcoded values like365.25instead ofCalendarAPIs. Check for leap year handling, timezone assumptions. - Currency computation safety: Properties combining
*InCentsfields. Verify they handle nil correctly and don't accidentally mix dollars and cents.
Method (Deep):
- Grep the model file and its extensions for
var.*:.*{(computed properties with getters) - For each computed property that involves arithmetic, date math, or chained optionals, read the implementation
- Check: are the inputs always available when this property is accessed? What happens when they're nil?
- For currency computations, verify consistent units (all cents, no mixed dollars/cents)
Method (Quick):
- List computed properties from the model file
- Check only currency and date computations
- Label grade as
(quick)
Output per finding: The property, what it computes, what's wrong, and a concrete example of incorrect output.
Domain 2: Serialization Coverage enumerate-required
MANDATORY CHECKLIST — verify each target explicitly:
For the model being audited, check ALL applicable serialization targets. Do not skip any.
- Backup struct — Read the
BackupXxxstruct. Diff every model field against backup fields. List gaps. - CSV export — Find the CSV export code (e.g.,
CSVExportManager,ExportManager). Read it. List which model fields map to CSV columns and which don't. - CSV import — Find the CSV import code (e.g.,
CSVImportManager). Read it. List which CSV columns map back to model fields. The export→import gap is where data loss hides. - CloudKit sync — Find CKRecord mapping code (e.g.,
CloudSyncManager,SharedZoneSyncManager). List synced fields. - JSON API — If the model receives data from an API, check the response mapping.
Do not report a target as "covered" without reading the actual code. "Backup looks complete" without reading BackupItem is not verification.
Method:
- Read the model — list all stored properties (exclude
@Transientcomputed properties) - Read each serialization struct/function listed above
- Diff: model fields NOT in serialization = potential gap
- For each gap, classify using the Intentional Exclusion Framework below
Intentional Exclusion Framework
Not every serialization gap is a bug. Some fields are intentionally excluded from certain targets. Before reporting a gap as a finding, classify it:
| Classification | Meaning | Action | Example |
|---|---|---|---|
| Gap (report) | Field carries user data that would be lost on round-trip | Report as finding | purchasePrice missing from CSV import |
| Intentional: format mismatch | Field type doesn't fit the target format | Document, don't report | priceHistoryData (JSON blob) excluded from CSV |
| Intentional: internal metadata | Field is sync/system infrastructure, not user data | Document, don't report | cloudSyncID, cloudKitRecordID excluded from CSV |
| Intentional: relationship data | Nested objects that need their own serialization | Document, note if the child model IS serialized | attachments excluded from CSV (binary data) |
| Intentional: scope boundary | Target intentionally covers a subset | Document, don't report | SharedZone CloudKit syncs 22 fields (household subset) |
How to classify:
- User data test: If this field were lost during export-import, would the user notice? If yes, it's a gap. If no (sync IDs, internal timestamps), it's intentional.
- Format test: Could this field reasonably be represented in the target format? JSON blobs and binary data can't go in CSV. That's a format mismatch, not a gap.
- Subset test: Does the target explicitly define a subset? (e.g., SharedZone syncs "household-visible" fields only.) If the exclusion matches the subset definition, it's intentional scope.
Output: In the Domain 2 coverage table, add an Exclusion column for fields not in a target:
| Field | Model | Backup | CSV Export | CSV Import | CloudKit | Exclusion Reason |
|-------|:-----:|:------:|:---------:|:----------:|:--------:|-----------------|
| cloudSyncID | yes | yes | no | no | yes | Internal metadata |
| priceHistoryData | yes | yes | no | no | yes | Format mismatch (JSON blob) |
| attachments | yes | yes | no | no | yes | Relationship data (binary) |
Grade impact: Intentional exclusions do NOT lower the domain grade. Only unclassified gaps or gaps classified as "report" lower the grade.
Output: Side-by-side coverage table:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 20
- Forks
- 1
- Last commit
- Aug 2026
ahel review
K1binfo
installs-packages (in radar-suite-core.md)
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
data-model-radar- Source
- github.com/terryc21/radar-suite