diff --git a/.planning/phases/04-usb-manager-label-printing/04-02-SUMMARY.md b/.planning/phases/04-usb-manager-label-printing/04-02-SUMMARY.md new file mode 100644 index 0000000..84f71db --- /dev/null +++ b/.planning/phases/04-usb-manager-label-printing/04-02-SUMMARY.md @@ -0,0 +1,153 @@ +--- +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*