docs: finalize repair parts tracking task

This commit is contained in:
lussierj committed 2026-08-02 19:15:55 +00:00
1 parent 73f98685ce
commit 6e550c1c13
1 file changed
+623 -133
+623 -133
View File
@@ -1,38 +1,192 @@
# Task 005: Repair Parts Tracking
## Status
Design specification. Not yet implemented.
Approved implementation specification. Not yet implemented.
This task introduces a minimal durable component registry only to support
tracked-component references in repair-part history. It does not implement the
broader component inventory, lifecycle, installation, location, or warehouse
model described as future direction in
`docs/design/COMPONENT_DOMAIN_MODEL.md`.
## Objective
Allow authorized operators to record parts installed, removed, or consumed
during a repair while preserving immutable history and leaving room for a later
spare-parts inventory system.
Allow authorized operators to record what was used during an active repair as
exactly one of:
1. a quantity-based consumable; or
2. one tracked component identified by a durable component UUID.
Repair-part history is immutable. Corrections use an explicit void action and,
when necessary, a new replacement entry. Original entries and events are never
edited or physically deleted.
## Product Intent
A technician should be able to determine what parts were installed, removed, or
consumed; their quantity and identifying serial number where relevant; any
concise notes; and the authoritative actor and timestamp.
A technician should be able to determine which consumables and tracked
components were used during a repair. Consumable records preserve their
description, quantity, and unit. Tracked-component records preserve both the
live component UUID and an immutable display snapshot captured when the entry
is recorded.
All authoritative actors come from authentication. All authoritative
timestamps and repair-event sequences are server or database controlled.
## Authoritative Boundaries
Task 004 remains authoritative for:
- control-board replacement;
- MAC ownership;
- control-board installation and removal history;
- current control-board and MAC projections;
- `CONTROL_BOARD_REPLACED` events; and
- the `/api/miners/{miner_id}/change-mac` compatibility alias.
Task 005 records repair usage only. It must not install, remove, replace,
transfer, relocate, or change ownership of a tracked component. In particular,
recording a control-board component must not:
- replace a control board or transfer its MAC address;
- modify `miners.control_board_sn` or `miners.current_mac`;
- modify `miner_network_identities`, `miner_mac_ownerships`, or
`miner_control_board_installations`;
- append `CONTROL_BOARD_REPLACED`; or
- substitute for Task 004's explicit replacement workflow.
The existing `miner_control_board_installations.id` identifies an installation
history row, not a durable component. The existing `miner_hashboards.id`
identifies a miner-slot projection, not a component that retains identity
through movement. Task 005 must not repurpose either UUID as a component UUID or
infer component identity from a serial number, MAC address, miner, slot, or
installation.
## Scope
- proposed focused repair-part read and action APIs
- proposed capability-based permissions
- a deliberately small part type and action vocabulary
- part type, action, quantity, optional serial number, and notes
- serialized component identifiers where applicable
- explicit void/correction rather than physical deletion
- immutable repair events
- migration, constraints, tests, and documentation
## Out of Scope
Purchasing, suppliers, pricing, invoices, warehouse transfers, reorder automation, warranties, RMA, full inventory, generalized bill of materials, bulk import, automatic stock deduction, photos, work orders, and control-board identity replacement.
- a minimal durable component registry;
- narrow component creation and detail reads;
- focused repair-part list, record, and void routes;
- capability-based permissions;
- mutually exclusive consumable and tracked-component request shapes;
- fixed-precision quantities and controlled units;
- immutable tracked-component display snapshots;
- explicit void and replacement-entry corrections;
- immutable, explicitly sequenced repair events;
- atomic transactions and concurrency control;
- PostgreSQL constraints, foreign keys, and immutability protections; and
- focused, migration, regression, architecture, and route tests.
Task 004 remains authoritative for control-board identity and MAC changes.
Task 005 must not accept `control_board` as a generic part type or update
`miners.control_board_sn`, `miners.current_mac`, or
`miner_network_identities`.
## Out Of Scope
- generalized component inventory or generic component CRUD;
- component update, delete, list, search, merge, or deduplication APIs;
- component locations, movements, installations, or removals;
- component lifecycle or condition workflows;
- component ownership transfer;
- warehouse inventory and transfers;
- stock levels, allocation, or automatic stock deduction;
- purchasing, suppliers, vendors, pricing, invoices, and cost accounting;
- warranties and RMA workflows;
- reorder automation;
- automatic creation of components while recording repair parts;
- automatic installation, removal, ownership transfer, or location tracking;
- specialized component tables or attributes;
- generalized bills of materials;
- bulk import;
- photos and work orders; and
- redesign or migration of Task 004 structures.
## Terminology
**Component:** A minimally registered physical part with an immutable UUID and
constrained type. Task 005 records identity and basic display metadata only.
**Repair-part entry:** An immutable record associating one consumable use or one
tracked component with a repair.
**Consumable:** A normalized description, positive quantity, controlled unit,
and optional notes. It has no component UUID.
**Tracked component:** One existing component referenced by UUID. Its repair-part
entry includes a display snapshot and has no consumable quantity or unit.
**Display snapshot:** Immutable component type, Asset ID, manufacturer, model,
and manufacturer serial values copied from the component when the repair-part
entry is recorded. Nullable source values remain null.
**Void:** A one-way audited correction that preserves the original entry. A
corrected value is represented by voiding the incorrect entry and recording a
new entry.
## Minimal Component Registry
### Component Types
The initial controlled component types are:
- `control_board`;
- `hashboard`;
- `power_supply`; and
- `fan`.
Do not accept `other` or arbitrary free-text types. Expanding the vocabulary
requires a reviewed migration and corresponding model and test updates.
### Component Schema
Create the focused `components` table with:
- `id` UUID primary key with a database default;
- `component_type` constrained text, required;
- `asset_id` text, optional;
- `manufacturer` text, optional;
- `model` text, optional;
- `manufacturer_serial` text, optional;
- `created_at` timezone-aware timestamp, required and server controlled; and
- `created_by` authenticated user UUID, required.
Rules:
- `id`, `component_type`, `created_at`, and `created_by` are immutable;
- optional text is trimmed, nonblank when present, and limited to 255
characters;
- Asset IDs are case-insensitively unique among components after trimming;
- component Asset IDs do not share a database uniqueness namespace with miner
Asset IDs in this task;
- manufacturer serials are not assumed globally unique;
- `created_by` references the existing user identity with `RESTRICT` deletion
behavior;
- no client may supply `id`, `created_at`, or `created_by`;
- no component is created implicitly from a repair-part request; and
- Task 005 provides no component metadata update or deletion workflow.
The UUID is the authoritative component identity. Asset ID and manufacturer
metadata do not replace it. A later explicit task may evolve mutable display
metadata while preserving the UUID and history.
## API
Proposed routes:
### Component Routes
Task 005 adds exactly these narrow component routes:
```http
POST /api/components
GET /api/components/{component_id}
```
`POST /api/components` creates one component and returns `201 Created`.
`GET /api/components/{component_id}` returns one component and `200 OK`.
Unknown component UUIDs return `404`. Malformed UUIDs use the existing `422`
validation response.
Do not add component list, search, update, patch, delete, install, remove, move,
or lifecycle routes.
### Repair-Part Routes
Task 005 adds exactly these repair-part routes:
```http
GET /api/repairs/{repair_id}/parts
@@ -40,150 +194,486 @@ POST /api/repairs/{repair_id}/parts
POST /api/repairs/{repair_id}/parts/{repair_part_id}/void
```
Do not physically delete authoritative entries.
The list returns `200 OK`. Recording returns `201 Created`. A successful void
returns `200 OK` with the voided repair-part representation. No route physically
deletes or destructively edits a repair-part entry.
Register and test these routes without changing the precedence or behavior of
legacy static routes, including `/api/repairs/parts-summary`.
### Response Envelopes
Use the existing typed success envelope containing `data`, `meta`, and `error`.
Component responses expose only the approved component fields. Repair-part
responses expose the stored consumable fields or stored component snapshot,
plus component UUID, notes, recording metadata, and void metadata.
A tracked repair-part response must use its immutable snapshot for historical
display. It may expose `component_id` so a caller can separately request the
live component. It must not silently substitute current component metadata for
the snapshot.
### List Ordering
Return repair-part entries in ascending recorded repair-event sequence. The
explicit database sequence, not timestamps, determines order. Void state is
shown on the original entry; void events remain separately visible in the
existing repair timeline.
No pagination or filtering is added because the collection is bounded to one
repair. A later change requires compatibility review.
## Permissions
The proposed authorization model is:
- use implemented `repairs.read` for the read route
- introduce `repairs.parts.manage` for add and void actions
Introduce these proposed capabilities:
Grant the proposed write permission to Admin, Manager, and Technician. Do not
grant it to Read Only. `repairs.parts.manage` does not exist yet and must be
introduced by the Task 005 migration. The router checks capabilities rather than
role names.
- `components.read` for component detail reads;
- `components.create` for component creation; and
- `repairs.parts.manage` for recording and voiding repair-part entries.
Reuse implemented `repairs.read` for the repair-part list.
Role grants:
| Capability | Admin | Manager | Technician | Read Only |
| --- | --- | --- | --- | --- |
| `components.read` | yes | yes | yes | yes |
| `components.create` | yes | yes | yes | no |
| `repairs.read` | yes | yes | yes | yes |
| `repairs.parts.manage` | yes | yes | yes | no |
Routers check capabilities, never role names. Every route requires
authentication and focused unauthenticated, unauthorized, and authorized-role
tests.
## Request Models
Reject unknown fields in every request. Actor UUIDs, timestamps, sequences,
event payloads, snapshots, and persistence identifiers are never accepted from
clients.
### Create Component
## Add Request
```json
{
"part_type": "fan",
"action": "installed",
"quantity": 1,
"serial_number": null,
"notes": "Replacement fan installed."
"component_type": "hashboard",
"asset_id": "HB-0042",
"manufacturer": "MicroBT",
"model": "M50 Hashboard",
"manufacturer_serial": "HB-SN-12345"
}
```
## Initial Vocabulary
Proposed part types:
Only `component_type` is required. Optional text values are normalized by
trimming surrounding whitespace and are rejected when blank after trimming.
Duplicate normalized Asset IDs return `409 Conflict` without leaking a database
exception.
- `fan`
- `power_supply`
- `hashboard`
- `cable`
- `consumable`
- `other`
### Record Consumable
Proposed actions:
```json
{
"consumable": {
"description": "Thermal paste",
"quantity": 1,
"unit": "gram"
},
"tracked_component_uuid": null,
"notes": "Applied during board service."
}
```
- `installed`
- `removed`
- `consumed`
### Record Tracked Component
Task 005 does not model source inventory, stock movement, return destination,
condition, or disposition. Those concepts require a later inventory decision
and must not be approximated with overlapping action/value combinations here.
Use notes for concise context that is not authoritative structured inventory
data.
```json
{
"consumable": null,
"tracked_component_uuid": "c79f9f36-1e89-4dbc-8a84-9ff3167614be",
"notes": "Used during this repair."
}
```
## Quantity Rules
- quantity greater than zero
- integer quantities for this task
- serialized components normally quantity 1
- consumables may exceed 1
- voiding does not create a negative entry or reverse inventory; it marks the
original record void
Exactly one of `consumable` and `tracked_component_uuid` must be non-null. A
tracked-component request refers to one existing component and never creates,
installs, removes, transfers, or mutates it.
### Void Repair Part
```json
{
"reason": "Recorded against the wrong repair."
}
```
The reason is required, trimmed, nonblank, and limited to 1000 characters. The
actor and timestamp come from authentication and the server.
## Consumable Rules
A consumable requires:
- normalized, nonblank `description`, limited to 255 characters;
- `quantity` represented as `NUMERIC(12,3)` or the repository-equivalent fixed
precision;
- quantity greater than zero;
- one controlled `unit`; and
- optional shared notes.
The initial controlled units are:
- `each`;
- `gram`;
- `millilitre`;
- `metre`; and
- `centimetre`.
Do not accept aliases or arbitrary free-text units. The service and request
model reject more than three fractional digits or values outside the fixed
precision range.
When `unit` is `each`, quantity must be a whole number. Measurement units permit
fractional quantities to three decimal places. Quantity describes repair usage
only and never changes stock.
## Repair-Part Schema
Create the focused `repair_parts` table with:
- `id` UUID primary key with a database default;
- `repair_id` UUID, required;
- `consumable_description` text, nullable;
- `quantity` `NUMERIC(12,3)`, nullable;
- `unit` constrained text, nullable;
- `component_id` UUID, nullable;
- `component_type_snapshot` constrained text, nullable;
- `component_asset_id_snapshot` text, nullable;
- `component_manufacturer_snapshot` text, nullable;
- `component_model_snapshot` text, nullable;
- `component_manufacturer_serial_snapshot` text, nullable;
- `notes` text, nullable;
- `recorded_by` authenticated user UUID, required;
- `recorded_at` timezone-aware timestamp, required and server controlled;
- `recorded_event_id` repair-event UUID, required;
- `voided_by` authenticated user UUID, nullable;
- `voided_at` timezone-aware timestamp, nullable;
- `void_reason` text, nullable;
- `void_event_id` repair-event UUID, nullable.
Foreign keys use `RESTRICT` deletion behavior for repairs, users, components,
and events so authoritative history cannot disappear through cascades.
The repository captures tracked-component snapshots from the locked component
row in the same transaction as the repair-part insert and event. Client values
cannot populate snapshot columns. Nullable component metadata remains null in
the snapshot rather than being fabricated.
The original entry shape, consumable values, component UUID and snapshot,
notes, recording actor, timestamp, and recorded event data are immutable. A
void adds only the write-once void fields and immutable void event.
## PostgreSQL Invariants
PostgreSQL must enforce the complete XOR shape:
- consumable branch:
- `consumable_description`, `quantity`, and `unit` are all present;
- `component_id` and every component snapshot field are null; or
- tracked-component branch:
- `component_id` and `component_type_snapshot` are present;
- `consumable_description`, `quantity`, and `unit` are all null;
- optional snapshot fields match the nullable values captured at record time.
Additional constraints and protections:
- quantity is greater than zero when present;
- quantity uses at most three fractional digits;
- `each` requires `quantity = trunc(quantity)`;
- unit is one of `each`, `gram`, `millilitre`, `metre`, or `centimetre`;
- component and snapshot types use the approved component-type constraint;
- descriptions and snapshot text are trimmed and nonblank when present;
- notes are null or trimmed, nonblank, and limited to 4000 characters;
- recording actor, timestamp, and event UUID are required;
- component UUID references an existing component;
- recorded and void event UUIDs are unique and reference repair events;
- void actor, timestamp, reason, and event UUID are either all
null or all present;
- void reason is trimmed, nonblank, and limited to 1000 characters;
- void timestamp is not earlier than recorded timestamp;
- a repair part cannot be associated with another repair through its event
references;
- original entry fields cannot be updated;
- void fields transition only once from all-null to all-present; and
- repair-part and repair-event rows cannot be physically deleted.
Use PostgreSQL constraints, foreign keys, indexes, and narrowly scoped triggers
where a declarative constraint cannot enforce cross-row immutability or event
ownership. Service validation provides clear domain errors but does not replace
database integrity.
## Repair State Rules
- add entries only while the repair is active
- void entries only while the repair is active
- read entries and void state after completion
- completed repairs remain read-only
- no unrestricted editing in any repair state
## Data Model
Proposed table: `repair_parts`. No such table currently exists. The legacy
`/api/repairs/parts-summary` route summarizes generic maintenance-event data and
is not an implementation of this model.
- component creation and detail reads do not change repair state;
- record repair-part entries only while the repair is active;
- void repair-part entries only while the repair is active;
- list entries and void state after repair completion;
- completed and cancelled repairs are read-only for Task 005;
- no unrestricted editing is allowed in any repair state; and
- Task 005 never changes repair status or miner lifecycle.
Potential fields:
- UUID
- repair UUID
- part type
- action
- quantity
- serial number
- notes
- recorded actor and timestamp
- void actor, timestamp, and reason
- related event UUID
Entries are never physically deleted. A void changes only the one-way void
metadata. It must not edit the original part type, action, quantity, serial
number, notes, recording actor, or recording timestamp. The void reason is
required; void actor and timestamp come from authentication and the server.
## Constraints
- quantity > 0
- void fields all-null or all-present
- void timestamp after recorded timestamp
- actor and recorded timestamp required
- valid constrained values and combinations
- serialized entries with a serial number have quantity 1
Begin Repair, Repair Timeline, Return to Service, and Task 004 behavior must
remain unchanged.
## Events
Proposed types:
- `REPAIR_PART_RECORDED`
- `REPAIR_PART_VOIDED`
The events are immutable history; the table is the queryable projection.
Add these immutable repair event types:
## Atomicity
Adding a part and appending its event occur in one transaction. Voiding and appending the void event occur in one transaction.
- `REPAIR_PART_RECORDED`; and
- `REPAIR_PART_VOIDED`.
Voiding must lock the repair-part row and succeed only when its void fields are
still null. Concurrent void requests must produce one success and one conflict,
with exactly one `REPAIR_PART_VOIDED` event.
`REPAIR_PART_RECORDED` contains:
- repair-part UUID;
- entry kind: `consumable` or `tracked_component`;
- the stored description, quantity, and unit for a consumable; or
- the stored component UUID and complete component display snapshot for a
tracked component; and
- optional notes.
It must not claim that a tracked component was installed, removed, replaced,
transferred, relocated, or changed.
`REPAIR_PART_VOIDED` contains:
- repair-part UUID; and
- required correction reason.
The event actor, timestamp, and sequence use existing repair-event columns and
are server controlled. Clients request domain actions, not raw events. Events
are append-only and ordered only by the repair's explicit database sequence.
The `repair_parts` table is the queryable record and does not replace Task 004
history.
## Transactions And Concurrency
### Component Creation
Component creation is one transaction. The database generates its UUID and
timestamp, the authenticated actor is required, and the normalized Asset ID
constraint resolves concurrent duplicates. No repair or component event is
invented for this minimal identity registration.
### Record Repair Part
Resolve the repair's miner UUID, then lock in this order:
```text
Miner -> Repair -> Component (tracked entries only)
```
After locking, revalidate the miner and active repair state. For a tracked
entry, lock and load the component, capture its snapshot, insert the repair-part
row, allocate the next repair-event sequence, and append exactly one
`REPAIR_PART_RECORDED` event in one transaction.
For a consumable, perform the same workflow without a component lock. Any
validation, insert, snapshot, sequence, or event failure rolls back the complete
action.
### Void Repair Part
Resolve the repair's miner UUID, then lock in this order:
```text
Miner -> Repair -> Repair Part
```
Revalidate the active repair and route ownership after locking. Set the
write-once void fields and append exactly one `REPAIR_PART_VOIDED` event in one
transaction. Concurrent void requests produce one success and one `409`
conflict, with exactly one void event.
The Miner lock and Repair lock serialize record and void actions with Return to
Service and Task 004. If repair completion wins, the later Task 005 action sees
a terminal repair and fails with `409`. If Task 005 wins, its complete entry and
event commit before completion proceeds.
## Architecture
Preserve `Router -> Service -> Repository -> PostgreSQL`. No generic parts CRUD, router SQL, or physical deletion.
## Tests
Cover:
- authorization
- serialized and consumable entries
- removed part
- list chronology
- read after completion
- explicit void
- invalid quantities and combinations
- control-board part rejection
- add to completed repair
- already-voided conflict
- cross-repair entry conflict
- concurrent void
- rollback on event failure
- direct constraints
- Tasks 001-004 regressions
- migration lifecycle and full backend suite
Preserve:
```text
Router
-> Service
-> Repository
-> PostgreSQL
```
Routers own authentication, permission checks, request validation, HTTP status
mapping, and response envelopes. Services own normalization, component and
repair-part rules, workflow coordination, and domain exceptions. Repositories
own parameterized SQL, locks, snapshots, persistence, and PostgreSQL integrity
translation.
Routers contain no SQL, database-driver imports, transaction handling, or
business rules. Services and repositories contain no FastAPI imports.
## Error Behavior
- unauthenticated: `401`;
- missing permission: `403`;
- unknown component, repair, or repair part: `404`;
- malformed UUID, unknown field, invalid type, invalid unit, invalid shape,
invalid precision, or invalid text: `422`;
- duplicate normalized component Asset ID: `409`;
- inactive repair, already-voided entry, or concurrent state conflict: `409`;
and
- persistence failures: translated without exposing SQL or driver details.
A repair-part UUID that does not belong to the repair in the route is treated as
not found and returns `404`.
## Migration
Create the focused table, add permissions and event constraints, add justified indexes, preserve legacy truth, and refuse destructive downgrade when authoritative Task 005 history exists.
Create additive Alembic revision `0014_repair_parts_tracking` with parent
`0013_control_board_replacement`.
The migration must:
- create `components` with its type, text, actor, timestamp, Asset ID uniqueness,
immutability, and deletion protections;
- create `repair_parts` with XOR, quantity, unit, snapshot, actor, timestamp,
event, void, immutability, and deletion protections;
- add `components.read`, `components.create`, and `repairs.parts.manage` with the
approved role assignments;
- extend the repair-event type constraint with `REPAIR_PART_RECORDED` and
`REPAIR_PART_VOIDED`;
- add only indexes justified by component lookup, normalized Asset ID
uniqueness, repair-part listing, foreign keys, and event relationships;
- preserve all existing data without inventing components, repair parts,
actors, timestamps, snapshots, or events; and
- leave Task 004 tables, constraints, projections, routes, and history intact.
There is no legacy backfill. Generic maintenance events, telemetry, serial
numbers, miner hashboard projections, and Task 004 installation rows are not
sufficient evidence for durable component identity or repair-part usage.
Downgrade is permitted only when no component, repair-part, or Task 005 repair
event rows exist. Otherwise it must refuse with a clear actionable error rather
than delete authoritative identity or history. An empty safe downgrade removes
Task 005 constraints, permissions, tables, and event values in dependency order,
then supports re-upgrade.
Validate previous-head upgrade, preflight behavior, safe downgrade, downgrade
refusal, re-upgrade, and an empty database from base through head.
## Tests
### Component Registry
- create and detail-read authorization for every role;
- successful creation with required fields only and with all metadata;
- UUID, actor, and timestamp authority;
- all approved component types and rejection of unknown types;
- whitespace normalization, text limits, and blank optional values;
- case-insensitive normalized Asset ID uniqueness and concurrent duplicates;
- nullable manufacturer metadata and nonunique manufacturer serials;
- immutable identity, type, actor, and timestamp;
- no implicit component creation from a repair-part request;
- no list, search, update, delete, lifecycle, location, or installation routes;
and
- missing and malformed component UUID behavior.
### Repair-Part Requests And Reads
- consumable and tracked-component happy paths;
- controlled units and rejection of aliases or free text;
- `NUMERIC(12,3)` boundaries and rejection of excess precision;
- positive fractional measurement quantities;
- whole-number `each` quantities and rejection of fractional `each`;
- both entry branches supplied and neither branch supplied;
- incomplete consumable shapes;
- unknown, malformed, and client-created component UUIDs;
- optional notes and blank or overlength text;
- server-controlled actors, timestamps, sequences, events, and snapshots;
- stable list ordering by recorded event sequence;
- response envelopes and historical reads after completion;
- component metadata changed after recording without changing the stored
snapshot or historical display;
- no N+1 component or actor queries; and
- route ownership for nested repair-part UUIDs.
### Workflow And Integrity
- add and void authorization for every role;
- active repair enforcement;
- explicit void and void-plus-replacement correction;
- already-voided and cross-repair behavior;
- concurrent record versus Return to Service;
- concurrent void requests;
- rollback on component lookup, snapshot, part insert, sequence, or event
failure;
- direct XOR, type, quantity, unit, text, foreign-key, event, and void
constraints;
- original-field, event, and physical-deletion protections;
- confirmation that tracked-component recording changes no component, miner,
lifecycle, installation, ownership, MAC, location, or condition state;
- confirmation that a control-board reference cannot bypass or duplicate Task
004's replacement behavior; and
- lock-order and transaction-boundary tests.
### Regression And Validation
- Begin Repair tests;
- Repair Timeline tests;
- Return to Service tests;
- Task 004 control-board replacement and compatibility-alias tests;
- Inventory lifecycle and permission regressions;
- legacy static repair-route precedence tests;
- migration upgrade, downgrade, refusal, re-upgrade, previous-head, and empty
database tests;
- full backend suite;
- disposable development baseline;
- Python compilation and shell syntax checks;
- router SQL, driver, transaction, and business-rule audit;
- service and repository FastAPI audit;
- exact route-count audit; and
- `git diff --check`.
## Acceptance Criteria
1. Parts can be recorded against an active repair.
2. Historical entries remain readable.
3. Entries are never physically deleted.
4. Voids are explicit and audited.
5. Actor and timestamps are authoritative.
6. Writes and events are atomic.
7. Values and quantities are constrained.
8. No warehouse or purchasing system is introduced.
9. Task 004 remains authoritative for control-board identity changes.
10. No commit or push is performed.
1. Components receive immutable UUIDs and only the minimal approved identity and
display metadata.
2. Component types and consumable units use the approved controlled values.
3. Every repair-part entry represents exactly one complete consumable or one
existing tracked component, never both or neither.
4. Consumable quantities use fixed precision, remain positive, and enforce
whole numbers for `each`.
5. Tracked entries preserve a live component foreign key and immutable display
snapshot.
6. Historical display does not change when live component metadata later
changes.
7. Recording a tracked component does not install, remove, replace, transfer,
relocate, create, or merge a component or miner.
8. Original entries and events are immutable; corrections use an audited void
and optional replacement entry without physical deletion.
9. Actors, timestamps, snapshots, and event sequences are authoritative and
server controlled.
10. Record and void actions are atomic and concurrency safe.
11. Task 004 remains authoritative for control-board and MAC behavior.
12. No generalized inventory, warehouse, purchasing, supplier, stock, location,
lifecycle, warranty, RMA, or cost system is introduced.
13. Existing repair workflows and static route precedence remain unchanged.
14. Migration lifecycle, focused tests, regressions, architecture audits, and
the disposable baseline pass.
## Completion Report
Report files, migration, routes, permissions, schema, constraints, events, tests, migration lifecycle, architecture audit, limitations, and final status.
Report files, migration revision and parent, routes and exact route count,
permissions and role grants, component registry schema, repair-part schema,
request and response models, constraints, snapshots, event payloads and
sequencing, transaction and lock strategy, downgrade behavior, tests and counts,
regression results, architecture and route audits, known limitations, and final
status.
Do not commit.
Do not push.