# HealthProbe - Multi-Model Development Guide

Canonical path: `HealthProbe/Doc/00-agent-guides/AGENTS.md`

Start every documentation lookup from [`../README.md`](../README.md).

## Overview

HealthProbe is built by multiple AI models, each owning a distinct domain.
This document defines boundaries, interfaces, and handoff contracts.

**Agentic reality:** The repo is developed largely via agents (Codex CLI, Claude, and dedicated model sessions). When updating product scope, update docs first, then implement behind flags, and add tests for the new behavior.

**Objective updated 2026-05-23:** HealthProbe is a single-device local
Health database time machine. It captures the local HealthKit database over time,
lets the user inspect how accessible health data looked at a chosen observation
date, explains what changed between local observations, and preserves exportable
evidence that may no longer be available after Apple Health consolidates,
aggregates, or prunes historical records. The app no longer treats raw
record-count drops as inherently alarming, no longer studies differences between
snapshots from different devices, and does not sync HealthProbe data through
CloudKit/iCloud. Device metadata may still be stored as local provenance for the
current device's chain, but UI and algorithms should compare snapshots only
within one local device timeline.

**Storage decision updated 2026-05-23:** HealthProbe targets legacy devices still
used for Health collection, including iPhone 6s / Apple Watch Series 3 class
setups. SwiftData is therefore not an acceptable long-term foundation because it
requires newer OS versions. The target architecture is:
1. a direct SQLite archive/analysis database as source of truth;
2. differential observation storage, never recurring complete snapshot copies;
3. SQL-first analysis using indexes, joins, CTEs, temporary tables, and paged
   result sets without loading large archives into RAM;
4. a rebuildable Core Data UI/reporting cache for expensive counts, summaries,
   timeline rows, report metadata, and display state.

Current SwiftData models are legacy/prototype implementation details until the
Core Data cache replacement is implemented. New product/storage work should not
expand SwiftData dependency.

**Deployment/reset note updated 2026-05-23:** HealthProbe has no real
deployments, only test installations. Existing SwiftData stores and prototype
SQLite archives are disposable for the archive v2 refactor: agents should reset,
ignore, or reinitialize them rather than building one-way migrations or backward
compatibility layers. Future real archive versions may require migrations, but
the current prototype schema does not.

**Recovery compatibility note updated 2026-05-23:** HealthProbe will not perform
disaster-recovery workflows such as transplanting Health database files into
encrypted iOS backups or re-publishing archived values into HealthKit. However,
the local archive and user exports should preserve enough structure to support
external recovery/salvage procedures: stable record identity, values, dates,
units, source/provenance metadata where available, observation history,
relationships, hashes, and manifests. Recovery compatibility is an archive/export
requirement, not an in-app restore feature.

---

## Model Allocation

| Domain | Owner | Tools |
|--------|-------|-------|
| **UI / SwiftUI Views** | Claude Code | Xcode, SwiftUI, `HealthProbe/Doc/00-agent-guides/CLAUDE.md` |
| **Archive Store** | Dedicated model session | SQLite/local archive format, HealthKit metadata mapping |
| **Data Models (Core Data cache)** | Dedicated model session | Xcode, Swift; derived UI/cache/settings/log/report models only |
| **HealthKit Integration** | Dedicated model session | Xcode, HealthKit docs |
| **Change Explanation Algorithms** | Dedicated model session | Swift, archive diffing, consolidation heuristics |
| **Context Monitoring** | Dedicated model session | Xcode; logs Health/iCloud state as context only |
| **Documentation** | Claude Code + dedicated session | Markdown |
| **Tests** | Dedicated model session | XCTest, Swift Testing |

---

## Directory Ownership

```
HealthProbe/
├── Views/           ← Claude Code (UI)
├── ViewModels/      ← Claude Code (UI)
├── Utilities/       ← Claude Code (shared helpers, mocks)
├── Models/          ← Models agent (legacy SwiftData now; target Core Data UI/cache schemas)
├── Services/        ← Services agent (HealthKit, archive store, change explanation, context)
└── Tests/           ← Tests agent
```

**Rule:** Each agent writes only within its owned directories.
Cross-boundary changes require an explicit interface contract (protocol) first.

**Documentation scope:** `HealthProbe/Doc/` is shared. Keep it consistent with shipped behavior, and add a dated entry when objectives change.

**Database work starts here:** `HealthProbe/Doc/02-architecture/Database-Design.md`.
The database is the central project artifact. Agents changing archive schema,
capture persistence, diff logic, aggregate caches, exports, reset behavior, or
future migrations must read and update that document before code changes.

---

## Interface Contracts

All service boundaries are defined as Swift protocols.
Claude Code (UI) consumes protocols — never concrete implementations.

### CaptureMonitorProtocol

```swift
/// Owned by: Services agent
/// Consumed by: UI (DashboardViewModel)
protocol CaptureMonitorProtocol {
    var archiveStatus: ArchiveStatus { get }
    var lastObservedAt: Date? { get }
    func captureNow() async throws
}
```

### ChangeSummaryStoreProtocol

```swift
/// Owned by: Services agent
/// Consumed by: UI (change timeline/detail views)
protocol ChangeSummaryStoreProtocol {
    var changes: [DetectedChange] { get }
    func markReviewed(_ change: DetectedChange) async throws
}
```

### AuditTrailProtocol

```swift
/// Owned by: Services agent
/// Consumed by: UI (AuditTrailView)
protocol AuditTrailProtocol {
    var entries: [AuditTrailEntry] { get }
    func export() async throws -> Data  // JSON
}
```

### ContextMonitorProtocol

```swift
/// Owned by: Services agent
/// Consumed by: UI (ContextViewModel)
protocol ContextMonitorProtocol {
    var iCloudEnabled: Bool { get }
    var lastObservedAt: Date? { get }
    var stateChanges: [ContextStateChange] { get }
}
```

---

## Shared Types (Models Agent)

These types are defined once in `Models/` and shared across all agents:

```swift
// Models/TypeDistributionBin.swift
struct TypeDistributionBin: Codable, Sendable {
    var bucketStart: Date
    var bucketEnd: Date
    var count: Int
}

// Models/TypeCount.swift
// TypeCount stores yearly counts and distribution bins as compact Codable
// payloads, not child SwiftData models.

// Interface updated 2026-05-26 — see AGENTS.md
// Models/HealthRecord.swift no longer defines a SwiftData model. It now keeps
// the HealthRecordValue DTO plus compact archive helpers used by archive/cache
// record previews. The authoritative record store is SQLite.

// Interface updated 2026-05-26 — see AGENTS.md
// TypeDistributionBin and YearlyCount no longer define SwiftData models. They
// are Codable payload structs embedded in TypeCount while capture is still on
// the prototype bridge. Target archive/cache rows should come from SQLite/Core
// Data instead.

// Interface updated 2026-05-18 — see AGENTS.md
// SwiftData is not the forensic source of truth and is legacy/prototype storage
// for the current app. Target architecture uses Core Data as a rebuildable
// UI/reporting cache only. Complete HealthKit samples and metadata belong in the
// SQLite archive store, in one schema that can preserve relationships across data
// types, sources, devices, workouts, and metadata.

// Interface updated 2026-05-24 — see AGENTS.md
// Services/Protocols/HealthArchiveStore.swift defines the local archive boundary.
// SQLiteHealthArchiveStore is the current implementation. Each user-visible
// capture creates one archive observation; HealthKit anchored-query pages,
// verification runs, and deleted-object evidence for that capture must attach to
// that observation id before UI/cache rows are saved. Deletions are recorded by
// sampleUUIDHash because HKDeletedObject exposes UUIDs, not complete sample
// payloads.

// Interface updated 2026-05-24 — see AGENTS.md
// HealthArchiveStore exposes SQL-first observation diff APIs:
// diffSummary(_:) returns appeared/disappeared/representationChanged counts and
// diffRecords(_:) returns a paged record list for one change kind.
// aggregateComparison(_:) compares materialized daily aggregates between two
// observations. sourceProvenanceBreakdown(_:) returns visible source/device
// composition, and consolidationEvidence(_:) returns conservative evidence labels
// with counts, aggregate sums, coverage, density, source compatibility, and
// uncertainty text. UI/cache agents should consume these APIs instead of loading
// full observation record sets.

// Storage objective updated 2026-05-23 — see AGENTS.md
// Recurring complete snapshots are out of scope for the target architecture.
// Store differential observations, versioned sample payloads, observation ranges,
// and materialized aggregates. Expensive counts used by reports/UI should be
// cached in Core Data and be rebuildable from SQLite.

// Interface updated 2026-05-24 — see AGENTS.md
// Services/CoreDataArchiveCacheStore.swift defines the rebuildable Core Data
// UI/report cache boundary. It owns programmatic cache entities for observation
// rows, type summaries, daily aggregates, diff summaries, export manifests, and
// archive health. Cache rows carry source observation ids plus archive/cache
// schema versions; deleting this cache must never delete the SQLite archive.

// Objective updated 2026-05-23 — see AGENTS.md
// HealthProbe is a local Health DB Time Machine. Snapshot/device identifiers are
// retained only to preserve local provenance and keep comparisons within one
// device chain. Record-count drops must be explained with aggregate and
// representation context, not treated as inherently alarming.

// Interface updated 2026-05-17 — see AGENTS.md
// Models/TypeCount.detailCacheData is legacy SwiftData UI cache for comparing a
// TypeCount with the immediately previous snapshot on the same device. It must not
// be precomputed during snapshot save: real-device imports showed Core Data
// mutated-while-enumerated crashes even with small detail caches. Current UI should
// rely on SQLite archive queries and rebuildable Core Data cache rows instead.

// Interface updated 2026-05-17 — see AGENTS.md
// Models/HealthSnapshot.contentEquivalentSnapshotID marks snapshots whose TypeCount
// content is identical to a previous snapshot on the same device. These snapshots are
// retained as temporal labels but behave as aliases to the representative content
// snapshot for expensive detail cache/diff work.

// Interface updated 2026-05-17 — see AGENTS.md
// Models/TypeCount.contentEquivalentTypeCountID marks individual data types whose
// content is identical to the previous snapshot's same TypeCount. This allows a
// snapshot to contain real changes for some metrics while long-stable metrics behave
// as temporal aliases and skip per-type detail cache/diff work.

// Interface updated 2026-05-17 — see AGENTS.md
// Models/HealthSnapshot stores cached overview scalars for UI consumption:
// tracked type count, aggregate record count, and overall oldest/newest record dates.
// These values must be computed during snapshot save while TypeCount data is already
// in memory, so snapshot list/detail screens never recompute them by traversing
// snapshot.typeCounts on the UI thread.

// Interface updated 2026-05-26 — see AGENTS.md
// SnapshotDelta/TypeDelta and their post-save DeltaService were deleted.
// Active overview and detail screens consume SQLite/Core Data archive/cache
// observation diff summaries instead of recalculating changes from
// HealthSnapshot.typeCounts.

// Interface updated 2026-05-23 — see AGENTS.md
// Future UI/domain naming should prefer "change" or "observation diff" over
// "anomaly". The legacy AnomalyRecord/AnomalyType models have been removed.
enum ChangeClassification: String, Codable {
    case appeared
    case disappeared
    case representationChanged = "representation_changed"
    case consolidationLikely = "consolidation_likely"
    case aggregateChanged = "aggregate_changed"
    case uncertain
}

enum ReviewPriority: String, Codable, Comparable {
    case info, review, important
}

enum ArchiveStatus: String {
    case ready, needsCapture, degraded, unknown
}
```

Any model changes must be announced in this file before other agents consume them.

---

## Handoff Process

When a module is ready to be consumed by another agent:

1. **Define the protocol** in `Services/Protocols/` (services agent)
2. **Implement a mock** in `Utilities/Mocks.swift` (Claude Code)
3. **Build UI against the mock** (Claude Code)
4. **Replace mock with real implementation** (services agent)
5. **Integration test** (tests agent)

This allows UI development and service development to proceed in parallel.

---

## Algorithms & Change Explanation Logic

The following modules involve non-trivial logic and should be reviewed carefully:

| Module | File | Description |
|--------|------|-------------|
| **Archive Diff Queries** | `Services/SQLiteHealthArchiveStore.swift` | SQL-first appeared/disappeared/representation-changed counts and paged record diffs |
| **Consolidation Evidence** | `Services/SQLiteHealthArchiveStore.swift` | Aggregate, coverage, density, source, and uncertainty evidence for HealthKit consolidation-like changes |
| **Archive Fingerprinting** | `Services/HashService.swift` and archive write path | Stable sample/content hashes used by differential storage and exports |
| **UI/Report Cache** | `Services/CoreDataArchiveCacheStore.swift` | Rebuildable cached observation/type/diff/export/health rows for UI/reporting |
| **Export Manifesting** | `Services/SQLiteHealthArchiveStore.swift` | Paged JSON export and manifest hashing without full archive materialization |

**Guidelines for algorithm modules:**
- Document assumptions explicitly (e.g., "HealthProbe can only preserve detail it observed")
- All thresholds (e.g., `age > 7 days`) must be configurable constants, not magic numbers
- Include unit tests for edge cases (empty observations, partial data, clock skew, consolidation-like rewrites)
- No UI code; return plain Swift types only

---

## Privacy Directives — All Agents

**Mandatory across all modules:**
- No credentials, API keys, tokens, or certificates in any file
- No personal data: names, emails, phone numbers, dates of birth
- No device identifiers: UDID, serial number, advertising ID, device name
- No account identifiers: Apple ID, iCloud account info, CloudKit record IDs
- No raw health values in code, tests, previews, logs, or comments
- No location data or patterns enabling re-identification
- Synthetic data only in tests and previews

**Clarification:** “No raw health values” applies to this repository’s contents. The app may optionally store a user's raw HealthKit samples *locally on-device* for forensic backup purposes, but such samples must never appear in source control, logs, or docs.

---

## Communication Between Agents

When one agent needs to communicate a decision or change to another:

1. **Update this file** (`HealthProbe/Doc/00-agent-guides/AGENTS.md`) with the protocol/interface change
2. **Update the relevant protocol** in `Services/Protocols/`
3. **Add a comment** in the affected file: `// Interface updated YYYY-MM-DD — see AGENTS.md`

---

## Current Status

| Module | Status | Owner |
|--------|--------|-------|
| Core Data UI/Report Cache | ⏳ Started: programmatic model + full rebuild service | Models agent |
| HealthKit Integration | ✅ Done | Services agent |
| Snapshot Diff Service | ✅ Done | Services agent |
| Service Protocols | ⏳ Not started | Services agent |
| Change Explanation / Consolidation Heuristics | ⏳ Needs refocus | Services agent |
| Context Monitor | ⏳ Not started | Services agent |
| UI – App entry + TabView | ✅ Done | Claude Code |
| UI – Dashboard | ✅ Done (functional, minimal) | Claude Code |
| UI – Snapshots + Detail | ✅ Done | Claude Code |
| UI – Data Types | ✅ Done | Claude Code |
| UI – Settings | ✅ Done | Claude Code |
| Unit Tests | ⏳ Started: SQLite archive init/reset/idempotency tests | Tests agent |
