diff --git a/docs/tasks/005-repair-parts-tracking.md b/docs/tasks/005-repair-parts-tracking.md index fcc6d49..e3b7517 100644 --- a/docs/tasks/005-repair-parts-tracking.md +++ b/docs/tasks/005-repair-parts-tracking.md @@ -1,38 +1,192 @@ # Task 005: Repair Parts Tracking ## Status -Design specification. Not yet implemented. + +Approved implementation specification. Not yet implemented. + +This task introduces a minimal durable component registry only to support +tracked-component references in repair-part history. It does not implement the +broader component inventory, lifecycle, installation, location, or warehouse +model described as future direction in +`docs/design/COMPONENT_DOMAIN_MODEL.md`. ## Objective -Allow authorized operators to record parts installed, removed, or consumed -during a repair while preserving immutable history and leaving room for a later -spare-parts inventory system. + +Allow authorized operators to record what was used during an active repair as +exactly one of: + +1. a quantity-based consumable; or +2. one tracked component identified by a durable component UUID. + +Repair-part history is immutable. Corrections use an explicit void action and, +when necessary, a new replacement entry. Original entries and events are never +edited or physically deleted. ## Product Intent -A technician should be able to determine what parts were installed, removed, or -consumed; their quantity and identifying serial number where relevant; any -concise notes; and the authoritative actor and timestamp. + +A technician should be able to determine which consumables and tracked +components were used during a repair. Consumable records preserve their +description, quantity, and unit. Tracked-component records preserve both the +live component UUID and an immutable display snapshot captured when the entry +is recorded. + +All authoritative actors come from authentication. All authoritative +timestamps and repair-event sequences are server or database controlled. + +## Authoritative Boundaries + +Task 004 remains authoritative for: + +- control-board replacement; +- MAC ownership; +- control-board installation and removal history; +- current control-board and MAC projections; +- `CONTROL_BOARD_REPLACED` events; and +- the `/api/miners/{miner_id}/change-mac` compatibility alias. + +Task 005 records repair usage only. It must not install, remove, replace, +transfer, relocate, or change ownership of a tracked component. In particular, +recording a control-board component must not: + +- replace a control board or transfer its MAC address; +- modify `miners.control_board_sn` or `miners.current_mac`; +- modify `miner_network_identities`, `miner_mac_ownerships`, or + `miner_control_board_installations`; +- append `CONTROL_BOARD_REPLACED`; or +- substitute for Task 004's explicit replacement workflow. + +The existing `miner_control_board_installations.id` identifies an installation +history row, not a durable component. The existing `miner_hashboards.id` +identifies a miner-slot projection, not a component that retains identity +through movement. Task 005 must not repurpose either UUID as a component UUID or +infer component identity from a serial number, MAC address, miner, slot, or +installation. ## Scope -- proposed focused repair-part read and action APIs -- proposed capability-based permissions -- a deliberately small part type and action vocabulary -- part type, action, quantity, optional serial number, and notes -- serialized component identifiers where applicable -- explicit void/correction rather than physical deletion -- immutable repair events -- migration, constraints, tests, and documentation -## Out of Scope -Purchasing, suppliers, pricing, invoices, warehouse transfers, reorder automation, warranties, RMA, full inventory, generalized bill of materials, bulk import, automatic stock deduction, photos, work orders, and control-board identity replacement. +- a minimal durable component registry; +- narrow component creation and detail reads; +- focused repair-part list, record, and void routes; +- capability-based permissions; +- mutually exclusive consumable and tracked-component request shapes; +- fixed-precision quantities and controlled units; +- immutable tracked-component display snapshots; +- explicit void and replacement-entry corrections; +- immutable, explicitly sequenced repair events; +- atomic transactions and concurrency control; +- PostgreSQL constraints, foreign keys, and immutability protections; and +- focused, migration, regression, architecture, and route tests. -Task 004 remains authoritative for control-board identity and MAC changes. -Task 005 must not accept `control_board` as a generic part type or update -`miners.control_board_sn`, `miners.current_mac`, or -`miner_network_identities`. +## Out Of Scope + +- generalized component inventory or generic component CRUD; +- component update, delete, list, search, merge, or deduplication APIs; +- component locations, movements, installations, or removals; +- component lifecycle or condition workflows; +- component ownership transfer; +- warehouse inventory and transfers; +- stock levels, allocation, or automatic stock deduction; +- purchasing, suppliers, vendors, pricing, invoices, and cost accounting; +- warranties and RMA workflows; +- reorder automation; +- automatic creation of components while recording repair parts; +- automatic installation, removal, ownership transfer, or location tracking; +- specialized component tables or attributes; +- generalized bills of materials; +- bulk import; +- photos and work orders; and +- redesign or migration of Task 004 structures. + +## Terminology + +**Component:** A minimally registered physical part with an immutable UUID and +constrained type. Task 005 records identity and basic display metadata only. + +**Repair-part entry:** An immutable record associating one consumable use or one +tracked component with a repair. + +**Consumable:** A normalized description, positive quantity, controlled unit, +and optional notes. It has no component UUID. + +**Tracked component:** One existing component referenced by UUID. Its repair-part +entry includes a display snapshot and has no consumable quantity or unit. + +**Display snapshot:** Immutable component type, Asset ID, manufacturer, model, +and manufacturer serial values copied from the component when the repair-part +entry is recorded. Nullable source values remain null. + +**Void:** A one-way audited correction that preserves the original entry. A +corrected value is represented by voiding the incorrect entry and recording a +new entry. + +## Minimal Component Registry + +### Component Types + +The initial controlled component types are: + +- `control_board`; +- `hashboard`; +- `power_supply`; and +- `fan`. + +Do not accept `other` or arbitrary free-text types. Expanding the vocabulary +requires a reviewed migration and corresponding model and test updates. + +### Component Schema + +Create the focused `components` table with: + +- `id` UUID primary key with a database default; +- `component_type` constrained text, required; +- `asset_id` text, optional; +- `manufacturer` text, optional; +- `model` text, optional; +- `manufacturer_serial` text, optional; +- `created_at` timezone-aware timestamp, required and server controlled; and +- `created_by` authenticated user UUID, required. + +Rules: + +- `id`, `component_type`, `created_at`, and `created_by` are immutable; +- optional text is trimmed, nonblank when present, and limited to 255 + characters; +- Asset IDs are case-insensitively unique among components after trimming; +- component Asset IDs do not share a database uniqueness namespace with miner + Asset IDs in this task; +- manufacturer serials are not assumed globally unique; +- `created_by` references the existing user identity with `RESTRICT` deletion + behavior; +- no client may supply `id`, `created_at`, or `created_by`; +- no component is created implicitly from a repair-part request; and +- Task 005 provides no component metadata update or deletion workflow. + +The UUID is the authoritative component identity. Asset ID and manufacturer +metadata do not replace it. A later explicit task may evolve mutable display +metadata while preserving the UUID and history. ## API -Proposed routes: + +### Component Routes + +Task 005 adds exactly these narrow component routes: + +```http +POST /api/components +GET /api/components/{component_id} +``` + +`POST /api/components` creates one component and returns `201 Created`. +`GET /api/components/{component_id}` returns one component and `200 OK`. +Unknown component UUIDs return `404`. Malformed UUIDs use the existing `422` +validation response. + +Do not add component list, search, update, patch, delete, install, remove, move, +or lifecycle routes. + +### Repair-Part Routes + +Task 005 adds exactly these repair-part routes: ```http GET /api/repairs/{repair_id}/parts @@ -40,150 +194,486 @@ POST /api/repairs/{repair_id}/parts POST /api/repairs/{repair_id}/parts/{repair_part_id}/void ``` -Do not physically delete authoritative entries. +The list returns `200 OK`. Recording returns `201 Created`. A successful void +returns `200 OK` with the voided repair-part representation. No route physically +deletes or destructively edits a repair-part entry. + +Register and test these routes without changing the precedence or behavior of +legacy static routes, including `/api/repairs/parts-summary`. + +### Response Envelopes + +Use the existing typed success envelope containing `data`, `meta`, and `error`. +Component responses expose only the approved component fields. Repair-part +responses expose the stored consumable fields or stored component snapshot, +plus component UUID, notes, recording metadata, and void metadata. + +A tracked repair-part response must use its immutable snapshot for historical +display. It may expose `component_id` so a caller can separately request the +live component. It must not silently substitute current component metadata for +the snapshot. + +### List Ordering + +Return repair-part entries in ascending recorded repair-event sequence. The +explicit database sequence, not timestamps, determines order. Void state is +shown on the original entry; void events remain separately visible in the +existing repair timeline. + +No pagination or filtering is added because the collection is bounded to one +repair. A later change requires compatibility review. ## Permissions -The proposed authorization model is: -- use implemented `repairs.read` for the read route -- introduce `repairs.parts.manage` for add and void actions +Introduce these proposed capabilities: -Grant the proposed write permission to Admin, Manager, and Technician. Do not -grant it to Read Only. `repairs.parts.manage` does not exist yet and must be -introduced by the Task 005 migration. The router checks capabilities rather than -role names. +- `components.read` for component detail reads; +- `components.create` for component creation; and +- `repairs.parts.manage` for recording and voiding repair-part entries. + +Reuse implemented `repairs.read` for the repair-part list. + +Role grants: + +| Capability | Admin | Manager | Technician | Read Only | +| --- | --- | --- | --- | --- | +| `components.read` | yes | yes | yes | yes | +| `components.create` | yes | yes | yes | no | +| `repairs.read` | yes | yes | yes | yes | +| `repairs.parts.manage` | yes | yes | yes | no | + +Routers check capabilities, never role names. Every route requires +authentication and focused unauthenticated, unauthorized, and authorized-role +tests. + +## Request Models + +Reject unknown fields in every request. Actor UUIDs, timestamps, sequences, +event payloads, snapshots, and persistence identifiers are never accepted from +clients. + +### Create Component -## Add Request ```json { - "part_type": "fan", - "action": "installed", - "quantity": 1, - "serial_number": null, - "notes": "Replacement fan installed." + "component_type": "hashboard", + "asset_id": "HB-0042", + "manufacturer": "MicroBT", + "model": "M50 Hashboard", + "manufacturer_serial": "HB-SN-12345" } ``` -## Initial Vocabulary -Proposed part types: +Only `component_type` is required. Optional text values are normalized by +trimming surrounding whitespace and are rejected when blank after trimming. +Duplicate normalized Asset IDs return `409 Conflict` without leaking a database +exception. -- `fan` -- `power_supply` -- `hashboard` -- `cable` -- `consumable` -- `other` +### Record Consumable -Proposed actions: +```json +{ + "consumable": { + "description": "Thermal paste", + "quantity": 1, + "unit": "gram" + }, + "tracked_component_uuid": null, + "notes": "Applied during board service." +} +``` -- `installed` -- `removed` -- `consumed` +### Record Tracked Component -Task 005 does not model source inventory, stock movement, return destination, -condition, or disposition. Those concepts require a later inventory decision -and must not be approximated with overlapping action/value combinations here. -Use notes for concise context that is not authoritative structured inventory -data. +```json +{ + "consumable": null, + "tracked_component_uuid": "c79f9f36-1e89-4dbc-8a84-9ff3167614be", + "notes": "Used during this repair." +} +``` -## Quantity Rules -- quantity greater than zero -- integer quantities for this task -- serialized components normally quantity 1 -- consumables may exceed 1 -- voiding does not create a negative entry or reverse inventory; it marks the - original record void +Exactly one of `consumable` and `tracked_component_uuid` must be non-null. A +tracked-component request refers to one existing component and never creates, +installs, removes, transfers, or mutates it. + +### Void Repair Part + +```json +{ + "reason": "Recorded against the wrong repair." +} +``` + +The reason is required, trimmed, nonblank, and limited to 1000 characters. The +actor and timestamp come from authentication and the server. + +## Consumable Rules + +A consumable requires: + +- normalized, nonblank `description`, limited to 255 characters; +- `quantity` represented as `NUMERIC(12,3)` or the repository-equivalent fixed + precision; +- quantity greater than zero; +- one controlled `unit`; and +- optional shared notes. + +The initial controlled units are: + +- `each`; +- `gram`; +- `millilitre`; +- `metre`; and +- `centimetre`. + +Do not accept aliases or arbitrary free-text units. The service and request +model reject more than three fractional digits or values outside the fixed +precision range. + +When `unit` is `each`, quantity must be a whole number. Measurement units permit +fractional quantities to three decimal places. Quantity describes repair usage +only and never changes stock. + +## Repair-Part Schema + +Create the focused `repair_parts` table with: + +- `id` UUID primary key with a database default; +- `repair_id` UUID, required; +- `consumable_description` text, nullable; +- `quantity` `NUMERIC(12,3)`, nullable; +- `unit` constrained text, nullable; +- `component_id` UUID, nullable; +- `component_type_snapshot` constrained text, nullable; +- `component_asset_id_snapshot` text, nullable; +- `component_manufacturer_snapshot` text, nullable; +- `component_model_snapshot` text, nullable; +- `component_manufacturer_serial_snapshot` text, nullable; +- `notes` text, nullable; +- `recorded_by` authenticated user UUID, required; +- `recorded_at` timezone-aware timestamp, required and server controlled; +- `recorded_event_id` repair-event UUID, required; +- `voided_by` authenticated user UUID, nullable; +- `voided_at` timezone-aware timestamp, nullable; +- `void_reason` text, nullable; +- `void_event_id` repair-event UUID, nullable. + +Foreign keys use `RESTRICT` deletion behavior for repairs, users, components, +and events so authoritative history cannot disappear through cascades. + +The repository captures tracked-component snapshots from the locked component +row in the same transaction as the repair-part insert and event. Client values +cannot populate snapshot columns. Nullable component metadata remains null in +the snapshot rather than being fabricated. + +The original entry shape, consumable values, component UUID and snapshot, +notes, recording actor, timestamp, and recorded event data are immutable. A +void adds only the write-once void fields and immutable void event. + +## PostgreSQL Invariants + +PostgreSQL must enforce the complete XOR shape: + +- consumable branch: + - `consumable_description`, `quantity`, and `unit` are all present; + - `component_id` and every component snapshot field are null; or +- tracked-component branch: + - `component_id` and `component_type_snapshot` are present; + - `consumable_description`, `quantity`, and `unit` are all null; + - optional snapshot fields match the nullable values captured at record time. + +Additional constraints and protections: + +- quantity is greater than zero when present; +- quantity uses at most three fractional digits; +- `each` requires `quantity = trunc(quantity)`; +- unit is one of `each`, `gram`, `millilitre`, `metre`, or `centimetre`; +- component and snapshot types use the approved component-type constraint; +- descriptions and snapshot text are trimmed and nonblank when present; +- notes are null or trimmed, nonblank, and limited to 4000 characters; +- recording actor, timestamp, and event UUID are required; +- component UUID references an existing component; +- recorded and void event UUIDs are unique and reference repair events; +- void actor, timestamp, reason, and event UUID are either all + null or all present; +- void reason is trimmed, nonblank, and limited to 1000 characters; +- void timestamp is not earlier than recorded timestamp; +- a repair part cannot be associated with another repair through its event + references; +- original entry fields cannot be updated; +- void fields transition only once from all-null to all-present; and +- repair-part and repair-event rows cannot be physically deleted. + +Use PostgreSQL constraints, foreign keys, indexes, and narrowly scoped triggers +where a declarative constraint cannot enforce cross-row immutability or event +ownership. Service validation provides clear domain errors but does not replace +database integrity. ## Repair State Rules -- add entries only while the repair is active -- void entries only while the repair is active -- read entries and void state after completion -- completed repairs remain read-only -- no unrestricted editing in any repair state -## Data Model -Proposed table: `repair_parts`. No such table currently exists. The legacy -`/api/repairs/parts-summary` route summarizes generic maintenance-event data and -is not an implementation of this model. +- component creation and detail reads do not change repair state; +- record repair-part entries only while the repair is active; +- void repair-part entries only while the repair is active; +- list entries and void state after repair completion; +- completed and cancelled repairs are read-only for Task 005; +- no unrestricted editing is allowed in any repair state; and +- Task 005 never changes repair status or miner lifecycle. -Potential fields: -- UUID -- repair UUID -- part type -- action -- quantity -- serial number -- notes -- recorded actor and timestamp -- void actor, timestamp, and reason -- related event UUID - -Entries are never physically deleted. A void changes only the one-way void -metadata. It must not edit the original part type, action, quantity, serial -number, notes, recording actor, or recording timestamp. The void reason is -required; void actor and timestamp come from authentication and the server. - -## Constraints -- quantity > 0 -- void fields all-null or all-present -- void timestamp after recorded timestamp -- actor and recorded timestamp required -- valid constrained values and combinations -- serialized entries with a serial number have quantity 1 +Begin Repair, Repair Timeline, Return to Service, and Task 004 behavior must +remain unchanged. ## Events -Proposed types: -- `REPAIR_PART_RECORDED` -- `REPAIR_PART_VOIDED` -The events are immutable history; the table is the queryable projection. +Add these immutable repair event types: -## Atomicity -Adding a part and appending its event occur in one transaction. Voiding and appending the void event occur in one transaction. +- `REPAIR_PART_RECORDED`; and +- `REPAIR_PART_VOIDED`. -Voiding must lock the repair-part row and succeed only when its void fields are -still null. Concurrent void requests must produce one success and one conflict, -with exactly one `REPAIR_PART_VOIDED` event. +`REPAIR_PART_RECORDED` contains: + +- repair-part UUID; +- entry kind: `consumable` or `tracked_component`; +- the stored description, quantity, and unit for a consumable; or +- the stored component UUID and complete component display snapshot for a + tracked component; and +- optional notes. + +It must not claim that a tracked component was installed, removed, replaced, +transferred, relocated, or changed. + +`REPAIR_PART_VOIDED` contains: + +- repair-part UUID; and +- required correction reason. + +The event actor, timestamp, and sequence use existing repair-event columns and +are server controlled. Clients request domain actions, not raw events. Events +are append-only and ordered only by the repair's explicit database sequence. +The `repair_parts` table is the queryable record and does not replace Task 004 +history. + +## Transactions And Concurrency + +### Component Creation + +Component creation is one transaction. The database generates its UUID and +timestamp, the authenticated actor is required, and the normalized Asset ID +constraint resolves concurrent duplicates. No repair or component event is +invented for this minimal identity registration. + +### Record Repair Part + +Resolve the repair's miner UUID, then lock in this order: + +```text +Miner -> Repair -> Component (tracked entries only) +``` + +After locking, revalidate the miner and active repair state. For a tracked +entry, lock and load the component, capture its snapshot, insert the repair-part +row, allocate the next repair-event sequence, and append exactly one +`REPAIR_PART_RECORDED` event in one transaction. + +For a consumable, perform the same workflow without a component lock. Any +validation, insert, snapshot, sequence, or event failure rolls back the complete +action. + +### Void Repair Part + +Resolve the repair's miner UUID, then lock in this order: + +```text +Miner -> Repair -> Repair Part +``` + +Revalidate the active repair and route ownership after locking. Set the +write-once void fields and append exactly one `REPAIR_PART_VOIDED` event in one +transaction. Concurrent void requests produce one success and one `409` +conflict, with exactly one void event. + +The Miner lock and Repair lock serialize record and void actions with Return to +Service and Task 004. If repair completion wins, the later Task 005 action sees +a terminal repair and fails with `409`. If Task 005 wins, its complete entry and +event commit before completion proceeds. ## Architecture -Preserve `Router -> Service -> Repository -> PostgreSQL`. No generic parts CRUD, router SQL, or physical deletion. -## Tests -Cover: -- authorization -- serialized and consumable entries -- removed part -- list chronology -- read after completion -- explicit void -- invalid quantities and combinations -- control-board part rejection -- add to completed repair -- already-voided conflict -- cross-repair entry conflict -- concurrent void -- rollback on event failure -- direct constraints -- Tasks 001-004 regressions -- migration lifecycle and full backend suite +Preserve: + +```text +Router + -> Service + -> Repository + -> PostgreSQL +``` + +Routers own authentication, permission checks, request validation, HTTP status +mapping, and response envelopes. Services own normalization, component and +repair-part rules, workflow coordination, and domain exceptions. Repositories +own parameterized SQL, locks, snapshots, persistence, and PostgreSQL integrity +translation. + +Routers contain no SQL, database-driver imports, transaction handling, or +business rules. Services and repositories contain no FastAPI imports. + +## Error Behavior + +- unauthenticated: `401`; +- missing permission: `403`; +- unknown component, repair, or repair part: `404`; +- malformed UUID, unknown field, invalid type, invalid unit, invalid shape, + invalid precision, or invalid text: `422`; +- duplicate normalized component Asset ID: `409`; +- inactive repair, already-voided entry, or concurrent state conflict: `409`; + and +- persistence failures: translated without exposing SQL or driver details. + +A repair-part UUID that does not belong to the repair in the route is treated as +not found and returns `404`. ## Migration -Create the focused table, add permissions and event constraints, add justified indexes, preserve legacy truth, and refuse destructive downgrade when authoritative Task 005 history exists. + +Create additive Alembic revision `0014_repair_parts_tracking` with parent +`0013_control_board_replacement`. + +The migration must: + +- create `components` with its type, text, actor, timestamp, Asset ID uniqueness, + immutability, and deletion protections; +- create `repair_parts` with XOR, quantity, unit, snapshot, actor, timestamp, + event, void, immutability, and deletion protections; +- add `components.read`, `components.create`, and `repairs.parts.manage` with the + approved role assignments; +- extend the repair-event type constraint with `REPAIR_PART_RECORDED` and + `REPAIR_PART_VOIDED`; +- add only indexes justified by component lookup, normalized Asset ID + uniqueness, repair-part listing, foreign keys, and event relationships; +- preserve all existing data without inventing components, repair parts, + actors, timestamps, snapshots, or events; and +- leave Task 004 tables, constraints, projections, routes, and history intact. + +There is no legacy backfill. Generic maintenance events, telemetry, serial +numbers, miner hashboard projections, and Task 004 installation rows are not +sufficient evidence for durable component identity or repair-part usage. + +Downgrade is permitted only when no component, repair-part, or Task 005 repair +event rows exist. Otherwise it must refuse with a clear actionable error rather +than delete authoritative identity or history. An empty safe downgrade removes +Task 005 constraints, permissions, tables, and event values in dependency order, +then supports re-upgrade. + +Validate previous-head upgrade, preflight behavior, safe downgrade, downgrade +refusal, re-upgrade, and an empty database from base through head. + +## Tests + +### Component Registry + +- create and detail-read authorization for every role; +- successful creation with required fields only and with all metadata; +- UUID, actor, and timestamp authority; +- all approved component types and rejection of unknown types; +- whitespace normalization, text limits, and blank optional values; +- case-insensitive normalized Asset ID uniqueness and concurrent duplicates; +- nullable manufacturer metadata and nonunique manufacturer serials; +- immutable identity, type, actor, and timestamp; +- no implicit component creation from a repair-part request; +- no list, search, update, delete, lifecycle, location, or installation routes; + and +- missing and malformed component UUID behavior. + +### Repair-Part Requests And Reads + +- consumable and tracked-component happy paths; +- controlled units and rejection of aliases or free text; +- `NUMERIC(12,3)` boundaries and rejection of excess precision; +- positive fractional measurement quantities; +- whole-number `each` quantities and rejection of fractional `each`; +- both entry branches supplied and neither branch supplied; +- incomplete consumable shapes; +- unknown, malformed, and client-created component UUIDs; +- optional notes and blank or overlength text; +- server-controlled actors, timestamps, sequences, events, and snapshots; +- stable list ordering by recorded event sequence; +- response envelopes and historical reads after completion; +- component metadata changed after recording without changing the stored + snapshot or historical display; +- no N+1 component or actor queries; and +- route ownership for nested repair-part UUIDs. + +### Workflow And Integrity + +- add and void authorization for every role; +- active repair enforcement; +- explicit void and void-plus-replacement correction; +- already-voided and cross-repair behavior; +- concurrent record versus Return to Service; +- concurrent void requests; +- rollback on component lookup, snapshot, part insert, sequence, or event + failure; +- direct XOR, type, quantity, unit, text, foreign-key, event, and void + constraints; +- original-field, event, and physical-deletion protections; +- confirmation that tracked-component recording changes no component, miner, + lifecycle, installation, ownership, MAC, location, or condition state; +- confirmation that a control-board reference cannot bypass or duplicate Task + 004's replacement behavior; and +- lock-order and transaction-boundary tests. + +### Regression And Validation + +- Begin Repair tests; +- Repair Timeline tests; +- Return to Service tests; +- Task 004 control-board replacement and compatibility-alias tests; +- Inventory lifecycle and permission regressions; +- legacy static repair-route precedence tests; +- migration upgrade, downgrade, refusal, re-upgrade, previous-head, and empty + database tests; +- full backend suite; +- disposable development baseline; +- Python compilation and shell syntax checks; +- router SQL, driver, transaction, and business-rule audit; +- service and repository FastAPI audit; +- exact route-count audit; and +- `git diff --check`. ## Acceptance Criteria -1. Parts can be recorded against an active repair. -2. Historical entries remain readable. -3. Entries are never physically deleted. -4. Voids are explicit and audited. -5. Actor and timestamps are authoritative. -6. Writes and events are atomic. -7. Values and quantities are constrained. -8. No warehouse or purchasing system is introduced. -9. Task 004 remains authoritative for control-board identity changes. -10. No commit or push is performed. + +1. Components receive immutable UUIDs and only the minimal approved identity and + display metadata. +2. Component types and consumable units use the approved controlled values. +3. Every repair-part entry represents exactly one complete consumable or one + existing tracked component, never both or neither. +4. Consumable quantities use fixed precision, remain positive, and enforce + whole numbers for `each`. +5. Tracked entries preserve a live component foreign key and immutable display + snapshot. +6. Historical display does not change when live component metadata later + changes. +7. Recording a tracked component does not install, remove, replace, transfer, + relocate, create, or merge a component or miner. +8. Original entries and events are immutable; corrections use an audited void + and optional replacement entry without physical deletion. +9. Actors, timestamps, snapshots, and event sequences are authoritative and + server controlled. +10. Record and void actions are atomic and concurrency safe. +11. Task 004 remains authoritative for control-board and MAC behavior. +12. No generalized inventory, warehouse, purchasing, supplier, stock, location, + lifecycle, warranty, RMA, or cost system is introduced. +13. Existing repair workflows and static route precedence remain unchanged. +14. Migration lifecycle, focused tests, regressions, architecture audits, and + the disposable baseline pass. ## Completion Report -Report files, migration, routes, permissions, schema, constraints, events, tests, migration lifecycle, architecture audit, limitations, and final status. + +Report files, migration revision and parent, routes and exact route count, +permissions and role grants, component registry schema, repair-part schema, +request and response models, constraints, snapshots, event payloads and +sequencing, transaction and lock strategy, downgrade behavior, tests and counts, +regression results, architecture and route audits, known limitations, and final +status. Do not commit. Do not push.