Documentation
¶
Overview ¶
Package device is the daemon-side durable registry of paired remote-control devices (R-DEV.1). Each record pins a device's identity keys and its R-POL.6 capability tier; the daemon reads it to authorize every remote command (R-POL.9). It is fed from a pairing MachineOutcome (whose DevicePayload now carries the Ed25519 command-signing key, ADR-007 2026-07-20).
Index ¶
- Constants
- func DeviceIDFor(commandSignPub []byte) string
- func ValidatePushBinding(push PushBinding) error
- type Action
- type Capability
- type PushBinding
- type Record
- type Registry
- func (r *Registry) Add(rec Record) error
- func (r *Registry) AddSole(rec Record) error
- func (r *Registry) Authorized(deviceID string, a Action) bool
- func (r *Registry) Count() int
- func (r *Registry) Get(deviceID string) (Record, bool)
- func (r *Registry) List() []Record
- func (r *Registry) Remove(deviceID string) (bool, error)
Constants ¶
const PushTransportGateway = "gateway"
PushTransportGateway is the only transport a persisted PushBinding may select. A legacy or foreground-only pairing carries no PushBinding at all.
Variables ¶
This section is empty.
Functions ¶
func DeviceIDFor ¶
DeviceIDFor derives the canonical device id from the device's Ed25519 command-signing public key: the hex SHA-256 of the key. It is deterministic and collision-avoiding, and it binds the device id to exactly the key R-POL.9 verifies command signatures against.
func ValidatePushBinding ¶ added in v0.13.19
func ValidatePushBinding(push PushBinding) error
ValidatePushBinding validates the complete authority-bearing record before either the pairing protocol accepts it or the registry persists it. Sharing this boundary prevents a phone from receiving acceptance for a shape enrollment will reject one frame later.
Types ¶
type Action ¶
type Action uint8
Action is a class of remote operation the daemon authorizes against a device's Capability (R-POL.9). Each concrete wire op maps onto exactly one of these classes.
const ( // ActionRead covers non-mutating ops (list, journal_read/subscribe, snapshot peek). ActionRead Action = iota // ActionApprove covers approving an agent interaction (R-POL.8). ActionApprove // ActionControl covers mutating control ops (input, interrupt, kill, launch, // take_control). ActionControl )
type Capability ¶
type Capability uint8
Capability is a device's authorization tier (R-POL.6). The zero value is deliberately invalid and grants nothing, so a zero-initialised or corrupted Capability fails closed.
const ( // CapReadOnly may only read (list/journal/snapshot); it may not approve. CapReadOnly Capability // CapReadApprove may read and approve agent interactions, but not control // (input/interrupt/kill/launch). CapReadApprove // CapFull may read, approve, and control. CapFull )
func (Capability) Allows ¶
func (c Capability) Allows(a Action) bool
Allows reports whether a device holding capability c may perform action a. It is fail-closed: an unknown capability value grants nothing, and read_only/read_approve never reach ActionControl.
func (Capability) MarshalText ¶
func (c Capability) MarshalText() ([]byte, error)
MarshalText renders the tier as its stable snake_case name; an invalid tier is an error so a corrupted Capability can never be persisted.
func (*Capability) UnmarshalText ¶
func (c *Capability) UnmarshalText(b []byte) error
UnmarshalText parses a snake_case tier name. An unknown string is an error (fail-closed) -- it never silently decodes to a tier, above all not CapFull.
type PushBinding ¶ added in v0.13.19
type PushBinding struct {
GatewayURL string `json:"gateway_url"`
Address []byte `json:"address"`
SubmitCapability string `json:"submit_capability"`
MachineRevokeCapability string `json:"machine_revoke_capability"`
WakeKey []byte `json:"wake_key"`
CapabilityRecordVersion int `json:"capability_record_version"`
Transport string `json:"transport"`
}
PushBinding is the machine's durable half of the authenticated msg4 extension. WakeKey is phone-generated per pairing and deliberately independent of epoch keys.
type Record ¶
type Record struct {
DeviceID string `json:"device_id"`
Name string `json:"name"`
NoiseStaticPub []byte `json:"noise_static_pub"`
RelayAuthPub []byte `json:"relay_auth_pub"`
CommandSignPub []byte `json:"command_sign_pub"`
RecipientPub []byte `json:"recipient_pub"`
RoutingID []byte `json:"routing_id"`
Capability Capability `json:"capability"`
PairedAt time.Time `json:"paired_at"`
GrantedEpoch uint32 `json:"granted_epoch"`
// ConsentSig is the device's relay-route consent for THIS machine, signed with the
// device's relay-auth key during the pairing ceremony and carried in msg3 (ADR-007
// B27/B38). It is persisted because the two machine-side callers that need it —
// cmd/swarm/remote.go authorizeAtRelay at pairing and cmd/swarm-remote's
// deliverEpochGrant on every gateway connect — run in different processes at
// different times, and neither can re-derive it: only the device can produce it.
//
// It is NOT a secret and storing it beside the pinned public keys leaks nothing: it
// names this machine's routing id as its sole grantee, so a copy authorizes nobody
// else (relay.ConsentMessage). It is not validated here for length, because the
// relay is the authority that verifies it and a record that merely fails there is a
// re-pair, not a corrupt registry.
ConsentSig []byte `json:"consent_sig,omitempty"`
// Push is the authenticated, per-pairing push binding conveyed in msg4. Keeping it
// inside the registry record makes device admission and its runtime push authority one
// atomic rename: no process can observe a paired device with another pairing's address
// or wake key. Nil is the legacy/foreground-compatible record shape.
Push *PushBinding `json:"push,omitempty"`
}
Record is one paired device pinned in the registry (R-DEV.1): its identity keys, routing/name, capability tier, pairing time, and the epoch it was granted at.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the durable, mutex-guarded device store (R-DEV.1). Its in-memory map is always consistent with the single devices.json on disk: every mutation persists atomically (temp+rename+Sync) and rolls back the in-memory change on a write error.
func Open ¶
Open opens (creating the directory if needed) the device registry rooted at dir, loading any previously persisted devices. A malformed registry file is an error (fail-closed): the daemon must not silently start with zero paired devices, which would both drop remote access and, via R-KS.2, flip the kill switch.
func (*Registry) Add ¶
Add validates and upserts one device, persisting durably. It rejects (without persisting) a record with an empty device id, a non-32-byte command-signing key, or an unknown capability. On a persistence error the in-memory map is rolled back so it never diverges from disk.
func (*Registry) AddSole ¶
AddSole atomically enrolls rec as the registry's SOLE device: under the registry mutex it rejects the commit when a DIFFERENT device is already present, so two concurrent single-device enrollments that both passed a Count()==0 pre-check cannot both commit (finding C1 -- a non-atomic check-then-Add would leave two devices and brick the gateway). Re-adding the SAME device id is an idempotent upsert. Like Add it validates, persists durably, and rolls back the in-memory change on a write error. The general Add stays uncapped; the single-device cap lives ONLY here, so the registry's multi-device tests are unaffected.
func (*Registry) Authorized ¶
Authorized reports whether the device may perform action a. It is fail-closed: an unknown device is authorized for nothing.