Skip to content

fleet: client roster row carries friendly name + device type (shared schema derivation) #1371

Closed

Description

@jeonghun-jj-lee

Important

Problem — The client enroll POSTs a roster row with name = hostname() (raw FQDN) and no device_type, so peers see an ugly, typeless row while the machine labels itself nicely locally. See #1368 for the full symmetric problem and approaches considered.
Approach — Move the name+type derivation into a form shared by the enroll producer and the extension self-row so they cannot disagree: pure classifiers in @amicode/schema, an impure command-runner seam per node package. Populate the client's POSTed row via the resolution precedence.
Scope — in: the pure schema functions; the per-package impure detector behind one injectable command-runner seam; the client enroll row carrying friendly name + device_type; the amicode.device.name / amicode.device.type override namespace unified across the enroll producer AND the extension self-row; rehoming the extension's classifyDeviceType to schema. · out: the server self-registration and the server/self-row de-dupe (that is the sibling slice); the roster JSON schema (fields already exist).
Assumptions — Detection is best-effort; an undefined type falls back honestly to server_mode. The enroll verb already reads settings.json (for amicode.fleetTransport), so reading the override keys reuses that path.

Acceptance Criteria

  • @amicode/schema exports pure classifyMacModel(modelName) → laptop/desktop/undefined (rehomed from the extension's classifyDeviceType; existing behavior preserved).
  • @amicode/schema exports pure classifyLinuxChassis(chassis) → laptop (laptop/notebook/portable), desktop (desktop/tower), server (server/rack), else undefined.
  • @amicode/schema exports pure normalizeDeviceName(raw) proven by a table: Mac.mynetworksettings.com → Mac, host.local → host, JJ's Mac Studio → JJ's Mac Studio, "" → "". Rule: strip from the first . onward only when the remainder is a DNS-suffix-shaped label sequence; leave dot-free / space-bearing names intact.
  • @amicode/schema exports pure isWslKernel(procVersion) → true on a Microsoft/WSL marker, extracted from the currently-inline regex in the extension's detectWSLVersion.
  • The extension self-row imports classifyDeviceType from @amicode/schema (behavioral) and a lint/grep gate confirms no private classifier body remains in the extension (structural).
  • Each node package (extension host + amico-run) exposes a single injectable command-runner seam; a per-package test feeds canned command output through the real caller into the shared pure classifier (so platform behavior is failing-test-able without shelling out).
  • Linux type source order is hostnamectl chassis → /sys/class/dmi/id/chassis_type → abstain; hostnamectl absent ⇒ prettified hostname + device_type omitted.
  • A client enroll POSTs a roster row whose name is the resolved friendly name and whose device_type is the resolved type.
  • Resolution precedence per field, exercised by injected seams (detectDeviceType?, a setting reader): explicit setting (amicode.device.name / amicode.device.type) → OS detection → prettified hostname (name) / undefined (type). Override-wins and honest-fallback are tested independently.
  • The self-row and the POSTed row resolve the same override string (both read amicode.device.name / amicode.device.type); the amicode.device.name setting description is updated to reflect it also names the fleet device.
  • Wiring a friendly name does NOT change canonical.host or the minted join token (the friendly-name seam is decoupled from the canonical-host derivation).

Testing Decisions

  • Extend packages/schema's roster tests with a table-driven suite for the four pure functions (Mac model strings, Linux chassis values, /proc/version WSL strings, the normalizeDeviceName table).
  • Extend packages/amico-run's enroll tests (injected seams): the POSTed client row's name/device_type, override-wins, honest-fallback, and the friendly-name-does-not-touch-canonical case.
  • Add a per-package impure-detector test feeding canned command output through the real command-runner seam.
  • Reuse-first: extend the existing extension suite covering readLocalDevice/classifyDeviceType for the import + override-parity assertions; no new file.

Key Decisions

  • Pure classification lives in @amicode/schema beside KNOWN_DEVICE_TYPES; the producer and the extension host self-row both call it. The browser sidebar module remains a third re-declaring site and cannot import schema (schema pulls node builtins).
  • The impure execSync/file-read platform branch hides behind one injectable command-runner seam per package so both copies are test-covered against canned output.
  • One override namespace read by both producers: amicode.device.name (existing) + new amicode.device.type.
  • The friendly name never feeds canonical.host / the join token.

Constraints & Invariants

  • @amicode/schema stays side-effect-free (only pure functions added; no child_process).
  • No silent fallback: undetected type → server_mode fallback; no friendly-name source → prettified hostname; nothing fabricated.
  • Roster JSON schema unchanged (name/device_type? already exist and validate).
  • Never-fork (ADR 0005): no engine-spawn path added.

Prior Art

  • The roster contract + vocabulary (RosterRow, KNOWN_DEVICE_TYPES, parseRosterRow) in the schema package.
  • The client enroll path + injectable seams + settings.json reader (defaultSetTransport/defaultSettingsPath) in amico-run.
  • The existing derivation (scutil ComputerName, system_profiler classifier, readLocalDevice reading amicode.device.name) in the extension; the proven WSL /proc/version regex in the rebuild host-matrix code.

Source

Activity

  1. jeonghun-jj-lee commented on Sep 21, 2026

    @jeonghun-jj-lee
    ContributorAuthor

    Merged into feature/free-tier-fleet via #1374 (merge commit 1b2a3df). Auto-close was skipped because the PR's base was a feature branch, not the default branch — closing as merge bookkeeping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    hitlNeeds human review before merge

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions