homelabby/.planning/phases/01-foundation/01-VERIFICATION.md

13 KiB

phase verified status score overrides_applied human_verification
01-foundation 2026-04-10T08:00:00Z human_needed 5/5 must-haves verified 0
test expected why_human
Run `go run scripts/provision-netbox.go` with a real 40-character NetBox API token set as HWLAB_NETBOX_TOKEN Script completes with log output showing 8 custom fields created (or already existing) and Site/Location/Rack hierarchy created. Verify via `curl -H 'Authorization: Token <token>' http://10.5.0.130:8000/api/extras/custom-fields/` that all 8 HWLab fields exist. Requires a real NetBox API token. Token in .env is a placeholder (22 chars, not 40). Cannot verify live provisioning programmatically.
test expected why_human
SSH to LXC 130, run `pip show netbox-inventory` to verify the netbox-inventory plugin is installed (NB-03) Output shows netbox-inventory package with a version number. If not installed, run `pip install netbox-inventory` and restart NetBox. Plugin installation on LXC 130 is a manual infrastructure step that cannot be verified from the Go codebase. NB-03 has no automated test equivalent.
test expected why_human
Obtain a real NetBox token, set HWLAB_NETBOX_TOKEN, and run `go test ./internal/netbox/... ./internal/queue/... -v` to exercise integration tests TestPingLive, TestListDevicesLive, TestWAQEnqueueDequeue all PASS (not SKIP). TestPatchCustomFieldsRoundTrip PASS when HWLAB_TEST_DEVICE_ID is also set. Integration tests are correctly guarded by token length check (skip if not 40 chars). DragonFlyDB integration (WAQ) was confirmed live; NetBox integration requires real token.

Phase 1: Foundation Verification Report

Phase Goal: The Go binary connects to NetBox with all custom fields provisioned and a write-ahead queue buffering operations during downtime Verified: 2026-04-10T08:00:00Z Status: human_needed Re-verification: No — initial verification

Goal Achievement

Observable Truths (ROADMAP Success Criteria)

# Truth Status Evidence
1 Running Go binary serves a health endpoint and embeds a stub React SPA VERIFIED go build ./... exits 0; health.go returns {"status":"ok","version":"0.1.0"}; assets.go has //go:embed web/dist; router.go passes fs.FS to NewRouter; web/dist/index.html contains "HWLab"
2 All HWLab custom fields are readable and writable via NetBox API with round-trip test coverage VERIFIED (unit) internal/netbox/custom_fields.go: ParseCustomFields, BuildCustomFieldsPatch, BuildFullCustomFieldsPatch, PatchCustomFields all implemented; unit tests pass (5 tests); integration round-trip skips gracefully on placeholder token — requires human to confirm against live NetBox
3 A new item can be created in NetBox with a sequential HW-XXXXX ID auto-assigned VERIFIED internal/netbox/hwid.go: AllocateNextHWID implemented with optimistic-lock retry, getHighestHWIDNumber, hwIDExists; unit tests pass (10 cases via TestFormatHWID + TestParseHWID)
4 catalog_status transitions from draft through complete are enforced by the backend quality gate VERIFIED internal/inventory/quality_gate.go: validTransitions map, CatalogStatus.CanTransitionTo, Transition(); internal/inventory/catalog_updater.go: UpdateCatalogStatus calls Transition() then PatchCustomFields — enforcement is wired; 12+4 unit tests pass
5 A write-ahead queue in DragonFlyDB buffers failed NetBox operations and retries them on reconnect VERIFIED internal/queue/waq.go: Enqueue/Dequeue/Len via RPUSH/BLPOP; worker.go: RunWorker with backoff and max-attempts drop; main.go: non-fatal WAQ init wired with go waq.RunWorker(ctx, ...); DragonFlyDB integration test PASSED live (TestWAQEnqueueDequeue 0.02s)

Score: 5/5 truths verified

Deferred Items

None.

Required Artifacts

Artifact Expected Status Details
cmd/hwlab/main.go Binary entry point VERIFIED Wires config, WAQ, server; graceful shutdown via signal.NotifyContext
internal/api/router.go Chi router VERIFIED Accepts fs.FS param; /api routes; SPA fallback handler
internal/api/handlers/health.go GET /api/health VERIFIED Returns {"status":"ok","version":"0.1.0"}
internal/config/config.go Viper config VERIFIED HWLAB_ env prefix; BindEnv for all fields; JSON + .env loading
web/dist/index.html Stub SPA VERIFIED Exists; contains "HWLab"
assets.go go:embed directive VERIFIED //go:embed web/dist; StaticFiles passed into NewRouter
go.mod Module + deps VERIFIED Module: git.georgsen.dk/hwlab; chi v5.2.5; netbox v4.3.0; go-redis v9.18.0; viper v1.21.0
internal/netbox/client.go NetBox client wrapper VERIFIED NewClient, Ping, ListDevices, GetDevice; strips /api suffix from URL
internal/netbox/custom_fields.go Custom field helpers VERIFIED ParseCustomFields, BuildCustomFieldsPatch, BuildFullCustomFieldsPatch, PatchCustomFields — all real API calls
internal/netbox/types.go Domain types VERIFIED Device, CustomFields structs
internal/netbox/provision.go Provisioning VERIFIED ProvisionCustomFields, ProvisionLocationHierarchy, createCustomField, ensureSite/Location/Rack — no stubs
scripts/provision-netbox.go Provision CLI VERIFIED //go:build ignore; calls Provision() + CheckNetBoxInventoryPlugin()
internal/netbox/hwid.go HW-ID allocation VERIFIED AllocateNextHWID with 3-attempt retry, getHighestHWIDNumber, hwIDExists
internal/inventory/quality_gate.go State machine VERIFIED validTransitions map; CatalogStatus.CanTransitionTo; Transition; ParseCatalogStatus
internal/inventory/types.go HardwareRecord VERIFIED Composes netbox.CustomFields + CatalogStatus
internal/inventory/catalog_updater.go Quality gate persistence VERIFIED UpdateCatalogStatus calls Transition() then PatchCustomFields
internal/netbox/tags.go AI tag sync VERIFIED normalizeTags, tagNameToSlug, SyncTags, ensureTag — uses real ExtrasTagsCreate API
internal/queue/waq.go WAQ core VERIFIED RPUSH/BLPOP FIFO; parseRedisURL with slash-in-password workaround; Enqueue/Dequeue/Len/Close
internal/queue/worker.go WAQ worker VERIFIED RunWorker with context cancellation, backoff, max-attempts drop; NoOpHandler placeholder
From To Via Status Details
cmd/hwlab/main.go internal/api/router.go NewRouter(staticFS) WIRED Line 45: router := api.NewRouter(staticFS)
assets.go web/dist //go:embed web/dist WIRED StaticFiles var; passed as fs.FS to NewRouter
internal/netbox/client.go NetBox API nb.NewAPIClientFor WIRED Line 32; URL /api suffix stripped correctly
internal/netbox/custom_fields.go internal/netbox/client.go PatchCustomFields method on Client WIRED Method on *Client; SetCustomFields + DcimDevicesPartialUpdate
scripts/provision-netbox.go internal/netbox/provision.go Provision(client) WIRED client.Provision(ctx) called directly
internal/inventory/catalog_updater.go internal/netbox/client.go PatchCustomFields("catalog_status") WIRED Line 34: u.client.PatchCustomFields(ctx, deviceID, patch)
internal/queue/waq.go DragonFlyDB:6379 go-redis ParseURL + NewClient WIRED parseRedisURL regex fallback handles slash-in-password; RPush/BLPop confirmed live
cmd/hwlab/main.go internal/queue/worker.go go waq.RunWorker(ctx) WIRED Line 40: goroutine started after successful WAQ init

Data-Flow Trace (Level 4)

Not applicable for this phase — no dynamic data rendering components. All artifacts are backend services (HTTP server, queue, NetBox client). The health endpoint returns static data by design.

Behavioral Spot-Checks

Behavior Result Status
go build ./... Exit 0, no errors PASS
go test ./... 5 packages: api/handlers OK, config OK, inventory OK, netbox OK, queue OK PASS
go vet ./... (confirmed via SUMMARY 01-03 and 01-05) PASS
WAQ live integration (TestWAQEnqueueDequeue) PASS 0.02s — DragonFlyDB at 10.5.0.10:6379 reachable PASS
NetBox integration tests SKIP — placeholder token (correct guard behavior) SKIP (expected)

Requirements Coverage

Requirement Source Plan Description Status Evidence
INF-01 01-01 Go binary serves React SPA via go:embed SATISFIED assets.go + router.go + web/dist/index.html; go build green
INF-02 01-01 Config via JSON file + environment variables SATISFIED config.go: viper with HWLAB_ prefix, BindEnv, config.json defaults
INF-03 01-04 HW-XXXXX sequential ID auto-assigned at intake SATISFIED hwid.go: AllocateNextHWID with optimistic-lock retry; unit tests pass
NB-01 01-02 NetBox REST API CRUD on devices SATISFIED client.go: NewClient, Ping, ListDevices, GetDevice; PatchCustomFields in custom_fields.go
NB-02 01-02, 01-03 8 custom fields provisioned (hw_id, catalog_status, photo_urls, etc.) SATISFIED (code) / NEEDS HUMAN (live) provision.go: all 8 field specs defined; createCustomField real API; idempotent check-before-create. Actual provisioning requires real token.
NB-03 01-03 netbox-inventory plugin installed NEEDS HUMAN CheckNetBoxInventoryPlugin() can verify via API; actual plugin installation on LXC 130 is a manual step
NB-04 01-03 Location hierarchy (Site → Location → Rack) SATISFIED (code) / NEEDS HUMAN (live) ProvisionLocationHierarchy: ensureSite/ensureLocation/ensureRack all implemented with real go-netbox v4 API calls
NB-05 01-05 Write-ahead queue in DragonFlyDB SATISFIED waq.go + worker.go; live integration test PASSED; main.go non-fatal wiring verified
NB-06 01-04 Catalog quality gate enforced SATISFIED quality_gate.go: validTransitions; Transition() enforced; catalog_updater.go wires to PatchCustomFields
NB-07 01-04 AI tags synced to NetBox tag system SATISFIED tags.go: SyncTags, ensureTag with real ExtrasTagsCreate; normalizeTags deduplication tested

Anti-Patterns Found

No blockers detected.

File Pattern Severity Assessment
internal/queue/worker.go: NoOpHandler Placeholder handler logs and drains queue Info Intentional Phase 1 placeholder; Phase 2 replaces with real NetBox retry handler. Documented in SUMMARY. Not a blocker.
go.mod: // indirect on netbox and redis Cosmetic — packages are directly imported Info go mod tidy would fix. Packages compile and link correctly; no functional impact.

Human Verification Required

1. NetBox Custom Fields and Location Hierarchy (NB-02, NB-04)

Test: Obtain a real 40-character NetBox API token from http://10.5.0.130:8000/ (Admin → API Tokens → Add). Set HWLAB_NETBOX_TOKEN=<token> in .env. Run go run scripts/provision-netbox.go.

Expected: Log output shows custom fields created/skipped (8 total) and location hierarchy created ("Homelab" site, "Lab Bench" location, "Primary Rack" rack). Verify with: curl -s -H "Authorization: Token <token>" "http://10.5.0.130:8000/api/extras/custom-fields/" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['count'])" should print 8 or more.

Why human: Requires real NetBox token. Placeholder in .env is homelab-netbox-api-token-2024 (22 chars, not 40). All code is verified — provisioning script and functions are fully implemented. This is operator action to run the already-built tool.

2. netbox-inventory Plugin (NB-03)

Test: SSH to LXC 130 (ssh root@10.5.0.130 or equivalent). Run pip show netbox-inventory.

Expected: Output shows Name: netbox-inventory with a version string. If not installed: pip install netbox-inventory, then restart NetBox (systemctl restart netbox or supervisorctl restart netbox:*).

Why human: Plugin installation is an SSH/admin operation on the NetBox LXC container. CheckNetBoxInventoryPlugin() in provision.go can verify presence via the API, but initial installation cannot be automated from the Go codebase.

3. NetBox Integration Tests (NB-01 round-trip)

Test: With real token set in HWLAB_NETBOX_TOKEN and an existing device ID in HWLAB_TEST_DEVICE_ID: go test ./internal/netbox/... -v.

Expected: TestPingLive PASS, TestListDevicesLive PASS, TestPatchCustomFieldsRoundTrip PASS. All 3 currently skip cleanly with placeholder token — correct behavior.

Why human: Requires real NetBox token. Skip guards are correctly implemented: if len(token) != 40 { t.Skip(...) }.

Gaps Summary

No automated gaps. All code artifacts exist, are substantive (not stubs), and are correctly wired. The go build ./... and go test ./... suite passes cleanly with 0 failures.

Three items require human operator action before the phase goal is fully realized in the live environment:

  1. Running the provisioning script with a real NetBox token to materialize the 8 custom fields and location hierarchy in NetBox
  2. Verifying or installing the netbox-inventory plugin on LXC 130
  3. Running integration tests to confirm live NetBox connectivity end-to-end

These are operational prerequisites, not code gaps. The code is complete and correct.


Verified: 2026-04-10T08:00:00Z Verifier: Claude (gsd-verifier)