Data Model Radar

SkillAI & models

Audits 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.

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

CommandDescription
/data-model-radarFull 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 modelsShow all models with risk ranking (no audit, discovery only)
/data-model-radar serializationDomain 2 only — backup/export coverage
/data-model-radar relationshipsDomain 3 only — cascade rules, orphan risk
/data-model-radar migrationDomain 6 only — schema version safety
/data-model-radar dead-fieldsDomain 5 only — unused model fields
/data-model-radar time-bombsDomain 8 only — deferred operations on aged data
/data-model-radar statusShow audit progress
--show-suppressedShow findings suppressed by known-intentional entries
--accept-intentionalMark 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.

DomainWhat It FindsEst. Time
1. Field CompletenessMissing fields, enum gaps, semantic holes~3-5 min
1.5 Computed PropertiesBusiness logic bugs in computed properties (nil chains, fallback defaults, currency math)~5-10 min
2. Serialization CoverageBackup/export fields that don't round-trip~10-20 min
3. Relationship IntegrityCascade 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 SaveManager saves in own context; caller's @Model reference goes stale~3-5 min
4. Semantic Claritynil vs zero ambiguity, missing type distinctions~2-3 min
5. Field Usage MappingDead fields (no UI reads), phantom fields (UI shows, model doesn't store)~10-20 min
6. Migration SafetySchema versions, VersionedSchema coverage, migration plan gaps~5-10 min
7. Cross-Model ConsistencyIdentifier strategy, naming conventions, shared pattern violations~3-5 min
7.5 Near-Duplicate DetectionModels 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)

  1. Known-intentional suppression: Run the protocol in radar-suite-core.md § Known-Intentional Suppression. Core owns this — do not restate the steps here.

  2. Pattern reintroduction detection: Run the protocol in radar-suite-core.md § Pattern Reintroduction Detection. Core owns this.

  3. Experience-level auto-apply (data-model-radar local): If USER_EXPERIENCE = Beginner, auto-set EXPLAIN_FINDINGS = true and default sort to impact. If Senior/Expert, default sort to effort. Apply output rules from radar-suite-core.md § Experience-Level Output Rules.


Audit Depth

Each domain can be run at two depths:

DepthWhen to UseWhat It Does
QuickTriage, low-risk models, <10 fieldsStructural analysis — read the model, check patterns, report what's visible
DeepHigh-risk models, >20 fields, multiple serialization targetsGrep 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.yaml for companion skill findings
  • .agents/research/*-audit.md for 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.

  1. Search for @Model classes across ALL of Sources/: Grep pattern="@Model" glob="**/*.swift" path="Sources/"
  2. Search for Core Data entities if applicable: Glob pattern="**/*.xcdatamodeld"
  3. Search for Codable structs used in serialization: backup structs, export structs
  4. Search for relationship models: Grep pattern="@Relationship" glob="**/*.swift" path="Sources/"
  5. Check for recently changed models: git log --since="3 months ago" --name-only --diff-filter=M -- "Sources/**/*.swift" (note: ** not just Sources/Models/)

Common locations missed if only checking Sources/Models/:

  • Sources/Features/*/ (feature-specific models like ScoutBookmark, ScoutRecentScan)
  • Sources/Data/ or Sources/Database/
  • Root of Sources/ itself

Risk Criteria

RiskCriteria
HighMany fields (>20) + multiple serialization targets + external sync or externalStorage
MediumModerate fields (10-20) + some serialization
LowSimple 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 an acquisitionType enum should exist)
    • Check if sibling models have fields this model lacks (e.g., all models have cloudSyncID except one)
  • Are enums complete? Check every switch statement — are there default cases hiding missing enum values?
  • Are optional fields appropriately optional? (Should warrantyMonths really 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 effectiveReplacementCostInCents correctly fall back from replacementCostInCents to priceInCents to 0? 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 0 for a missing price may be wrong if downstream code interprets 0 as "free" rather than "unknown."
  • Stale input assumptions: Properties like isReplacementCostStale that use assetAge > 2.0 embed 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 like 365.25 instead of Calendar APIs. Check for leap year handling, timezone assumptions.
  • Currency computation safety: Properties combining *InCents fields. Verify they handle nil correctly and don't accidentally mix dollars and cents.

Method (Deep):

  1. Grep the model file and its extensions for var.*:.*{ (computed properties with getters)
  2. For each computed property that involves arithmetic, date math, or chained optionals, read the implementation
  3. Check: are the inputs always available when this property is accessed? What happens when they're nil?
  4. For currency computations, verify consistent units (all cents, no mixed dollars/cents)

Method (Quick):

  1. List computed properties from the model file
  2. Check only currency and date computations
  3. 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 BackupXxx struct. 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:

  1. Read the model — list all stored properties (exclude @Transient computed properties)
  2. Read each serialization struct/function listed above
  3. Diff: model fields NOT in serialization = potential gap
  4. 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:

ClassificationMeaningActionExample
Gap (report)Field carries user data that would be lost on round-tripReport as findingpurchasePrice missing from CSV import
Intentional: format mismatchField type doesn't fit the target formatDocument, don't reportpriceHistoryData (JSON blob) excluded from CSV
Intentional: internal metadataField is sync/system infrastructure, not user dataDocument, don't reportcloudSyncID, cloudKitRecordID excluded from CSV
Intentional: relationship dataNested objects that need their own serializationDocument, note if the child model IS serializedattachments excluded from CSV (binary data)
Intentional: scope boundaryTarget intentionally covers a subsetDocument, don't reportSharedZone 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