docs: add repair domain design documents
This commit is contained in:
1 parent
f2b736b08e
commit
62c91a2c3e
4 files changed
+778
No files matched your search
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
Reference in new issue
Block a user