docs: add repair domain design documents

This commit is contained in:
lussierj committed 2026-08-02 03:44:15 +00:00
1 parent f2b736b08e
commit 62c91a2c3e
4 files changed
+778

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
# MinerOps API Style Guide
## Purpose
Keep MinerOps APIs explicit, consistent, auditable, and aligned with the domain architecture. Implemented conventions and approved task specifications take precedence.
## Principles
1. Prefer domain commands over generic CRUD.
2. Use nouns for resources and explicit action routes for operational commands.
3. Keep writes focused and atomic.
4. Return truthful domain representations.
5. Separate authentication, permission, validation, domain logic, and persistence.
6. Preserve static-route compatibility.
7. Do not expose database structure as the public API.
## Current and Proposed Examples
Implemented modular repair routes:
```http
POST /api/repairs
GET /api/repairs/{repair_id}
GET /api/repairs/{repair_id}/events
POST /api/repairs/{repair_id}/complete
```
Relevant legacy compatibility routes include:
```http
GET /api/repairs/queue
GET /api/repairs/parts-summary
POST /api/miners/{miner_id}/change-mac
```
These integrated routes predate the modular domain conventions and are not
models for new code.
Proposed by Task 004, not implemented:
```http
POST /api/miners/{miner_id}/replace-control-board
```
Good:
```http
POST /api/repairs/{repair_id}/complete
```
Avoid:
```http
PATCH /api/repairs/{repair_id}
{"status":"completed"}
```
## Methods
- GET is read-only.
- POST creates a resource or invokes an explicit command.
- PATCH is limited to non-operational metadata when partial update semantics are
appropriate.
- Meaningful operational changes use explicit commands rather than generic PUT,
PATCH, or DELETE routes.
- Historical records are corrected with explicit append-only or void workflows,
not physical deletion.
## Status Codes
- 200: successful read or command with response
- 201: created resource
- 204: success with no useful response
- 401: unauthenticated
- 403: lacks permission
- 404: resource missing
- 409: valid request conflicts with domain state
- 422: request validation failure
## Identifiers
Use typed UUIDs for authoritative entity identifiers. Do not expose sequential database IDs as public identity.
## Time and Actors
Use timezone-aware ISO 8601 timestamps. Authoritative timestamps and actor IDs are server controlled. Never accept client-supplied event actor, event timestamp, completion timestamp, or sequence.
## Permissions
Implemented modular repair capabilities are:
- `repairs.read`
- `repairs.create`
- `repairs.complete`
Task 004 proposes `miners.replace_control_board`; it is not registered today.
Do not duplicate role logic in routers.
## Request Models
Use constrained types, normalize whitespace, reject unknown values, enforce length limits, avoid persistence-only fields, and never accept arbitrary client event payloads.
## Response Models
The modular Inventory and Repairs APIs use typed success envelopes containing
`data`, `meta`, and `error`. Expose useful domain state, not secrets or unrelated
nested data. Preserve null when historical truth is unknown.
## Errors
The application does not yet have one uniform typed error envelope. Modular
routes currently use FastAPI validation responses and `HTTPException` detail
responses while translating domain exceptions to 404, 409, or 422 as
appropriate. New routes must follow the established behavior of their domain
and must not leak SQL or driver errors. Repositories do not raise HTTP
exceptions. Services do not import FastAPI.
## Pagination
Potentially large collections require a bounded pagination design. The current
Inventory collection uses bounded `limit` and `offset`; do not claim cursor
pagination until a domain adopts it. The implemented repair timeline returns
all events for one repair by explicit Task 002 design. Any later change to that
contract requires compatibility review.
## Filtering
Use explicit allowlisted filters and sort fields. Never pass arbitrary column names into queries.
## Static Routes
Register static routes before overlapping UUID routes:
```text
/api/repairs/queue
/api/repairs/parts-summary
/api/repairs/{repair_id}
```
Add route-precedence tests.
## Idempotency and Concurrency
Use database invariants, row locks, affected-row checks, and narrow operation
identifiers where demonstrated need exists. Do not add a generalized
idempotency platform speculatively.
The implemented repair workflows prevent duplicate active repairs, duplicate
completion, duplicate event sequences, and partial state. Task 004 proposes the
additional invariant that concurrent requests cannot assign one normalized MAC
to two miners.
## Events
Clients request domain actions, not raw events. The service constructs immutable events.
## Versioning
MinerOps does not currently expose versioned API prefixes. Any incompatible API
versioning scheme requires a separate compatibility decision; this guide does
not reserve a future path.
## Route Specification Checklist
Every new route should define:
- method and path
- authentication and permission
- request and response
- status codes
- domain rules
- transaction boundary
- concurrency behavior
- events
- tests
- out-of-scope items
## Review Checklist
Confirm explicit action choice, status codes, permission, actor and timestamp authority, UUID typing, limits, transaction, concurrency, event construction, route precedence, architecture boundaries, and regressions.
+174
View File
@@ -0,0 +1,174 @@
# MinerOps Repair Domain Model
## Purpose
This design guide describes repair-domain concepts, relationships, lifecycle, invariants, and operational boundaries. Implemented repository behavior and approved task specifications remain authoritative.
## Core Concepts
### Miner
A durable physical mining asset whose identity is independent of IP address, MAC address, control board, hostname, location, and repair state.
### Repair
A bounded operational episode for one miner. It has one start, zero or one completion, immutable timeline events, actors, and timestamps.
### Repair Event
An immutable fact ordered by explicit sequence. Implemented event types are
`REPAIR_STARTED`, `INITIAL_NOTE`, and `REPAIR_COMPLETED`.
### Network Identity History
`miner_network_identities` associates MAC, IP, and hostname observations with a
durable miner and identifies current versus historical rows. It is not a
separate asset identity.
### Proposed Component History
Task 004 proposes preserving control-board replacement details without
redefining the miner. No dedicated immutable component-history table currently
exists; the legacy generic maintenance-event table is not the modular repair
timeline.
### Proposed Repair Part Entry
Task 005 proposes a record of a part installed, removed, or consumed during a
repair. It is not a warehouse inventory transaction, and no `repair_parts`
table currently exists.
## Relationships
Current relationships:
```text
Miner -> many Repairs -> ordered Repair Events
Miner -> Network Identity History
```
Control-board replacement history and repair-part entries are proposed
extensions, not implemented relationships.
## Lifecycle
Current miner states relevant to repair:
- `active`
- `spare`
- `in_repair`
- `retired`
Eligible Begin Repair sources:
- active
- spare
Eligible Return to Service destinations:
- active
- spare
Terminal:
- retired
The repair schema defines these statuses:
- Active: `OPEN`, `IN_PROGRESS`, `WAITING_FOR_PARTS`, `READY_FOR_TEST`
- Terminal: `COMPLETED`, `CANCELLED`
The modular API currently creates repairs as `OPEN` and can complete any active
repair. It does not provide status-transition or cancellation routes. The
implemented modular workflow is therefore:
```text
OPEN -> COMPLETED
```
The other active values remain valid schema states for compatibility and later
explicit workflows. Cancellation or abandonment requires a separate task.
## Current Workflow
```text
Miner active/spare
-> Begin Repair
-> Miner in_repair
-> Timeline events
-> Return to Service
-> Miner active/spare
-> Repair completed
```
## Invariants
- replacing a board or MAC must not create a new miner
- asset-ID and network-identity history must be preserved
- no more than one active repair per miner is database-enforced
- modular repair actions require the miner and repair state to agree
- Return to Service prevents a completed repair from leaving the miner in `in_repair`
- multi-record transitions are atomic
- events are append-only and explicitly sequenced
- actor comes from authentication
- timestamps are server/database controlled
- new completions require completion time, actor, and eligible resulting lifecycle
- legacy unknown values remain unknown rather than fabricated
## Transaction Boundaries
### Begin Repair
Lock miner, create repair, transition miner to `in_repair`, append start event.
### Return to Service
Lock miner, lock repair, validate active state, complete repair, transition miner to active/spare, append completion event.
### Proposed Control Board Replacement
Task 004 proposes locking miner, active repair, and network-identity rows;
validating MAC ownership; updating current projections; and appending one event.
### Proposed Repair Part Recording
Task 005 proposes locking the affected repair or repair-part row, adding or
voiding the entry, and appending the corresponding event atomically.
## Lock Order
Use:
```text
Miner -> Repair -> related rows
```
This aligns workflows and reduces deadlock risk.
## Commands vs CRUD
Implemented explicit commands:
- Begin Repair
- Return to Service
Proposed explicit commands:
- Replace Control Board
- Record Repair Part
- Void Repair Part
Avoid generic repair patches for meaningful operations.
## Read Models
Implemented modular reads:
- repair detail
- ordered repair timeline
The integrated application also has legacy repair-queue, parts-summary, and
miner network-identity reads. Those routes are compatibility behavior, not the
proposed Task 004 or Task 005 read models.
Proposed focused reads include repair parts and control-board replacement
history. Read models do not weaken write invariants.
## Permissions
Implemented permissions:
- `repairs.read`
- `repairs.create`
- `repairs.complete`
Proposed permissions:
- `repairs.parts.manage`
- `miners.replace_control_board`
Task 005 reuses `repairs.read` for its proposed read route. Roles receive capabilities;
endpoints should not hard-code role names when permission checks exist.
## Legacy Data
Preserve truth, allow nullable historical fields where necessary, enforce stronger rules for new or updated rows, use PostgreSQL `NOT VALID` constraints where appropriate, and never backfill invented actors, states, or events.
## Future Extensions
Repair cancellation, diagnosis, work orders, technician assignment, burn-in testing, photos, warranty/RMA, component inventory, and donor-unit tracking should each arrive through explicit tasks.
+259
View File
@@ -0,0 +1,259 @@
# Task 004: Control Board Replacement
## Status
Design specification. Not yet implemented.
## Objective
Add an explicit, auditable workflow for replacing a miner control board while
preserving durable asset identity, network-identity history, repair context, and
immutable operational history.
## Product Intent
A miner is a durable physical asset. Its control board is replaceable. A
replacement may change the MAC address and control-board serial, but it must not
create a new miner or erase prior history.
**Current implementation context:** `miners.id` is the durable asset UUID.
`miners.current_mac` and `miners.control_board_sn` are current-state fields.
`miner_network_identities` already stores MAC, IP, hostname, source, seen
timestamps, and an `is_current` marker for a miner. The legacy
`miner_maintenance_events` table can contain MAC and component details, but
those records are editable, deletable, and not part of the immutable,
repair-scoped timeline. No dedicated control-board component-history table
exists.
The integrated application also exposes the legacy
`POST /api/miners/{miner_id}/change-mac` route, and Inventory, discovery, and
location-import code can write current network fields. These are existing
compatibility paths, not implementations of Task 004.
## Scope
- proposed route `POST /api/miners/{miner_id}/replace-control-board`
- proposed permission `miners.replace_control_board`
- require an active repair and miner lifecycle `in_repair`
- normalize and validate the new MAC address
- reuse and strengthen `miner_network_identities` for MAC history rather than
creating a competing MAC-history table
- update `miners.current_mac` and `miners.control_board_sn` as current-state
projections
- preserve the old and new control-board serials in immutable repair history
- append one immutable `CONTROL_BOARD_REPLACED` event
- execute atomically and safely under concurrency
- add migration, constraints, tests, and documentation
## Out of Scope
Automatic discovery reconciliation, generic component inventory, stock quantities, purchasing, warranties, firmware flashing, pool changes, remote commands, bulk replacement, automatic miner merge, UI redesign, and replacement of hashboards, fans, or PSUs.
## Permission
The proposed permission is:
```text
miners.replace_control_board
```
Proposed role assignment, following the implemented operational-action policy:
- Admin: allowed
- Manager: allowed
- Technician: allowed
- Read Only: denied
The permission and assignments do not exist yet. They must be introduced by the
Task 004 migration. The route must check the capability rather than hard-code
role names.
## Authentication and Response
Authentication is required. On success, return `200 OK` using a typed response
envelope. The response must identify the durable miner UUID, resolved active
repair UUID, current MAC, current control-board serial, and appended event ID and
sequence without exposing unrelated miner data.
## Request
Suggested shape:
```json
{
"new_mac_address": "AA:BB:CC:DD:EE:FF",
"new_control_board_serial": "CB-2026-001234",
"notes": "Replacement board installed and tested."
}
```
The new MAC is required. `new_control_board_serial` is a required, nullable
field so the operator explicitly records a serial or confirms that it is
unknown. A null or normalized blank value clears the old current-board serial;
it must never leave the removed board serial as current. Normalize nonblank
serials and limit them to the Inventory field maximum of 255 characters.
Notes are optional, trim like existing repair notes, treat whitespace-only as
absent, and use the established 4,000-character limit. The client must not
supply the old MAC, old serial, repair UUID, actor, timestamp, event sequence,
or event payload; the service derives those values from authoritative state.
## MAC Rules
Define and test one canonical display format, such as
`AA:BB:CC:DD:EE:FF`, while comparing ownership by a normalized 12-hex-digit
key. Existing data must be checked for normalized conflicts before a uniqueness
constraint is added.
Reject malformed, broadcast, multicast, all-zero, and currently duplicated MAC
addresses. A MAC historically associated with another asset must produce a
conflict requiring a later deliberate transfer or correction workflow rather
than automatic reassignment. Task 004 must not silently rewrite, merge, or
delete a conflicting association.
## Durable Identity Rules
- miner UUID and asset record remain unchanged
- current MAC changes
- prior MAC remains associated with the same miner in
`miner_network_identities`
- repair, location, ownership, and asset-ID history remain unchanged
- no second miner is auto-created
- no automatic asset merge occurs
## Preconditions
1. Miner exists.
2. Miner is `in_repair`.
3. Miner has exactly one active repair.
4. New MAC is valid and canonical.
5. New MAC is not current or historical on another miner.
6. When a current MAC exists, old and new MAC differ.
7. Actor and timestamp are authoritative.
## Data Model
Task 004 must build on the existing structures:
- `miners.current_mac` remains the current MAC projection.
- `miners.control_board_sn` remains the current control-board serial projection.
- `miner_network_identities` remains the MAC-history structure. The migration
may add only the constraints or indexes required to enforce one current
association and prevent cross-miner normalized-MAC ownership conflicts.
- `repair_events` remains the immutable repair timeline. Extend its constrained
event vocabulary and payload validation for `CONTROL_BOARD_REPLACED`.
Do not create a second MAC-history table. Do not treat legacy
`miner_maintenance_events` as authoritative component history for this
workflow. A separate component-installation table is outside this task unless
repository inspection during implementation identifies a concrete query or
integrity requirement that cannot be met by the current projections, network
identity history, and immutable repair event. Report that conflict before
adding such a table.
## Constraints
- no more than one current network-identity row per miner
- one miner owner per normalized MAC across current and historical identities
- valid normalized MAC for rows written by the new workflow
- current projections agree with the newly current identity after replacement
- foreign-key behavior preserves operational history
The migration must preflight existing normalized MAC duplicates and fail with
an actionable message rather than changing existing ownership silently.
## Legacy Data
Existing current MAC values may be reconciled with
`miner_network_identities` only when the association is unambiguous. Existing
identity rows and legacy maintenance events must be preserved. Unknown
installation actor, time, control-board serial, or source must remain unknown;
never fabricate a replacement event.
## Event
Proposed type: `CONTROL_BOARD_REPLACED`.
Suggested payload:
```json
{
"old_mac_address": "CC:0C:15:00:0A:49",
"new_mac_address": "CC:0C:15:00:9D:82",
"old_control_board_serial": null,
"new_control_board_serial": "CB-2026-001234",
"notes": "Replacement board installed and tested."
}
```
Include only authoritative values.
## Atomicity and Locking
Use one transaction and the established lock order:
```text
Miner -> Repair -> network-identity rows
```
The service resolves and locks the miner first, then its one active repair, then
the relevant network-identity rows. This matches Tasks 001 and 003.
The operation must atomically validate MAC ownership, mark prior network
identities non-current, create or activate the new association without
reassigning another miner's history, update the current miner fields, and append
exactly one event to the active repair.
Concurrent requests must not assign one MAC to two miners, create two current boards, duplicate event sequence values, or leave partial state.
## Errors
- `401`: unauthenticated
- `403`: authenticated user lacks `miners.replace_control_board`
- `404`: miner missing
- `409`: miner not in repair, no active repair, same current MAC, MAC conflict,
historical ownership conflict, or concurrent replacement
- `422`: malformed UUID, invalid MAC, missing required fields, excessive text,
unknown fields, or other invalid request data
## Architecture
Preserve `Router -> Service -> Repository -> PostgreSQL`.
No SQL or transaction management in routers. No FastAPI in services or repositories. Do not add generic component CRUD.
## Tests
Cover:
- role authorization
- valid replacement
- durable asset identity unchanged
- old MAC remains searchable
- malformed and prohibited MACs
- current and historical MAC conflicts
- duplicate existing normalized-MAC migration preflight
- missing active repair
- miner not in repair
- concurrent replacement
- rollback at each write step
- direct database constraints
- event payload and sequence
- Tasks 001-003 regressions
- migration upgrade, downgrade/refusal, re-upgrade, and empty database
## Decisions Required Before Implementation
Before Task 004 is marked ready for implementation, decide:
1. Whether the legacy `/api/miners/{miner_id}/change-mac` route will delegate to
the new service or be deprecated through an explicit compatibility plan. It
must not remain a competing writer that bypasses the active-repair workflow.
2. Whether an operator-recorded replacement MAC counts as a network observation
for the existing `first_seen_at` and `last_seen_at` semantics, or whether the
schema needs a distinct association timestamp. Do not label an unobserved MAC
as discovered accidentally.
3. How Inventory creation, discovery, and location-import writers will translate
normalized-MAC ownership conflicts without exposing raw database errors or
auto-merging assets.
These decisions do not permit automatic asset creation, merging, or MAC
transfer. A separate deliberate transfer workflow remains out of scope.
## Acceptance Criteria
1. One explicit route exists.
2. Durable miner identity remains unchanged.
3. Old and new MACs remain searchable.
4. Normalized MAC ownership and current-association invariants are
database-backed.
5. Active repair and `in_repair` are required.
6. Exactly one immutable event is written.
7. Operation is atomic and concurrency-safe.
8. Legacy data remains truthful.
9. Migration and regression suites pass.
10. No commit or push is performed.
## Completion Report
Report branch, starting commit, migration, files, route, permission, schema, constraints, normalization, history, transaction strategy, event, tests, validation, limitations, and final `git status --short`.
Do not commit.
Do not push.
+189
View File
@@ -0,0 +1,189 @@
# Task 005: Repair Parts Tracking
## Status
Design specification. Not yet implemented.
## 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.
## 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.
## 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.
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`.
## API
Proposed routes:
```http
GET /api/repairs/{repair_id}/parts
POST /api/repairs/{repair_id}/parts
POST /api/repairs/{repair_id}/parts/{repair_part_id}/void
```
Do not physically delete authoritative entries.
## Permissions
The proposed authorization model is:
- use implemented `repairs.read` for the read route
- introduce `repairs.parts.manage` for add and void actions
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.
## Add Request
```json
{
"part_type": "fan",
"action": "installed",
"quantity": 1,
"serial_number": null,
"notes": "Replacement fan installed."
}
```
## Initial Vocabulary
Proposed part types:
- `fan`
- `power_supply`
- `hashboard`
- `cable`
- `consumable`
- `other`
Proposed actions:
- `installed`
- `removed`
- `consumed`
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.
## 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
## 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.
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
## Events
Proposed types:
- `REPAIR_PART_RECORDED`
- `REPAIR_PART_VOIDED`
The events are immutable history; the table is the queryable projection.
## Atomicity
Adding a part and appending its event occur in one transaction. Voiding and appending the void event occur in one transaction.
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.
## 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
## 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.
## 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.
## Completion Report
Report files, migration, routes, permissions, schema, constraints, events, tests, migration lifecycle, architecture audit, limitations, and final status.
Do not commit.
Do not push.