545 lines
11 KiB
Markdown
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.
|