Files
minerops/docs/tasks/001-begin-repair.md

545 lines
11 KiB
Markdown

# Task 001: Begin Repair
## Status
Ready for implementation
## Phase
Phase 3: Operations
## Goal
Implement the first explicit operational workflow in MinerOps: beginning a repair for a miner.
This workflow creates permanent repair history. It is not generic CRUD and must not be implemented as a generic miner update.
## Endpoint
```http
POST /api/repairs
```
## Permission
Introduce and require:
```text
repairs.create
```
Do not reuse `miners.update`.
Register the permission using the repository's existing permission and role-seeding conventions.
At minimum, administrators must retain access. Do not grant this permission to viewer or auditor roles.
## Request
Use a typed request model equivalent to:
```json
{
"miner_id": "UUID",
"reason": "CONTROL_BOARD",
"notes": "Miner not booting after power outage."
}
```
### Fields
#### `miner_id`
- Required
- Durable internal miner UUID
- Must reference an existing miner
#### `reason`
- Required
- Must use the constrained repair-reason values below
#### `notes`
- Optional
- Trim surrounding whitespace using existing normalization conventions
- Treat whitespace-only notes as absent unless established project validation conventions require rejection
- Store the initial note as a repair timeline event, not as a mutable column on the repair record
Do not accept client-provided values for:
- repair ID
- repair status
- opened or started timestamp
- opened or started actor
- miner lifecycle
- event IDs
- event timestamps
- event actors
## Repair Reasons
Support exactly these initial values:
- `HARDWARE_FAILURE`
- `LOW_HASHRATE`
- `CONTROL_BOARD`
- `HASHBOARD`
- `PSU`
- `FAN`
- `OVERHEATING`
- `NETWORKING`
- `PREVENTIVE_MAINTENANCE`
- `INSPECTION`
- `OTHER`
Use the project's existing enum or check-constraint conventions. Do not substitute a free-form reason.
## Repair Statuses
The schema must anticipate:
- `OPEN`
- `IN_PROGRESS`
- `WAITING_FOR_PARTS`
- `READY_FOR_TEST`
- `COMPLETED`
- `CANCELLED`
This endpoint creates repairs only with status:
```text
OPEN
```
Active repair statuses are:
- `OPEN`
- `IN_PROGRESS`
- `WAITING_FOR_PARTS`
- `READY_FOR_TEST`
## Workflow
Beginning a repair must atomically:
1. Confirm the miner exists.
2. Lock the miner row.
3. Confirm the miner is eligible to enter repair.
4. Confirm the miner does not already have an active repair.
5. Create the repair record.
6. Create a `REPAIR_STARTED` timeline event.
7. Create an `INITIAL_NOTE` timeline event when a non-empty note was supplied.
8. Change the miner lifecycle to `IN_REPAIR`.
9. Commit all changes together.
If any step fails, none of the changes may persist.
## Business Rules
### One active repair
A miner may have no more than one active repair.
This rule must be protected by the database against concurrent requests. Do not rely only on a service-level pre-check.
A likely implementation is a PostgreSQL partial unique index on `miner_id` for active statuses, but inspect existing migration and database conventions before choosing the mechanism.
Duplicate active-repair attempts return:
```text
409 Conflict
```
### Miner lifecycle
Beginning a repair moves the miner lifecycle to:
```text
IN_REPAIR
```
Inspect the current lifecycle values and transition conventions before implementing.
Reject incompatible or terminal lifecycle states with a domain-specific conflict mapped to:
```text
409 Conflict
```
If a miner is already `IN_REPAIR` but has no active repair, treat that as inconsistent state. Reject it rather than silently creating ambiguous history.
Do not add another repair-status field to the miner.
### Actor attribution
The authenticated user's UUID is mandatory.
Pass the actor UUID explicitly from the router to the service.
Do not:
- derive the actor from request data
- permit actorless repair creation
- allow the client to override the actor
### Timestamps
All timestamps must be generated by the server or database.
Do not accept client timestamps.
Initial events must have deterministic ordering. If timestamps can match, use a stable secondary order consistent with repository conventions.
## Database
Create one Alembic migration after the current head.
Use Alembic only. Do not use runtime table creation or ORM auto-create behaviour.
### `repairs`
Required logical fields:
- `id`: UUID primary key
- `miner_id`: foreign key to miners
- `status`
- `reason`
- `opened_at` or `started_at`
- `opened_by` or `started_by`
- `completed_at`: nullable
- `completed_by`: nullable
Inspect existing naming conventions and choose either `opened_*` or `started_*`. Use the chosen terminology consistently across schema, models, service, API, and tests.
Add appropriate:
- foreign keys
- actor references
- indexes
- status constraints
- reason constraints
- server-generated timestamps
- database-backed active-repair uniqueness
### `repair_events`
Required logical fields:
- `id`: UUID primary key
- `repair_id`: foreign key to repairs
- `event_type`
- `created_at`
- `created_by`
- `payload`
Initial supported event types:
- `REPAIR_STARTED`
- `INITIAL_NOTE`
#### `REPAIR_STARTED` payload
Preserve relevant starting context, including at least:
- repair reason
- miner lifecycle transition where practical
#### `INITIAL_NOTE` payload
Preserve the normalized submitted note.
Use JSON only if it fits existing repository conventions or is clearly the best design. Do not store unvalidated, inconsistent payloads merely for convenience.
## Architecture
Preserve:
```text
Router
↓
RepairService
↓
RepairRepository
↓
PostgreSQL
```
The service may coordinate inventory persistence through an existing repository abstraction or a clean shared transaction boundary.
Do not bypass repository boundaries merely to simplify the transaction.
### Router responsibilities
- authentication
- authorization
- request validation
- HTTP status mapping
- response envelope
The router must contain:
- no SQL
- no database-driver imports
- no transaction management
- no business rules
### Service responsibilities
- workflow orchestration
- business rules
- transaction coordination
- domain exception propagation or translation
- no FastAPI imports
### Repository responsibilities
- persistence
- row locking
- parameterized SQL
- no FastAPI imports
Individual repository operations must not commit in a way that breaks atomic workflow orchestration.
## Response
Return:
```text
201 Created
```
Reuse the existing typed response envelope.
The response must contain at least:
- `repair_id`
- `miner_id`
- `status`
- `reason`
- `opened_at` or `started_at`
- `opened_by` or `started_by`
Use terminology consistent with the schema.
Do not include the full repair timeline in this response.
## Error Behaviour
Use existing response-envelope and error conventions.
### `401 Unauthorized`
- user is not authenticated
### `403 Forbidden`
- user lacks `repairs.create`
### `404 Not Found`
- miner does not exist
### `409 Conflict`
- miner already has an active repair
- miner lifecycle is incompatible with beginning repair
- miner is in an inconsistent repair state
### `422 Unprocessable Entity`
- malformed UUID
- invalid reason
- invalid request body
Do not expose raw database errors.
## Out of Scope
Do not implement:
- repair retrieval
- repair listing
- repair editing
- repair deletion
- repair cancellation
- repair completion
- return to service
- adding later notes
- diagnostics
- parts
- photos
- attachments
- uploads
- testing workflows
- control-board replacement
- notifications
- generic repair PATCH endpoints
- alert pausing unless an existing clean abstraction makes it unavoidable
Exactly one new API route is permitted in this task.
## Tests
Follow existing test conventions.
### Migration tests
Cover:
- upgrade from previous head
- downgrade
- re-upgrade
- migration at head
- empty-database migration
- table constraints
- foreign keys
- indexes
- active-repair uniqueness
- supported status values
- supported reason values
- supported event types
- nullable completion fields
- actor requirements
### Repository tests
Cover:
- repair creation
- `REPAIR_STARTED` event creation
- optional `INITIAL_NOTE` event creation
- no note event for absent or normalized-empty notes
- miner lifecycle transition
- actor persistence
- reason persistence
- timestamp persistence
- active-repair detection
- database uniqueness under duplicate active-repair attempts
- miner isolation
- rollback when event insertion fails
- rollback when lifecycle update fails
- incompatible lifecycle rejection
- concurrency or row-lock protection where practical
### Service tests
Cover:
- successful Begin Repair
- successful Begin Repair with an initial note
- unknown miner
- active-repair conflict
- incompatible lifecycle conflict
- actor required
- orchestration behaviour
- domain exception propagation
- no partial persistence when orchestration fails
### Router tests
Cover:
- authentication required
- `repairs.create` required
- malformed miner UUID
- invalid reason
- unknown miner
- active-repair conflict
- lifecycle conflict
- successful creation returns `201`
- response envelope
- typed response fields
- actor comes from authenticated principal
- client cannot override actor
- client cannot override status or timestamps
- router contains no SQL
- router contains no database-driver imports
### Permission tests
Cover:
- `repairs.create` registration
- administrator access
- unauthorized roles are not granted access accidentally
- idempotent permission seeding
## Documentation
After implementation and validation pass:
- Update `docs/ROADMAP.md` to mark Begin Repair complete.
- Update architecture or decision documentation only when the implementation introduces a genuinely new architectural decision.
If a new ADR is warranted, explain concisely:
- why repair is an operational entity
- why active-repair uniqueness is database-enforced
- why lifecycle transition and timeline creation are atomic
Avoid documentation churn for principles already documented.
## Validation
Run:
- Python compilation
- shell syntax validation
- migration upgrade from previous head
- migration downgrade
- migration re-upgrade
- migration head verification
- empty-database migration
- focused repair suite
- focused inventory regression suite
- permission and role tests
- duplicate active-repair or concurrency tests
- full disposable development baseline
- router SQL and database-driver audit
- service and repository FastAPI import audit
- route-count audit
- `git diff --check`
- `git status --short`
## Final Review Checklist
Confirm:
- exactly one endpoint was added
- no generic repair CRUD was added
- active-repair uniqueness is enforced by the database
- transaction boundaries prevent partial persistence
- authenticated actor UUID is explicit and mandatory
- timestamps are server-controlled
- initial notes are timeline events
- no unrelated changes are present
## Implementation Report
Report:
- implementation summary
- files created
- files modified
- migration revision and parent revision
- tables and constraints
- endpoint
- permission
- request model
- response model
- repair reasons
- repair statuses
- event types
- lifecycle behaviour
- actor attribution
- transaction design
- concurrency protection
- tests added
- validation results
- git diff summary
- `git status --short`
Do not commit.
Do not push.