Skip to content

patch(activity backend): record device name/IP/MAC on per-miner bulk action detail#71

Merged
rongxin-liu merged 4 commits intomainfrom
feat/22-activity-log-backend-patch
Apr 24, 2026
Merged

patch(activity backend): record device name/IP/MAC on per-miner bulk action detail#71
rongxin-liu merged 4 commits intomainfrom
feat/22-activity-log-backend-patch

Conversation

@rongxin-liu
Copy link
Copy Markdown
Contributor

@rongxin-liu rongxin-liu commented Apr 24, 2026

Problem

PR #25 shipped the per-miner batch detail RPC, but each row returns only the device UUID — a value the app surfaces nowhere else. Operators auditing a past bulk action have no way to recognize which miners were affected without cross-referencing UUIDs against another system. Frontend PR #60 renders whatever identifiers the backend hands it; today that's just the UUID.

What ships

Backend only. Follows the write-time capture convention already used by ActivityEntry.scope_label. Closes nothing; companion to #22 / #60.

  • Record raw device-identity fields at first write. Five new nullable columns on command_on_device_log (custom_name, manufacturer, model, ip_address, mac_address), populated by UpsertCommandOnDeviceLog joining device + discovered_device. The read path composes the display name via fleetmanagement.ComposeDeviceName — the same helper the live fleet read path uses, so the audit log and live UI never disagree on what a miner is called.
  • Immutable after first write. ON CONFLICT leaves the captured columns untouched so retries and the reaper-after-success path preserve the original audit values — even if the device was renamed or moved IPs between the first and second Upsert.
  • Optional proto fields with UUID fallback. CommandBatchDeviceResult.{device_name, ip_address, mac_address} are optional string; historical rows (pre-migration) surface as unset so the frontend can fall back to UUID.

Where to review

Schema

  • server/migrations/000037_add_device_snapshot_to_command_on_device_log.up.sql — five nullable TEXT columns. Nullable so historical rows stay NULL and the frontend can detect them.

SQL

  • server/sqlc/queries/command.sqlUpsertCommandOnDeviceLog has a dev CTE that copies device.custom_name / device.mac_address / discovered_device.{manufacturer, model, ip_address} verbatim; no composition or rendering happens in SQL. ON CONFLICT DO UPDATE intentionally excludes the captured columns. ListBatchDeviceResults selects the five new columns.

Shared helper

  • server/internal/domain/fleetmanagement/device_name.goComposeDeviceName(customName, manufacturer, model string) string is the single source of truth for the display-name rule. Both fleetmanagement/service.go (live fleet read) and command/service.go (audit log read) call it; the live-fleet call site was refactored to use it too so there's nowhere for the rule to drift.

Proto

  • proto/minercommand/v1/command.proto — three optional string fields on CommandBatchDeviceResult (tags 5/6/7). optional is deliberate so the frontend can distinguish "never captured" (historical) from "captured empty." The field doc describes the consumer contract, not the composition recipe.

Service

  • server/internal/domain/command/service.go — mapper reads the five raw sqlc fields, composes device_name via fleetmanagement.ComposeDeviceName, and passes ip_address / mac_address through using the existing ErrorMessage NullString-to-pointer pattern.

Tests

  • server/internal/domain/fleetmanagement/device_name_test.go — table-driven unit test for ComposeDeviceName covering the five relevant cases (custom set, custom empty with both mfr/model, only mfr, only model, all empty).
  • server/internal/domain/command/results_integration_test.goTestGetCommandBatchDeviceResults_DeviceSnapshot covers the full invariant: first Upsert captures the fields; a rename + IP change; second Upsert (reaper-style) flips status/error_info but leaves the captured values alone. TestGetCommandBatchDeviceResults_HistoricalRowsOmitSnapshot covers backward compatibility by inserting a codl row directly with NULL captured columns and asserting the proto optionals surface as unset.

Test plan

  • cd server && just test passes (the two new integration tests run against the TimescaleDB container and should both pass)
  • Run a bulk action (e.g. "Blink LEDs") on a rack. Expand the batch in the activity log detail view (via PR feat(activity): per-miner bulk action detail modal [frontend] #60) and confirm name / IP / MAC appear alongside each device row
  • Rename a miner (/miners/:id → rename) and expand the same batch again — original name still shown (audit invariant)
  • Seed a pre-migration row manually (INSERT INTO command_on_device_log ... without captured columns) — verify the RPC returns unset optionals and the frontend falls back to UUID
  • Kill a worker mid-command so the reaper fires; confirm the reaped row still has captured values (the reap path writes through the same UpsertCommandOnDeviceLog)

🤖 Generated with Claude Code

At first write, the Upsert query joins device + discovered_device and
freezes the composed display name (custom_name, else manufacturer + model),
IP, and MAC onto the log row. The ON CONFLICT branch leaves the snapshot
untouched so retries and the reaper never overwrite first-write values.

Exposed as optional fields on CommandBatchDeviceResult so the activity log
detail view can render human-recognizable identifiers without re-resolving
against mutable current state (names can be renamed, IPs are DHCP).
Historical rows (pre-migration) surface as unset optionals; the frontend
falls back to the device UUID.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added javascript Pull requests that update javascript code client server shared labels Apr 24, 2026
@rongxin-liu rongxin-liu changed the title feat(activity): snapshot device name/IP/MAC on per-miner bulk action detail [backend] feat(activity): record device name/IP/MAC on per-miner bulk action detail [backend] Apr 24, 2026
@rongxin-liu rongxin-liu changed the title feat(activity): record device name/IP/MAC on per-miner bulk action detail [backend] feat(activity): record device name/IP/MAC on per-miner bulk action detail Apr 24, 2026
@rongxin-liu rongxin-liu marked this pull request as ready for review April 24, 2026 03:05
@rongxin-liu rongxin-liu requested a review from a team as a code owner April 24, 2026 03:05
Copilot AI review requested due to automatic review settings April 24, 2026 03:05
@rongxin-liu rongxin-liu changed the title feat(activity): record device name/IP/MAC on per-miner bulk action detail patch(activity backend): record device name/IP/MAC on per-miner bulk action detail Apr 24, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an immutable “device identity snapshot” (name/IP/MAC) to per-miner bulk action results so operators can audit historical batches without cross-referencing UUIDs.

Changes:

  • Add nullable snapshot columns to command_on_device_log and populate them on first write via UpsertCommandOnDeviceLog.
  • Extend GetCommandBatchDeviceResults RPC to return optional device_name, ip_address, and mac_address.
  • Add integration tests covering snapshot immutability and backward compatibility for pre-migration rows.

Reviewed changes

Copilot reviewed 6 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/sqlc/queries/command.sql Populates snapshot fields on first upsert and returns them in batch result listing
server/migrations/000037_add_device_snapshot_to_command_on_device_log.up.sql Adds nullable device snapshot columns to command_on_device_log
server/migrations/000037_add_device_snapshot_to_command_on_device_log.down.sql Drops the snapshot columns on rollback
server/internal/domain/command/service.go Maps new nullable DB fields to optional proto fields in the RPC response
server/internal/domain/command/results_integration_test.go Adds integration tests for snapshot immutability + historical NULL behavior
proto/minercommand/v1/command.proto Adds optional device_name, ip_address, mac_address fields to CommandBatchDeviceResult
server/generated/sqlc/models.go sqlc regeneration to include new model fields
server/generated/sqlc/command.sql.go sqlc regeneration for updated queries/row structs
server/generated/grpc/minercommand/v1/command.pb.go Regenerated Go protobuf for new optional fields
client/src/protoFleet/api/generated/minercommand/v1/command_pb.ts Regenerated TS protobuf for new optional fields

Comment thread server/sqlc/queries/command.sql
Comment thread server/sqlc/queries/command.sql Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0896d3c15

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/sqlc/queries/command.sql
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

🔐 Codex Security Review

Note: This is an automated security-focused code review generated by Codex.
It should be used as a supplementary check alongside human review.
False positives are possible - use your judgment.

Scope summary

  • Reviewed pull request diff only (79e1347193b81875186824b1be6fd3a79579bfb3...6c4f7e3dff9ac5a6e082827649df9fac26638303, exact PR three-dot diff)
  • Model: gpt-5.4

💡 Click "edited" above to see previous reviews for this PR.


Review Summary

Overall Risk: LOW

Findings

No concrete security, correctness, or reliability defects stood out in the reviewed diff.

Notes

  • Review scope was limited to .git/codex-review.diff for 79e1347193b81875186824b1be6fd3a79579bfb3...6c4f7e3dff9ac5a6e082827649df9fac26638303.
  • The protobuf change is wire-compatible: it only adds optional fields with new tag numbers to CommandBatchDeviceResult.
  • The new SQL stays parameterized and does not introduce SQL injection or command-injection surface.
  • Auth behavior on GetCommandBatchDeviceResults appears unchanged and remains org-scoped through GetBatchHeaderForOrg.
  • The main residual consideration is data sensitivity/retention: this patch intentionally persists historical device_name, ip_address, and mac_address in command_on_device_log. That looks deliberate, but it is worth confirming it matches your RBAC and retention expectations.
  • I could not run Go tests in this sandbox because the filesystem is read-only and Go could not create its module/cache directories, so this is a static review only.

Generated by Codex Security Review |
Triggered by: @rongxin-liu |
Review workflow run

Comment thread proto/minercommand/v1/command.proto Outdated
Comment thread server/sqlc/queries/command.sql Outdated
rongxin-liu and others added 2 commits April 24, 2026 10:14
Single source of truth for the miner display-name rule (custom_name, else
manufacturer + " " + model). Replaces an inline conditional in the live
fleet read path; other consumers can now call it instead of reimplementing
the fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capture custom_name / manufacturer / model / ip_address / mac_address on
command_on_device_log instead of a pre-composed device_name. The read path
composes the display name via fleetmanagement.ComposeDeviceName so the
audit log stays in lockstep with the live fleet read. The UpsertCommandOn-
DeviceLog query is now rendering-free; the proto doc comment describes
intent rather than the implementation recipe.

Wire-level proto shape and frontend expectations are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The comments on the command_on_device_log snapshot columns paired
"at command-completion time" with "when the action ran" / "Reflects
state at action-time", which read as enqueue-time. In fact the
capture happens on the first terminal write (worker result or reaper
timeout), so for a long-running command a rename / DHCP change
between enqueue and that write is recorded here, not the pre-enqueue
identity.

Tightens the proto field docs, migration header, UpsertCommandOnDeviceLog
query comment, and DeviceSnapshot test doc. Generated .pb.go / _pb.ts /
sqlc Go files follow. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Made-with: Cursor
@rongxin-liu rongxin-liu merged commit 9708c6f into main Apr 24, 2026
64 checks passed
@rongxin-liu rongxin-liu deleted the feat/22-activity-log-backend-patch branch April 24, 2026 15:52
rongxin-liu added a commit that referenced this pull request Apr 24, 2026
…ce info

Switch from chevron-based inline expand to a click-to-open modal for
activity detail. Every activity row is now clickable, showing a summary
(event, timestamp, scope, user, result) and — for batch actions — the
per-miner device results table with name, IP, and MAC from PR #71.

- Add ActivityDetailModal with summary section + StatusCircle indicator
- Remove ActivityBatchDetails (superseded by modal)
- Collapse initiated + completed batch rows; fix count mismatch
- Strip "completed" from descriptions in the list view
- Extract isCompletedEvent/baseEventType to shared eventType utility
- Add fetchedRef to useCommandBatchDeviceResults to skip redundant RPCs
- Stabilize onDismiss callback and drop derived isPending prop

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client javascript Pull requests that update javascript code server shared

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants