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,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.
|
||||
Reference in new issue
Block a user