# Task 003: Return to Service ## Status Ready for implementation. ## Objective Complete the MinerOps repair lifecycle by adding an explicit **Return to Service** operation that closes an active repair, records an immutable completion event, and transitions the miner from `in_repair` to an eligible post-repair lifecycle state. The operation must be atomic, permission-controlled, auditable, safe under concurrent requests, and compatible with the existing Begin Repair and Repair Timeline implementations. ## Product Intent A repair should not remain open indefinitely after work is complete. An authorized operator must be able to: 1. identify the active repair, 2. confirm the miner is ready to leave repair, 3. select or confirm the resulting lifecycle state, 4. optionally record a concise completion summary, 5. close the repair, 6. transition the miner out of `in_repair`, 7. preserve immutable operational history. This is an operational command, not generic repair editing. ## Background Task 001 introduced the Begin Repair workflow. Task 002 introduced repair detail and immutable repair timeline read APIs. The current repair model supports: - creating an active repair, - moving a miner into `in_repair`, - preserving the miner's pre-repair lifecycle in the repair-start event payload, - enforcing one active repair per miner, - reading repair details, - reading ordered repair events. Task 003 completes that lifecycle. The implementation must inspect the repository and reuse current names, models, exception conventions, migration patterns, event sequencing, and transaction infrastructure rather than inventing parallel abstractions. ## Scope This task includes: - a new `repairs.complete` permission, - a new explicit Return to Service API, - request and response models, - service-layer completion workflow, - focused repository methods, - repair completion persistence, - miner lifecycle transition, - immutable completion event, - concurrency protection, - migration and permission assignment, - full authorization, domain, migration, routing, and regression tests, - release-document update after implementation passes. ## Explicitly Out of Scope Do not implement: - generic repair update or patch endpoints, - reopening a completed repair, - cancelling a repair, - abandoning a repair, - repair diagnosis fields, - repair photos, - repair parts consumption, - technician assignment, - work orders, - control board replacement, - MAC transfer, - automatic monitoring confirmation, - automated burn-in or test workflows, - bulk Return to Service, - notifications, - UI changes unless the repository already requires generated API client updates, - changes to unrelated legacy repair routes. Those belong in later tasks. ## User and Operator Workflow A typical flow is: 1. A technician opens the active repair. 2. The technician completes physical work and any required validation. 3. The technician invokes Return to Service. 4. The technician chooses the miner's destination lifecycle: - the original pre-repair lifecycle, when eligible, - or another explicitly allowed lifecycle. 5. The technician may enter a completion summary. 6. MinerOps atomically: - verifies the repair is active, - verifies the miner is still in repair, - validates the destination lifecycle, - closes the repair, - transitions the miner, - appends a completion event. 7. The repair becomes read-only historical data. ## API Add one explicit action route. ```http POST /api/repairs/{repair_id}/complete ``` Use `complete` unless existing route naming conventions make `return-to-service` materially more consistent. Do not add both aliases. ### Authentication Required. ### Permission ```text repairs.complete ``` ### Path Parameter - `repair_id`: UUID ### Request Body The request should contain: ```json { "target_lifecycle": "active", "completion_summary": "Replaced failed fan and completed load test." } ``` Use repository-established lifecycle values and naming. #### `target_lifecycle` Required unless the service can unambiguously restore the original lifecycle under existing product rules. Preferred design: require an explicit target lifecycle in the request so the operator confirms the resulting operational state. Allowed target values must be restricted to valid non-repair lifecycle states. At minimum: - `active` - `inactive` Also allow other existing non-terminal lifecycle states only if they are already legitimate post-repair states in the current domain model. Never allow: - `in_repair` - unknown values - deprecated values - terminal or destructive states that are not valid Return to Service destinations Do not invent new lifecycle states. #### `completion_summary` Optional. Requirements: - trim leading and trailing whitespace, - empty after trimming becomes `null`, - enforce a sensible maximum length using current project conventions, - preserve text exactly after normalization, - do not interpret it as structured diagnosis or parts data. ### Successful Response Return the completed repair using the existing repair-detail response model where practical. The response must allow the client to determine: - repair UUID, - miner UUID, - completed status, - completion timestamp, - completing actor where exposed by current conventions, - resulting miner lifecycle, - timeline or event count only if already part of the established response. Do not expand the response with unrelated miner details. ### Status Codes #### `200 OK` Preferred when returning the completed repair representation. Use `201` only if existing operational-command conventions clearly require it. Do not use `204` if the existing repair response can be returned cleanly. #### `401 Unauthorized` Unauthenticated request. #### `403 Forbidden` Authenticated actor lacks `repairs.complete`. #### `404 Not Found` The repair UUID does not exist. Do not reveal authorization-sensitive information beyond existing project behavior. #### `409 Conflict` Use for valid requests that conflict with current domain state, including: - repair is already completed, - repair is otherwise not active, - miner is no longer in `in_repair`, - repair does not match the miner's active repair, - target lifecycle is known but not eligible for Return to Service, - a concurrent request completed the repair first. #### `422 Unprocessable Entity` Use for malformed UUIDs, invalid JSON, missing required fields, unknown lifecycle values, excessive summary length, or other request-validation errors. ## Permissions Add: ```text repairs.complete ``` ### Intended Role Assignment Follow current role conventions and migrations. Expected behavior: - Administrator: allowed - Technician: allowed - Read Only / Viewer: denied - Auditor: denied unless current policy explicitly grants operational actions - Unauthenticated: denied Do not grant the permission to every role by default. Do not overload `repairs.create`. ## Domain Rules ### Active Repair Requirement Only an active repair can be completed. A completed repair cannot be completed again. ### Miner Lifecycle Requirement The associated miner must currently be in `in_repair`. If it is not, return a domain conflict and do not modify anything. ### Destination Lifecycle The destination must: - exist in the current lifecycle domain, - not equal `in_repair`, - be explicitly allowed as a post-repair state, - be applied only after all validation succeeds. ### Original Lifecycle The Begin Repair workflow preserves the previous lifecycle in the `REPAIR_STARTED` event payload. The service may use this value for: - validation, - response context, - defaulting only if the API contract and existing model conventions support it safely. The implementation must not assume every miner should return to `active`. If restoration of the previous lifecycle is supported, preserve exact historical meaning. Do not infer or fabricate it when missing. ### Completion Is Final Once completed: - the repair is historical, - the completion event is immutable, - the active-repair constraint is released, - a later repair must be a new repair record. ### No Silent Repair Mutation Do not edit prior events. Do not replace the start event. Do not overwrite historical lifecycle values. ## Architecture Preserve: ```text Router -> Service -> Repository -> PostgreSQL ``` ### Router Responsibilities The router should: - define the action route, - validate the UUID and request model, - enforce authentication and `repairs.complete`, - call the service, - map established domain exceptions to HTTP responses, - return the established response model. The router must not: - contain SQL, - manage commits or rollbacks, - choose lifecycle transitions, - construct event sequence values, - read actor UUIDs from the request body. ### Service Responsibilities The service should: - load and lock the repair, - verify it exists and is active, - load and lock the associated miner when required, - verify the miner is in repair, - validate target lifecycle, - normalize summary, - close the repair, - update miner lifecycle, - append the completion event, - return the completed domain result, - ensure the operation is atomic. ### Repository Responsibilities Repositories should provide focused methods for: - retrieving an active repair for update, - retrieving the associated miner for update, - marking a repair completed, - updating lifecycle, - obtaining or allocating the next repair event sequence, - appending a repair event, - reading the completed repair representation efficiently. Do not introduce a broad generic repair-update method. ## Database and Migration A migration is required if the current schema does not already support all required completion data and permission assignment. Inspect the existing repair table before adding columns. ### Preferred Repair Completion Fields If not already present, add only the fields needed to represent completion authoritatively: - `completed_at`: timezone-aware timestamp, nullable while active - `completed_by_user_id`: UUID foreign key, nullable while active - `completion_summary`: nullable text - `resulting_lifecycle`: constrained lifecycle value, nullable while active Use existing naming conventions if equivalent columns already exist. ### Status Representation Do not add redundant mutable status data if active versus completed can be derived safely from established fields. If the current repair schema already uses a status column, reuse and constrain it rather than introducing a second status mechanism. ### Constraints Enforce critical invariants in PostgreSQL where practical. Examples: - active repair has no completion timestamp, - completed repair has a completion timestamp, - completed repair has a completing actor, - resulting lifecycle is null while active and present when completed, - resulting lifecycle cannot equal `in_repair`, - completion summary length is constrained if the project uses database length checks, - one active repair per miner remains enforced. Adapt the exact checks to the existing schema. ### Foreign Keys `completed_by_user_id` should reference the established user table. Use the project's existing historical-record deletion policy. Operational history should not silently disappear. ### Indexes Add only indexes justified by actual read patterns. Likely useful: - existing active-repair partial uniqueness, - lookup by miner, - lookup by completion timestamp only if current repair queue/history queries need it. Avoid speculative indexes. ### Migration Data Handling Existing active repairs must remain active. Existing completed repairs, if legacy data exists, must be handled without inventing actor IDs, summaries, or lifecycle states. If new non-null completion invariants cannot be truthfully applied to legacy completed rows: - use nullable historical fields with conditional checks, - preserve legacy data, - document the limitation, - do not fabricate values. ### Downgrade The downgrade must restore the previous schema safely. Before dropping completion columns or constraints, restore miner lifecycle only if the downgrade actually reverses newly created completion data and there is a truthful historical source. Do not map every historical completed repair to `active`. Do not invent original lifecycle values. If a fully safe downgrade is impossible once real completion records exist, use the project's established explicit-refusal pattern rather than destructive guessing. ## Events and History Append one immutable completion event. Use the existing repair event naming conventions. Preferred semantic name: ```text REPAIR_COMPLETED ``` Reuse an existing equivalent event type if already defined. ### Event Fields The event must include: - event UUID, - repair UUID, - next explicit sequence number, - authenticated actor UUID, - server/database timestamp, - structured payload. ### Payload Include only durable, meaningful values. Recommended payload: ```json { "previous_miner_lifecycle": "in_repair", "resulting_miner_lifecycle": "active", "pre_repair_lifecycle": "inactive", "completion_summary": "Replaced failed fan and completed load test." } ``` `pre_repair_lifecycle` may be omitted if the existing event model or service cannot retrieve it reliably without ambiguity. Do not duplicate arbitrary repair fields in the payload. Do not store display names as authoritative actor identity. ### Event Ordering Use the established explicit sequence mechanism. Do not calculate order from timestamps alone. Concurrent event insertion must not produce duplicate sequence values. ## Models and Validation Create or extend focused models. Suggested names, adapted to current conventions: - `RepairCompleteRequest` - `RepairDetailResponse` or existing equivalent - internal completion result model if needed ### Validation Requirements - UUID path validation, - finite lifecycle validation, - summary trimming, - empty summary normalization, - maximum summary length, - no client-supplied actor, - no client-supplied completion timestamp, - no arbitrary event payload supplied by the client. ## Concurrency and Atomicity The entire operation must use one transaction. The following must succeed or fail together: 1. repair validation, 2. miner validation, 3. repair completion update, 4. miner lifecycle transition, 5. completion event insertion. ### Concurrent Completion Two simultaneous completion requests must not both succeed. Expected result: - one request succeeds, - the other receives `409 Conflict`, - one completion event exists, - one final lifecycle transition exists. Use row locking, compare-and-update semantics, or another database-backed mechanism consistent with the repository. ### Concurrent Miner Changes A simultaneous lifecycle-changing operation must not leave: - a completed repair with miner still in `in_repair`, - an active repair with miner already returned, - conflicting lifecycle history. Lock ordering must be consistent with Begin Repair and other miner workflows to reduce deadlock risk. Document the chosen lock order in code comments only where it is not obvious. ## Error and Failure Behaviour On any failure after the transaction starts: - no partial completion fields persist, - miner lifecycle remains unchanged, - no completion event persists, - repair remains active. Unexpected database integrity failures should be translated using established service/repository exception conventions. Do not catch broad exceptions merely to convert them into `409`. ## Tests Add focused tests following current test organization. ### Authorization Tests Verify: - unauthenticated request is denied, - user without `repairs.complete` is denied, - Read Only / Viewer is denied, - Technician is allowed, - Administrator is allowed, - existing `repairs.read` behavior is unchanged, - existing `repairs.create` behavior is unchanged. ### Happy Path Tests Verify: - active repair completes successfully, - miner transitions from `in_repair` to requested eligible lifecycle, - repair is marked completed, - completion timestamp is server-controlled, - completing actor is authenticated user, - optional summary is persisted, - blank summary normalizes to null, - completion event is appended, - event sequence follows existing events, - response represents the completed repair. Test at least: - return to `active`, - return to `inactive`, - restoration to a valid original lifecycle when relevant. ### Domain Conflict Tests Verify `409` for: - already completed repair, - inactive/non-active repair, - miner not in `in_repair`, - mismatched active repair state, - disallowed destination lifecycle, - duplicate concurrent completion. ### Validation Tests Verify `422` for: - malformed repair UUID, - missing required lifecycle, - unknown lifecycle, - `in_repair` as destination, - summary over maximum length, - invalid request shape. If lifecycle defaulting is intentionally supported, replace the missing-field expectation with tests for documented default behavior. ### Missing Resource Test Verify `404` for unknown repair UUID. ### Transaction Rollback Tests Force failures at meaningful steps, such as: - miner update failure, - repair completion update failure, - event insertion failure. Verify no partial state persists. ### Event Tests Verify: - exactly one completion event, - immutable event behavior remains, - actor is correct, - payload is structured, - ordering is explicit, - prior events remain unchanged. ### Database Constraint Tests Verify applicable constraints directly, including: - completed record requirements, - active/completed consistency, - resulting lifecycle restriction, - one-active-repair rule remains valid. ### Migration Tests Verify: - previous head upgrades, - new permission exists, - intended roles receive the permission, - unintended roles do not, - schema constraints exist, - downgrade works safely or refuses explicitly according to project convention, - re-upgrade works, - empty database upgrades through head. ### Routing Tests Verify: - static repair routes such as `/api/repairs/queue` and `/api/repairs/parts-summary` still resolve, - `/api/repairs/{repair_id}/complete` resolves correctly, - UUID detail and event routes remain unchanged, - malformed static-looking paths are not accidentally consumed by UUID routes, - expected route count increases only by the intended amount. ### Regression Tests Run at minimum: - Begin Repair tests, - Repair Timeline tests, - inventory lifecycle tests, - permission tests, - migration lifecycle suite, - relevant legacy repair tests, - complete project suite if practical. ## Documentation Updates After implementation and validation succeed: - mark Return to Service complete in `docs/releases/v0.2-repair-workflow.md`, - update `docs/ROADMAP.md` only if the repository uses it as current implementation status, - update API documentation if maintained separately. Do not mark Control Board Replacement or Repair Parts Tracking complete. Do not perform broad stale-document cleanup in this task unless a directly changed statement would become materially false. ## Acceptance Criteria The task is complete when all of the following are true: 1. `POST /api/repairs/{repair_id}/complete` exists. 2. Authentication and `repairs.complete` are enforced. 3. Only an active repair can be completed. 4. The associated miner must be in `in_repair`. 5. The target lifecycle is explicit and validated. 6. Repair completion and miner transition are atomic. 7. Exactly one immutable completion event is appended. 8. Concurrent completion cannot succeed twice. 9. Existing repair static and UUID routes still resolve. 10. Existing Begin Repair and Repair Timeline behavior remains unchanged. 11. Migration lifecycle validation passes. 12. Permission assignments are correct. 13. Focused and regression tests pass. 14. `git diff --check` passes. 15. No unrelated files are changed. 16. No commit or push is performed. ## Validation Commands Use repository-native commands where available. At minimum run: ```bash python -m compileall . ``` Run shell syntax checks for changed shell files, if any. Run: - focused Return to Service tests, - Begin Repair tests, - Repair Timeline tests, - inventory lifecycle tests, - permission tests, - migration tests, - route audits, - architecture audits, - full suite when practical. Validate migrations through: - previous head to new head, - downgrade, - re-upgrade, - empty database to head, - disposable development baseline. Run: ```bash git diff --check git status --short ``` ## Architecture Audit Confirm: - no SQL in routers, - no database driver imports in routers, - no transaction management in routers, - no FastAPI imports in services or repositories, - no generic repair update repository introduced, - actor identity comes from authentication, - timestamps are server-controlled, - events are append-only, - route ordering is safe. ## Completion Report Return: - branch and starting commit, - files created, - files modified, - migration revision and parent, - route added, - permission added and role assignments, - domain behavior, - concurrency strategy, - event type and payload, - tests added, - focused test results, - regression results, - migration validation results, - architecture audit results, - `git diff --check`, - known limitations, - final `git status --short`. Do not commit. Do not push.