diff --git a/docs/design/COMPONENT_DOMAIN_MODEL.md b/docs/design/COMPONENT_DOMAIN_MODEL.md new file mode 100644 index 0000000..669827a --- /dev/null +++ b/docs/design/COMPONENT_DOMAIN_MODEL.md @@ -0,0 +1,457 @@ +# MinerOps Component Domain Model + +## Status + +Proposed design baseline for tracked physical components. The generalized +component model, tables, routes, permissions, lifecycles, and events described +here are not currently implemented. Task 004's implemented control-board +replacement structures and behavior remain authoritative. + +## Purpose + +MinerOps should represent important physical parts as durable entities with their own identity, installation history, condition, location, and repair relationships. + +A miner remains the durable parent asset. Replacing a control board, hashboard, PSU, fan, or other component must not create a new miner or erase prior history. + +## Design Principles + +- Model meaningful physical objects when tracking them creates operational value. +- Give every individually tracked component an immutable UUID. +- Separate internal UUID, human Asset ID, and manufacturer identifiers. +- Preserve installation, movement, repair, and disposition history. +- Keep current-state projections fast without treating them as historical truth. +- Add complexity progressively rather than requiring full warehouse management. + +## Core Model + +### Component + +The base durable identity for an individually tracked physical part. + +Conceptual fields: + +- immutable UUID +- optional human Asset ID +- component type +- manufacturer +- model +- manufacturer serial +- lifecycle +- condition +- notes +- creation and retirement metadata + +A component type should not change after meaningful operational history exists. + +### Specialized Components + +Use type-specific tables only where structured attributes or rules justify them. +The following names are conceptual and do not reserve future table names: + +```text +components + ├── control_boards + ├── hashboards + ├── power_supplies + ├── fans + └── cables +``` + +Examples: + +- Control board: model, hardware revision, firmware, and relationships to the + implemented Task 004 installation and MAC history. A specialization must not + create competing MAC history. +- Hashboard: serial, revision, nominal hashrate, chip count. +- PSU: serial, model, rated power, voltage range. +- Fan: model, dimensions, connector, rated RPM. + +Many simple parts may remain quantity-based rather than individually tracked. + +## Identity + +Every tracked component may have: + +1. **Internal UUID**: immutable database and API identity. +2. **Human Asset ID**: operator-facing label such as `CB-00183`. +3. **Manufacturer identity**: serial, MAC, QR code, PCB identifier, or firmware-reported value. + +Manufacturer values help identify a component but do not define its durable identity. + +## Component Installation + +A Component Installation records where a component was installed over time. + +Conceptual fields: + +- installation UUID +- component UUID +- miner UUID +- component slot +- installed timestamp and actor +- related repair +- removed timestamp and actor +- removal disposition +- notes + +A current installation has no removal timestamp. + +### Invariants + +- A component has at most one current installation. +- It cannot be installed in two miners simultaneously. +- A miner cannot have conflicting components in an exclusive slot. +- Removal occurs after installation. +- Unknown legacy actor or timestamp values remain unknown. +- Historical installations are not physically deleted. + +## Component Slots + +Examples: + +```text +control_board +hashboard_1 +hashboard_2 +hashboard_3 +power_supply +front_fan_1 +rear_fan_1 +``` + +Slots may later be constrained by miner model or hardware profile. + +## Lifecycle and Condition + +Lifecycle and condition are different concepts. + +Candidate lifecycle values: + +- available +- installed +- under_repair +- quarantined +- sent_for_rma +- retired +- scrapped +- lost +- unknown + +Candidate condition values: + +- new +- working +- used +- repaired +- untested +- failed +- damaged +- unknown + +Only introduce values when the corresponding workflows exist. + +## Locations + +A component may be: + +- installed in a miner +- stored at a site, room, rack, shelf, or bin +- on a repair bench +- in transit +- with a vendor +- in RMA + +Installed components normally inherit physical location from the miner. Uninstalled components require location history. + +A component must not simultaneously have an incompatible current installation and current storage location. + +## Events + +Component history is append-only. + +Candidate events: + +- component created +- Asset ID changed +- installed +- removed +- moved +- condition changed +- repair started +- repair completed +- sent for RMA +- returned from RMA +- retired +- scrapped + +The implemented `CONTROL_BOARD_REPLACED` event remains valid. It does not +currently contain a generalized component UUID. A later migration may add +component or installation references only when those identities exist +authoritatively; it must not reconstruct or invent them for older events. + +Do not duplicate the same authoritative event across unrelated streams. One event may appear in several read models. + +## Current Projections + +Implemented Task 004 current-state projections are: + +```text +miners.current_mac +miners.control_board_sn +``` + +`miners.firmware_version` is an existing miner field, but Task 004 records the +replacement firmware on the installation and does not update that miner field. + +Possible generalized component projections include: + +```text +components.lifecycle_status +components.current_location_id +``` + +Projection updates must occur in the same transaction as authoritative history. Constraints or deferred checks should prevent drift. + +## Repairs and Components + +Repairs and components are related but distinct. + +- A repair belongs to one miner. +- A component may move through several miners. +- A component may appear in many repair histories. +- A repair event may reference a component or installation. +- The repair timeline explains what happened in that repair. +- The component history explains the component's lifetime. + +## Parts Versus Components + +Not every part needs an individual UUID. + +### Individually tracked + +Appropriate for: + +- control boards +- hashboards +- PSUs +- expensive or serialized fans +- specialty harnesses +- tagged network hardware + +### Quantity-based + +Appropriate for: + +- screws +- thermal pads +- connectors +- common cables +- cleaning supplies +- untracked fans + +Task 005 remains a focused quantity-based repair record and does not require a +generalized component inventory. Whether it may optionally reference an +existing tracked component is an open decision for its implementation; it must +not create or infer component identity automatically. + +## Donor Components + +A component moved from Miner A to Miner B retains the same UUID. + +```text +Installed in Miner A +Removed from Miner A +Stored or repaired +Installed in Miner B +``` + +Never clone the component record merely because ownership or location changes. + +## Unknown Components + +A component may be tracked without a known serial, Asset ID, exact installation date, or complete history. Unknown values remain null or explicitly unknown. Do not fabricate identifiers or dates. + +## MAC Identity + +A control board MAC is a mutable manufacturer/network identifier. + +- It does not define the miner. +- It does not define the component UUID. +- Current and historical ownership remain searchable. +- Automatic transfer between durable miners is prohibited. +- Conflict resolution is explicit. +- Ownership, network association, and installation history must not drift. + +Implemented Task 004 structures remain authoritative until an explicit, +history-preserving migration generalizes them: + +- `miners.current_mac` and `miners.control_board_sn` are current projections. +- `miner_network_identities` stores current and historical network associations. +- `miner_mac_ownerships` prevents normalized MAC ownership from silently moving + between durable miners. +- `miner_control_board_installations` preserves installation and removal + history and enforces one current board per miner. +- `repair_events` stores the immutable, sequenced `CONTROL_BOARD_REPLACED` fact. + +Task 004 requires the miner to be `in_repair`, requires one active repair, and +updates these records in one transaction. Deferred database checks keep the +current miner, installation, and network-identity projections aligned. Legacy +unknown actors and installation timestamps remain null. + +## Permissions + +Candidate capabilities: + +```text +components.read +components.create +components.update_metadata +components.install +components.remove +components.move +components.change_condition +components.retire +components.scrap +components.rma.manage +``` + +`miners.replace_control_board` is implemented for Task 004 and is granted to +Admin, Manager, and Technician, but not Read Only. Task 005 proposes +`repairs.parts.manage`; it is not implemented. The candidate component +capabilities above remain future concepts. + +## Transactions + +Every future generalized component operation must own one transaction. Task 004 +already performs its control-board replacement as one transaction. + +### Install + +- lock miner and component +- validate lifecycle and slot +- close prior installation only through an explicit replacement +- create installation +- update projections +- append event + +### Remove + +- lock component and current installation +- close installation +- update projections and location/lifecycle +- append event + +### Move + +- close current location +- create new location +- update projection +- append event + +## Lock Ordering + +Implemented repair and control-board workflows use: + +```text +Miner -> Repair -> related component, network-identity, and ownership rows +``` + +Before generalized component workflows are implemented, define one global order across Miner, Component, Repair, Installation, and Location. No task should introduce a conflicting lock order without review. + +## Database Invariants + +Where practical, enforce: + +- an explicitly decided Asset ID uniqueness scope when Asset IDs are introduced +- one current installation per component +- one component per exclusive miner slot +- installation chronology +- no incompatible installation and storage location +- constrained lifecycle and condition values +- immutable MAC ownership across durable miners +- actor and timestamp requirements for new rows +- history-preserving foreign keys + +Use legacy-safe constraints where historical truth is incomplete. + +## API Principles + +Implemented Task 004 operation: + +```http +POST /api/miners/{miner_id}/replace-control-board +``` + +The legacy `POST /api/miners/{miner_id}/change-mac` route is a compatibility +alias that uses the same permission and Task 004 service workflow. + +Possible future explicit operations include: + +```http +POST /api/miners/{miner_id}/components/{component_id}/install +POST /api/components/{component_id}/remove +POST /api/components/{component_id}/move +POST /api/components/{component_id}/retire +``` + +Avoid generic ownership or lifecycle patches for meaningful operations. + +## Relationship to Task 004 + +Task 004 is the first implemented component-history workflow. Its route requires +`miners.replace_control_board`, an active repair, and miner lifecycle +`in_repair`. + +Do not immediately rewrite it into a generic framework. Generalize only when Task 005 or later work proves a shared abstraction and a safe migration path. + +## Relationship to Task 005 + +Task 005 should: + +- record repair parts +- support quantity-based consumables +- remain usable without generalized component inventory +- reference an existing tracked component only if that optional relationship is + explicitly approved during Task 005 implementation +- preserve explicit void/correction history +- leave control-board identity changes under Task 004 +- avoid warehouse, purchasing, vendor, and supplier scope + +## Future Directions + +This model can later support: + +- spare-parts inventory +- vendors and purchasing +- warranty and RMA +- donor units +- test benches and results +- QR/barcode labels +- costs +- failure analytics +- predictive maintenance + +These are future directions, not present commitments. + +## Open Product Decisions + +Before generalized component inventory is implemented, decide: + +1. Which component types require individual tracking? +2. Are human Asset IDs required for each type? +3. Can technicians create components during repair work? +4. Can installation occur before a formal inventory record exists? +5. Which types require serial numbers? +6. How are donor miners represented? +7. What is the global lock order? +8. How does a removed component move to available, under repair, or scrapped? +9. Are component repairs separate records or work orders? +10. How will Task 004 history migrate into generalized components? +11. Do component Asset IDs share the miner Asset ID namespace or use a separate + uniqueness scope? +12. Should Task 005 accept an optional tracked-component UUID, or remain limited + to its approved quantity, type, and serial fields? + +## Decision Test + +A component feature should help identify, locate, repair, install, move, preserve history for, or prevent conflicting ownership of a physical part. Interesting data alone is not enough.