Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
76c9a7e
docs(spec): P4.3 equipment hub + device registry design
Jul 19, 2026
335f87f
docs(plan): P4.3 equipment hub implementation plan — 11 tasks
Jul 19, 2026
9a3ea97
agent: GET /info — persisted machine id, hostname, version, uptime
Jul 19, 2026
6c4d095
backend: equipment registry storage — migration 000023, models, store…
Jul 19, 2026
4be540b
backend: equipment registry endpoints — tenant-scoped CRUD, default r…
Jul 19, 2026
7a7e0b5
backend: readiness equipment step — tenant has a tested default printer
Jul 19, 2026
13a912b
panel: agent /info client, cached machine identity, useAgentInfo with…
Jul 19, 2026
e38992e
panel: fix useAgentInfo review findings — gate info on enabled, warn-…
Jul 19, 2026
92edd95
panel: equipment registry hooks + agent reconcile logic
Jul 19, 2026
21fb023
docs(spec): drop impossible ip:port cross-check — agent /printers car…
Jul 19, 2026
a497056
panel: equipment hub — agent card, registry-reconciled device columns…
Jul 19, 2026
4526df3
panel: equipment hub review fixes — success-gated reconcile, distinct…
Jul 19, 2026
383848a
panel: printer setup wizard — find/test/save with physical verification
Jul 19, 2026
4de290f
panel: printer wizard review fixes — readable mirror warning, network…
Jul 19, 2026
7f65f20
panel: scanner setup wizard — wedge listen-detect + COM port flow
Jul 19, 2026
c69d756
panel: scanner wizard review fixes — discard-first COM listen, honest…
Jul 19, 2026
22944bc
panel: wedge listen editable-target guard — typing in the wizard's fi…
Jul 19, 2026
77c9d63
panel: move equipment machine query to shared/agent (layering)
Jul 19, 2026
46fca89
panel: default-printer precedence — server registry wins over agent c…
Jul 19, 2026
9cbaa6f
panel: revert printers-gated registry lookup; fix root cause with har…
Jul 19, 2026
ac9618e
panel: P4.3 whole-branch review polish — RU copy naturalness, no dang…
Jul 19, 2026
d873cd6
docs(sdd): record P4.3 execution trail — 11 tasks, review rounds, fin…
Jul 19, 2026
4cba775
agent: propagate generateUUIDv4 rand failure instead of Fatal-ing
Jul 19, 2026
3c02c73
backend: enforce default-printer device_id presence + per-kind config…
Jul 19, 2026
8532f41
panel: PR #83 bot-review round 1 fixes — equipment hub (10 findings)
Jul 19, 2026
3108fc2
chore: drop force-added internal fix reports from the branch — .super…
Jul 19, 2026
4804a69
backend: equipment store — atomic test-pass stamp on create, uncondit…
Jul 19, 2026
a7547fc
backend: PATCH device response echoes the re-read row, not the stale …
Jul 19, 2026
0cab1fc
panel: PR #83 bot-review round 2 fixes — equipment hub wizard races (…
Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .superpowers/sdd/p4.3-board-5a-5d-extract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# P4.3 board extract — screens 5a–5d "Equipment hub"

Source: `docs/design-briefs/idento-event-check-in-landing/project/Idento Panel.dc.html`,
turn `t5` (lines 454–662), group headers: *"Hub — org-level page · this
computer's agent + saved devices that survive reloads"* (5a, 5d) and
*"Guided setup — one small flow per device class, replaces the 1,002-line
wall"* (5b, 5c).

Turn title: *"Equipment hub — agent as a citizen of the UI, per-device
guided setup & test flows, honest disconnected state"*.

## Placement in the board

Unlike 2a–2d (a flow sequence) and 7e (a single screen), t5 is two pairs:
the hub in its two agent states (5a connected / 5d down) and the two
wizards (5b printer / 5c scanner). All four are fully drawn. The board's
footer note (t5, monospace) is load-bearing:

> "One AgentStatus model (1b) drives the hub card, the badge editor's
> Test print (4a) and the launch ceremony (2a). Devices + the
> default-printer rule live on the server per machine — reload-safe
> (WEB-QUAL-04/05). Each device class gets its own 3-step flow (5b, 5c)
> with a physical verification step; a passed printer test marks the
> pipeline's Equipment step ready (1f)."

## 5a — Equipment hub (1280px, full app chrome)

- Full app shell: top nav with **Equipment tab active** (org-level page,
sibling of Events / Team / Organization) — matches the panel's existing
`/equipment` placeholder route.
- Page header: `Equipment` h2 + inline caption *"Devices are per
computer — the agent on each machine talks to its own hardware."* +
right-aligned primary **"+ Add device"** button.
- **Agent card** (green-tinted border `#c6e6d6`, full-width, above the
device grid): agent icon in a green tile; title **"Idento Agent — this
computer"** + green **Connected** pill (check icon + text);
mono subline `localhost:3000 · v1.8.2 · REG-DESK-01 · uptime 3 h 12 m`
(⇒ agent must expose version / hostname / uptime — none exist today);
right-aligned secondary button **"Agent settings"** (nothing behind it
anywhere on the board — no settings screen exists).
- **Two-column device grid** below the agent card:
- **Printers card** (header: bold "Printers" + mono count `2` +
right-aligned green text-button **"+ Set up printer"**):
- Row 1 (highlighted: `#f6faf8` bg + 3px green left border): green
dot · **Zebra ZD421** + **DEFAULT** chip (small green bordered
badge) · mono meta `network · 192.168.1.44:9100 · 300 dpi · ribbon
OK` · secondary **Test print** button · `⋯` overflow menu.
- Row 2 (amber): amber dot · **Godex G500** `· backup` (muted
inline suffix on the name) · amber text *"Saved · not seen since
Jul 8 — check power & network"* · secondary **Retry** button · `⋯`.
- Card footer (gray strip): *"Default = what check-in stations on
this computer use. One rule, stored on the server."*
- **Scanners & cameras card** (header: bold "Scanners & cameras" +
mono `2` + green **"+ Set up scanner"**):
- Row 1: green dot · **Honeywell Voyager 1200g** · mono meta
`USB-HID · keyboard wedge · suffix: Enter` · **Test scan** · `⋯`.
- Row 2: green dot · **Built-in camera** · *"Fallback QR reader ·
permission granted"* · **Test scan** · `⋯`.
- Card footer: *"Saved devices persist on the server — a reload
never empties this list."*
- Reading of the two states in the Printers card: green = saved device
currently visible to the live agent; amber = saved in the registry but
NOT currently visible ("not seen since <date>") — i.e. the hub
reconciles the server registry against the agent's live list and shows
per-device liveness with a date, never silently dropping saved rows.

## 5d — Same hub, agent down (620px, content region only)

- Label: *"Same hub, agent down — saved devices stay visible (grayed),
the failure explains itself · no more silent empty lists"*.
- **Agent card in red** (`#f5cfcf` border, red icon tile): title
**"Agent not reachable"** + red **Disconnected** pill (✕ icon + text);
subline *"Printing and scanning are unavailable on this computer until
it's running."*
- **"Start the agent" card**: numbered steps —
`1 · Find Idento Agent in the system tray — if missing, launch it from
the Start menu`, `2 · Still nothing? Download the agent for this
computer` (link) — then a primary **Retry connection** button + mono
caption **"auto-retry in 8 s"** (⇒ automatic reconnect polling with a
visible countdown).
- **Saved devices at opacity .55**: Printers card header gains a muted
*"saved · unreachable"* caption; rows keep name + DEFAULT chip
(grayed) but dots go gray and all action buttons disappear. Only the
Printers card is drawn in the mock (620px crop) — the pattern applies
to both cards.
- ⇒ Saved devices must be renderable WITHOUT the agent: the registry
(and the machine's identity) must be readable when the agent is down.

## 5b — Printer setup wizard (700px, modal over dimmed bg)

- Modal header: **"Set up a printer"** + inline 3-step progress
`✓ Find — 2 Test — 3 Save` (steps 1–2 green, 3 gray — the mock shows
the Test step) + ✕ close.
- **Find result row** (green-tinted): green dot · **Zebra ZD421** · mono
`found on network · 192.168.1.44:9100 · 300 dpi` · right link
**"Enter IP manually"** (underlined escape hatch).
- **Test block** (2-col): left — a test-label preview placeholder
(hatched, mono "test label"); right — bold **"Did the test label print
correctly?"** + copy *"A label with alignment marks and «Кириллица
123» is printing now. Check darkness, position and that the text is
readable."* + three buttons: primary **"Yes, looks right"**, secondary
**"Print again"**, amber-bordered **"Something's off…"**.
- **Default checkbox** (checked in mock): *"Make this the default
printer for check-in stations on this computer"*.
- Footer: secondary **"← Back"**, primary **"Save printer"**.
- ⇒ Physical verification is IN the flow: a human confirms the label
before Save; the Cyrillic sample is deliberate (encoding truth).

## 5c — Scanner setup wizard (700px, modal)

- Modal header: **"Set up a scanner"** + segmented toggle
**"USB / COM scanner"** (active) | **"Camera"** + ✕ close.
- **Listen panel** (large, dashed green border): scan-frame icon ·
bold **"Listening… scan any barcode now"** · copy *"Point the scanner
at the test code below or at any badge. We'll detect the input mode
and terminator for you."* · a drawn test barcode.
- **Detection result row** (green-tinted): ✓ · **"Scan received —
`TEST-4471`"** · mono `keyboard wedge · suffix: Enter · 34 ms` ·
underlined **"Scan again"**.
- **Two fields**: "Device name" text input (`Honeywell Voyager — desk
2`) · "Terminator" select showing `Enter · detected`.
- Footer: secondary **Cancel**, primary **"Save scanner"**.
- ⇒ The wedge test runs in the browser (keystroke capture + timing);
detection output = input mode + terminator + keystroke latency. The
toggle names COM in the same tab ("USB / COM scanner") — COM flows
through the agent's serial endpoints instead.

## Cross-references the board makes

- 1b: AgentStatus model (already shipped as `@idento/ui` agent-status).
- 4a: badge editor Test print (P3.2 — already consumes useAgentPrinters).
- 2a: launch ceremony (P4.1 — already consumes useAgentPrinters).
- 1f: readiness pipeline Equipment step (backend stub `not_done` today).
- WEB-QUAL-04/05: the audit findings this design answers (localStorage
network-printer restore bug; default-printer priority triplicated).

## Deviations decided at brainstorm (user-approved 2026-07-19)

- **Camera dropped entirely** (no "Built-in camera" row, no Camera tab):
camera scanning at the station has been backlog since P4.1 ("vestigial
even in web") — showing a "Fallback QR reader" the station cannot use
fails the honesty rule. Registry schema reserves the class.
- **"Agent settings" button dropped**: no settings surface exists
anywhere on the board; shipping a dead button fails the same rule.
- Scanners card is therefore titled "Scanners" (not "Scanners &
cameras") until camera ships.
14 changes: 14 additions & 0 deletions .superpowers/sdd/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,17 @@ Codex only, 5 findings, ALL real, all fixed (commits c759439 backend + d489766 p
FIXED (cc6cb14): ExternalImport (API-key /api/public/import path) now publishes 1/request; UpdateAttendeeInfo (PATCH) publishes on success (name edits reach the last-scans feed on open monitors).
DECLINED: (1, P1) "count mobile/offline check-ins in rate/peak/recent" -- correct observation but a cross-phase DESIGN decision, not a patch: those metrics are station-path-scoped by P4.1's data-model contract (mobile paths deliberately write no action rows); totals/zones correctly count everyone via unattributed, nothing shown is ever WRONG; writing synthetic action rows would have to reconcile client-supplied offline timestamps with the tx-stable-now() attribution predicate, client_uuid dedup, and absent station provenance -- follow-up chip filed (task_1ba8c49f) with the full (a)/(b)/(c) option analysis for its own spec cycle. (2) "wrap all 5 snapshot reads in REPEATABLE READ" -- the invariant-bearing pair is ALREADY one statement (round-1 fix); remaining cross-section skew is sub-second, violates no documented relationship, and self-heals by construction (the commit causing it also publishes -> coalesced refetch within ~1s); restructuring 4 store methods around a shared tx for an unobservable discrepancy fails cost/benefit.
All threads replied + resolved. Gates green (532 backend without live-PG env this run, lint clean). Pushed (cc6cb14). Round-6 @codex review triggered.

=== PANEL P4.3 EXECUTION (subagent-driven) — branch worktree-panel-p4.3-equipment-hub, base 335f87f (spec 76c9a7e / plan 335f87f) ===
Task 1: complete (commit 9a3ea97, review Approved; Minor noted: report overstated the RLock->Lock race reachability — fix kept as defense-in-depth). GET /info + persisted machine_id, httpauth exemption, first-run gap closed via machineIDJustGenerated mirror. 49/49 agent tests -race, lint clean.
Task 2: complete (commit 6c4d095, review Approved; Minor: "8-method" wording vs 9 actual — cosmetic). Migration 000023 + models + 9 store methods + ErrDeviceNotFound; pgxmock 24 new subtests w/ byte-pinned SQL; live real-PG integration run (constraints/cascade/ANY-bind verified by BOTH implementer and reviewer against local :5438). Handler note carried to Task 3: pre-validate class on makeDefault for clean 400. 573 backend tests -race, lint clean.
Task 3: complete (commit 4be540b, review Approved). 7 equipment endpoints, full 400 matrix incl. camera-reserved + make_default-class pre-validation (Task 2 note honored), 23505->409, machine-scoped default target, coverage ledger green, schema.d.ts regen same-commit zero-drift. Minor CARRIED to final review: POST device on unregistered machine_id -> FK 500 (clean 4xx would be nicer; panel reconcile always upserts machine first so unreachable via our own client). 598 backend tests -race, panel typecheck clean.
Task 4: complete (commit 7a7e0b5, review Approved/COMPLIANT, 0 issues). Readiness equipment step live (TenantHasTestedDefaultPrinter), ready-ignores-equipment pinned by test, openapi caveat scrubbed, regen zero-drift. Justified deviation: 2 fakeStore fixtures in badge-template contract test gained the field (nil-panic otherwise). 598 backend -race, panel typecheck clean.
Task 5: complete (commits 13a912b+e38992e, review round1 2 findings [info not gated on enabled -> stale live info after disable, CONFIRMED via scratch repro; localStorage write inside queryFn could fail a healthy probe] -> fixed red-then-green, round2 Approved). agentClient.getInfo (404->null legacy), per-URL identity cache warn-dont-fail, useAgentInfo w/ connected_legacy + 8s error-only refetchInterval + isSuccess-gated info (v5 keeps stale data across failed refetches — implementer self-caught). 1206/1206 panel, typecheck clean. rtk lint-wrapper mis-parses empty eslint output (pre-existing, direct eslint used).
Task 6: complete (commit 92edd95, review Approved, 0 issues). Equipment hooks (isEmptyRegistry 404-shape traced to library source by reviewer), 6 mutations w/ exact-key invalidation, reconcile pure fns (wedge=none always, display_name never matches), agentClient.getScanners. Spec corrected in-flight: ip:port cross-check impossible (agent list has name+type only) — line rewritten, commit follows Task 6. 1241/1241 panel. ⚠️ Task7-wiring of reconcile fns deferred to Task 7 itself (natural).
Task 7: complete (commits a497056+4526df3, review round1 2 Important [reconcile fired on SETTLED not SUCCESS -> errored live list under-reported seen_device_ids w/ false not-seen later; non-404 registry error masked as empty registry vs "no silent empty lists"] + 3 Minor -> all fixed red-first (reviewer re-reproduced by swapping old component back), round2 Approved. Hub 5a/5d live: 3-state AgentCard + cold-start, liveness dots always w/ text, wedge dotless, unsaved-live rows, overflow rename/default/delete, once-per-machine_id reconcile w/ skip-not-consume budget, distinct 500-vs-404 states, ReadinessCell regression. Deferred to T8/T9 (controller-approved): row Test print/scan buttons, +Add-device chooser activation. Nit noted: registry outage hides unsaved live rows (no-partial-truth choice). 1274/1274 panel.
Task 8: complete (commits 383848a+4de290f, review round1 2 Important [mirror warn NEVER painted: setState+onClose same tick, React18 batching + Radix Presence unmount — implementer had flagged the tension, reviewer proved plan-literal reading was a defect, BulkBar convention governs; network-from-Find dead end: agent /printers has no ip/port, save validation guaranteed to fail — address form now required at Save] + 5 Minor [in-handler guards, dismissal locks+tests, Find excludes registered, retest kind not hardcoded, timeout copy de-badged] -> all fixed red-first (reviewer re-verified by reverting PrinterWizard alone: 7/20 fail on exactly the fixed behaviors), round2 Approved. Wizard 5b + retest entry (controller plan-addition honoring spec §4.2 test-passed) + Add-device chooser (printer wired, scanner pending T9). 1311/1311 panel.
Task 9: complete (commits 7f65f20+c69d756+22944bc, THREE review rounds). R1: 1 Critical [COM listen trusted first consume — shared not-per-port buffer meant a stale pre-wizard scan false-passed physical verification and auto-fired retest markTestPassed; fixed discard-first, atomicity makes post-discard reads provably fresh; open->discard race handled honestly (eaten scan = still Listening, rescan)] + 2 Important [save hard-gate loosened to printer parity test_passed:Boolean(detection); AbortSignal on in-flight consume] + fake timers. R2: NEW Important FROM r1 fix (5th fix-needing-fix of the initiative): armed wedge listener + editable fields = phantom detection from fast typing into name input; fixed via editable-target guard clearing accumulator (accepted+documented limitation: scan while field focused lands in field, no detection — wedge physics, Scan again re-arms) + abort comment de-overstated. R3: Approved, red-first mechanically re-verified both rounds. Wizard 5c wedge+COM, retest->test-passed (device-generic endpoint, only printer tests feed readiness), last wizard-todo removed, no Camera regression-guard. 1355/1355 panel.
Task 10: complete (commits 77c9d63 [pure move to shared/agent, layering] + 46fca89 [precedence] + 9cbaa6f [fix], review round1 Important: implementer's hasLivePrinters gate was NOT the claimed provably-safe optimization — serialized query start, left root cause (unmocked /info in consumer suites) unfixed, coincidentally dodged StationPage timer math; reviewer verified live. Fixed per adjudication: literal enabled gating + harness-level lowest-priority MSW 404 defaults (/info->connected_legacy no-retry-no-cache, machines->empty-registry), +concurrency-pinning test (mutation-tested by reviewer: re-applying gate fails it). Round2 Approved. Server>agent>first live for ALL P3.2 surfaces, six consumer suites byte-untouched. 1362/1362 panel. ⚠️ one unreproducible full-suite exit-1 (3 subsequent green + 6x StationPage stable) — resolved: final sweep runs suite twice.
Task 11: complete (verification-only + polish commit ac9618e). ALL GATES GREEN x2 panel runs (T10 flake did not recur): panel 1362->1363/1363 + typecheck + eslint + build; ui 152/152; backend 598 -race incl. OPENAPI_COVERAGE + live-PG skips honest (6 SKIP); agent 49/49; schema regen zero-drift; grep-proofs clean (no Agent settings, camera hits = reserved-comment/regression-test only, web/ diff empty); keyParity 4/4. RU audit 3 nits + agent-card empty-hostname separator fixed post-final-review (ac9618e).
=== P4.3 FINAL WHOLE-BRANCH REVIEW (opus, f72e963..9cbaa6f) — READY TO MERGE, 0 Critical / 0 Important / 3 Minor (all deferred: test_passed_at second-statement window on create; meta-line cosmetic FIXED in ac9618e; precedence adds deduped second health probe per cycle — documented). Carried roll-up ALL DEFERRED with reasoning (T3 FK-500 unreachable via own client; T7 no-partial-truth; T8 Escape-dismiss deliberate; RU nits FIXED in ac9618e; main.go SA1019 pre-existing). Cross-task seams traced clean: wizard-write<->reconcile-read key contract, precedence blast radius on station, T10 MSW harness masks nothing. HEAD=ac9618e. ===
3 changes: 2 additions & 1 deletion agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ go build -o idento-agent
| Метод | Путь | Назначение |
|---|---|---|
| GET | `/health` | Проверка работоспособности (без авторизации) |
| GET | `/info` | Идентификация агента: `{machine_id, hostname, version, uptime_seconds}` (без авторизации) |
| GET | `/printers` | Список принтеров: массив `{name, type}`, `type` = `system` \| `network` |
| POST | `/print` | Печать ZPL: `{printer_name, zpl}` (legacy-форма: `{printer_name, template, data}`) |
| POST | `/print-pdf` | Печать PDF: `{printer_name, pdf_base64}` — только системные принтеры |
Expand All @@ -72,7 +73,7 @@ go build -o idento-agent

### Авторизация

Все эндпоинты, кроме `GET /health`, `GET /docs` и `GET /openapi.yaml`, требуют авторизации (реализация — [internal/httpauth/httpauth.go](internal/httpauth/httpauth.go)). Исключённые эндпоинты read-only и не содержат секретов; их эффективная защита — привязка агента к loopback-адресу. Запрос к защищённым эндпоинтам пропускается одним из двух способов:
Все эндпоинты, кроме `GET /health`, `GET /docs`, `GET /openapi.yaml` и `GET /info`, требуют авторизации (реализация — [internal/httpauth/httpauth.go](internal/httpauth/httpauth.go)). Исключённые эндпоинты read-only и не содержат секретов; их эффективная защита — привязка агента к loopback-адресу. Запрос к защищённым эндпоинтам пропускается одним из двух способов:

1. **Bearer-токен** — заголовок `Authorization: Bearer <token>`. Токен генерируется автоматически при первом запуске и хранится в `~/.idento/agent_config.json` (права `0600`). Используется desktop-приложением.
2. **Браузерный fallback (без токена)** — запрос должен идти на loopback-хост (`localhost` / `127.0.0.1`), мутации (`POST`/`PUT`/`PATCH`/`DELETE`) — с `Content-Type: application/json`, а заголовок `Origin` должен входить в allowlist. По умолчанию: `http://localhost:5173`, `http://localhost:5174`, `http://localhost:3000`; переопределяется переменной окружения `AGENT_ALLOWED_ORIGINS` (CSV) или полем `allowed_origins` в конфиге.
Expand Down
Loading
Loading