7.5 KiB
Shelf-Mounted Devices Design
Date: 2026-06-24
Status: Approved
Parent spec: notes/dev/specs/2026-06-24-rack-documentation-design.md (extension)
Goal
Let the rack model represent cabinet/tower-style PCs that sit on a rack shelf
rather than bolting into the rails. Several PCs share one shelf, side by side and
front/back, and the assembly spans a tall U-range (e.g. a shelf at U46 with three
towers standing up to U37 = a reserved block U37–U46). The current model cannot
express this: two PCs in the same U-range on the same face trip check_overlaps.
Context
- Placement today (
scripts/gen_rack.py): a rack item declaresrack_u(lowest U),u_height, andrack_face∈ {front, rear, both, left, right}.left/rightare 0U side-rail items that omitrack_u/u_heightand attach to a rail.check_overlapsrejects two items sharing a U on the same face; it already skips 0U rail items. - This gives a precedent: some items don't claim a U-range directly (0U rails attach to a rail). Shelf-mounted PCs are a third placement style — they attach to a shelf.
- The physical rack is labeled U1 at the top → U48 at the bottom, so a shelf at U46 (near the bottom) with towers standing upward to U37 reserves U37–U46.
shelfis already a validkind(abbrevshf) with a color inKIND_COLORS.
Decisions
1. Container model
The shelf is the rack-placed item; tower PCs are contained by it.
- A shelf (
kind: shelf) is placed normally:rack_u,u_height,rack_face: both(full depth). Itsrack_u/u_heightreserve the whole assembly's U-range (the shelf plus the towers standing on it), e.g.rack_u: 37, u_height: 10. - A mounted device declares, instead of
rack_u/u_height/rack_face:mounted_on: <shelf hostname>— the shelf it sits on.shelf_face: front | rear— which side of the shelf.shelf_slot: <integer ≥ 1>— left-to-right position within that face.
Mounted items still declare rack: <rack> (so they load as rack items) and may
carry cluster:, power:, links: unchanged — those key off hostname, not
placement.
No scripts/overview_config.yml change: shelf and server kinds already
exist; mounted_on/shelf_face/shelf_slot are extra fields gen_overview.py
ignores and gen_rack.py validates.
2. Validation (in gen_rack.py)
validate_item gains a placement branch, checked before the existing rail logic:
- If
mounted_onis present, the item is mounted:- require
shelf_face ∈ {front, rear}; - require
shelf_slotto be an integer ≥ 1; - forbid
rack_u,u_height, andrack_face(mutually exclusive with mounting, mirroring the 0U rule).
- require
- Otherwise the existing rules apply unchanged (rail item:
rack_face∈ FACES, 0U rules for left/right, U-range for the rest).
check_overlaps skips mounted items (they claim no U-range; their shelf
reserves the block) — added alongside the existing 0U skip. The shelf itself is
a normal placed item, so it still cannot overlap rail gear.
New check_shelves(items: list[dict]) -> None (called per rack from generate,
alongside check_overlaps/validate_power/validate_links):
- every
mounted_onresolves to an item in the same rack whosekindisshelfand which is itself placed (has integerrack_u/u_height); (mounted_on, shelf_face, shelf_slot)is unique — no two devices in the same spot on the same shelf.
3. Rendering (gen_rack.py)
Elevation SVG (render_svg):
- The shelf draws as a thin shelf strip (shelf-colored rect) at the bottom 1U of its reserved U-range, in both columns, labeled with the shelf hostname.
- Mounted occupants draw inside the reserved range, above the strip: for each
face (
front→ front column,rear→ rear column), gather that shelf's occupants for that face, order them byshelf_slotascending, and subdivide the column width by the number of occupants on that face — one labeled box per tower (hostname), drawn side by side. This produces the approved "two front, one back" picture. - Determinism: occupants ordered by
(shelf_slot, hostname); shelves processed in hostname order.
Occupancy table (render_page):
- Mounted devices list the shelf's U-range in the U column (e.g.
U37–U46) and a\<shelf_face\> · \<mounted_on\>/\<shelf_slot\>note in the Face column (e.g.front · shf01/1). - Ordering: rail items by U; each shelf's mounted devices appear immediately
after the shelf, ordered by
(shelf_face, shelf_slot); 0U rail items last.
Power/network graphs: unchanged — mounted PCs appear by hostname exactly as rail-mounted devices do.
Provisional demo data
Applies the worked example to the existing TaPPaaS nodes (user-confirmed):
- New
docs/hardware/shf01.md:kind: shelf,status: in-use,rack: rack01,rack_u: 37,u_height: 10,rack_face: both,cluster: tappaas. srv01: droprack_u/u_height/rack_face; addmounted_on: shf01,shelf_face: front,shelf_slot: 1. (Keepscluster: tappaas,power:,links:.)srv02: same,shelf_face: front,shelf_slot: 2; addcluster: tappaas.srv03: same,shelf_face: rear,shelf_slot: 1; addcluster: tappaas.srv04,srv05stay rail-mounted at U5–U6 (unchanged). The shelf block U37–U46 does not overlap any existing rail item (sw01 U10, pp01 U24, srv04/05 U5–U6, pdu rails).
All placements remain provisional placeholders; check_shelves/check_overlaps
reject inconsistent data loudly.
Integration
| File | Change |
|---|---|
scripts/gen_rack.py |
validate_item mounted branch; check_overlaps skip mounted; new check_shelves; render_svg shelf strip + subdivided occupant boxes; render_page occupancy rows for mounted items |
tests/test_gen_rack.py |
mounted-validation, check_shelves, SVG, and occupancy cases |
docs/hardware/shf01.md |
New shelf file |
docs/hardware/srv01.md..srv03.md |
Convert to mounted; srv02/srv03 gain cluster: tappaas |
docs/hardware/index.md, docs/infrastructure/racks/rack01.* |
Regenerated |
No mkdocs.yml, Makefile, .forgejo/workflows/docs.yml, or
overview_config.yml changes.
Test plan
- Unit —
validate_item: accept a valid mounted item; reject a mounted item that also hasrack_u/u_height/rack_face, a badshelf_face, or a non-integer/<1shelf_slot. - Unit —
check_overlaps: two mounted items sharing their shelf's U-range do not raise (mounted items are skipped). - Unit —
check_shelves: accept valid mounts; rejectmounted_onpointing at a missing item, at a non-shelfkind, or at an unplaced shelf; reject two occupants sharing(shelf, face, slot). - Unit —
render_svg: a shelf with two front + one rear occupant produces a shelf strip and three labeled occupant boxes; front occupants share the front column side by side; deterministic for reordered input. - Unit —
render_page: mounted devices appear in the occupancy table with the shelf's U-range and theface · shelf/slotnote. - Integration —
generate: valid fixtures write a page; a danglingmounted_onreturns1and writes nothing. - Drift / visual:
make docs-checkexits 0;mkdocs build --strictrenders the rack page with the shelf block showing srv01/srv02 front and srv03 rear.
Out of scope
- Shelf capacity limits (max slots per face) — slot-collision detection suffices.
- Modeling individual tower height separately from the shelf's reserved block.
- Nested shelves or shelves spanning racks.