Files
2026-08-10 19:55:20 +00:00

753 lines
31 KiB
Markdown

# MinerOps Mine Map v1
## Status
This document is the design baseline for the implemented Mine Map v1. The
current application exposes the read-only operational view at `/monitoring/map`
and retains the legacy Location Map at `/locations` for existing location
management workflows. Those mutation workflows are not redesigned by v1.
## Purpose
Mine Map v1 answers one primary question:
> What is happening physically in the mine right now, and where do I need to
> look?
It must let a technician or manager scan a physical room, identify explicit
runtime and work states, locate affected miners, understand empty capacity, and
inspect one miner without losing spatial context. It is an operational tool,
not a geographic GIS map and not a decorative dashboard.
## Current Data Model
### Physical Hierarchy
The implemented hierarchy is:
```text
Site (UUID)
-> Room (UUID, belongs to Site)
-> Rack (text label on a location position)
-> Shelf (text label and optional numeric index)
-> Position (UUID and numeric index)
-> Miner (UUID, optional occupant)
```
`sites` and `rooms` are normalized entities. A room belongs to one site.
`location_positions` stores `room_id`, denormalized `room_name`, `rack_name`,
`shelf_name`, optional `shelf_index`, `position_index`, and optional
`position_label`. Rack and shelf are not independent entities and do not have
durable UUIDs. Mine Map must therefore treat rack and shelf values as ordered
labels within a room, not as globally addressable assets.
A position may reference one miner. `miner_location_states` separately stores
the miner's current location state and optional position reference. The miner's
durable identity remains `miners.id`; neither its position, IP address, nor MAC
address is asset identity.
### Position Vocabulary
The current code uses these position kinds:
- `miner_slot`
- `switch`
- `blocked`
The current code uses these position statuses:
- `available`
- `occupied`
- `switch`
- `blocked`
- `conflict`
These values are used by the active implementation but are not constrained by
the database. V1 must render unknown values as **Unknown position state** and
must not silently treat them as empty or occupied. A later schema task should
constrain the vocabulary after legacy-data preflight.
The current miner location-state vocabulary is:
- `shelved`
- `waiting_placement`
- `storage`
- `repair`
- `decommissioned`
These values are also not database-constrained. They describe location or
placement state, not miner lifecycle and not authoritative repair status.
### Lifecycle And Repair State
Miner lifecycle is independently constrained to:
- `active`
- `spare`
- `retired`
- `in_repair`
The modular Repairs domain uses `repairs` and immutable `repair_events` as the
authoritative repair workflow. Active repair statuses are `OPEN`,
`IN_PROGRESS`, `WAITING_FOR_PARTS`, and `READY_FOR_TEST`. `COMPLETED` and
`CANCELLED` are not active repairs.
The legacy location API currently exposes `miner_location_states.asset_state`
but does not expose lifecycle or the authoritative active repair. Mine Map v1
must not equate legacy `asset_state = repair` with a modular repair record.
## User Outcomes
V1 must support these outcomes without requiring location mutation:
- Select a site and room and see its known physical layout.
- Scan racks, shelves, and positions in their stored order.
- Distinguish occupied, explicitly empty, blocked, switch, conflict, and
incomplete positions.
- Identify miners explicitly reported offline or hash-unstable.
- Identify miners in the authoritative repair lifecycle or an active repair.
- Distinguish active, spare, retired, and in-repair assets.
- See latest available hashrate, power, and temperature values with timestamps.
- Inspect a miner and open its existing detail page.
- Return to the same room, rack, layer, filters, selection, and scroll context.
## Page Composition
Mine Map uses the current authenticated application shell at
`/monitoring/map`. The page contains four functional regions.
### Context Bar
The context bar contains:
- site selector;
- room selector;
- layer selector, defaulting to **Operational Status**;
- search;
- filters;
- data freshness summary; and
- a link to the existing location-management page when the user has
`locations.manage`.
The selected site limits the room selector. The selected room is required for
the primary rack layout. An optional all-room summary may show room counts, but
v1 must not render every tile from every room by default at large sites.
### Spatial Layout
The main region renders the selected room as ordered rack groups. Each rack
contains shelf rows, and each row contains positions ordered by
`position_index`. The layout uses ordinary semantic DOM elements and CSS grid.
Canvas, WebGL, pan physics, and a geographic basemap are not required.
The visual order follows stored room `sort_order`, shelf `shelf_index` where
present, and position index. Rack ordering is label-based because no rack sort
field exists. The UI must not infer missing racks, shelves, or positions from
gaps in names or numbers.
### Legend And Summary
The legend is always visible for the active layer and pairs color with text and
an icon or pattern. A compact summary reports only counts derivable from the
loaded response, such as occupied positions, empty positions, conflicts,
explicit offline miners, in-repair miners, and unknown-status miners.
Counts must identify their scope and freshness. They must not claim site-wide
coverage when only one room is loaded or when data is partial.
### Miner Inspector
Selecting an occupied miner opens a side panel on desktop. On narrow screens it
opens a full-height sheet with an obvious close control. The map remains the
underlying context and selection is visible on the selected tile.
Closing the inspector restores focus to the tile that opened it. Opening the
full miner detail view uses the existing durable route:
```text
/miners/{miner_id}/detail
```
## Position And Miner Rendering
### Occupied Positions
An occupied miner tile shows, in priority order:
- position label or `Position {position_index}`;
- Asset ID when available, otherwise current MAC, otherwise **Unidentified
miner**;
- operational condition text and icon;
- the active layer's value; and
- observation age or **Never observed**.
The position remains the visible frame; filtering and status styling must not
make its physical address ambiguous.
### Empty And Non-Miner Positions
Empty positions are operationally meaningful and remain visible.
| Condition | V1 presentation |
| --- | --- |
| `miner_slot`, no miner, no expected MAC, `available` | **Empty position** |
| `miner_slot`, expected MAC, no miner | **Expected miner not resolved**; not empty |
| `blocked` kind or status | **Blocked position** with reason when available |
| `switch` kind or status | **Switch/equipment position** with equipment label |
| `conflict` or `conflict_reason` | **Location conflict**; never shown as empty |
| Unknown kind or status | **Unknown position state** |
| Filtered occupied miner | Tile remains in place but is visually deemphasized |
| Missing response data | **Unavailable**, distinct from an empty position |
An absent position record is not an empty position. V1 must not synthesize a
tile merely because a numeric position is missing from a shelf sequence.
### Unracked And Operationally Removed Miners
Miners returned in `virtual_miners` appear outside the rack canvas in an
**Unplaced and operational areas** section grouped by the stored location
state. V1 maps the stored values to plain labels without inventing workflow:
- `waiting_placement`: Waiting for placement
- `storage`: Storage
- `repair`: Legacy repair location state
- `decommissioned`: Decommissioned location state
Lifecycle is shown separately. A spare miner is not automatically in storage,
and a retired miner is not automatically decommissioned. Where lifecycle and
location state disagree, both are displayed and the discrepancy is not hidden.
### Repair Miners
A miner with `lifecycle_status = in_repair` or an authoritative active repair is
visually unmistakable through text, icon, and a repair treatment independent of
the selected data layer.
V1 distinguishes:
- in repair while still assigned to a physical position;
- in repair with no rack position;
- repair complete with lifecycle `active` or `spare`, awaiting placement; and
- legacy `asset_state = repair` without an authoritative active repair.
The last case is labelled **Legacy repair location state** rather than being
presented as a confirmed active repair.
## Operational Status Model
Operational Status is the default layer. It composes current facts with this
display precedence:
1. Location conflict, blocked position, switch, or empty position.
2. Active repair or lifecycle `in_repair`.
3. Lifecycle `retired` or decommissioned location state.
4. Lifecycle `spare`.
5. Runtime status explicitly equal to `offline`.
6. `hash_stable = false`, labelled **Hash unstable**.
7. Runtime status explicitly equal to `online`.
8. Unknown or unrecognized runtime status.
Lifecycle and repair treatments do not erase runtime facts. For example, an
in-repair miner may also show that its last runtime observation was online.
`miners.status` is free text and not database-constrained. V1 must not infer
that arbitrary values containing `error`, `warn`, or `fault` mean a particular
severity. Unknown values may be displayed as secondary raw context after safe
formatting.
### Freshness
`last_seen_at` and latest telemetry `observed_at` are available, but MinerOps has
no single authoritative staleness policy. Existing legacy screens use
inconsistent thresholds, including 10 and 30 minutes. Therefore:
- v1 always displays observation time or age;
- missing timestamps are **Never observed** or **Freshness unknown**;
- explicit runtime `offline` may be shown as Offline;
- v1 does not label a miner Stale based on age until a centralized policy is
approved and supplied by the backend; and
- late or failed refreshes keep the last successful values visible with a
prominent **Update failed** or **Data may be stale** state.
### Conditions Requiring Policy
The following labels require an approved backend policy and are not inferred by
v1:
- stale based on observation age;
- down based on missing hashrate or elapsed time;
- hot or overheating based on temperature;
- degraded based on hashrate, rejection rate, power, or temperature; and
- low hashrate relative to model expectation.
The legacy Location Map and dashboard endpoints contain hard-coded thresholds,
but they are not a consistent policy contract. V1 may display raw measurements
and the explicit `hash_stable` boolean without adopting those thresholds.
## Layer Model
The layer selector is extensible, but the first implementation is not required
to enable every layer.
| Layer | V1 status | Current data | Required behavior |
| --- | --- | --- | --- |
| Operational Status | Required, default | Position kind/status, runtime status, lifecycle after API addition, `hash_stable`, timestamps | Uses the factual precedence above; no invented thresholds |
| Repair State | Required presentation, optional standalone layer | Lifecycle and modular repair after API addition; legacy location state exists now | Repair treatment remains visible on every layer |
| Temperature | Optional v1 value layer | Latest chip maximum and average are already in the map response; environment temperature exists in snapshots but is omitted | Show numeric value and timestamp; no hot classification without policy |
| Hashrate | Optional v1 value layer | Latest average hashrate in TH/s is already in the map response | Show value and missing-data state; no low-hashrate threshold |
| Power | Optional v1 value layer | Latest power in watts is already in the map response | Show value and missing-data state; no density or efficiency inference |
| Thermal heat map | Future | Point temperatures exist for some miners | Requires approved thresholds, aggregation semantics, coverage disclosure, and legend |
The frontend implements layers through a shared tile contract rather than
separate maps. Adding a future layer changes tile content and legend, not the
physical hierarchy or selection model.
## Filtering And Search
The implemented filters are allowlisted and use returned miner facts: derived
operational status, lifecycle, model, and authoritative active-repair presence.
Search covers Asset ID, miner serial, current MAC, current IP, and model. Location
context remains visible while nonmatching occupied tiles are deemphasized. Users
without `miners.view` receive none of these miner fields; miner search, miner
filters, and non-status layers are disabled rather than operating on redacted
values.
Filtering preserves physical context. Positions remain in the layout;
nonmatching occupied tiles are deemphasized and marked as filtered. A separate
**Show only matches** density option may collapse nonmatching rack groups, but
it is off by default and clearly indicates that context is hidden. Empty,
blocked, switch, and conflict positions remain visible unless explicitly
filtered by position type.
No-results differs from load failure and from an empty room:
- **No positions configured**: the selected room has no position records.
- **No miners match these filters**: layout remains visible, with miners
deemphasized.
- **Map unavailable**: the request failed or returned an invalid response.
## Miner Inspector Fields
Where authorized and available, the inspector shows:
- Asset ID;
- model or miner type;
- miner serial number;
- current MAC and IP address;
- full site, room, rack, shelf, and position path;
- runtime status and telemetry observation age;
- lifecycle;
- active repair status and reason;
- latest hashrate in TH/s;
- latest power in watts;
- latest chip maximum temperature;
- telemetry observation time; and
- a link to full miner detail.
Null values render as **Unavailable**. Numeric zero is displayed as zero, not as
missing. The panel must not fetch one API resource per visible tile. A single
detail request for the selected miner is acceptable when it supplies fields not
needed by the map response.
## Context Preservation
Shareable state is URL-backed. The initial query contract is:
```text
/monitoring/map?site_id={uuid}&room_id={uuid}&layer=status&miner_id={uuid}
```
Optional encoded parameters hold allowlisted filters and search. Rack and shelf
do not have URL identifiers because they are labels rather than durable entities.
Unknown, unauthorized, or malformed parameters are discarded and the canonical
URL is replaced; they never broaden data access.
The URL preserves:
- selected site and room;
- layer;
- filters and search; and
- selected miner.
Selecting or closing a miner updates history so browser Back and Forward restore
predictable URL-backed selection. Refresh and bookmarked links reconstruct the
same valid shareable state. Scroll position is not persisted by v1.
When full miner detail opens in the same tab, the return URL contains the map
query state. A new tab remains available through normal browser behavior.
## Responsive Behavior
### Desktop
- Context controls remain visible above the map.
- The room layout occupies the main content width.
- The inspector uses a fixed-width side panel without resizing rack tiles.
- Horizontal scrolling is contained within an individual wide rack or map
viewport, not the whole page.
### Tablet And Phone
- Site, room, layer, and filter controls stack without hiding the active scope.
- One rack is shown at a time when the full room cannot remain legible.
- Shelf rows retain physical position order and allow contained horizontal
scrolling.
- The inspector becomes a full-screen sheet.
- Closing the sheet restores the selected tile and scroll location.
- Touch targets meet the shared UX guide sizing rules.
The mobile view does not turn positions into an unrelated flat miner list. Rack
and shelf context remains visible.
## Accessibility
Mine Map v1 must:
- communicate every state with text and an icon or pattern, not color alone;
- provide one page heading and semantic site, room, rack, shelf, and position
labels;
- expose each rack as a labelled region and each shelf as a labelled group;
- expose miner tiles as buttons when they open the inspector and noninteractive
elements when they do not;
- include an accessible name containing position, miner identity, condition,
and freshness;
- support logical keyboard movement through positions in stored spatial order;
- preserve visible focus and return focus after closing the inspector;
- provide keyboard-operable filters, layer selection, scrolling, and zoom or
density controls;
- avoid hover-only values;
- support browser zoom without clipping labels or controls; and
- avoid required motion and respect reduced-motion preferences if motion is
introduced later.
A screen-reader summary provides counts and a structured rack/shelf/position
list. It describes empty and blocked positions rather than omitting them.
## Loading, Failure, And Partial Data
The map has explicit states:
- **Loading**: layout skeletons preserve the eventual page structure.
- **Ready**: layout and response scope are known.
- **Empty room**: valid response with no configured positions.
- **No configured sites or rooms**: directs authorized users to existing
location management without implying a load failure.
- **Denied**: permission failure without leaking counts or miner previews.
- **Error**: request failed or response shape is invalid; remains visible until
retry or navigation.
- **Partial data**: physical layout is usable, but one or more miner fields or
telemetry observations are unavailable.
- **Refresh failed**: last successful map remains visible, clearly marked with
the failure time.
The frontend validates the response envelope and required position identifiers.
A malformed response is an error, not an empty room.
## Performance And Refresh
V1 is designed for hundreds or thousands of positions without prematurely
adopting canvas or WebGL.
- Fetch a map-shaped bulk response; never issue one request per position.
- Load one selected site and room where backend filtering is available.
- Render the visible room first and defer secondary summaries.
- Build rack and shelf fragments off-DOM, then attach them in batches.
- Update changed tile content without replacing the full page or shell.
- Keep layout refresh separate from telemetry refresh because positions change
less frequently than observations.
- Do not overlap refresh requests; cancel or ignore superseded responses.
- Pause automatic telemetry refresh while the page is hidden.
- Base any polling interval on collector cadence or backend guidance; this
document does not invent an interval.
- Preserve last successful values during transient failure and expose their
observation timestamps.
- Introduce list virtualization only after measurement shows the selected-room
DOM is too large; accessibility and keyboard order must be preserved.
The current bulk location query selects the latest snapshot per miner and is a
useful foundation. Before production use at large scale, its query plan and
indexes must be measured with representative site data.
## Current API Assessment
### `GET /api/location/map`
Permission: `locations.view`.
This is the primary existing map-shaped response and avoids N+1 tile requests.
It returns:
- `data.positions`: position UUID, room/rack/shelf labels, shelf and position
indexes, position label/kind/status, expected MAC, equipment metadata,
conflict information, miner UUID, current MAC/IP, runtime status, hostname,
serials, model/type, firmware, last seen, legacy location state, and latest
hashrate, power, chip temperatures, hash stability, rejection percentage, and
observation time;
- `data.virtual_miners`: miners in waiting-placement, storage, legacy repair,
or decommissioned location states; and
- counts in `meta`.
The implemented additive response also supplies site and room UUIDs, Asset ID,
lifecycle, authoritative active repair context, generated-at metadata, typed
contracts, and optional server-side site and room filters. Its read path follows
Router -> Service -> Repository -> PostgreSQL. Users with `locations.view` but
without `miners.view` receive physical layout and occupancy with miner identity,
telemetry, repair, and virtual-miner details removed.
Tags and environment temperature are not included because v1 does not require
them. Rack and shelf remain labels. The frontend renders legacy positions without
`room_id` in a separate **Unscoped legacy positions** section; it does not attach
their denormalized room labels to a selected site or room.
### `GET /api/sites`
Permission: `locations.view`.
Returns site UUID, name, description, and timestamps. It supplies the site
selector. It does not include room or map counts.
### `GET /api/rooms`
Permission: `locations.view`.
Returns room UUID, site UUID/name, room name/description, sort order, and
timestamps. It supplies site-scoped room selection.
### `GET /api/location/rooms-order`
Permission: `locations.view`.
Returns room UUID/name/order and position, miner-slot, switch, blocked, and
assigned-miner counts. Its location join is name-based and not site-scoped, so
it cannot be the sole source for a correct multi-site selector.
### `GET /api/inventory/miners`
Permission: `miners.view`.
Returns durable miner UUID, Asset ID, current MAC/IP, model, lifecycle, runtime
status, site/room UUIDs, tags, and timestamps. Search covers Asset ID, MAC, IP,
model, miner serial, control-board serial, PSU serial, and notes. The collection
is bounded to 500 records per request and has no physical position or telemetry.
It can validate the missing identity fields during development, but Mine Map
must not join paginated inventory calls in the browser for every room. The map
read model should include the small identity and lifecycle subset it needs.
### `GET /api/inventory/miners/{miner_id}`
Permission: `miners.view`.
Adds hostname, miner serial, control-board serial, PSU serial, firmware, notes,
and first-seen time. One call after selection is acceptable when the inspector
needs these fields, but it is not used to populate tiles.
### `GET /api/miners/{miner_id}/status-snapshots`
Authentication and `miners.view` are required. Admin is allowed by the existing
admin override; Manager, Technician, Read Only, and custom roles are authorized
according to their effective `miners.view` permission. Mine Map does not call
this endpoint per tile.
Returns up to 100 recent snapshots with hashrate, power, efficiency,
environment and chip temperatures, fans, uptime, stability, rejection rate,
performance mode, timestamps, and raw JSON. It is too heavy for every tile and
must not be called per visible miner. It may support a future selected-miner
trend, which is outside v1.
### Repair APIs
`GET /api/repairs/{repair_id}` and its event route expose authoritative modular
repair detail when a repair UUID is already known. There is no current modular
read endpoint that returns the active repair for each map miner.
`GET /api/repairs/queue` is a bulk legacy queue based on
`miner_location_states.asset_state = repair` and legacy maintenance events. It
does not represent the modular `repairs` table and must not be used as the
authoritative Mine Map repair source.
The additive map read model therefore needs a set-based join to at most one
active modular repair per miner. It must not perform per-miner repair lookups.
### Dashboard APIs
`GET /api/dashboard/miner-table` exposes latest telemetry in a fleet table.
The hot, unstable, and stale endpoints expose useful operational lists, but
they use endpoint-specific or caller-supplied thresholds. Mine Map does not
need these endpoints because the location map already includes the relevant raw
latest measurements. V1 must not combine their classifications into a new
status policy by accident.
## Implemented Map Read Model
The additive `GET /api/location/map` contract provides:
- site UUID and name;
- room UUID as well as room name;
- Asset ID;
- lifecycle status;
- a nullable active repair object containing repair UUID, status, and reason;
- response generation time;
- explicit scope metadata for selected site and room;
- per-miner latest telemetry observation time; and
- truthful nulls for unavailable fields.
The query is set-based, parameterized, permission-controlled, and uses four
fixed database executions independent of fleet size. Optional site and room UUID
filters are supported by the API; the current frontend performs one bulk request
and renders the selected room locally. The repository owns SQL, the service owns
response composition, and the router owns HTTP validation, authorization, and
the typed response envelope.
V1 does not require a write endpoint. Reading the map, changing layer, applying
filters, selecting a miner, or opening the inspector must never mutate location,
lifecycle, repair, or telemetry state.
## Permissions
- The page and location layout require `locations.view`.
- Supplemental Inventory detail requires `miners.view`.
- The existing miner detail link is shown only when the user can view miners.
- The location-management link is shown only with `locations.manage`.
- No placement or repair command is exposed by Mine Map v1.
- Frontend permission checks control presentation only; backend dependencies
remain authoritative.
All default roles currently receive both `locations.view` and `miners.view`,
but custom role combinations are possible. The backend implements the boundary:
`locations.view` alone returns layout and occupancy while redacting Asset ID,
miner UUID, expected/current MAC, IP, serials, model, runtime/lifecycle/location
state, telemetry, active repair, and virtual-miner data. This is authoritative
server-side redaction rather than frontend hiding.
## Architecture Boundary
Mine Map v1 follows:
```text
Frontend page
-> FastAPI router
-> Location map service/read model
-> Location repository
-> PostgreSQL
```
The frontend contains no SQL, status policy, repair reconstruction, or location
mutation. The read repository may join location, Inventory-owned projections,
latest telemetry, and active repair state for this explicit read model. It does
not write those domains. The service maps persistence results into the typed
map contract and preserves null and unknown values.
The existing integrated route may be migrated incrementally behind the same
path. The legacy `/locations` page remains available during rollout. Mine Map
v1 must not copy its inline HTML/JavaScript or hard-coded classification rules
into the new frontend shell.
## V1 Implementation Boundary
The smallest useful v1 includes:
- real site and room selection;
- real racks, shelves, positions, and miners;
- explicit empty, blocked, switch, conflict, and unknown positions;
- unplaced and operational-area miners;
- Operational Status as the default layer;
- repair and lifecycle treatments based on authoritative data;
- raw latest telemetry values and freshness;
- status legend and scoped counts;
- basic allowlisted filters and search;
- selected-miner inspector;
- existing miner-detail navigation;
- URL-backed context and predictable browser history;
- loading, empty, denied, error, and partial-data states;
- desktop, tablet, and phone behavior;
- keyboard, screen-reader, touch, zoom, and reduced-motion support;
- permission-aware presentation; and
- focused route, contract, rendering, permission, accessibility, and context
preservation tests.
Location creation, movement, import, rack/shelf editing, and position-type
changes remain in existing management workflows. V1 may link to those workflows
but does not reproduce them.
## Deferred Capabilities
The following are explicitly outside v1:
- advanced thermal heat maps;
- historical map playback;
- drag-and-drop or click-to-move relocation;
- map, rack, shelf, or position editing;
- predictive failure visualization;
- animated telemetry;
- power-density and electrical-capacity modelling;
- saved map views;
- multi-site geographic maps;
- multi-selection and bulk commands;
- repair, lifecycle, or component mutations;
- alerts and notifications;
- selected-miner telemetry charts; and
- generalized infrastructure modelling beyond current position records.
## Remaining Backend And Data Gaps
1. **Freshness policy:** No single approved stale/down threshold exists. V1
shows explicit offline state and observation age but does not classify stale.
2. **Health and thermal policy:** Hashrate, rejection, power, and temperature
measurements exist, but model-aware degraded and hot thresholds do not.
3. **Location vocabularies:** Position and location-state values are not
database-constrained. Unknown legacy values require truthful fallback.
4. **Rack and shelf identity/order:** Rack and shelf are labels, and racks lack
an explicit sort field. V1 cannot provide durable rack or shelf links or
operator-defined rack ordering.
## Acceptance Criteria
Mine Map v1 is accepted when all of the following are true:
- An authorized user can select a site and room and see every configured
position in stored spatial order.
- Occupied, empty, expected-but-unresolved, blocked, switch, conflict, unknown,
and unavailable states are visually and semantically distinct.
- Filtering does not silently erase physical context.
- Explicit offline, hash-unstable, spare, retired, and in-repair miners are
identifiable without inventing thresholds.
- Every telemetry value is tied to an observation time or marked unavailable.
- A user can identify whether apparent problems cluster by room, rack, or shelf
from factual tile states and measurements.
- A selected position identifies its miner or states why no miner is present.
- The inspector shows the key current identity, location, lifecycle, repair,
runtime, and telemetry details available to the user.
- The full miner detail page can be opened using the durable miner UUID.
- Closing the inspector or returning from detail restores the exact shareable
map context and practical scroll/focus context.
- Browser Back, Forward, refresh, bookmarks, and shared URLs behave
predictably.
- Permission failures expose no unauthorized counts, previews, search results,
or inspector fields.
- The page remains usable with keyboard, screen reader, browser zoom, touch,
narrow viewports, missing telemetry, partial records, and transient refresh
failures.
- The implementation performs no per-tile or per-miner request fan-out.
- The map is read-only; existing location management remains available.
Until the freshness and health-policy gaps are resolved, the map can answer
which miners are explicitly offline, hash-unstable, in repair, or associated
with location conflicts. It cannot truthfully claim that every down, stale,
hot, or degraded miner has been identified. The interface must state that
boundary rather than implying complete health classification.
## Unresolved Decisions
The following decisions remain for later map and data work:
1. Which backend-owned policy defines stale, down, hot, and degraded states,
including scope, thresholds, collector cadence, and configuration source.
2. Whether rack ordering may use a documented natural-label sort or requires an
additive persisted sort field.
3. Whether `location_positions.room_id` can be made required after legacy-data
preflight, eliminating name-only room association for production map data.
These decisions do not require changing the durable miner identity, existing
repair workflow, or v1 read-only boundary.