atom

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package atom provides the shared Atom authentication, authorization, projection, bootstrap, and service-token integration used by Magistrala Community and Enterprise services.

Index

Constants

View Source
const (
	ValueTypeNumber  = "number"
	ValueTypeInteger = "integer"
	ValueTypeString  = "string"
	ValueTypeBoolean = "boolean"
)

Value types a measurement or command parameter may take. These are the JSON Schema names; normalizeValueType also accepts the shorthand a declaration is likely to be written with.

View Source
const (
	MeasurementAccessRead      = "r"
	MeasurementAccessReadWrite = "rw"
)

Measurement access. Access is advisory: it is rendered as JSON Schema's readOnly annotation, which validators record but do not enforce.

View Source
const (
	DeviceTypeStatusActive     = "active"
	DeviceTypeStatusDeprecated = "deprecated"
	DeviceTypeStatusDisabled   = "disabled"
)

Device type status. A deprecated or disabled device type accepts no new bindings; devices already bound to it are unaffected.

View Source
const (
	DeviceTypeVersionStatusDraft      = "draft"
	DeviceTypeVersionStatusActive     = "active"
	DeviceTypeVersionStatusDeprecated = "deprecated"
	DeviceTypeVersionStatusDisabled   = "disabled"
)

Device type version status. Only an active version can be bound to.

View Source
const (
	KindUser             = "user"
	KindDevice           = "device"
	KindChannel          = "channel"
	KindRule             = "rule"
	KindAlarm            = "alarm"
	KindReport           = "report"
	KindBootstrapConfig  = "bootstrap_config"
	KindBootstrapProfile = "bootstrap_profile"
)
View Source
const AttributeGateways = "gateways"

AttributeGateways is a device's own list of the gateways it relays through (spec §8 A12). Two invariants apply, and nothing enforces either at the type level -- every writer of this attribute (and of AttributeIsGateway) is responsible for upholding both itself:

  1. Every id named here must have AttributeIsGateway set on its own entity -- readAuthorizer's R1 scope leg trusts a held device's publisher identity unconditionally whenever is_gateway is false, so an id named here without it reopens finding 02's cross-tenant leak (Q1). Call MarkGateways with the ids it names immediately after writing, or use GatewaysDeclared to find them.
  2. A device may not carry both this attribute (non-empty) and AttributeIsGateway=true -- gateways do not chain (architecture.md §8 A15). Call ValidateGatewayChain with the values about to be persisted before writing them.

As of this writing the writers are SetDeviceGateways (this file) and the CLI's device create/update commands (cli/devices.go) -- a new one (another service, the SDK, a future API handler) must do the same or these invariants silently break for it. Exported (T2) so that a new writer can actually find this contract in godoc, rather than needing to already know an unexported identifier exists to look for it.

View Source
const AttributeIsGateway = "is_gateway"

AttributeIsGateway marks a device as a gateway (spec §8 A12) — a gateway is not a separate entity kind, just a device with this set. Exported alongside AttributeGateways: see its doc comment above for the two invariants this attribute is half of.

Variables

View Source
var ErrDeviceTypeNotBindable = errors.New("atom: device type is not active")

ErrDeviceTypeNotBindable means a bind named a device type that is not active. Atom enforces this too; checking here turns it into a typed error.

View Source
var ErrDeviceTypeVersionNotBindable = errors.New("atom: device type version is not active")

ErrDeviceTypeVersionNotBindable means a bind named a version that is not active. Atom only rejects this when it picks the version itself; when the caller names one explicitly it binds without checking status, so this client checks instead.

View Source
var ErrGatewayCannotHaveGateways = errors.New("atom: a gateway cannot declare its own gateways")

ErrGatewayCannotHaveGateways means the write being validated would leave a device both a gateway and reachable through gateways of its own. Decided: gateways do not chain -- a device with AttributeIsGateway set may not declare AttributeGateways, and vice versa. See ValidateGatewayChain.

View Source
var ErrGatewaysConflict = errors.New("atom: device gateways changed since last read")

ErrGatewaysConflict means the device's gateways attribute no longer matches expectedCurrent — the caller's view was stale, so nothing was written.

View Source
var ErrInvalidBearerToken = errors.New("invalid bearer token")

Functions

func Authorize

func Authorize(ctx context.Context, client Authorizer, session authn.Session, action, legacyObjectType, objectID, resourceKind string) error

func BuildCapabilitySchema added in v1.0.0

func BuildCapabilitySchema(doc CapabilityDocument) (jsonSchema, uiSchema map[string]any, err error)

BuildCapabilitySchema renders a capability document to the json_schema and ui_schema a device type version stores.

json_schema constrains a device's *attributes*, which is what Atom validates on entity write. It does not constrain telemetry — messages never reach Atom.

additionalProperties is deliberately left open. A Magistrala device carries housekeeping attributes no device type declares (source, tags, metadata, gateways, timestamps — see EntityFromFields), so closing the schema would reject every device write.

func CapabilityName

func CapabilityName(action string) string

func GatewaysDeclared added in v1.0.0

func GatewaysDeclared(e Entity) []string

GatewaysDeclared returns the gateway ids e's gateways attribute names, in whatever form JSON decoding produced them. Exported (P3) for callers that construct or receive an Entity directly instead of going through SetDeviceGateways -- the CLI's device create/update commands, which unmarshal arbitrary Entity JSON -- so they can find out which ids need MarkGateways after writing it.

func IsConflict

func IsConflict(err error) bool

IsConflict and IsNotFound unwrap, so they still answer for an error a richer type wraps — SchemaValidationError, say.

func IsNotFound

func IsNotFound(err error) bool

func NewChannelsCompat

func NewChannelsCompat(client Authorizer) channelsv1.ChannelsServiceClient

func NewDevicesCompat added in v1.0.0

func NewDevicesCompat(authn smqauthn.Authentication, client ...*Client) devicesv1.DevicesServiceClient

func NewWorkspacesCompat added in v1.0.0

func NewWorkspacesCompat(client *Client) workspacesv1.WorkspacesServiceClient

func ObjectKind

func ObjectKind(legacyObjectType, resourceKind string) string

func SubjectID

func SubjectID(session authn.Session) string

func ValidateGatewayChain added in v1.0.0

func ValidateGatewayChain(deviceID string, isGateway bool, gatewayIDs []string) error

ValidateGatewayChain rejects a write that would leave deviceID both a gateway and reachable through gateways of its own (architecture.md §8 A15: gateways do not chain). isGateway and gatewayIDs are the AttributeIsGateway and AttributeGateways values the write in progress is about to persist for deviceID -- not necessarily what is stored today.

Self-reference -- deviceID present in gatewayIDs -- is rejected by the same rule: MarkGateways would flag deviceID itself is_gateway as a side effect of writing gatewayIDs, which is exactly the state this guards against, whether or not isGateway was already true going in.

Types

type AccessTokenResponse

type AccessTokenResponse struct {
	CredentialID string     `json:"credentialId"`
	Token        string     `json:"token"`
	Name         string     `json:"name,omitempty"`
	ExpiresAt    *time.Time `json:"expiresAt,omitempty"`
}

type ActionAssignmentRule

type ActionAssignmentRule struct {
	ID         string `json:"id"`
	TenantID   string `json:"tenant_id,omitempty"`
	EntityKind string `json:"entity_kind"`
	ActionName string `json:"action_name"`
	ObjectKind string `json:"object_kind"`
	ObjectType string `json:"object_type,omitempty"`
	Decision   string `json:"decision"`
	IsAbsolute bool   `json:"is_absolute"`
	CreatedAt  string `json:"created_at,omitempty"`
}

type ActionAssignmentRuleList

type ActionAssignmentRuleList struct {
	Items []ActionAssignmentRule `json:"items"`
	Total int64                  `json:"total,omitempty"`
}

type ActionAssignmentRuleSpec

type ActionAssignmentRuleSpec struct {
	TenantID   string
	EntityKind string
	ActionName string
	ObjectKind string
	ObjectType string
	Decision   string
	IsAbsolute bool
}

type AtomChannelsCompat

type AtomChannelsCompat struct {
	Client Authorizer
	Atom   *Client
}

func (AtomChannelsCompat) Authorize

func (AtomChannelsCompat) RemoveDeviceConnections added in v1.0.0

func (AtomChannelsCompat) RetrieveIDByRoute

type AtomDevicesCompat added in v1.0.0

type AtomDevicesCompat struct {
	Authn  smqauthn.Authentication
	Client devicesCompatAtomClient
}

func (AtomDevicesCompat) AddConnections added in v1.0.0

func (AtomDevicesCompat) Authenticate added in v1.0.0

func (AtomDevicesCompat) RemoveChannelConnections added in v1.0.0

func (AtomDevicesCompat) RemoveConnections added in v1.0.0

func (AtomDevicesCompat) RetrieveEntities added in v1.0.0

func (AtomDevicesCompat) RetrieveEntity added in v1.0.0

type AtomWorkspacesCompat added in v1.0.0

type AtomWorkspacesCompat struct {
	Client *Client
}

func (AtomWorkspacesCompat) DeleteUserFromWorkspaces added in v1.0.0

func (AtomWorkspacesCompat) RetrieveIDByRoute added in v1.0.0

func (AtomWorkspacesCompat) RetrieveStatus added in v1.0.0

type Attributes

type Attributes map[string]any

type AuthorizationCompat

type AuthorizationCompat struct {
	Client Authorizer
}

func NewAuthorizationCompat

func NewAuthorizationCompat(client Authorizer) AuthorizationCompat

func (AuthorizationCompat) Authorize

type AuthorizedObjectIDs

type AuthorizedObjectIDs struct {
	IDs   []string `json:"ids"`
	Total uint64   `json:"total"`
}

type AuthorizedObjectIDsQuery

type AuthorizedObjectIDsQuery struct {
	SubjectID  string
	Action     string
	ObjectKind string
	ObjectType string
	TenantID   string
	Q          string
	Limit      uint64
	Offset     uint64
}

type Authorizer

type Authorizer interface {
	CheckAuthz(ctx context.Context, req AuthzRequest) (AuthzResponse, error)
}

type AuthzRequest

type AuthzRequest struct {
	SubjectID  string         `json:"subject_id"`
	Action     string         `json:"action"`
	ResourceID string         `json:"resource_id,omitempty"`
	ObjectKind string         `json:"object_kind,omitempty"`
	ObjectID   string         `json:"object_id,omitempty"`
	Context    map[string]any `json:"context,omitempty"`
}

type AuthzResponse

type AuthzResponse struct {
	Allowed bool   `json:"allowed"`
	Reason  string `json:"reason,omitempty"`
}

type Capability

type Capability struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

type CapabilityApplicability

type CapabilityApplicability struct {
	ActionID    string `json:"action_id"`
	ActionName  string `json:"action_name"`
	Description string `json:"description,omitempty"`
	ObjectKind  string `json:"object_kind"`
	ObjectType  string `json:"object_type,omitempty"`
}

type CapabilityApplicabilitySpec

type CapabilityApplicabilitySpec struct {
	ActionName  string
	Description string
	ObjectKind  string
	ObjectType  string
}

type CapabilityDocument added in v1.0.0

type CapabilityDocument struct {
	Measurements []Measurement `json:"measurements,omitempty"`
	Commands     []Command     `json:"commands,omitempty"`
}

CapabilityDocument is what a device type declares, in the terms callers think in, so nobody hand-writes JSON Schema. BuildCapabilitySchema renders it for storage and ParseCapabilityDocument reads it back.

func ParseCapabilityDocument added in v1.0.0

func ParseCapabilityDocument(jsonSchema, uiSchema map[string]any) (CapabilityDocument, error)

ParseCapabilityDocument reads a capability document back out of a stored device type version, so callers never hand-parse JSON Schema.

It prefers the declaration BuildCapabilitySchema stashed in ui_schema, which round-trips exactly. Failing that — a version whose schema was written by hand — it reconstructs what it can from json_schema's properties. That fallback recovers measurements only: commands have no json_schema representation, since a command is not device state.

type CapabilityList

type CapabilityList struct {
	Items []Capability `json:"items"`
	Total int64        `json:"total,omitempty"`
}

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(cfg Config) *Client

func (*Client) ActiveDeviceTypeVersion added in v1.0.0

func (c *Client) ActiveDeviceTypeVersion(ctx context.Context, deviceTypeID string) (DeviceTypeVersion, error)

ActiveDeviceTypeVersion returns the version Atom binds a device to when none is named: the highest-numbered active one. It returns a not-found error when the type has no active version, which is also what Atom does on bind.

func (*Client) AddCapabilityApplicability

func (c *Client) AddCapabilityApplicability(ctx context.Context, actionID, objectKind, objectType string) (CapabilityApplicability, error)

func (*Client) AddGroupMember added in v1.0.0

func (c *Client) AddGroupMember(ctx context.Context, groupID, entityID string) error

AddGroupMember adds an entity to an object group (device/channel sharing). Despite the name, this sends addEntityToObjectGroup, not Atom's principal-group addGroupMember mutation — same spelling, different table.

func (*Client) AuthorizedObjectIDs

func (c *Client) AuthorizedObjectIDs(ctx context.Context, q AuthorizedObjectIDsQuery) (AuthorizedObjectIDs, error)

func (*Client) BindDeviceType added in v1.0.0

func (c *Client) BindDeviceType(ctx context.Context, deviceID, deviceTypeID, versionID string) (Entity, error)

BindDeviceType binds an existing device to a device type, optionally to a named version. An empty versionID leaves the choice to Atom, which takes the highest-numbered active version.

The type and version are checked before the write because Atom only checks them itself when it chooses the version — a caller-named version is bound whatever its status. Without this check, deprecating a version would not stop new bindings to it.

Binding validates attributes against the *previously* bound version, not the new one: Atom's update path looks up the schema by the entity's current binding. The first write after a bind is what the new schema sees.

func (*Client) CapabilityID

func (c *Client) CapabilityID(ctx context.Context, name string) (string, error)

CapabilityID resolves a capability name to its ID, paginating past the first page if needed. Results are cached since a capability's ID never changes once created.

func (*Client) ChangeOwnPassword added in v1.0.0

func (c *Client) ChangeOwnPassword(ctx context.Context, currentPassword, newPassword string) error

func (*Client) ChangeTenantStatus

func (c *Client) ChangeTenantStatus(ctx context.Context, id, action string) (Tenant, error)

func (*Client) CheckAuthz

func (c *Client) CheckAuthz(ctx context.Context, req AuthzRequest) (AuthzResponse, error)

func (*Client) CheckAuthzWithToken

func (c *Client) CheckAuthzWithToken(ctx context.Context, token string, req AuthzRequest) (AuthzResponse, error)

func (*Client) ChildGroups added in v1.0.0

func (c *Client) ChildGroups(ctx context.Context, parentID string, limit, offset uint64) (GroupList, error)

ChildGroups lists the immediate children of a group.

func (*Client) CreateActionAssignmentRule

func (c *Client) CreateActionAssignmentRule(ctx context.Context, spec ActionAssignmentRuleSpec) (ActionAssignmentRule, error)

func (*Client) CreateCapability

func (c *Client) CreateCapability(ctx context.Context, name, description string) (Capability, error)

func (*Client) CreateDeviceType added in v1.0.0

func (c *Client) CreateDeviceType(ctx context.Context, deviceType DeviceType) (DeviceType, error)

CreateDeviceType registers a device type. It creates the type only — add a capability document with CreateDeviceTypeVersion, which is a separate call because Atom has no deleteProfile mutation, so a combined create could not roll back a half-built type.

TenantID is required. Atom also supports tenant-less global types, but its profiles query cannot ask for "this tenant plus global" — passing no tenant returns every tenant's types — so Magistrala does not expose global types and always scopes to a tenant.

func (*Client) CreateDeviceTypeVersion added in v1.0.0

func (c *Client) CreateDeviceTypeVersion(ctx context.Context, deviceTypeID string, version DeviceTypeVersion) (DeviceTypeVersion, error)

CreateDeviceTypeVersion adds a version to a device type. It never mutates an existing version, so devices bound to earlier versions keep validating against exactly what they were created against.

Version numbers off Version, or the next unused number when it is zero. Deriving it costs a read, and two concurrent creates can still collide — the unique constraint turns that into a conflict rather than a silent overwrite.

JSONSchema and UISchema are rendered from Capabilities unless the caller supplies JSONSchema directly, which is the escape hatch for schemas the capability model cannot express.

Status defaults to active. Atom has no mutation to change it afterwards, so a version's status is decided here and only here: publish as DeviceTypeVersionStatusDraft to stage a version without making it bindable.

func (*Client) CreateDirectPolicy

func (c *Client) CreateDirectPolicy(ctx context.Context, policy CreateDirectPolicy) (DirectPolicy, error)

func (*Client) CreateEntity

func (c *Client) CreateEntity(ctx context.Context, entity Entity) (Entity, error)

CreateEntity writes entity as-is: if entity.Attributes declares AttributeGateways ("gateways"), the caller is responsible for calling MarkGateways with those ids afterward (see GatewaysDeclared) -- this method has no way to know it is a device create, let alone flag the gateways it names. See AttributeGateways's doc comment for the invariant this maintains and who currently maintains it.

func (*Client) CreateObjectGroup added in v1.0.0

func (c *Client) CreateObjectGroup(ctx context.Context, group Group) (Group, error)

CreateObjectGroup creates a group of devices/channels. Atom keeps object groups and principal groups in separate tables; this always lands in the object table.

func (*Client) CreatePassword

func (c *Client) CreatePassword(ctx context.Context, entityID, password string) error

func (*Client) CreatePermissionBlock

func (c *Client) CreatePermissionBlock(ctx context.Context, block CreatePermissionBlock) (PermissionBlock, error)

func (*Client) CreatePrincipalGroup added in v1.0.0

func (c *Client) CreatePrincipalGroup(ctx context.Context, group Group) (Group, error)

CreatePrincipalGroup creates a group of users, for use as a policy subject. Atom keeps object groups and principal groups in separate tables; this always lands in the principal table.

func (*Client) CreateResource

func (c *Client) CreateResource(ctx context.Context, resource Resource) (Resource, error)

func (*Client) CreateSharedKey

func (c *Client) CreateSharedKey(ctx context.Context, entityID, key, description string) (SharedKeyResponse, error)

func (*Client) CreateTenant

func (c *Client) CreateTenant(ctx context.Context, tenant Tenant) (Tenant, error)

func (*Client) CreateUnscopedAccessToken

func (c *Client) CreateUnscopedAccessToken(ctx context.Context, entityID, name, description string) (AccessTokenResponse, error)

func (*Client) DeleteDirectPolicy

func (c *Client) DeleteDirectPolicy(ctx context.Context, id string) error

func (*Client) DeleteEntity

func (c *Client) DeleteEntity(ctx context.Context, id string) error

func (*Client) DeleteGroup

func (c *Client) DeleteGroup(ctx context.Context, id string) error

func (*Client) DeleteResource

func (c *Client) DeleteResource(ctx context.Context, id string) error

func (*Client) DeleteTenant

func (c *Client) DeleteTenant(ctx context.Context, id string) error

func (*Client) DeviceGateways added in v1.0.0

func (c *Client) DeviceGateways(ctx context.Context, deviceID string) ([]string, error)

DeviceGateways returns the gateway IDs a device declares, dropping any that point at a since-deleted gateway (nothing else sweeps those).

func (*Client) EntityDeviceInfo added in v1.0.0

func (c *Client) EntityDeviceInfo(ctx context.Context, ids []string) (map[string]DeviceInfo, map[string]string, error)

EntityDeviceInfo resolves entity UUIDs to the external identifier and gateway flag they were registered with, in the same round trip. Only entities that exist and are readable appear in the resolved map, so it is a subset of ids and callers must treat a missing key as "could not resolve" rather than "confirmed not a gateway with no external id" — the zero value of DeviceInfo would otherwise read as both.

The second return value distinguishes why an id is missing from the first, the same way batchEntities does for entitiesExist: an id with an entry here failed to read (a permission failure, a transient Atom error), while an id absent from both simply does not exist. Callers that only need the safe, tolerant behaviour (folding either case into "no info") can ignore it; readAuthorizer's resolveDeviceInfo uses it to avoid caching a transient failure as though it were a confirmed, stable fact (Q4).

This is the UUID → attributes direction readAuthorizer needs both halves of: the external id to translate a policy grant into the identity message rows carry (MG-05, MG-06), and the gateway flag to decide whether a row naming this id as publisher can be trusted on that basis alone or must additionally be confirmed by device_id (R1: a device's own bn can populate device_id on its self-published rows, which must not be mistaken for a gateway relaying on someone else's behalf).

func (*Client) EntityExternalIDs added in v1.0.0

func (c *Client) EntityExternalIDs(ctx context.Context, ids []string) (map[string]string, error)

EntityExternalIDs resolves entity UUIDs to the external identifiers they were registered with (ATOM-06). Only entities that exist and carry an external id appear in the result, so the map is a subset of ids and callers must treat a missing key as "no external identity" rather than an error.

This is the UUID → external_id direction that authorization needs: policy lookups deal in Atom UUIDs, while message rows carry the device's serial verbatim (MG-05, MG-06). Without the translation a filter built from policy results matches no rows at all, which reads as a permissions bug rather than a missing conversion.

func (*Client) EntityGroups added in v1.0.0

func (c *Client) EntityGroups(ctx context.Context, entityID string) ([]string, error)

EntityGroups returns every object group the entity belongs to, via entity(id: ...) { objectGroupIds } — there is no top-level "entityGroups" query.

func (*Client) GatewayDevices added in v1.0.0

func (c *Client) GatewayDevices(ctx context.Context, gatewayID string, q Query) (EntityList, error)

GatewayDevices lists devices whose gateways attribute contains gatewayID. Any AttributesContains already set on q is preserved alongside it.

func (*Client) GetDeviceType added in v1.0.0

func (c *Client) GetDeviceType(ctx context.Context, id string) (DeviceType, error)

func (*Client) GetEntity

func (c *Client) GetEntity(ctx context.Context, id string) (Entity, error)

func (*Client) GetGroup

func (c *Client) GetGroup(ctx context.Context, id string) (Group, error)

func (*Client) GetResource

func (c *Client) GetResource(ctx context.Context, id string) (Resource, error)

func (*Client) GetTenant

func (c *Client) GetTenant(ctx context.Context, id string) (Tenant, error)

func (*Client) GroupMembers added in v1.0.0

func (c *Client) GroupMembers(ctx context.Context, groupID string) ([]Entity, error)

GroupMembers lists an object group's entities via entities(parentGroupId: ...) — despite the name, that filters by many-to-many object-group membership, not a single parent. There is no dedicated "groupMembers" query.

func (*Client) Introspect

func (c *Client) Introspect(ctx context.Context, token string) (IntrospectionResponse, error)

func (*Client) ListActionAssignmentRules

func (c *Client) ListActionAssignmentRules(ctx context.Context, spec ActionAssignmentRuleSpec) (ActionAssignmentRuleList, error)

func (*Client) ListCapabilities

func (c *Client) ListCapabilities(ctx context.Context) (CapabilityList, error)

func (*Client) ListCredentials

func (c *Client) ListCredentials(ctx context.Context, entityID string) (CredentialList, error)

func (*Client) ListDeviceTypeVersions added in v1.0.0

func (c *Client) ListDeviceTypeVersions(ctx context.Context, deviceTypeID string) ([]DeviceTypeVersion, error)

ListDeviceTypeVersions returns every version of a device type, oldest first, each with its stored capability document parsed back out.

func (*Client) ListDeviceTypes added in v1.0.0

func (c *Client) ListDeviceTypes(ctx context.Context, q DeviceTypeQuery) (DeviceTypeList, error)

ListDeviceTypes lists a tenant's device types. Global (tenant-less) types are not returned: Atom's filter is "this tenant" or "no filter at all", and no filter at all crosses tenants.

func (*Client) ListDirectPolicies

func (c *Client) ListDirectPolicies(ctx context.Context, q DirectPolicyQuery) (DirectPolicyList, error)

func (*Client) ListEntities

func (c *Client) ListEntities(ctx context.Context, q Query) (EntityList, error)

func (*Client) ListGroups

func (c *Client) ListGroups(ctx context.Context, q Query) (GroupList, error)

func (*Client) ListResources

func (c *Client) ListResources(ctx context.Context, q Query) (ResourceList, error)

func (*Client) ListTenants

func (c *Client) ListTenants(ctx context.Context, q Query) (TenantList, error)

func (*Client) LoginCredential

func (c *Client) LoginCredential(ctx context.Context, identifier, secret, kind string) (LoginResponse, error)

func (*Client) LoginPassword

func (c *Client) LoginPassword(ctx context.Context, identifier, secret string) (LoginResponse, error)

func (*Client) LoginSharedKey

func (c *Client) LoginSharedKey(ctx context.Context, identifier, secret string) (LoginResponse, error)

func (*Client) MarkGateways added in v1.0.0

func (c *Client) MarkGateways(ctx context.Context, gatewayIDs []string) error

MarkGateways sets attributes.is_gateway on every id in gatewayIDs that does not already have it. is_gateway is otherwise entirely manual — operators set it themselves when creating or updating a device (see cli/devices.go) — so without this, the two facts that identify a gateway can drift apart: a device can be named in another device's gateways list, making it a real relay, while its own is_gateway attribute stays unset because whoever created it never flagged it. readAuthorizer's R1 scope leg trusts a held device's publisher identity unconditionally whenever is_gateway is false, so that drift reopened finding 02's cross-tenant leak for any gateway an operator forgot to flag (Q1).

This only ever sets the flag, never clears it: always setting rather than reconciling both directions means a device dropped from every gateways list simply keeps a flag it no longer strictly needs — the safe direction to be wrong in, unlike the leak this closes.

Exported (P3) because SetDeviceGateways is not the only place in this repo that can establish the relationship: the CLI's device create/update commands unmarshal arbitrary Entity JSON and write it straight through, bypassing SetDeviceGateways entirely, and need to call this themselves for whatever GatewaysDeclared finds in what they just wrote.

Reads gatewayIDs in one batched round trip via batchEntities rather than one GetEntity per id (P1) — the exact per-id shape round 2's finding 15 retired from DeviceGateways below, reappearing here. An id neither resolved nor readable is treated the same as liveGateways treats it elsewhere: silently skipped if it simply does not (yet, or any longer) exist, but a hard error if it merely could not be read this time — "could not tell" must not be read as "nothing to mark", the same distinction entitiesExist exists to preserve.

func (*Client) ObjectGroups added in v1.0.0

func (c *Client) ObjectGroups(ctx context.Context, q Query) (GroupList, error)

ObjectGroups lists groups in the object namespace (devices/channels), as opposed to principal groups (users).

func (*Client) PrincipalGroups added in v1.0.0

func (c *Client) PrincipalGroups(ctx context.Context, q Query) (GroupList, error)

PrincipalGroups lists groups in the principal namespace (users), as opposed to object groups (devices/channels).

func (*Client) RemoveGroupMember added in v1.0.0

func (c *Client) RemoveGroupMember(ctx context.Context, groupID, entityID string) error

RemoveGroupMember removes membership in this object group only — the entity may still be reachable through other groups. Like AddGroupMember, it sends the object-group mutation despite matching a principal-group name.

func (*Client) RemoveGroupParent added in v1.0.0

func (c *Client) RemoveGroupParent(ctx context.Context, id string) error

RemoveGroupParent detaches a group from its parent, making it top-level.

func (*Client) RemoveObjectGroupParent added in v1.0.0

func (c *Client) RemoveObjectGroupParent(ctx context.Context, id string) error

RemoveObjectGroupParent detaches an object group from its parent.

func (*Client) RevealSharedKey

func (c *Client) RevealSharedKey(ctx context.Context, entityID, credentialID string) (SharedKeyResponse, error)

func (*Client) RevokeCredential

func (c *Client) RevokeCredential(ctx context.Context, entityID, credentialID string) error

func (*Client) SeedDefaultDeviceTypes added in v1.0.0

func (c *Client) SeedDefaultDeviceTypes(ctx context.Context, tenantID string) error

SeedDefaultDeviceTypes creates the default device type catalogue for a tenant.

func (*Client) SetDeviceGateways added in v1.0.0

func (c *Client) SetDeviceGateways(ctx context.Context, deviceID string, gatewayIDs, expectedCurrent []string) error

SetDeviceGateways replaces a device's entire gateway list, preserving its other attributes. expectedCurrent must match what DeviceGateways would return right now (order independent) or it fails with ErrGatewaysConflict — Atom has no compare-and-swap, so this only narrows the race window, not closes it.

The comparison is against the same live, since-deleted-gateways-dropped view DeviceGateways returns, not the raw stored attribute: every caller reads expectedCurrent from DeviceGateways, so once a declared gateway is deleted the raw attribute and that live view disagree permanently, and comparing against the raw one would fail this call forever with no way to retry into success.

func (*Client) SetGroupParent added in v1.0.0

func (c *Client) SetGroupParent(ctx context.Context, id, parentID string) (Group, error)

SetGroupParent nests a group under a parent, replacing any existing parent.

func (*Client) SetObjectGroupParent added in v1.0.0

func (c *Client) SetObjectGroupParent(ctx context.Context, id, parentID string) (Group, error)

SetObjectGroupParent nests an object group under an object-group parent.

func (*Client) UpdateDeviceType added in v1.0.0

func (c *Client) UpdateDeviceType(ctx context.Context, id string, deviceType DeviceType) (DeviceType, error)

UpdateDeviceType changes a device type's name, description or status. Key, tenant and kind are fixed at creation.

Setting Status to anything but active is how a whole device type is retired: Atom refuses new bindings to a non-active type and leaves bound devices alone.

func (*Client) UpdateEntity

func (c *Client) UpdateEntity(ctx context.Context, id string, entity Entity) (Entity, error)

UpdateEntity replaces entity's whole attributes column with entity as given -- see CreateEntity's doc comment: the same MarkGateways responsibility falls on any caller whose entity.Attributes declares AttributeGateways.

func (*Client) UpdateGroup

func (c *Client) UpdateGroup(ctx context.Context, id string, group Group) (Group, error)

func (*Client) UpdateResource

func (c *Client) UpdateResource(ctx context.Context, id string, resource Resource) (Resource, error)

func (*Client) UpdateTenant

func (c *Client) UpdateTenant(ctx context.Context, id string, tenant Tenant) (Tenant, error)

func (*Client) UpsertEntity

func (c *Client) UpsertEntity(ctx context.Context, entity Entity) error

func (*Client) UpsertResource

func (c *Client) UpsertResource(ctx context.Context, resource Resource) error

func (*Client) UpsertTenant

func (c *Client) UpsertTenant(ctx context.Context, tenant Tenant) error

type Command added in v1.0.0

type Command struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`

	// Params maps parameter name to a ValueType* constant.
	Params map[string]string `json:"params,omitempty"`
}

Command is one instruction a device of a given type accepts. Declaring a command does not route it: dispatch is unspecified and out of scope.

type Config

type Config struct {
	URL           string
	JWKSURL       string
	JWTIssuer     string
	JWTAudience   string
	Token         string
	AdminUsername string
	AdminSecret   string
	Timeout       time.Duration
	UserAgent     string
}

Config controls Magistrala's optional Atom integration.

func LoadConfig

func LoadConfig() Config

LoadConfig reads Atom integration settings from environment variables.

type CreateDirectPolicy

type CreateDirectPolicy struct {
	TenantID          string `json:"tenant_id,omitempty"`
	SubjectKind       string `json:"subject_kind"`
	SubjectID         string `json:"subject_id"`
	PermissionBlockID string `json:"permission_block_id"`
}

type CreatePermissionBlock

type CreatePermissionBlock struct {
	TenantID   string         `json:"tenant_id,omitempty"`
	ScopeMode  string         `json:"scope_mode"`
	ObjectKind string         `json:"object_kind,omitempty"`
	ObjectType string         `json:"object_type,omitempty"`
	ObjectID   string         `json:"object_id,omitempty"`
	GroupID    string         `json:"group_id,omitempty"`
	Effect     string         `json:"effect,omitempty"`
	Conditions map[string]any `json:"conditions,omitempty"`
	ActionIDs  []string       `json:"action_ids"`
}

type Credential

type Credential struct {
	ID         string     `json:"id"`
	EntityID   string     `json:"entity_id,omitempty"`
	Kind       string     `json:"kind"`
	Identifier string     `json:"identifier,omitempty"`
	Status     string     `json:"status"`
	ExpiresAt  *time.Time `json:"expires_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at,omitempty"`
}

type CredentialList

type CredentialList struct {
	Items []Credential `json:"items"`
	Total uint64       `json:"total"`
}

type DeviceInfo added in v1.0.0

type DeviceInfo struct {
	// ExternalID is the identifier the entity was registered under (ATOM-06),
	// or "" if it has none.
	ExternalID string
	// IsGateway reports attributes.is_gateway (spec §8 A12) — a gateway is
	// not a separate entity kind, just a device with this attribute set.
	IsGateway bool
}

DeviceInfo is what EntityDeviceInfo resolves an id to.

type DeviceType added in v1.0.0

type DeviceType struct {
	ID       string `json:"id,omitempty"`
	TenantID string `json:"tenant_id,omitempty"`

	// Key identifies the type within a tenant and cannot be changed after
	// creation.
	Key         string    `json:"key"`
	Name        string    `json:"name"`
	Description string    `json:"description,omitempty"`
	Status      string    `json:"status,omitempty"`
	CreatedAt   time.Time `json:"created_at,omitempty"`
	UpdatedAt   time.Time `json:"updated_at,omitempty"`
}

DeviceType is a named, versioned declaration of what a class of device measures and accepts. Devices bind to a specific version of one.

It maps to an Atom Profile with object_kind "entity" and kind "device". The Atom-side name is never exposed here: "profile" collides with Bootstrap's unrelated Profile, so this concept is Device Type on every Magistrala surface.

type DeviceTypeList added in v1.0.0

type DeviceTypeList struct {
	Items []DeviceType `json:"items"`
	Total uint64       `json:"total"`
}

type DeviceTypeQuery added in v1.0.0

type DeviceTypeQuery struct {
	TenantID string
	Status   string
	Limit    uint64
	Offset   uint64
}

DeviceTypeQuery filters a device type listing. TenantID is required: see ListDeviceTypes for why global types are not exposed.

type DeviceTypeVersion added in v1.0.0

type DeviceTypeVersion struct {
	ID           string `json:"id,omitempty"`
	DeviceTypeID string `json:"device_type_id,omitempty"`
	Version      int    `json:"version"`
	Status       string `json:"status,omitempty"`

	// Capabilities is the declaration, filled in on read.
	Capabilities CapabilityDocument `json:"capabilities"`

	// JSONSchema and UISchema are what Atom stores and validates against.
	// They are exposed for advanced use; Capabilities is the documented path.
	JSONSchema map[string]any `json:"json_schema,omitempty"`
	UISchema   map[string]any `json:"ui_schema,omitempty"`

	CreatedAt time.Time `json:"created_at,omitempty"`
}

DeviceTypeVersion is one immutable revision of a device type's capability document. Devices stay bound to the version they were created against, so a new version never invalidates deployed devices.

type DirectPolicy

type DirectPolicy struct {
	ID                string          `json:"id"`
	TenantID          string          `json:"tenant_id,omitempty"`
	SubjectKind       string          `json:"subject_kind"`
	SubjectID         string          `json:"subject_id"`
	PermissionBlockID string          `json:"permission_block_id"`
	PermissionBlock   PermissionBlock `json:"permission_block,omitempty"`
	CreatedAt         time.Time       `json:"created_at,omitempty"`
}

type DirectPolicyList

type DirectPolicyList struct {
	Items []DirectPolicy `json:"items"`
	Total uint64         `json:"total"`
}

type DirectPolicyQuery

type DirectPolicyQuery struct {
	TenantID    string
	SubjectKind string
	SubjectID   string
	Limit       uint64
	Offset      uint64
}

type Entity

type Entity struct {
	ID   string `json:"id,omitempty"`
	Kind string `json:"kind"`
	Name string `json:"name"`
	// ExternalID is the identifier assigned outside Atom — a device's serial,
	// MAC or SKU (ATOM-06). Opaque and unvalidated, case-sensitive, trimmed,
	// unique per tenant among live entities, and returned byte-identical to
	// what was stored.
	ExternalID string `json:"external_id,omitempty"`
	TenantID   string `json:"tenant_id,omitempty"`

	// DeviceTypeID and DeviceTypeVersionID bind the entity to a device type.
	// With a device type bound, Atom validates Attributes against that
	// version's schema on every write. Leaving DeviceTypeVersionID empty binds
	// to the type's highest-numbered active version.
	//
	// Both are write-once-per-call in the sense that Atom coalesces them: an
	// update that leaves them empty keeps the existing binding rather than
	// clearing it. There is no way to unbind a device through Atom's API.
	DeviceTypeID        string `json:"device_type_id,omitempty"`
	DeviceTypeVersionID string `json:"device_type_version_id,omitempty"`

	Status     string     `json:"status,omitempty"`
	Attributes Attributes `json:"attributes,omitempty"`
	CreatedAt  time.Time  `json:"created_at,omitempty"`
	UpdatedAt  time.Time  `json:"updated_at,omitempty"`
}

func EntityFromFields

func EntityFromFields(f ObjectFields) Entity

Object-group membership is many-to-many; Atom rejects parent_group_id as an entity attribute, so it isn't set here — use AddGroupMember instead.

type EntityList

type EntityList struct {
	Items []Entity `json:"items"`
	Total uint64   `json:"total"`
}

type Error

type Error struct {
	StatusCode int
	Message    string
}

func (Error) Error

func (e Error) Error() string

type Group

type Group struct {
	ID          string     `json:"id,omitempty"`
	Name        string     `json:"name"`
	TenantID    string     `json:"tenant_id,omitempty"`
	Description string     `json:"description,omitempty"`
	ParentID    string     `json:"parent_id,omitempty"`
	Status      string     `json:"status,omitempty"`
	Attributes  Attributes `json:"attributes,omitempty"`
	CreatedAt   time.Time  `json:"created_at,omitempty"`
	UpdatedAt   time.Time  `json:"updated_at,omitempty"`
}

func GroupFromFields

func GroupFromFields(f ObjectFields) Group

type GroupGrant added in v1.0.0

type GroupGrant struct {
	TenantID           string
	GroupID            string
	SubjectKind        string
	SubjectID          string
	ObjectKind         string
	ObjectType         string
	Actions            []string
	IncludeDescendants bool
}

GroupGrant grants a subject one or more actions over a group's members: its direct members, or the members of its descendant groups if IncludeDescendants is set. A grant maps to exactly one PermissionBlock and one DirectPolicy, so it costs the same to write whether the group has one member or thousands, and it automatically covers members added later.

type GroupList

type GroupList struct {
	Items []Group `json:"items"`
	Total uint64  `json:"total"`
}

type IntrospectionResponse

type IntrospectionResponse struct {
	Active    bool   `json:"active"`
	EntityID  string `json:"entity_id"`
	TenantID  string `json:"tenant_id,omitempty"`
	SessionID string `json:"session_id,omitempty"`
}

type LoginRequest

type LoginRequest struct {
	Identifier string `json:"identifier"`
	Secret     string `json:"secret"`
	Kind       string `json:"kind,omitempty"`
}

type LoginResponse

type LoginResponse struct {
	Token     string    `json:"token"`
	EntityID  string    `json:"entity_id"`
	SessionID string    `json:"session_id"`
	ExpiresAt time.Time `json:"expires_at"`
}

type Measurement added in v1.0.0

type Measurement struct {
	Name string `json:"name"`
	Unit string `json:"unit,omitempty"`

	// Access is "r" (default) or "rw". Advisory only: it renders to JSON
	// Schema's readOnly annotation, which Atom's validator records but does
	// not enforce.
	Access string `json:"access,omitempty"`

	// Type is a ValueType* constant, defaulting to ValueTypeNumber.
	Type     string   `json:"type,omitempty"`
	Required bool     `json:"required,omitempty"`
	Min      *float64 `json:"min,omitempty"`
	Max      *float64 `json:"max,omitempty"`
	Enum     []string `json:"enum,omitempty"`
}

Measurement is one value a device of a given type reports.

Rendered into the device type's JSON Schema, a measurement constrains the device's *attributes*, not its telemetry — see BuildCapabilitySchema.

type ObjectFields

type ObjectFields struct {
	ID          string
	Kind        string
	Name        string
	TenantID    string
	OwnerID     string
	Status      string
	Route       string
	ParentID    string
	Tags        []string
	Metadata    map[string]any
	Private     map[string]any
	CreatedBy   string
	UpdatedBy   string
	CreatedAt   time.Time
	UpdatedAt   time.Time
	Description string
}

type PermissionBlock

type PermissionBlock struct {
	ID         string         `json:"id"`
	TenantID   string         `json:"tenant_id,omitempty"`
	ScopeMode  string         `json:"scope_mode"`
	ObjectKind string         `json:"object_kind,omitempty"`
	ObjectType string         `json:"object_type,omitempty"`
	ObjectID   string         `json:"object_id,omitempty"`
	GroupID    string         `json:"group_id,omitempty"`
	Effect     string         `json:"effect"`
	Conditions map[string]any `json:"conditions,omitempty"`
	Actions    []Capability   `json:"actions,omitempty"`
}

type PolicyEvaluator

type PolicyEvaluator struct {
	// contains filtered or unexported fields
}

func NewPolicyEvaluator

func NewPolicyEvaluator(client Authorizer) PolicyEvaluator

func (PolicyEvaluator) CheckPolicy

func (pe PolicyEvaluator) CheckPolicy(ctx context.Context, pr policies.Policy) error

type PolicyService

type PolicyService struct {
	// contains filtered or unexported fields
}

func NewPolicyService

func NewPolicyService(client policyClient) PolicyService

func (PolicyService) AddPolicies

func (ps PolicyService) AddPolicies(ctx context.Context, prs []policies.Policy) error

func (PolicyService) AddPolicy

func (ps PolicyService) AddPolicy(ctx context.Context, pr policies.Policy) error

func (PolicyService) CountObjects

func (ps PolicyService) CountObjects(ctx context.Context, pr policies.Policy) (uint64, error)

func (PolicyService) CountSubjects

func (ps PolicyService) CountSubjects(context.Context, policies.Policy) (uint64, error)

func (PolicyService) DeletePolicies

func (ps PolicyService) DeletePolicies(ctx context.Context, prs []policies.Policy) error

func (PolicyService) DeletePolicyFilter

func (ps PolicyService) DeletePolicyFilter(ctx context.Context, pr policies.Policy) error

func (PolicyService) GrantGroupAccess added in v1.0.0

func (ps PolicyService) GrantGroupAccess(ctx context.Context, grant GroupGrant) error

GrantGroupAccess grants a subject the given actions over a group's members: direct members, or members of descendant groups if grant.IncludeDescendants is set. It writes exactly one permission block and one direct policy, regardless of how many members the group has now or gains later — membership changes (see the group membership client) grant or withdraw access on their own, with no further policy writes.

func (PolicyService) ListAllObjects

func (ps PolicyService) ListAllObjects(ctx context.Context, pr policies.Policy) (policies.PolicyPage, error)

func (PolicyService) ListAllSubjects

func (PolicyService) ListGroupGrants added in v1.0.0

func (ps PolicyService) ListGroupGrants(ctx context.Context, groupID string) ([]GroupGrant, error)

ListGroupGrants returns every group-scoped grant (direct or descendant) recorded against groupID, across all tenants and subjects.

func (PolicyService) ListObjects

func (ps PolicyService) ListObjects(ctx context.Context, pr policies.Policy, _ string, limit uint64) (policies.PolicyPage, error)

func (PolicyService) ListPermissions

func (PolicyService) ListSubjects

func (PolicyService) RevokeGroupAccess added in v1.0.0

func (ps PolicyService) RevokeGroupAccess(ctx context.Context, grant GroupGrant) error

RevokeGroupAccess removes the permission block and direct policy that GrantGroupAccess created for this group. Group membership is many-to-many: this withdraws access granted through this specific group only. A member that also belongs to a different group with its own grant remains reachable through that grant afterwards — this method does not walk a member's other group memberships looking for other access to strip.

type Projector

type Projector interface {
	UpsertTenant(ctx context.Context, tenant Tenant) error
	UpsertEntity(ctx context.Context, entity Entity) error
	UpsertResource(ctx context.Context, resource Resource) error
	DeleteTenant(ctx context.Context, id string) error
	DeleteEntity(ctx context.Context, id string) error
	DeleteGroup(ctx context.Context, id string) error
	DeleteResource(ctx context.Context, id string) error
}

type Query

type Query struct {
	IDs      []string
	Q        string
	Kind     string
	TenantID string
	Name     string
	Route    string
	ParentID string
	Status   string
	Limit    uint64
	Offset   uint64

	// DeviceTypeID restricts an entity listing to devices bound to one device
	// type, across all of its versions.
	DeviceTypeID string

	// AttributesContains filters by JSONB containment — an entity must contain
	// every key/value given here (array values match by containment, not equality).
	AttributesContains map[string]any
}

type Resource

type Resource struct {
	ID         string     `json:"id,omitempty"`
	Kind       string     `json:"kind"`
	Name       string     `json:"name"`
	TenantID   string     `json:"tenant_id,omitempty"`
	OwnerID    string     `json:"owner_id,omitempty"`
	Attributes Attributes `json:"attributes,omitempty"`
	CreatedAt  time.Time  `json:"created_at,omitempty"`
	UpdatedAt  time.Time  `json:"updated_at,omitempty"`
}

func ResourceFromFields

func ResourceFromFields(f ObjectFields) Resource

Object-group membership is many-to-many; Atom rejects parent_group_id as a resource attribute, so it isn't set here — use AddGroupMember instead.

type ResourceList

type ResourceList struct {
	Items []Resource `json:"items"`
	Total uint64     `json:"total"`
}

type SchemaValidationError added in v1.0.0

type SchemaValidationError struct {
	Violations []SchemaViolation
	// contains filtered or unexported fields
}

SchemaValidationError is a device type schema rejection, translated from the validator output Atom returns verbatim. Untranslated, an operator creating a device sees a bare validator string with no indication of which attribute was wrong.

It wraps the underlying atom Error, so IsConflict, IsNotFound and errors.As on Error keep working.

func AsSchemaValidationError added in v1.0.0

func AsSchemaValidationError(err error) (*SchemaValidationError, bool)

AsSchemaValidationError reports whether err is a device type schema rejection, and if so returns it.

func (*SchemaValidationError) Error added in v1.0.0

func (e *SchemaValidationError) Error() string

func (*SchemaValidationError) Fields added in v1.0.0

func (e *SchemaValidationError) Fields() []string

Fields lists the attributes the violations name, in order and without duplicates.

func (*SchemaValidationError) Unwrap added in v1.0.0

func (e *SchemaValidationError) Unwrap() error

type SchemaViolation added in v1.0.0

type SchemaViolation struct {
	Field      string `json:"field,omitempty"`
	Constraint string `json:"constraint,omitempty"`
	Expected   string `json:"expected,omitempty"`
	Message    string `json:"message"`
}

SchemaViolation is one field-level reason a device's attributes were rejected.

Field is best effort. The validator names the property directly for missing and unexpected properties, but for every other failure it reports only the offending *value* — so the field is recovered by matching that value against the attributes that were submitted, and is left empty when that is ambiguous.

func (SchemaViolation) String added in v1.0.0

func (v SchemaViolation) String() string

type SharedKeyResponse

type SharedKeyResponse struct {
	CredentialID string     `json:"credentialId"`
	Key          string     `json:"key"`
	ExpiresAt    *time.Time `json:"expiresAt,omitempty"`
}

type Tenant

type Tenant struct {
	ID         string     `json:"id,omitempty"`
	Name       string     `json:"name"`
	Route      string     `json:"route,omitempty"`
	Tags       []string   `json:"tags,omitempty"`
	Status     string     `json:"status,omitempty"`
	Attributes Attributes `json:"attributes,omitempty"`
	CreatedBy  string     `json:"created_by,omitempty"`
	UpdatedBy  string     `json:"updated_by,omitempty"`
	CreatedAt  time.Time  `json:"created_at,omitempty"`
	UpdatedAt  time.Time  `json:"updated_at,omitempty"`
}

func TenantFromFields

func TenantFromFields(f ObjectFields) Tenant

type TenantList

type TenantList struct {
	Items []Tenant `json:"items"`
	Total uint64   `json:"total"`
}

type TokenClaims

type TokenClaims struct {
	SubjectID string `json:"sub"`
	SessionID string `json:"sid,omitempty"`
	TenantID  string `json:"tid,omitempty"`
	ExpiresAt int64  `json:"exp,omitempty"`
	IssuedAt  int64  `json:"iat,omitempty"`
}

type TokenVerifier

type TokenVerifier struct {
	// contains filtered or unexported fields
}

func NewTokenVerifier

func NewTokenVerifier(cfg Config) *TokenVerifier

func (*TokenVerifier) VerifyTokenClaims

func (v *TokenVerifier) VerifyTokenClaims(ctx context.Context, token string) (TokenClaims, error)

Directories

Path Synopsis
Package events consumes the workspace events Atom publishes from its transactional outbox (absmach/atom migrations/004_event_outbox.sql, src/events/publisher.rs) and turns them into cache invalidation, nothing else (MG-14).
Package events consumes the workspace events Atom publishes from its transactional outbox (absmach/atom migrations/004_event_outbox.sql, src/events/publisher.rs) and turns them into cache invalidation, nothing else (MG-14).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL