feat: add begin repair workflow
This commit is contained in:
1 parent
d738b8e27e
commit
f6a2512e23
21 files changed
+3108
-5
No files matched your search
+6
-1
@@ -92,13 +92,17 @@ location changes.
|
||||
|
||||
### Phase 3: Operations
|
||||
|
||||
**Status: Planned direction.**
|
||||
**Status: In progress.**
|
||||
|
||||
This phase develops day-to-day technician and operations workflows around the
|
||||
durable asset record. Repairs, maintenance, work orders, parts usage, equipment
|
||||
movement, testing, and return-to-service history become coherent operational
|
||||
records rather than disconnected actions.
|
||||
|
||||
The Begin Repair workflow is complete. An authorized actor can open a repair
|
||||
against a durable miner UUID, record sequenced starting events, and move the
|
||||
miner into repair atomically. Later repair lifecycle workflows remain planned.
|
||||
|
||||
Technician experience is a priority. Responsive and QR-assisted workflows may
|
||||
reduce lookup and data-entry effort where they reinforce the same asset record.
|
||||
Existing repair and location behavior should move incrementally into clear
|
||||
@@ -209,6 +213,7 @@ Major completed foundations include:
|
||||
- Authentication, login gating, and role foundation
|
||||
- Manual Inventory schema and backend domain foundation
|
||||
- Focused Inventory and migration test foundations
|
||||
- Begin Repair operational workflow
|
||||
- MinerOps branding package and project README
|
||||
- Architecture guide
|
||||
- Product guide
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user