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

11 KiB

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

POST /api/repairs

Permission

Introduce and require:

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:

{
  "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:

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:

409 Conflict

Miner lifecycle

Beginning a repair moves the miner lifecycle to:

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:

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:

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:

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.