--- phase: 04-usb-manager-label-printing plan: "02" subsystem: labels tags: [go, qrcode, image, label-printing, bitmap] # Dependency graph requires: - phase: 04-usb-manager-label-printing provides: "Phase context, label dimensions, printer interface design" - phase: 01-foundation provides: "internal/netbox/types.go Device and CustomFields structs" provides: - "internal/labels package: RenderStandard (384x120) and RenderCable (384x180) bitmap renderers" - "QR code generation encoding http://mac-mini.mg:8080/hw/HW-XXXXX URLs" - "IsCableDevice() heuristic detection for cable records" - "LabelData and CableLabelData structs as public API for printer driver" affects: - 04-03-printer-driver - 04-05-intake-integration # Tech tracking tech-stack: added: - "github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e" - "golang.org/x/image v0.39.0" patterns: - "Label renderers return image.Image — driver-agnostic; printer driver converts to bytes" - "HWID format validated (HW-\\d{5}) before any qrcode.New() call (T-04-07 mitigation)" - "TDD: failing tests committed first, implementation makes them pass" key-files: created: - internal/labels/renderer.go - internal/labels/renderer_test.go - internal/labels/cable.go - internal/labels/cable_test.go modified: - go.mod - go.sum key-decisions: - "DisableBorder=true (not DisableBorderPadding — that field doesn't exist in skip2/go-qrcode v0.0.0-20200617195104)" - "HWID format validated before qrcode.New() to mitigate T-04-07 DoS via malformed input" - "IsCableDevice uses name/notes heuristic only — real device_type detection deferred to Phase 5" - "Cable label is 180px tall (vs 120px standard) to fit 4 text lines" patterns-established: - "Label renderers: pure image.Image output, no USB/printer dependency — testable in isolation" - "HWID validation: regexp.MustCompile at package level, checked at render entry points" - "Text layout: TextOffsetX=112 (after QR block), y positions proportional to label height" requirements-completed: [LBL-01, LBL-02, LBL-03] # Metrics duration: 15min completed: 2026-04-10 --- # Phase 04 Plan 02: QR Code + Label Rendering Summary **image.NRGBA label bitmaps at 203 DPI using skip2/go-qrcode — standard (384x120) and cable (384x180) templates with HWID-encoded QR codes and basicfont text** ## Performance - **Duration:** ~15 min - **Started:** 2026-04-10T06:30:00Z - **Completed:** 2026-04-10T06:45:55Z - **Tasks:** 2 - **Files modified:** 6 (4 created, 2 modified) ## Accomplishments - Standard label renderer: 384x120px NRGBA bitmap with QR code (left 96x96), HW ID, name, spec line - Cable label renderer: 384x180px bitmap with 4 text lines — HW ID, name, USB version/speed, wattage/test date - IsCableDevice() heuristic returns true when device Name or AINotes contains "cable" (case-insensitive) - HWID format validated before QR generation (T-04-07 mitigation — prevents DoS via malformed input) - All 10 tests pass across both renderers and the cable detection helper ## Task Commits Each task was committed atomically: 1. **Task 1: QR code generation + standard label renderer** - `8cbd840` (feat) 2. **Task 2: Cable label renderer + IsCableDevice detection helper** - `7800917` (feat) ## Files Created/Modified - `internal/labels/renderer.go` — LabelData, RenderStandard(), drawText(), truncate(), HWID validation - `internal/labels/renderer_test.go` — 5 tests: dimensions, QR non-blank, URL encoding, empty name fallback, white background - `internal/labels/cable.go` — CableLabelData, RenderCable(), IsCableDevice() - `internal/labels/cable_test.go` — 5 tests: dimensions, full data, IsCableDevice true/false, empty test date - `go.mod` / `go.sum` — added github.com/skip2/go-qrcode and golang.org/x/image ## Decisions Made - Used `DisableBorder = true` (not `DisableBorderPadding` — that field does not exist in the installed version of go-qrcode) - HWID validated via `regexp.MustCompile("^HW-\\d{5}$")` at render entry points per T-04-07 threat disposition (mitigate) - Cable detection kept as heuristic (name/notes contains "cable") — device_type-based detection is a Phase 5 concern - go.mod upgraded from go 1.23.0 to 1.25.0 automatically when golang.org/x/image@v0.39.0 required it ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] DisableBorderPadding field does not exist in go-qrcode** - **Found during:** Task 1 (standard label renderer) - **Issue:** Plan code snippet used `qr.DisableBorderPadding = true` but the installed version of skip2/go-qrcode has `DisableBorder bool` not `DisableBorderPadding` - **Fix:** Changed to `qr.DisableBorder = true` (inspected package source) - **Files modified:** internal/labels/renderer.go, internal/labels/cable.go - **Verification:** All tests pass after fix - **Committed in:** 8cbd840 (Task 1 commit) **2. [Rule 2 - Missing Critical] Added HWID format validation (T-04-07)** - **Found during:** Task 1 — threat model review - **Issue:** Threat register marked T-04-07 (DoS via malformed HWID to qrcode.New) as `mitigate` — no validation was in the plan's code snippet - **Fix:** Added `hwIDPattern = regexp.MustCompile("^HW-\\d{5}$")` and guard at top of both RenderStandard and RenderCable - **Files modified:** internal/labels/renderer.go, internal/labels/cable.go - **Verification:** Passing invalid HWIDs returns error rather than passing to qrcode.New - **Committed in:** 8cbd840, 7800917 (both task commits) --- **Total deviations:** 2 auto-fixed (1 bug, 1 missing critical security mitigation) **Impact on plan:** Both fixes necessary for correctness and threat mitigation. No scope creep. ## Issues Encountered - golang.org/x/image@v0.39.0 requires go 1.25+ — `go get` automatically upgraded go.mod from 1.23.0 to 1.25.0. This is acceptable for the project. ## User Setup Required None — no external service configuration required. ## Next Phase Readiness - `internal/labels` package is complete and fully tested - Printer driver (04-03) can import `internal/labels` and call `RenderStandard` / `RenderCable` to get `image.Image`, then convert to bytes for the PRT Qutie - Intake integration (04-05) can use `IsCableDevice()` to route to the correct label template - No blockers ## Self-Check: PASSED - internal/labels/renderer.go: FOUND - internal/labels/renderer_test.go: FOUND - internal/labels/cable.go: FOUND - internal/labels/cable_test.go: FOUND - Commit 8cbd840: FOUND - Commit 7800917: FOUND --- *Phase: 04-usb-manager-label-printing* *Completed: 2026-04-10*