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.
|
||||
@@ -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