diff --git a/.directory b/.directory index 9dd830b..5a02205 100644 --- a/.directory +++ b/.directory @@ -1,2 +1,2 @@ [Desktop Entry] -Icon=orange-folder-git +Icon=orange-folder-github diff --git a/documentation/administrative/2026-0516_pilot_logbook_design.md b/documentation/administrative/2026-0516_pilot_logbook_design.md new file mode 100644 index 0000000..c76e4bc --- /dev/null +++ b/documentation/administrative/2026-0516_pilot_logbook_design.md @@ -0,0 +1,1657 @@ +# Pilot Logbook Database — Design Document + +**Status:** Draft v0.1 — design phase, pre-DDL +**Target platform:** PostgreSQL 16+ +**Scope:** Multi-user, audit-bearing pilot logbook with open interchange format + +--- + +## Table of Contents + +1. [Project Overview](#1-project-overview) +2. [Design Philosophy](#2-design-philosophy) +3. [Regulatory Framework](#3-regulatory-framework) +4. [High-Level Architecture](#4-high-level-architecture) +5. [Schema Organization](#5-schema-organization) +6. [Entity Reference](#6-entity-reference) +7. [Category-Specific Flight Details](#7-category-specific-flight-details) +8. [Time Modeling](#8-time-modeling) +9. [Audit Trail Design](#9-audit-trail-design) +10. [Entry Locking and Corrections](#10-entry-locking-and-corrections) +11. [Cryptographic Signatures](#11-cryptographic-signatures) +12. [Lookup Tables and Enums](#12-lookup-tables-and-enums) +13. [Views](#13-views) +14. [Book Reports](#14-book-reports) +15. [Constraints and Indexes](#15-constraints-and-indexes) +16. [Import / Export](#16-import--export) +17. [Migrations](#17-migrations) +18. [Backup Strategy](#18-backup-strategy) +19. [Out of Scope (Explicit)](#19-out-of-scope-explicit) +20. [Future Considerations / Open Questions](#20-future-considerations--open-questions) +21. [Glossary](#21-glossary) +22. [Appendix A — 14 CFR 61.51 Requirements Mapping](#22-appendix-a--14-cfr-6151-requirements-mapping) +23. [Appendix B — Regulatory Reference Index](#23-appendix-b--regulatory-reference-index) +24. [Appendix C — High-Level Entity Relationship Diagram](#24-appendix-c--high-level-entity-relationship-diagram) + +--- + +## 1. Project Overview + +### 1.1 Purpose + +A PostgreSQL database schema for pilot flight logbooks that: + +- Satisfies 14 CFR 61.51 logging requirements as a regulatory floor. +- Supports computation (via queries and views) of compliance status for currency, recency, and aeronautical-experience regulations across Part 61. +- Maintains a court-quality audit trail of all logbook modifications. +- Supports cryptographic signing of entries for instructor verification. +- Operates as a multi-user system with strict per-user data isolation. +- Exports to an open, documented interchange format to escape proprietary lock-in. + +### 1.2 Goals + +- **Regulatory correctness:** every required 61.51 element captured atomically; downstream regulation satisfied by query. +- **Audit-bearing:** every change captured with who, when, what, and why, with tamper-evidence. +- **Open standard:** define and publish the JSON interchange schema; permit and encourage adoption by other projects. +- **Multi-aviator:** support airplane, helicopter, glider, balloon, and other categories without forcing one-size-fits-all. +- **Future-proof:** prefer semantic honesty over short-term schema simplicity. Wide-but-correct beats narrow-with-nulls. + +### 1.3 Non-Goals + +See section 19 for a full enumeration. Briefly: duty/rest time calculation, aircraft maintenance tracking, scheduling, and dispatch are explicitly out of scope. + +--- + +## 2. Design Philosophy + +### 2.1 Atomic Facts, Derived Compliance + +Store the smallest indivisible facts about each flight; compute everything else by query. A "cross-country qualifying flight" is not a stored attribute — it's a query result based on stored leg distances and airports. A "currency status" is not a stored attribute — it's a function of stored flights filtered by date and aircraft category. + +This principle has cascading effects throughout the schema: + +- Time categories (PIC, SIC, dual, solo, night, instrument) are stored per flight. +- Aircraft attributes (complex, high-performance, tailwheel, type-rating-required) are stored on the aircraft *model*, not the flight, and inherited via join. +- Currency is never stored — always computed. +- The pilot's "totals" are aggregate queries, not maintained counters. + +This costs query complexity and benefits correctness. Counters drift; aggregate queries do not. + +### 2.2 Append-Only Spirit with Selective Locking + +Paper logbooks are physically append-only. Once you've signed your name and the CFI has signed theirs, the entry is fixed; corrections take the form of explicit subsequent entries that reference the error. + +This schema preserves that discipline selectively: + +- **Unlocked entries** support normal updates. The audit log captures every change. +- **Locked entries** (signed by a CFI, or explicitly locked by the user) cannot be modified in place. Corrections require a new entry that references the original. +- **Erroneous entries** are marked, not deleted. The original remains in the historical record; views exclude them from totals. + +### 2.3 Open Standard + +The export format is intended to be a community standard, not just this implementation's backup file. To that end: + +- The JSON export schema is documented, versioned, and licensed permissively (CC0 for the spec). +- The schema is designed for round-trip fidelity: export then re-import must reproduce identical logbook state, including audit history and signatures. +- Identifiers are globally unique (UUID v7) so logbooks can be merged or migrated across instances without collision. + +### 2.4 PostgreSQL-Native + +This schema is written for PostgreSQL 16+ and uses PostgreSQL-specific features without apology: + +- Partial unique indexes (e.g., "exactly one owner per logbook") +- JSONB for flexible attribute storage +- Generated columns for derived per-row fields +- Row-level security for multi-tenant isolation +- Triggers for audit log maintenance +- Range types where temporal queries are natural +- Schemas (namespaces) for organization + +No abstraction layer or ORM is assumed. Cross-database portability is explicitly not a goal. + +--- + +## 3. Regulatory Framework + +### 3.1 14 CFR 61.51 — Pilot Logbooks (The Floor) + +This is the regulation that directly governs pilot logbook content. Every flight log entry must include (per §61.51(b)): + +- **General:** date; total flight or lesson time; location where flight originated and terminated (or the location of a ground/sim lesson); type and identification of aircraft, FFS, FTD, or ATD. +- **Type of pilot experience or training:** solo; PIC; SIC; flight and ground training received from authorized instructor; training received in FFS/FTD/ATD from authorized instructor. +- **Conditions of flight:** day or night; actual instrument; simulated instrument in flight, FFS, FTD, or ATD. + +Per §61.51(c)-(e), additional logging applies to specific operations: flight instruction received and given (§61.51(e), (h)), instrument approaches and instrument time (§61.51(g)), instructor records (§61.51(h)), and the safety pilot's name when applicable. + +Per §61.51(i), logbook entries must be available for inspection by the Administrator, an authorized representative of the NTSB, or any federal, state, or local law enforcement officer. + +Per §61.51(j), instruction must be signed by the authorized instructor providing it; the instructor must include their certificate number and expiration date. + +### 3.2 Related Regulations + +These regulations impose requirements that are *derived* from logbook contents via query: + +- **§61.1** — Definitions, including cross-country flight (>50 NM variant for most ratings), night flight (end of evening civil twilight to beginning of morning civil twilight — the *logging* definition). +- **§61.31** — Type rating, complex, high-performance, tailwheel, high-altitude, and pressurized aircraft requirements; required endorsements. +- **§61.55** — SIC qualifications; familiarization training and recent flight experience for SIC in type-certificated multi-pilot aircraft. +- **§61.56** — Flight review (every 24 calendar months). +- **§61.57** — Recent flight experience: passenger-carrying (90-day takeoff/landing in category/class/type, day and night), instrument (6 approaches + holding + tracking in preceding 6 calendar months), with grace periods and IPC requirements after lapse. +- **§61.65** — Instrument rating aeronautical experience. +- **§61.129** — Commercial pilot aeronautical experience. +- **§61.159** — ATP aeronautical experience (1500-hour rule and subcategories). + +The "night" definition for §61.57(b) currency differs from the §1.1 definition for logging night time. The schema accommodates both. + +### 3.3 Related FAA Guidance + +- **AC 120-78B** — Acceptance and Use of Electronic Signatures, Electronic Recordkeeping Systems, and Electronic Manuals. +- **FAA Order 8900.1** — Flight Standards Information Management System (FSIMS). + +Compliance with AC 120-78B drives the design of the cryptographic signature subsystem (section 11). + +--- + +## 4. High-Level Architecture + +### 4.1 PostgreSQL-Specific Design + +PostgreSQL 16 or later. Features used liberally: + +- Multiple schemas for namespace organization +- JSONB columns for user-defined and extensible attributes +- Generated columns (STORED) where the source data lives in the same row +- Partial indexes (e.g., on locked entries, on recent flights) +- Range types for temporal validity +- Triggers (BEFORE INSERT, AFTER INSERT/UPDATE/DELETE) for audit and integrity +- Row-Level Security (RLS) for multi-user isolation +- `pgcrypto` for hashing in the audit chain and for UUID v7 generation (or built-in `uuidv7()` in PG18+) + +### 4.2 Multi-User Model + +The system is multi-user from the database's perspective. Each user has their own logbook. **No data is shared between users' logbooks** — not flights, not people records, not aircraft instances. The mental model is one-database-per-installation-with-many-tenants. + +Reference data (airports, aircraft model definitions, training device model definitions, FAR citations, lookup-table contents) is **shared and read-only** to users. Administrators populate it; users reference it. + +Per-user tables carry an `owner_user_id` column. Row-level security policies enforce `owner_user_id = current_user_session_id()` for all access. + +### 4.3 Single Logbook Owner Per User + +Each user owns exactly one logbook (their own). Within the user's `people` table, exactly one row represents the logbook owner themselves — the pilot the logbook is for. This is enforced by a partial unique index: + +```sql +CREATE UNIQUE INDEX one_owner_per_user + ON logbook.people (owner_user_id, is_logbook_owner) + WHERE is_logbook_owner = TRUE; +``` + +The user appears in their own `people` table as a regular row, distinguished only by the `is_logbook_owner` flag. This pattern keeps the schema consistent: the owner is "just another person" in the data model, special only by virtue of the boolean. + +### 4.4 Identifiers + +All primary keys are `UUID` (v7, time-ordered). UUID v7 is preferred over v4 because: + +- It sorts naturally by creation time, keeping B-tree index inserts efficient. +- It carries a millisecond timestamp embedded in the value, useful for forensic ordering. +- It's globally unique, so logbooks can be exported, imported, and merged across instances without collision. + +Surrogate UUIDs are used universally; natural keys (FAA certificate numbers, N-numbers, ICAO airport codes) appear as separate uniquely-indexed columns where they exist. + +### 4.5 Timestamps + +All timestamp columns are `timestamptz` (timestamp with time zone). Aviation is inherently multi-timezone; storing local times without zones invites data corruption. Display-layer conversion to local time happens at the application layer. + +Date-only columns (e.g., flight date for logging purposes) use `date`. Dates do not carry time zones; a flight logged on 2025-03-15 is logged on that date regardless of what time zone the pilot was in. + +For events that need timestamp precision (takeoffs, landings, push-back times), `timestamptz` is used and the moment is stored with full UTC offset information. + +### 4.6 Numeric Precision + +Time durations are `NUMERIC(5,1)` — tenths of an hour, up to 9999.9. This matches pilot logbook convention (entries are written in tenths) and avoids floating-point accumulation errors. The maximum 9999.9 hours per single entry is more than any real flight; per-pilot totals fit easily in `NUMERIC(7,1)` aggregated. + +Money or currency is not part of this schema. + +Distances are `NUMERIC(7,2)` nautical miles; great-circle calculations from airport lat/lon use the haversine formula in a SQL function. PostGIS is not used. + +--- + +## 5. Schema Organization + +The database uses four PostgreSQL schemas (namespaces) for logical separation: + +### 5.1 `reference` + +Shared, read-only data, populated by administrators. Same content for all users. No `owner_user_id` columns. No RLS. + +Contains: + +- `airports` — global airport reference +- `aircraft_models` — aircraft type/model definitions +- `training_device_models` — simulator and training device type definitions +- `far_citations` — regulation reference data +- Lookup tables: `aircraft_categories`, `aircraft_classes`, `approach_types`, `certificate_levels`, `rating_types`, `endorsement_types`, `medical_classes`, `role_on_flight_types`, `training_device_types`, `pic_basis_types`, `landing_event_types`, etc. + +### 5.2 `logbook` + +Per-user logbook data. Every table carries `owner_user_id` and is subject to RLS policies. + +Contains entities described in detail in section 6: people, person_roles, aircraft, training_devices, flights, flight_legs, training_sessions, landings_takeoffs, approaches, flight_crew, certificates, ratings, certificate_limitations, endorsements, medicals, flight_reviews, practical_tests, training_events, attachments, public_keys, entry_signatures, book_reports, tags, entry_tags, category-specific extension tables (helicopter_flight_details, glider_flight_details, etc.). + +### 5.3 `audit` + +Audit log. Append-only at the role level (INSERT permitted, UPDATE and DELETE revoked). Hash-chained for tamper-evidence. + +Contains: + +- `audit_log` — all changes to audited tables in `logbook` schema +- Possibly auxiliary tables for change-reason templates or session-tracking metadata. + +### 5.4 `auth` + +User authentication and identification. Distinct from `logbook.people` — a `users` row is an account that logs in; a `people` row is a person in the logbook (which may be the user themselves, or someone else they fly with). + +Contains: + +- `users` — authentication records, profile metadata +- Session-related tables if implemented in-database (could also be application-layer) + +--- + +## 6. Entity Reference + +This section describes every entity in the schema. Each subsection covers: + +- **Purpose** — what this entity represents +- **Key fields** — the columns that define it +- **Relationships** — foreign keys and references +- **Constraints** — important rules +- **Rationale** — why it's modeled this way + +### 6.1 `auth.users` + +**Purpose:** A user account that logs into the system. One per human who has their own logbook. + +**Key fields:** + +- `id` (UUID v7, PK) +- `email` (text, unique) +- `display_name` (text) +- `password_hash` (text) — or alternative auth scheme references +- `is_active` (boolean) +- `created_at`, `updated_at` (timestamptz) +- `last_login_at` (timestamptz, nullable) + +**Relationships:** Referenced by `owner_user_id` columns throughout the `logbook` schema. + +**Notes:** Authentication mechanism (password, OAuth, etc.) is an application-layer concern. This table provides the database-layer identity that RLS policies key against. + +### 6.2 `logbook.people` + +**Purpose:** Any person who appears in the user's logbook — the owner themselves, instructors, fellow pilots, examiners, flight attendants, dispatchers, anyone the user has flown with or trained under. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `is_logbook_owner` (boolean, default false) +- `first_name`, `middle_name`, `last_name` (text) +- `display_name` (text, optional override) +- `faa_certificate_number` (text, nullable) — primary FAA airman certificate number +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Constraints:** + +- Partial unique index ensuring exactly one `is_logbook_owner = TRUE` per `owner_user_id`. + +**Notes:** Biographical only. Role/qualification information lives in `person_roles`. The user's own row has `is_logbook_owner = TRUE`. + +### 6.3 `logbook.person_roles` + +**Purpose:** Time-ranged record of roles, qualifications, certificates that a person has held. Captures "John was a CFI from 2005 to 2018" or "Mary held an ATP starting 2020 (still current)." + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `person_id` (UUID, FK → `logbook.people`) +- `role_type` (text, FK → `reference.role_qualification_types`) — e.g., 'CFI', 'CFII', 'MEI', 'ATP', 'flight_attendant', 'dispatcher', 'A&P_mechanic', 'DPE' +- `identifier` (text, nullable) — qualification-specific number, e.g., CFI certificate number +- `valid_from` (date, nullable) — NULL means "unknown but presumed prior to first observation" +- `valid_to` (date, nullable) — NULL means "currently held / no known end" +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** NULL dates explicitly mean "unknown" — approximate dates were rejected in design (audit clarity > convenience). Multiple `person_roles` rows per person are expected; the same person may have CFI, MEI, and ATP rows, possibly with different validity periods. + +### 6.4 `reference.airports` + +**Purpose:** Global airport reference. Shared across all users. + +**Key fields:** + +- `id` (UUID v7, PK) +- `icao_code` (text, nullable, unique-where-not-null) +- `iata_code` (text, nullable) +- `faa_lid` (text, nullable) — FAA Location Identifier (e.g., "0B5" for fields without ICAO) +- `name` (text) +- `municipality` (text, nullable) +- `country_code` (text) — ISO 3166-1 alpha-2 +- `latitude` (numeric(9,6)) +- `longitude` (numeric(9,6)) +- `elevation_ft` (integer, nullable) +- `has_control_tower` (boolean) — current state; historical state captured per landing +- `time_zone` (text) — IANA tz name (e.g., "America/New_York") +- `is_active` (boolean) — for decommissioned airports +- `created_at`, `updated_at` (timestamptz) + +**Rationale:** Lat/lon needed for great-circle distance (cross-country qualification) and for sunrise/sunset calculation (night currency). Time zone needed for converting `timestamptz` events to local for night determination. `has_control_tower` is current state; the per-landing `was_at_towered_field` captures historical truth. + +### 6.5 `reference.aircraft_models` + +**Purpose:** Aircraft type/model reference. A Cessna 172S is one row; an individual N-number flying that model references this row. + +**Key fields:** + +- `id` (UUID v7, PK) +- `manufacturer` (text) — "Cessna", "Cirrus", "Boeing" +- `model_name` (text) — "172S Skyhawk", "SR22T G6", "737-800" +- `icao_type_designator` (text, nullable) — "C172", "B738", "SR22" +- `category_id` (UUID, FK → `reference.aircraft_categories`) — airplane, rotorcraft, glider, lighter-than-air, powered-lift, weight-shift-control, powered-parachute +- `class_id` (UUID, FK → `reference.aircraft_classes`) — single-engine land, multi-engine land, single-engine sea, multi-engine sea, helicopter, gyroplane, balloon, airship +- `engine_type` (text) — piston, turboprop, turbojet, turbofan, electric, none-glider +- `engine_count` (smallint) +- `gear_type` (text) — tricycle, conventional (tailwheel), skids, skis, floats, amphibious +- `is_complex` (boolean) — retractable + flaps + controllable-pitch prop (§61.31(e)) +- `is_high_performance` (boolean) — >200 HP (§61.31(f)) +- `is_tailwheel` (boolean) — derived from gear_type but stored for indexability +- `is_pressurized` (boolean) +- `service_ceiling_ft` (integer, nullable) — for §61.31(g) high-altitude flagging if >25,000 ft pressurization +- `requires_type_rating` (boolean) — large aircraft (>12,500 lb MGTOW), turbojet-powered, or specifically listed +- `type_rating_code` (text, nullable) — e.g., "B737", "CE-525S" +- `mtow_lbs` (integer, nullable) — Maximum Takeoff Weight +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Many flagging attributes (`is_complex`, `is_high_performance`, etc.) live on the model, not the flight. A flight in an Arrow automatically counts as complex time via join. This eliminates a class of data-entry error. + +### 6.6 `logbook.aircraft` + +**Purpose:** A specific aircraft instance — a particular N-number (or other-country registration). + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `model_id` (UUID, FK → `reference.aircraft_models`) +- `registration` (text) — "N12345", "G-ABCD", "C-FXYZ" +- `serial_number` (text, nullable) +- `year_manufactured` (smallint, nullable) +- `nickname` (text, nullable) — user's name for the aircraft +- `is_glass_cockpit` (boolean, default false) — installed equipment +- `is_ifr_certified` (boolean, default true) +- `equipment_notes` (text) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Constraints:** `(owner_user_id, registration)` unique within an owner's logbook. + +**Notes:** Two users flying the same N-number have two distinct rows (per the strict no-sharing principle). Equipment is an aircraft-level attribute; a flight in a G1000-equipped 172 is automatically "G1000 time." + +### 6.7 `reference.training_device_models` + +**Purpose:** Training device type reference. A model of FFS, FTD, ATD, BATD, or AATD. + +**Key fields:** + +- `id` (UUID v7, PK) +- `manufacturer` (text) +- `model_name` (text) +- `device_type_id` (UUID, FK → `reference.training_device_types`) — FFS, FTD, ATD, BATD, AATD, "unknown_historical" +- `device_level` (text, nullable) — "Level A", "Level D", "Level 7", etc., per the era's classification scheme; nullable +- `aircraft_model_simulated_id` (UUID, FK → `reference.aircraft_models`, nullable) — many ATDs simulate generic aircraft +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Per design discussion, device level is nullable. Historical training in pre-2008 devices may have no level information available; this is an acceptable state. `device_type_id` can also reference "unknown_historical" for very old records. + +### 6.8 `logbook.training_devices` + +**Purpose:** A specific training device instance — a particular sim at a particular facility. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `model_id` (UUID, FK → `reference.training_device_models`) +- `facility_name` (text, nullable) +- `facility_location` (text, nullable) — free text; not all sims are at registered airports +- `serial_number` (text, nullable) +- `faa_qualification_letter` (text, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.9 `logbook.flights` + +**Purpose:** The header for a flight — a logical flight composed of one or more legs. For single-leg flights this is functionally one row with one leg child. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `flight_date` (date) — the date the flight is logged on +- `aircraft_id` (UUID, FK → `logbook.aircraft`) — required (this table is for actual aircraft only) +- `pic_basis_id` (UUID, FK → `reference.pic_basis_types`) — sole_manipulator, acting_pic, atp_part_121, more_than_one_pilot_required +- Time category columns, each `NUMERIC(5,1)` and `NOT NULL DEFAULT 0`: + - `total_time` + - `pic_time` + - `sic_time` + - `dual_received` + - `dual_given` (instruction given by the pilot, if a CFI) + - `solo_time` + - `cross_country_time` (the simple "cross-country" flavor) + - `cross_country_50nm_time` (the >50 NM flavor for most ratings, §61.1) + - `night_time` (§1.1 logging definition: end of civil twilight) + - `actual_instrument` + - `simulated_instrument` (in-aircraft with view-limiting device) +- Block timestamps (all nullable timestamptz): + - `block_out` — pushback from gate / start of movement + - `block_off` — wheels off (takeoff) + - `block_on` — wheels on (landing) + - `block_in` — arrival at gate / shutdown +- Flight conditions (all nullable booleans, denormalized for query speed): + - `holding_performed` + - `course_intercepting_tracking_performed` +- CFI signature fields (simple, for "instructor signed this off" cases): + - `cfi_signature_person_id` (UUID, FK → `logbook.people`, nullable) + - `cfi_signed_at` (timestamptz, nullable) +- Locking and corrections: + - `is_locked` (boolean, default false) + - `locked_at` (timestamptz, nullable) + - `locked_reason` (text, nullable) — 'cfi_signature', 'user_locked', 'cryptographic_signature' + - `corrects_flight_id` (UUID, FK self-reference, nullable) + - `correction_reason` (text, nullable) + - `superseded_by_flight_id` (UUID, FK self-reference, nullable) — denormalized; trigger-maintained + - `is_erroneous` (boolean, default false) + - `erroneous_reason` (text, nullable) + - `marked_erroneous_at` (timestamptz, nullable) +- Free-form: + - `remarks` (text) — the pilot's notes, freely edited (unlocked entries) or via correction (locked entries) +- Provenance: + - `created_at`, `updated_at` (timestamptz) + - `created_by_user_id` (UUID, FK → `auth.users`) — usually equals `owner_user_id`; future-proofing for admin or assistant cases + +**Constraints:** + +- `total_time >= 0` +- Each time category column `>= 0` +- All time category columns `<= total_time + 0.1` (allowing for rounding) where logically applicable + - Note: PIC + dual_received can both be > 0 simultaneously and may both equal total_time in CFI-receiving-instruction scenarios; the constraints need careful crafting and are documented inline in DDL. +- `(corrects_flight_id IS NULL) OR (correction_reason IS NOT NULL)` +- `(is_erroneous = FALSE) OR (erroneous_reason IS NOT NULL)` +- Block-time ordering: if both endpoints present, end > start. `block_out < block_off < block_on < block_in` where all are present. + +**Notes:** `total_time` is the canonical §61.51 logged time and is **stored, not generated**. The application UI may compute it from block times and prefill, but the user can override. The schema does not enforce a relationship between block fields and total_time because too many edge cases exist (return-to-gate aborts, taxi-back flights with no actual flight). + +### 6.10 `logbook.flight_legs` + +**Purpose:** Individual legs within a flight. A KPHL → KLNS → KABE → KPHL day is one `flights` row with three `flight_legs` rows. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `flight_id` (UUID, FK → `logbook.flights`) +- `leg_number` (smallint) — 1-based ordering +- `from_airport_id` (UUID, FK → `reference.airports`, nullable) — nullable for off-airport departures +- `to_airport_id` (UUID, FK → `reference.airports`, nullable) — nullable for off-airport landings, diverts to unlisted fields +- `from_airport_description` (text, nullable) — for off-airport cases ("Smith family farm field") +- `to_airport_description` (text, nullable) +- `distance_nm` (numeric(7,2)) — typically generated; haversine from airport coords +- `leg_time` (numeric(5,1)) — pilot-logged time for this leg +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Constraints:** + +- `(flight_id, leg_number)` unique +- `leg_time >= 0` + +**Notes:** A single-leg flight has one row here with leg_number = 1. The application auto-creates this row on flight insert if only origin/destination are provided. The user is shielded from the legs abstraction unless they need it. + +Multi-leg sums: `SUM(flight_legs.leg_time) WHERE flight_id = X` should approximately equal `flights.total_time`. The schema does not enforce strict equality (rounding) but a sanity check trigger could warn. + +### 6.11 `logbook.landings_takeoffs` + +**Purpose:** Individual takeoff and landing events. Each row represents one event; touch-and-goes are a single row with both `is_takeoff = TRUE` and `is_landing = TRUE`. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `flight_leg_id` (UUID, FK → `logbook.flight_legs`) +- `airport_id` (UUID, FK → `reference.airports`, nullable) — nullable for off-airport events +- `airport_description` (text, nullable) — for off-airport cases +- `occurred_at` (timestamptz, nullable) — precise time; needed for sunrise/sunset determination +- `is_takeoff` (boolean, not null, default false) +- `is_landing` (boolean, not null, default false) +- `is_full_stop` (boolean, nullable) — meaningful only for landings +- `was_at_towered_field` (boolean, nullable) — captured at time of event (regulation cares about then, not now) +- `runway` (text, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Derived (via view or generated column):** + +- `is_day_per_part1` — based on `occurred_at` and airport coordinates, end of civil twilight definition +- `is_night_per_6157` — based on `occurred_at` and airport coordinates, 1-hour-after-sunset / 1-hour-before-sunrise definition + +**Constraints:** + +- `is_takeoff OR is_landing` (one event minimum) +- `(is_full_stop IS NULL) OR (is_landing = TRUE)` (full stop only meaningful with landing) + +**Notes:** This is the granular source of truth for landing/takeoff counts. Summary counts on `flight_legs` and `flights` (e.g., `day_landings`, `night_landings_full_stop`) are denormalized convenience fields maintained by triggers. A day takeoff that crosses into night before landing is correctly represented as two rows: takeoff row (is_day_per_part1 = TRUE), landing row (is_night_per_6157 = TRUE). + +Sunrise/sunset computation is implemented in a SQL/PLpgSQL function based on the NOAA solar position algorithm, taking lat/lon and a date. + +### 6.12 `logbook.approaches` + +**Purpose:** Instrument approaches logged for §61.51(g) and §61.57(c) currency. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `flight_leg_id` (UUID, FK → `logbook.flight_legs`, nullable) — for in-aircraft approaches +- `training_session_id` (UUID, FK → `logbook.training_sessions`, nullable) — for sim approaches +- `approach_type_id` (UUID, FK → `reference.approach_types`) — ILS, LOC, LOC-BC, VOR, NDB, RNAV-GPS-LNAV, RNAV-GPS-LPV, RNAV-GPS-LNAV/VNAV, GLS, PAR, ASR, etc. +- `airport_id` (UUID, FK → `reference.airports`, nullable) +- `runway` (text, nullable) +- `in_imc` (boolean) — actual instrument conditions +- `in_simulated_imc` (boolean) — view-limiting device or sim +- `was_circling` (boolean) +- `was_missed` (boolean) — flew the missed approach procedure +- `to_landing` (boolean) — landed off this approach +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Constraints:** + +- Exactly one of `flight_leg_id` or `training_session_id` is non-null. + +### 6.13 `logbook.training_sessions` + +**Purpose:** Training in a "bolted-to-the-earth" device (FFS, FTD, ATD, BATD, AATD). Distinct from flights in actual aircraft. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `session_date` (date) +- `training_device_id` (UUID, FK → `logbook.training_devices`) +- Time columns (NUMERIC(5,1)): + - `total_time` + - `instrument_time_simulated` (per §61.51(g)(5), sim instrument time logged as simulated) + - `dual_received` + - `dual_given` +- `instructor_person_id` (UUID, FK → `logbook.people`, nullable) +- `holding_performed` (boolean, nullable) +- `course_intercepting_tracking_performed` (boolean, nullable) +- Locking and corrections (same pattern as flights): + - `is_locked`, `locked_at`, `locked_reason` + - `corrects_training_session_id`, `correction_reason`, `superseded_by_training_session_id` + - `is_erroneous`, `erroneous_reason`, `marked_erroneous_at` +- CFI signature fields (same pattern as flights) +- `remarks` (text) +- `created_at`, `updated_at`, `created_by_user_id` + +**Notes:** No airport, no landings, no takeoffs in the regulatory sense. Approaches done in a sim are logged via the `approaches` table with `training_session_id` set. Time logged here does not count for §61.57(a) takeoff/landing currency under any interpretation; queries that compute such currency must filter on flights only. + +### 6.14 `logbook.flight_crew` + +**Purpose:** Junction table tying people to specific flights with their role on that flight. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `flight_id` (UUID, FK → `logbook.flights`, nullable) +- `training_session_id` (UUID, FK → `logbook.training_sessions`, nullable) +- `person_id` (UUID, FK → `logbook.people`) +- `role_on_flight_id` (UUID, FK → `reference.role_on_flight_types`) +- `seat_position` (text, nullable) — left, right, jumpseat, cabin +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Constraints:** + +- Exactly one of `flight_id` or `training_session_id` is non-null. + +**Notes:** Role types include: PIC, SIC, CFI_giving_instruction, student_receiving_instruction, safety_pilot, flight_attendant, jumpseat_observer, relief_pilot, IRO, check_airman, examiner, dispatcher_observer, mechanic_observer, etc. Same person can have different `role_on_flight_id` values on different flights — captures the "John was PIC on flight A, SIC on flight B" case naturally. + +### 6.15 `logbook.certificates` + +**Purpose:** FAA airman certificates held by the logbook owner. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `certificate_level_id` (UUID, FK → `reference.certificate_levels`) — Student, Sport, Recreational, Private, Commercial, ATP, Flight Instructor, Ground Instructor, Remote Pilot, Mechanic, etc. +- `certificate_number` (text) +- `date_of_issuance` (date) +- `date_of_first_issuance` (date, nullable) — for re-issued certificates +- `issuing_authority` (text, default 'FAA') — for non-FAA certificates (ICAO conversion, foreign) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.16 `logbook.ratings` + +**Purpose:** Ratings attached to a certificate. A Commercial certificate may carry SEL, MEL, Instrument-Airplane, and a type rating like B737. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `certificate_id` (UUID, FK → `logbook.certificates`) +- `rating_type_id` (UUID, FK → `reference.rating_types`) +- `date_added` (date) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.17 `logbook.certificate_limitations` + +**Purpose:** Limitations endorsed on a certificate. "Limited to centerline thrust," "VFR only," "Not valid for international flight," etc. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `certificate_id` (UUID, FK → `logbook.certificates`) +- `limitation_text` (text) +- `date_added` (date) +- `date_removed` (date, nullable) — limitations can be removed by subsequent checkride +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.18 `logbook.endorsements` + +**Purpose:** Instructor endorsements per §61.31, §61.39, §61.56, §61.57(d), and others. Distinct from certificates and ratings. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `endorsement_type_id` (UUID, FK → `reference.endorsement_types`) — high_performance, complex, tailwheel, high_altitude, type_specific, solo, solo_cross_country, flight_review, IPC, etc. +- `endorsement_date` (date) +- `endorsing_cfi_person_id` (UUID, FK → `logbook.people`) +- `cfi_certificate_number_at_time` (text) — captured at time of endorsement +- `cfi_certificate_expiration_at_time` (date, nullable) +- `far_reference` (text) — the regulation citation +- `endorsement_text` (text) — full text as written by CFI +- `aircraft_model_id` (UUID, FK → `reference.aircraft_models`, nullable) — for model-specific endorsements +- `expiration_date` (date, nullable) — for endorsements with currency implications +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.19 `logbook.medicals` + +**Purpose:** Medical certificates held. Supports Class 1, 2, 3 traditional medicals, BasicMed, and Special Issuance. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `medical_class_id` (UUID, FK → `reference.medical_classes`) — Class 1, Class 2, Class 3, BasicMed, Special Issuance +- `exam_date` (date) +- `ame_person_id` (UUID, FK → `logbook.people`, nullable) — for AME-issued +- `expiration_date_for_class_1_ops` (date, nullable) — computed by application or stored +- `expiration_date_for_class_2_ops` (date, nullable) +- `expiration_date_for_class_3_ops` (date, nullable) +- `is_special_issuance` (boolean, default false) +- `special_issuance_conditions` (text, nullable) +- `basicmed_cmec_date` (date, nullable) — Comprehensive Medical Examination Checklist date (for BasicMed) +- `basicmed_course_date` (date, nullable) — BasicMed online course completion +- `pilots_medical_certificate_number` (text, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Multiple expiration date columns reflect the fact that a Class 1 also confers Class 2 and Class 3 privileges, each expiring at a different time depending on the pilot's age. Application logic computes these from exam_date and pilot age; storing them allows fast queries. + +### 6.20 `logbook.flight_reviews` + +**Purpose:** §61.56 flight reviews and their equivalents (new certificate/rating issued in preceding 24 months, IPC that includes the review elements, WINGS phase). + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `review_date` (date) +- `review_type` (text) — 'flight_review', 'new_rating', 'IPC_combined', 'WINGS_phase', 'proficiency_check' +- `instructor_person_id` (UUID, FK → `logbook.people`) +- `associated_flight_id` (UUID, FK → `logbook.flights`, nullable) +- `aircraft_or_device_description` (text) +- `satisfactorily_completed` (boolean) +- `endorsement_id` (UUID, FK → `logbook.endorsements`, nullable) — link to corresponding endorsement +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +### 6.21 `logbook.practical_tests` + +**Purpose:** Checkrides and practical tests. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `test_date` (date) +- `examiner_person_id` (UUID, FK → `logbook.people`) +- `certificate_or_rating_sought` (text) +- `outcome` (text) — 'pass', 'fail', 'discontinuance', 'notice_of_disapproval' +- `aircraft_id` (UUID, FK → `logbook.aircraft`, nullable) +- `training_device_id` (UUID, FK → `logbook.training_devices`, nullable) +- `associated_flight_id` (UUID, FK → `logbook.flights`, nullable) +- `associated_training_session_id` (UUID, FK → `logbook.training_sessions`, nullable) +- `acs_or_pts_reference` (text, nullable) +- `resulted_in_certificate_id` (UUID, FK → `logbook.certificates`, nullable) — for passing tests +- `resulted_in_rating_id` (UUID, FK → `logbook.ratings`, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Examiner could be FAA inspector, DPE, ACR. The associated aircraft or training device records what the test was conducted in. Both being NULL is valid (e.g., oral exam retake). + +### 6.22 `logbook.training_events` + +**Purpose:** Training events of all kinds — recurrent training, type ratings programs, AQP, CQT, §61.55 familiarization training, ground school, etc. Supports future-dated planned events with unknown specifics. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `event_type_id` (UUID, FK → `reference.training_event_types`) — recurrent_training, AQP, CQT, type_rating_initial, type_rating_recurrent, §61.55_familiarization, ground_school, OE_initial, line_check, etc. +- `status` (text) — 'planned', 'scheduled', 'in_progress', 'completed', 'cancelled' +- `planned_period_description` (text, nullable) — "April 2027" or similar when exact dates unknown +- `planned_start_date` (date, nullable) +- `planned_end_date` (date, nullable) +- `actual_start_date` (date, nullable) +- `actual_end_date` (date, nullable) +- `provider` (text, nullable) — "FlightSafety International", "CAE", employer name +- `location` (text, nullable) +- `aircraft_type` (text, nullable) — "Boeing 737NG" +- `satisfactorily_completed` (boolean, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** This is intentionally flexible. The "April 2027 training is scheduled but no specific dates" scenario uses `status = 'planned'`, `planned_period_description = 'April 2027'`, all date columns NULL. Updates as schedule firms up. + +### 6.23 `logbook.attachments` + +**Purpose:** File attachments — scanned medical certificates, endorsement photos, 8710 forms, training certificates, anything else. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `owner_table` (text) — name of the table the attachment is linked to (`flights`, `endorsements`, etc.) +- `owner_row_id` (UUID) — UUID of the row in `owner_table` +- `filename` (text) — original filename +- `mime_type` (text) +- `file_size_bytes` (bigint) +- `storage_path` (text) — object storage key or filesystem path +- `sha256_hash` (bytea) — content hash for integrity +- `description` (text) +- `uploaded_at` (timestamptz) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** The `(owner_table, owner_row_id)` pair is a soft foreign key — not enforced by the database, validated by the application. This polymorphic pattern is accepted here despite the integrity tradeoff because attachments are loosely coupled and the alternative (one attachments table per attachable entity) is worse. + +File contents live in object storage (S3-compatible like MinIO for self-hosted) or on disk, not in the database. The `sha256_hash` allows detection of file corruption or tampering. + +### 6.24 `logbook.tags` and `logbook.entry_tags` + +**Purpose:** User-defined tags for flights and other entries. Supports niche tracking (aerobatic time, mountain flying, formation flight, specific routes, etc.) without schema changes. + +**`logbook.tags`:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `tag_name` (text) +- `tag_color` (text, nullable) — for UI display +- `description` (text) +- `tag_value_type` (text, default 'boolean') — 'boolean' (presence-only), 'numeric_hours' (aggregable hours), 'text' (descriptive) +- `created_at`, `updated_at` (timestamptz) + +Constraint: `(owner_user_id, tag_name)` unique. + +**`logbook.entry_tags`:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `tag_id` (UUID, FK → `logbook.tags`) +- `owner_table` (text) — `flights`, `training_sessions`, `flight_legs` +- `owner_row_id` (UUID) +- `numeric_value` (numeric(5,1), nullable) — for hours-type tags +- `text_value` (text, nullable) — for text-type tags +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Three tag value types support different use cases. A boolean tag like "formation_flight" is just presence. A numeric tag like "aerobatic_time" stores hours, allowing aggregations like "total aerobatic time." A text tag like "checkride_examiner_notes" captures descriptive metadata. + +### 6.25 `logbook.public_keys` + +**Purpose:** Registered public keys for people who sign entries (typically CFIs, but potentially anyone). Backs the cryptographic signature subsystem. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `person_id` (UUID, FK → `logbook.people`) +- `key_fingerprint` (text) +- `key_algorithm` (text) — 'rsa', 'ed25519', 'pgp', etc. +- `public_key_data` (text) — ASCII-armored public key or PEM +- `key_format` (text) — 'pgp_armored', 'x509_pem', 'openssh', etc. +- `registered_at` (timestamptz) +- `revoked_at` (timestamptz, nullable) +- `revocation_reason` (text, nullable) +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +Constraint: `(owner_user_id, key_fingerprint)` unique. + +### 6.26 `logbook.entry_signatures` + +**Purpose:** Cryptographic signatures over logbook entries. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `entry_table` (text) — `flights`, `training_sessions`, `endorsements`, `book_reports` +- `entry_id` (UUID) — soft FK to row in `entry_table` +- `signer_person_id` (UUID, FK → `logbook.people`) +- `public_key_id` (UUID, FK → `logbook.public_keys`) +- `canonical_form_version` (text) — version of the canonicalization algorithm used +- `canonical_data_hash` (bytea) — SHA-256 of canonical bytes signed +- `signature_data` (bytea) — the signature itself +- `signed_at` (timestamptz) +- `signature_purpose` (text) — 'cfi_endorsement_signature', 'student_acknowledgment', 'report_attestation', etc. +- `notes` (text) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** Signing an entry sets `is_locked = TRUE` on the entry via trigger. Modifications to a locked entry are rejected, preserving signature validity. + +### 6.27 `logbook.book_reports` + +**Purpose:** On-demand snapshots of currency, expirations, totals, and other derived data, generated at a moment and persisted. Reproducible historical reports. + +**Key fields:** + +- `id` (UUID v7, PK) +- `owner_user_id` (UUID, FK → `auth.users`) +- `generated_at` (timestamptz) +- `as_of_date` (date) — the date the report content is computed against +- `report_type` (text) — 'currency_summary', 'next_due', 'totals', 'pre_flight_check', 'experience_summary', etc. +- `report_data` (jsonb) — the computed report contents +- `notes` (text) +- Attestation fields: + - `is_attested` (boolean, default false) + - `attested_at` (timestamptz, nullable) + - `attested_by_person_id` (UUID, FK → `logbook.people`, nullable) + - `attestation_text` (text, nullable) — the actual statement attested to + - `attestation_signature_id` (UUID, FK → `logbook.entry_signatures`, nullable) +- `created_at`, `updated_at` (timestamptz) + +**Notes:** A function `generate_book_report(user_id, as_of_date, report_type)` runs the underlying queries and inserts a row. Reports for personal use have `is_attested = FALSE`. Reports for external presentation (job interview, FAA inspection response) go through an attestation step, optionally cryptographically signed. + +### 6.28 `audit.audit_log` + +**Purpose:** Tamper-evident record of every change to audited tables. + +**Key fields:** + +- `id` (bigserial, PK) +- `table_name` (text) +- `row_id` (UUID) +- `operation` (text) — 'INSERT', 'UPDATE', 'DELETE' +- `changed_at` (timestamptz) +- `changed_by_user_id` (UUID, nullable) +- `session_user` (text) — `current_user` at time of change +- `change_reason` (text, nullable) — set via `SET LOCAL audit.change_reason = '...'` before UPDATE/DELETE +- `old_values` (jsonb, nullable) — full row before change +- `new_values` (jsonb, nullable) — full row after change +- `prev_hash` (bytea) — hash of the immediately prior audit_log row +- `row_hash` (bytea) — SHA-256 of `(prev_hash || canonical content of this row)` + +**Access:** INSERT only at the application role level. UPDATE and DELETE privileges revoked. + +**Notes:** See section 9 for detailed mechanics. + +--- + +## 7. Category-Specific Flight Details + +Different aircraft categories have different relevant metadata. Rather than burdening `flights` with NULL columns for irrelevant categories, we use a **subclass table pattern**: a one-to-one optional extension table per category. + +### 7.1 Pattern + +Each subclass table: + +- Has `flight_id` (UUID) as both PK and FK to `logbook.flights`. +- Is created only when category-specific data needs to be recorded. +- Has its own columns for category-specific attributes. + +The presence of a row indicates "this flight has category-specific data captured." Absence is normal and not an error. + +### 7.2 Initial Subclass Tables to Implement + +- `logbook.helicopter_flight_details` — autorotations to touchdown count, hovering time, external load operations. +- `logbook.glider_flight_details` — launch method (aerotow / winch / self-launch / bungee), tow aircraft reference, release altitude, max altitude, soaring time. +- `logbook.balloon_flight_details` — balloon type (hot air / gas / hybrid), launch site description, landing site description, max altitude. + +### 7.3 Stubs for Future Categories + +- `logbook.lta_flight_details` (lighter-than-air — airships and balloons split if needed) +- `logbook.powered_lift_details` +- `logbook.weight_shift_control_details` +- `logbook.powered_parachute_details` + +These can be added as users emerge with the need. The pattern is fixed: PK/FK to flights, category-specific columns. + +### 7.4 Application Logic + +The application determines which subclass table(s) to populate based on the flight's aircraft model's category. A flight in a Robinson R44 may have a `helicopter_flight_details` row. A flight in a glider may have a `glider_flight_details` row. A flight in a Cessna 172 has no subclass row. + +--- + +## 8. Time Modeling + +### 8.1 Hours: NUMERIC(5,1) + +All time durations stored in hours, to one decimal place. Range up to 9999.9. Pilot convention is tenths; using `NUMERIC` avoids floating-point error accumulation across aggregations. + +Aggregated totals (career hours) fit in `NUMERIC(7,1)` easily. + +### 8.2 Moments: timestamptz + +All point-in-time events use `timestamptz`. Aviation crosses time zones routinely; naive timestamps invite corruption. + +For the night currency / sunrise-sunset determination, the actual moment of the takeoff or landing is needed in UTC, plus the airport's coordinates, to compute local solar time. + +### 8.3 Block Times + +Four optional fields on `flights`: + +- `block_out` — start of movement (pushback from gate, or first taxi) +- `block_off` — wheels off (takeoff) +- `block_on` — wheels on (landing) +- `block_in` — end of movement (arrival at gate, shutdown) + +Derived in views, never required: + +- block_time = block_in - block_out +- air_time = block_on - block_off +- taxi_out = block_off - block_out +- taxi_in = block_in - block_on + +`total_time` (the 61.51 logged time) is a separate stored column. Application-layer logic may compute and prefill it from block fields, but the user controls it. + +### 8.4 PIC Basis + +§61.51(e) defines multiple bases on which PIC time can be logged: + +1. Sole manipulator of controls when rated for the aircraft category/class/type +2. Acting PIC (legal PIC of flight) +3. ATP exercising privileges in 14 CFR Part 121 scheduled ops +4. Authorized PIC of an operation requiring more than one pilot + +A `pic_basis_id` field on `flights` references a `reference.pic_basis_types` lookup table with these values. Default: sole_manipulator. The distinction matters most for SIC currency under §61.55, which asks about sole-manipulator experience specifically. + +### 8.5 Night Definitions + +Two different definitions of "night" coexist in 14 CFR: + +- **§1.1 (logging definition):** the time between end of evening civil twilight and beginning of morning civil twilight. Used for `night_time` hours on the flight record. +- **§61.57(b) (currency definition):** 1 hour after sunset to 1 hour before sunrise. Used for night takeoff/landing currency. + +The schema accommodates both. `night_time` on `flights` uses the §1.1 definition. Individual landing rows have `is_night_per_6157` computed from the takeoff/landing timestamp and airport location. + +A SQL function `is_night_per_6157(occurred_at_utc, airport_id) RETURNS boolean` implements the §61.57(b) calculation. Similarly `is_night_per_part1(occurred_at_utc, airport_id) RETURNS boolean` for civil twilight. Both rely on the NOAA solar position algorithm implemented in PLpgSQL or as a `pgcrypto`/`pl/pgsql` function. + +--- + +## 9. Audit Trail Design + +### 9.1 Structure + +A single `audit.audit_log` table. One row per change to an audited table. Schema documented in section 6.28. + +### 9.2 Trigger Function + +A single trigger function `audit.audit_trigger_func()` is attached to every audited table for INSERT, UPDATE, and DELETE. + +Pseudo-logic: + +``` +ON event: + determine operation (INSERT/UPDATE/DELETE) + capture row id, table name, timestamp, user + capture old_values (NULL for INSERT) and new_values (NULL for DELETE) as JSONB + read change_reason from session variable (NULL if not set) + read prev_hash from latest audit_log row + compute row_hash = SHA256(prev_hash || canonical_content) + INSERT into audit_log +``` + +### 9.3 Hash Chain + +Each audit row contains: + +- `prev_hash` — `row_hash` of the immediately preceding audit_log row (NULL for the very first row) +- `row_hash` — SHA-256 of (`prev_hash` || canonical serialization of this row's other content) + +A verification function walks the audit log from start to finish, recomputing each row's hash and comparing to the stored value. Any tampering — modification, deletion, insertion in the middle — breaks the chain at the tampered point. + +The first row in the chain is special; `prev_hash` is NULL or a defined genesis value, and the chain is anchored there. + +### 9.4 Verification Function + +`audit.verify_chain(start_id, end_id) RETURNS table(broken_at_id, expected_hash, actual_hash)` + +Returns rows for any breaks detected. Empty result = chain intact. + +Verification can be run periodically (e.g., daily cron job) and the result logged elsewhere, providing a witness that the chain was intact at known times. + +### 9.5 Change Reason + +For INSERTs and routine current-row UPDATEs, `change_reason` is NULL. For UPDATEs and DELETEs to historical or sensitive data, the application sets a PostgreSQL session variable before the operation: + +```sql +SET LOCAL audit.change_reason = 'Corrected date typo: was 2025-03-14, should be 2025-03-15'; +UPDATE logbook.flights SET flight_date = '2025-03-15' WHERE id = ...; +``` + +The trigger function reads this variable via `current_setting('audit.change_reason', true)` and includes it in the audit row. + +UI workflows for editing historical or signed data should require the user to provide a reason, which the application then sets as the session variable. + +### 9.6 Audit Table Access Control + +The audit log table has its INSERT privilege granted to the application role. UPDATE and DELETE are explicitly revoked from all non-superuser roles. The `audit` schema's tables are read-only to users (via views or restricted SELECTs) for forensic review. + +A separate "audit administrator" role exists at the database level for emergency intervention (e.g., legitimate data correction following discovery of an old bug). Use of this role is itself subject to PostgreSQL's standard logging mechanisms. + +--- + +## 10. Entry Locking and Corrections + +### 10.1 Locking Mechanics + +The `is_locked` boolean on `flights`, `training_sessions`, `endorsements`, and `book_reports` indicates that the entry should not be modified in place. + +A trigger on each lockable table: + +``` +BEFORE UPDATE: + IF OLD.is_locked = TRUE AND non-trivial-field-changed THEN + RAISE EXCEPTION 'Locked entry cannot be modified. Create a correction.' + END IF +``` + +"Non-trivial" excludes the locking metadata itself (is_locked, locked_at, locked_reason can transition; superseded_by_* can be updated by the correction process). + +Locks are set by: + +- Trigger when an `entry_signatures` row is created for an entry → set is_locked = TRUE, locked_reason = 'cryptographic_signature'. +- User action via the application → locked_reason = 'user_locked'. +- CFI signature recorded in the simple signature fields → locked_reason = 'cfi_signature'. + +### 10.2 Correction Entries + +To correct a locked entry, a new entry is created with: + +- `corrects_flight_id` (or `corrects_training_session_id`) pointing to the original. +- `correction_reason` populated with the user's explanation. +- All other fields populated with the corrected values. + +A trigger then updates the original entry's `superseded_by_flight_id` to point to the correction (this is the only update permitted on a locked entry's content). + +The audit log captures all of this normally. + +### 10.3 Erroneous Entries + +If an entry should not have existed at all (e.g., accidental duplicate, fabricated entry discovered), it can be marked `is_erroneous = TRUE` with `erroneous_reason` populated. The entry is not deleted; it remains for the audit/historical record. + +Display views and aggregation queries filter `WHERE is_erroneous = FALSE`. + +For unlocked entries, hard DELETE is permitted and captured by the audit log. For locked entries, the `is_erroneous` mechanism is the only deletion path. + +### 10.4 Display Logic + +The flat flights view (`v_flat_flights`) presents the user's logbook as it should be read: + +- Entries with `superseded_by_flight_id IS NOT NULL` are excluded from the default view (the correction supersedes them). +- Entries with `is_erroneous = TRUE` are excluded. +- A separate `v_flat_flights_complete` view includes superseded and erroneous entries with status indicators, for full historical review. + +--- + +## 11. Cryptographic Signatures + +### 11.1 Compliance with AC 120-78B + +AC 120-78B sets five criteria for valid electronic signatures in FAA recordkeeping: + +1. Unique identification of the signer +2. Signature under signer's sole control +3. Intent to sign (deliberate action) +4. Linkage to the document (signature invalid if document modified) +5. Verifiability + +Standard asymmetric cryptographic signatures (PGP/GPG, S/MIME, raw Ed25519) satisfy all five when key management is sound. + +### 11.2 Key Registration + +Before someone (typically a CFI) can sign entries in a user's logbook, their public key is registered in `logbook.public_keys`: + +- Linked to a `person_id` in `logbook.people`. +- Fingerprint stored for quick identification. +- Full public key data stored for verification. +- Algorithm and format identified. +- Registration timestamp. +- Optional revocation date if the key is later compromised or replaced. + +### 11.3 Canonical Data Form + +To produce a verifiable signature, the bytes signed must be reproducible. Each signable entity has a defined canonicalization: + +- Fixed set of fields (defined per `canonical_form_version`) +- Sorted JSON keys +- UTF-8 encoding, no whitespace +- Specific format for nulls, dates, numerics + +The `canonical_form_version` on each signature row identifies which algorithm was used. New versions can be introduced without invalidating old signatures. + +### 11.4 Signing Workflow + +1. Pilot creates/finalizes a flight entry. +2. Pilot presents entry to CFI for signature (in person, via app, etc.). +3. CFI uses their private key (held in their own GPG keyring, smart card, or HSM — never in the logbook system) to sign the canonical representation. +4. Application receives the signature and inserts a row in `logbook.entry_signatures`: + - References the entry + - References the CFI's `person_id` and `public_key_id` + - Records the canonical form version + - Records the SHA-256 hash of the canonical bytes (for quick verification) + - Records the signature bytes themselves +5. Trigger sets `is_locked = TRUE` on the entry. + +### 11.5 Verification + +A verification function: + +1. Loads the signature row. +2. Reconstructs the canonical bytes from the current entry data (or the historical version via audit log if the entry has been corrected). +3. Recomputes the hash and compares to the stored hash. +4. Verifies the signature against the public key. +5. Returns a result: VALID, HASH_MISMATCH, SIGNATURE_INVALID, KEY_REVOKED, etc. + +### 11.6 Limitations and Caveats + +- Private key security is a user responsibility. The system enforces no key management practices on signers. +- Signature validity at a future date depends on the key not being revoked and the canonical form algorithm being preserved. +- For long-term archival (decades), additional measures (timestamping services, periodic re-signing) may be needed. Out of scope for v1. + +--- + +## 12. Lookup Tables and Enums + +### 12.1 Lookup Tables + +Used for category-like data with metadata. All lookup tables in `reference` schema, shared across users, follow a standard structure: + +- `id` (UUID v7, PK) +- `code` (text, unique) — short stable identifier used in queries and exports +- `name` (text) — display name +- `description` (text) +- `display_order` (integer) +- `is_deprecated` (boolean, default false) +- `effective_from` (date, nullable) — for time-varying definitions +- `effective_to` (date, nullable) +- `created_at`, `updated_at` (timestamptz) + +### 12.2 Effective-Dated Lookups + +Some reference data has changed over time (training device level definitions, regulatory categories). Effective dates let the lookup table represent the regulatory history: + +- "FTD Level X (1995-2010)" — `effective_to = '2010-XX-XX'` +- "FTD Level X (2010-present)" — `effective_from = '2010-XX-XX'` + +Entries from 2008 reference the old definition; entries from 2020 reference the new. Both definitions remain accessible. + +### 12.3 Lookup Tables to Define + +- `aircraft_categories` — airplane, rotorcraft, glider, lighter-than-air, powered-lift, weight-shift-control, powered-parachute +- `aircraft_classes` — single-engine land, multi-engine land, single-engine sea, multi-engine sea, helicopter, gyroplane, balloon, airship +- `approach_types` — ILS, LOC, LOC-BC, VOR, NDB, RNAV-GPS-LNAV, RNAV-GPS-LPV, RNAV-GPS-LNAV/VNAV, GLS, PAR, ASR, ILS-CAT-II, ILS-CAT-III, visual, contact, etc. +- `certificate_levels` — Student, Sport, Recreational, Private, Commercial, ATP, Flight Instructor, Ground Instructor, Remote Pilot, Mechanic, Repairman, etc. +- `rating_types` — Instrument-Airplane, Multi-Engine Land, Single-Engine Sea, Type ratings (with type code), CFI category/class, Sport pilot endorsements, etc. +- `endorsement_types` — high_performance, complex, tailwheel, high_altitude, pressurized, type-specific, solo, solo_cross_country, solo_90day_repeat, flight_review, IPC, BFR, etc. +- `medical_classes` — Class 1, Class 2, Class 3, BasicMed, Special Issuance +- `role_on_flight_types` — PIC, SIC, CFI_giving, student_receiving, safety_pilot, flight_attendant, jumpseat_observer, relief_pilot, IRO, check_airman, examiner, etc. +- `training_device_types` — FFS, FTD, ATD, BATD, AATD, unknown_historical +- `pic_basis_types` — sole_manipulator, acting_pic, atp_part_121, more_than_one_pilot_required +- `training_event_types` — recurrent_training, AQP, CQT, type_rating_initial, type_rating_recurrent, §61.55_familiarization, ground_school, OE_initial, line_check, etc. +- `role_qualification_types` — for `person_roles`: CFI, CFII, MEI, ATP, Commercial, Private, flight_attendant, dispatcher, A&P_mechanic, IA, DPE, ACR, FAA_inspector, etc. +- `far_citations` — regulation reference table for cross-linking + +### 12.4 Enums (Native PostgreSQL ENUM Types) + +Reserved for small, genuinely fixed bounded sets that don't need metadata: + +- `audit_operation` — 'INSERT', 'UPDATE', 'DELETE' +- `practical_test_outcome` — 'pass', 'fail', 'discontinuance', 'notice_of_disapproval' +- `training_event_status` — 'planned', 'scheduled', 'in_progress', 'completed', 'cancelled' +- `landing_full_stop_type` — 'full_stop', 'touch_and_go', 'stop_and_go' + +These are unlikely to change. Adding values is a simple ALTER TYPE in modern PostgreSQL. + +--- + +## 13. Views + +### 13.1 `v_flat_flights` + +Denormalized flat view of flights, one row per flight (or one per leg, with a parallel `v_flat_flight_legs`). All joins pre-resolved. The primary interface for the query builder and exports. + +Fields include: flight_id, leg_id, date, owner_user_id, aircraft tail and model, all time categories, all derived flags (is_complex, is_high_performance, etc.), origin and destination airports, approach counts, landings counts, instructor name, free-text remarks, signature status, lock status. + +Excludes superseded and erroneous entries by default. + +### 13.2 `v_flat_flights_complete` + +Same as `v_flat_flights` but includes superseded and erroneous entries, with explicit status columns. For audit review and full historical display. + +### 13.3 `v_flights_chronological` + +Flights only, ordered by date. The "scroll through my logbook" view. + +### 13.4 `v_events_chronological` + +Non-flight events only — endorsements, medicals, flight reviews, practical tests, training events, certificates issued — ordered by date. The view that lets you find "when did I last get a medical?" without scrolling past hundreds of flights. + +### 13.5 `v_logbook_chronological` + +Union of flights and events, ordered by date. The combined view. + +### 13.6 `v_currency_status_live` + +Real-time currency calculation. For each pilot and each relevant currency window (90-day passenger day, 90-day passenger night, IFR 6-month, flight review 24-month, medical class-specific): + +- Current status (current, expired, in grace period, requires IPC, etc.) +- Date of last qualifying event +- Date currency expires (or expired) +- Days remaining (negative if expired) + +### 13.7 `v_expirations_upcoming` + +All expirable items (medicals, flight reviews, currency windows, endorsements with expiration dates) with their expiration dates and days remaining. Filterable by days-out for "what expires in the next 90 days" displays. + +### 13.8 `v_aeronautical_experience_totals` + +Career totals across the standard 61.51 categories plus subcategories needed for 61.65, 61.129, 61.159 evaluations. Suitable for ATP qualification queries. + +### 13.9 `v_aircraft_summary` + +Per-aircraft time totals — useful for "how many hours have I flown in this airplane." + +### 13.10 `v_audit_log_readable` + +Joins audit_log with users and humans-friendly formatting for forensic review. + +--- + +## 14. Book Reports + +### 14.1 Generation + +A function `generate_book_report(user_id, as_of_date, report_type)` runs the appropriate queries against the live data as of the as-of date and inserts a row in `logbook.book_reports` capturing the result. + +`report_data` is JSONB containing whatever the report type defines. Example for `report_type = 'pre_flight_check'`: + +```json +{ + "as_of_date": "2026-05-16", + "medical_status": { "class": 1, "expires": "2026-08-31", "days_remaining": 107 }, + "flight_review": { "last_completed": "2024-08-15", "expires": "2026-08-31", "status": "current" }, + "currency_passenger_day": { "status": "current", "expires": "2026-07-04" }, + "currency_passenger_night": { "status": "expired", "expired_on": "2026-04-12" }, + "currency_ifr": { "status": "current", "approaches_logged": 6, "expires": "2026-09-30" }, + "endorsements_current": [...], + "upcoming_expirations": [...] +} +``` + +### 14.2 Attestation + +For reports intended for external presentation, the user invokes attestation via the application. The application: + +1. Displays the report content one more time. +2. Presents the attestation statement (configurable text, e.g., "I certify the information in this report is true and correct to the best of my knowledge.") +3. Requires explicit confirmation. +4. Updates the report row: `is_attested = TRUE`, `attested_at = now()`, `attested_by_person_id = (logbook owner)`, `attestation_text = (the statement)`. +5. Optionally invokes cryptographic signing — creates an `entry_signatures` row referencing the book report. + +The attestation is itself audited via the audit log. + +### 14.3 Reports as Historical Records + +Once generated, reports are immutable (the same locking principle as flights). They become part of the historical record — "what did my logbook say on 2026-05-16?" is answered by retrieving the report generated that day. + +Reports can be exported as PDF for paper retention. + +--- + +## 15. Constraints and Indexes + +### 15.1 Check Constraints + +Sanity constraints enforced at the database level: + +- All time columns >= 0 +- All time subcategories <= total_time + 0.1 (rounding allowance) +- Date sanity: `flight_date >= '1903-12-17'` (Wright Brothers floor) +- Block time ordering where multiple block fields present +- `(corrects_flight_id IS NULL) OR (correction_reason IS NOT NULL)` +- `(is_erroneous = FALSE) OR (erroneous_reason IS NOT NULL)` +- Locked entries can only have lock-related fields updated +- Exactly-one constraints for XOR foreign keys (e.g., approach belongs to either a flight leg or a training session) + +### 15.2 Partial Indexes + +Optimized for common query patterns: + +- `flights` recent flights index: `ON (owner_user_id, flight_date DESC) WHERE flight_date >= CURRENT_DATE - INTERVAL '24 months'` +- `landings_takeoffs` for night currency: `ON (owner_user_id, occurred_at DESC) WHERE is_landing = TRUE AND is_full_stop = TRUE` +- `approaches` for IFR currency: `ON (owner_user_id, flight_leg_id, training_session_id) WHERE was_missed = FALSE OR to_landing = TRUE` +- Active records: many tables have `WHERE is_erroneous = FALSE AND superseded_by_* IS NULL` partial indexes + +### 15.3 Standard Indexes + +- All foreign keys indexed +- All natural unique keys indexed (registration, certificate_number, etc.) +- Owner_user_id appears in most multi-column indexes (RLS optimization) + +### 15.4 Row-Level Security + +Every `logbook` schema table has RLS enabled with a policy: + +```sql +CREATE POLICY user_isolation ON logbook.xxx + USING (owner_user_id = current_setting('app.current_user_id')::uuid); +``` + +The application sets `app.current_user_id` per session/transaction. Reference schema tables have no RLS (shared read-only). + +--- + +## 16. Import / Export + +### 16.1 Open JSON Format + +The canonical export format is JSON. A documented, versioned schema. Includes: + +- Schema version +- Export metadata (timestamp, source system, user identifier — opaque, not the email) +- All flights with legs, landings, approaches +- All training sessions +- All people and person_roles +- All certificates, ratings, endorsements, medicals +- All flight reviews, practical tests, training events +- All tags and tag assignments +- All public keys and signatures (signatures verifiable post-import) +- Audit log +- Category-specific subclass table data +- References to (but not contents of) attachments — attachment files exported separately as a tarball or ZIP + +UUIDs are preserved across export/import. Re-importing into the same instance is a no-op (no duplicates); importing into a fresh instance reconstitutes the logbook. + +### 16.2 Format Versioning + +`schema_version: "1.0"` field at the top of every export. Reader code is version-aware and can read older versions. + +### 16.3 CSV Compatibility + +Two CSV import/export options for migration to/from major proprietary apps: + +- MyFlightbook CSV format +- ForeFlight CSV format + +These are lossy — they don't carry audit history, signatures, or full schema — but enable users to migrate in or out. + +### 16.4 PDF Export + +For §61.51(i) inspection: a printable PDF of the logbook in traditional layout, generated from `v_flat_flights` and related views. Application-layer functionality, not schema. Schema supports it by providing complete and consistent data. + +### 16.5 Open Standard Publication + +The JSON schema specification will be published separately (e.g., GitHub repository, CC0 license) to encourage adoption by other logbook software. A reference name (e.g., "Open Pilot Logbook Format" / OPLF) will be chosen. + +--- + +## 17. Migrations + +### 17.1 Tool: Flyway + +Plain SQL migration files in a versioned directory: + +``` +migrations/ + V001__initial_reference_schema.sql + V002__lookup_tables.sql + V003__core_entities.sql + V004__flights_and_legs.sql + V005__landings_takeoffs.sql + V006__approaches.sql + V007__training_sessions.sql + V008__certificates_ratings_endorsements.sql + V009__medicals_reviews_tests.sql + V010__training_events.sql + V011__attachments_tags.sql + V012__signatures_and_keys.sql + V013__book_reports.sql + V014__audit_log.sql + V015__audit_triggers.sql + V016__views.sql + V017__rls_policies.sql + V018__seed_reference_data.sql + V019__functions_sunrise_sunset.sql + V020__functions_currency.sql + ... +``` + +### 17.2 Conventions + +- Forward-only. Mistakes get a new forward migration, not a rollback. +- Each migration is transactional where PostgreSQL allows. +- Reference data seeds (lookup tables, well-known airports) live in dedicated migrations. +- Schema changes precede data migrations. +- Comments in migrations document the regulatory or design rationale where relevant. + +### 17.3 Docker Integration + +Flyway's official Docker image runs as a one-shot container during deployment. Migrations apply automatically on database initialization or service deployment. + +--- + +## 18. Backup Strategy + +Operational concern; mentioned here for schema-related considerations: + +- **Logical backup:** nightly `pg_dump` archived off-site. Recoverable across PostgreSQL versions. +- **Physical backup with WAL archiving:** continuous WAL streaming for point-in-time recovery within a recent window. +- **Attachment backup:** object storage (or filesystem) backed up separately, on a coordinated schedule. +- **Audit log priority:** verify that the audit log is included in every backup; verify periodically that restored audit logs validate their hash chains. +- **Backup testing:** schedule periodic restoration tests. An unverified backup is no backup. + +UUIDs in the schema ensure that backups can be restored to alternative instances without identifier collisions. + +--- + +## 19. Out of Scope (Explicit) + +The following are not addressed by this schema. They may be addressed by separate systems, possibly sharing this database server but not this schema: + +- **Duty and rest time tracking** (Part 117, Part 91/135 duty rules) — belongs in scheduling software. +- **Aircraft maintenance tracking** (next inspection due, AD compliance, component time tracking) — belongs in maintenance software. +- **Flight scheduling** — assignments, crew pairings, dispatch. +- **Weather data and integrations.** +- **Flight planning** — routes, fuel calculations, performance. +- **Real-time flight tracking** — ADS-B integration. +- **Cost/expense tracking** — fuel costs, rental costs, training expenses. +- **Insurance documentation** — though `book_reports` can produce documentation for insurance use. +- **Currency-action workflow** (e.g., automatically scheduling a refresher when currency lapses). + +--- + +## 20. Future Considerations / Open Questions + +Items deferred for later resolution: + +- **Query builder UI:** how to implement safe user-defined queries. Likely a point-and-click builder over `v_flat_flights` and friends. +- **WINGS program integration:** automatic credit detection from flight log. +- **PRD (Pilot Records Database) reporting:** the §44703 mandate for record-sharing between airlines. +- **Multi-key signatures:** a single entry signed by multiple parties (e.g., student and CFI both sign). +- **Long-term signature validity:** integration with RFC 3161 timestamping services or similar. +- **Aircraft and people deduplication:** if multi-user with sharing is ever revisited, mechanisms for users to optionally point at shared records. +- **Internationalization:** non-FAA aviation regulatory environments (EASA, Transport Canada, etc.). The schema is largely category-neutral but currency/certificate logic is currently FAA-specific. +- **Offline-first mobile app:** synchronization model if a mobile companion app is built. + +--- + +## 21. Glossary + +- **§61.51** — 14 CFR 61.51, *Pilot Logbooks*. The baseline regulation. +- **ACS / PTS** — Airman Certification Standards / Practical Test Standards. Examination criteria for practical tests. +- **AME** — Aviation Medical Examiner. +- **ATD** — Aviation Training Device. A category of training device less capable than an FTD. +- **AATD** — Advanced Aviation Training Device. Higher-fidelity ATD. +- **BATD** — Basic Aviation Training Device. Lower-fidelity ATD. +- **BasicMed** — A medical qualification alternative to traditional medical certificates, established by FAA reform circa 2017. +- **CFI / CFII / MEI** — Certified Flight Instructor / Instrument-Instructor / Multi-Engine Instructor. +- **DPE** — Designated Pilot Examiner. +- **FFS** — Full Flight Simulator. Highest-fidelity training device, certified to levels A-D. +- **FTD** — Flight Training Device. Mid-fidelity training device. +- **IPC** — Instrument Proficiency Check. Required to regain IFR currency after lapse beyond grace period. +- **MGTOW** — Maximum Gross Takeoff Weight. +- **PIC / SIC** — Pilot in Command / Second in Command. +- **TAA** — Technically Advanced Aircraft. Per §61.129, an airplane with specific avionics. +- **UUID v7** — Time-ordered universally unique identifier. + +--- + +## 22. Appendix A — 14 CFR 61.51 Requirements Mapping + +How each §61.51(b)-(j) requirement is satisfied by the schema: + +| Requirement | Source | Schema Element | +|---|---|---| +| Date | §61.51(b)(1)(i) | `flights.flight_date`, `training_sessions.session_date` | +| Total flight or lesson time | §61.51(b)(1)(ii) | `flights.total_time`, `training_sessions.total_time` | +| Location of departure and arrival | §61.51(b)(1)(iii) | `flight_legs.from_airport_id`, `to_airport_id` | +| Type and identification of aircraft / device | §61.51(b)(1)(iv) | `flights.aircraft_id` → model; `training_sessions.training_device_id` → model | +| Solo time | §61.51(b)(2)(i) | `flights.solo_time` | +| PIC time | §61.51(b)(2)(ii) | `flights.pic_time` | +| SIC time | §61.51(b)(2)(iii) | `flights.sic_time` | +| Flight instruction received | §61.51(b)(2)(iv) | `flights.dual_received`, `training_sessions.dual_received` | +| Training received in FFS/FTD/ATD | §61.51(b)(2)(v) | `training_sessions.dual_received` | +| Day / night | §61.51(b)(3)(i) | `flights.night_time`; landings/takeoffs `is_day_per_part1`, `is_night_per_6157` | +| Actual instrument | §61.51(b)(3)(ii) | `flights.actual_instrument` | +| Simulated instrument | §61.51(b)(3)(iii) | `flights.simulated_instrument`, `training_sessions.instrument_time_simulated` | +| Instrument approaches | §61.51(g)(1) | `logbook.approaches` | +| Safety pilot name | §61.51(c)(1) | `logbook.flight_crew` with role `safety_pilot` | +| Instruction given | §61.51(h) | `flights.dual_given`, `training_sessions.dual_given` | +| Instructor signature | §61.51(j) | `flights.cfi_signature_*`, `logbook.entry_signatures` | + +--- + +## 23. Appendix B — Regulatory Reference Index + +- **14 CFR 1.1** — Definitions; night (logging), cross-country +- **14 CFR 61.1** — Definitions; cross-country variants for ratings +- **14 CFR 61.31** — Type rating, complex, high-performance, tailwheel, high-altitude, pressurized +- **14 CFR 61.39** — Prerequisites for practical tests +- **14 CFR 61.51** — Pilot logbooks (the floor) +- **14 CFR 61.55** — SIC qualifications +- **14 CFR 61.56** — Flight review +- **14 CFR 61.57** — Recent flight experience (currency) +- **14 CFR 61.65** — Instrument rating +- **14 CFR 61.129** — Commercial pilot +- **14 CFR 61.159** — ATP (1500-hour rule) +- **AC 120-78B** — Electronic signatures and recordkeeping +- **FAA Order 8900.1** — FSIMS + +--- + +## 24. Appendix C — High-Level Entity Relationship Diagram + +```mermaid +erDiagram + USERS ||--o{ PEOPLE : owns + USERS ||--o{ AIRCRAFT : owns + USERS ||--o{ TRAINING_DEVICES : owns + USERS ||--o{ FLIGHTS : owns + USERS ||--o{ TRAINING_SESSIONS : owns + + PEOPLE ||--o{ PERSON_ROLES : has + PEOPLE ||--o{ FLIGHT_CREW : appears_in + PEOPLE ||--o{ PUBLIC_KEYS : registers + PEOPLE ||--o{ ENDORSEMENTS : endorses + PEOPLE ||--o{ ENTRY_SIGNATURES : signs + + AIRCRAFT_MODELS ||--o{ AIRCRAFT : "instantiated as" + TRAINING_DEVICE_MODELS ||--o{ TRAINING_DEVICES : "instantiated as" + + AIRCRAFT ||--o{ FLIGHTS : "flown in" + TRAINING_DEVICES ||--o{ TRAINING_SESSIONS : "trained in" + + FLIGHTS ||--o{ FLIGHT_LEGS : contains + FLIGHTS ||--o{ FLIGHT_CREW : "has crew" + FLIGHT_LEGS ||--o{ LANDINGS_TAKEOFFS : "has events" + FLIGHT_LEGS ||--o{ APPROACHES : "has approaches" + + TRAINING_SESSIONS ||--o{ FLIGHT_CREW : "has crew" + TRAINING_SESSIONS ||--o{ APPROACHES : "has approaches" + + AIRPORTS ||--o{ FLIGHT_LEGS : "departure/arrival" + AIRPORTS ||--o{ LANDINGS_TAKEOFFS : "location" + AIRPORTS ||--o{ APPROACHES : "location" + + USERS ||--o{ CERTIFICATES : holds + CERTIFICATES ||--o{ RATINGS : "has ratings" + CERTIFICATES ||--o{ CERTIFICATE_LIMITATIONS : "has limitations" + + USERS ||--o{ ENDORSEMENTS : received + USERS ||--o{ MEDICALS : holds + USERS ||--o{ FLIGHT_REVIEWS : completed + USERS ||--o{ PRACTICAL_TESTS : took + USERS ||--o{ TRAINING_EVENTS : participated_in + + USERS ||--o{ BOOK_REPORTS : generated + USERS ||--o{ ATTACHMENTS : "uploaded" + USERS ||--o{ TAGS : "defined" + + FLIGHTS ||--o{ ENTRY_SIGNATURES : "may be signed" + TRAINING_SESSIONS ||--o{ ENTRY_SIGNATURES : "may be signed" + BOOK_REPORTS ||--o{ ENTRY_SIGNATURES : "may be signed" + + PUBLIC_KEYS ||--o{ ENTRY_SIGNATURES : verifies +``` + +This is a simplified high-level ERD. Detailed entity-by-entity diagrams will accompany the DDL phase. + +--- + +**End of design document v0.1.** + +Comments, additions, and revisions to feed back into v0.2 before DDL generation: + +- [ ] (placeholder for review notes) + diff --git a/architecture-overview.md b/documentation/architecture-overview.md similarity index 100% rename from architecture-overview.md rename to documentation/architecture-overview.md