Proposal: Collection Control
Status
Proposed
Overview
Collection Control is the API surface for managing data collection at three levels: client, device, and user. It introduces a three-tier precedence model (Client > Device > User), a collection_mode on clients, collection_state + log_shipping_enabled on devices, and a status on users. All control changes are recorded in a unified audit log.
This proposal covers:
- A new Collection Control domain with an audit log endpoint
- A new
PATCH /clients/{client_id}endpoint for changing a client's collection mode - New
collection_modefield on theClientDTO - New
statusfield onUserRef(and by extensionUserandDeviceUserRef) - New
statusfilter and sort options onGET /users - Updated
PATCH /users/{user_id}accepting astatusfield with transition rules - New bulk user control endpoints: archive, activate, delete
- Updated
UserSummarywithtotal,archived_users, anddeleted_userscounts - New
collection_state,log_shipping_enabled, andeffective_modefields on theDeviceDTO - New
collection_stateandeffective_modefilter and sort options onGET /devices - New
PATCH /devices/{device_id}endpoint for changing device-level controls - New bulk device control endpoints: enable, disable, delete
Embedding Decisions
| Entity | Field | Embedded on Ref? | Reason |
|---|---|---|---|
User status | UserRef | Yes | Bounded (3 values), always needed for display (archived badge), stored directly on users table — no join required |
Device collection_state | DeviceRef | No | Operational control metadata, not identity/reference data |
Device log_shipping_enabled | DeviceRef | No | Same as above |
Device effective_mode | DeviceRef | No | Requires JOIN to clients table — violates DeviceRef's "no joins" rule |
effective_mode computation | Device | Yes (computed) | Client > Device precedence; requires JOIN on full Device queries only |
DTOs
Client (updated)
Added collection_mode field.
| Field | Type | Nullable | Description |
|---|---|---|---|
collection_mode | string | No | Agent data collection mode: discovery, saas_usage, disabled, deleted. Default: saas_usage. |
DB source: clients.collection_mode
UserRef (updated)
Added status field.
| Field | Type | Nullable | Description |
|---|---|---|---|
status | string | No | User collection status: active, archived, deleted. Default: active. |
DB source: users.status
UserSummary (updated)
Expanded with status-aware counts.
| Field | Type | Description |
|---|---|---|
total | number | Total user count across all statuses |
active_users | number | Users with status = 'active' whose last activity is within the stale threshold |
stale_users | number | Users with status = 'active' whose last activity is older than the threshold, or who have never reported activity |
stale_threshold | number | The threshold in seconds used for the calculation |
archived_users | number | Users with status = 'archived' |
deleted_users | number | Users with status = 'deleted' |
BulkUserControlResult (new)
Response shape for all bulk user control endpoints.
| Field | Type | Description |
|---|---|---|
succeeded | string (uuid)[] | User IDs successfully updated |
failed | object[] | User IDs that failed with error details |
failed[].id | string (uuid) | The user ID that failed |
failed[].error | string | Reason for the failure |
Device (updated)
Added three collection control fields.
| Field | Type | Nullable | Description |
|---|---|---|---|
collection_state | string | No | Device-level collection state: enabled, disabled, deleted. Default: enabled. |
log_shipping_enabled | boolean | No | Whether full log shipping is enabled for this device. Default: false. |
effective_mode | string | No | Computed effective collection mode after applying Client > Device precedence: discovery, saas_usage, disabled, deleted. |
DB source: devices.collection_state, devices.log_shipping_enabled, computed from clients.collection_mode + devices.collection_state
Effective Mode Computation
Client collection_mode | Device collection_state | effective_mode |
|---|---|---|
disabled or deleted | (any) | client mode (overrides device) |
discovery or saas_usage | disabled or deleted | device state |
discovery or saas_usage | enabled | client mode |
BulkDeviceControlResult (new)
Response shape for all bulk device control endpoints.
| Field | Type | Description |
|---|---|---|
succeeded | string (uuid)[] | Device IDs successfully updated |
failed | object[] | Device IDs that failed with error details |
failed[].id | string (uuid) | The device ID that failed |
failed[].error | string | Reason for the failure |
CollectionControlAuditEntry (new)
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string (uuid) | No | Unique audit entry ID |
entity_type | string | No | Type of entity affected: client, device, user |
entity_id | string (uuid) | No | ID of the affected entity |
actor_id | string (uuid) | No | ID of the user who made the change |
action | string | No | Type of change (e.g. collection_mode_changed, collection_state_changed, status_changed) |
previous_value | object | Yes | Previous state as a JSON object |
new_value | object | Yes | New state as a JSON object |
created_at | string (ISO 8601) | No | When the change was made |
Endpoints
PATCH /v2/clients/{client_id}
Update client-level settings. Supports changing collection_mode. Returns the updated Client object.
Request body: { collection_mode?: string }
Transition rules: discovery, saas_usage, disabled are freely interchangeable. Transition to deleted is irreversible.
GET /v2/clients/{client_id}/collection-control/audit-log
Returns a paginated audit trail of all collection control changes for a client.
Query params: entity_type, entity_id, sort_order, page_size, cursor
Response: { data: CollectionControlAuditEntry[], total_count, next_cursor }
GET /v2/clients/{client_id}/users (updated)
Added status query parameter (default: active, supports comma-separated) and status as a sort_by option.
PATCH /v2/clients/{client_id}/users/{user_id} (updated)
Added status field to request body with transition rules:
active→archived(reversible)active→deleted(irreversible)archived→active(reversible)archived→deleted(irreversible)deleted→ any (rejected, 400)
POST /v2/clients/{client_id}/users/archive
Bulk archive users. Returns BulkUserControlResult.
POST /v2/clients/{client_id}/users/activate
Bulk reactivate archived users. Returns BulkUserControlResult.
POST /v2/clients/{client_id}/users/delete
Bulk soft-delete users (irreversible). Returns BulkUserControlResult.
GET /v2/clients/{client_id}/devices (updated)
Added collection_state and effective_mode query parameters. Added collection_state and effective_mode as sort_by options.
PATCH /v2/clients/{client_id}/devices/{device_id}
Update a single device's collection controls. Supports collection_state and log_shipping_enabled. deleted is irreversible. Returns the updated Device object.
POST /v2/clients/{client_id}/devices/enable
Bulk enable device collection. Optionally set log_shipping_enabled alongside. Returns BulkDeviceControlResult.
POST /v2/clients/{client_id}/devices/disable
Bulk disable device collection. Optionally set log_shipping_enabled alongside. Returns BulkDeviceControlResult.
POST /v2/clients/{client_id}/devices/delete
Bulk delete devices (irreversible, triggers agent self-uninstall). Returns BulkDeviceControlResult.
Open Questions
None.
Guideline References
- UserRef embedding rule —
statussatisfies all three conditions: bounded, always needed for display, no join required. - DeviceRef embedding rule —
collection_stateandeffective_modeare excluded fromDeviceRefbecause they are operational metadata andeffective_moderequires a JOIN. - Bulk action pattern — partial success model: per-entity atomicity,
succeeded/failedresponse shape, scales to 10,000 entities. - Irreversibility pattern —
deletedtransitions are one-way for both users and devices;collection_mode=deletedon a client is also irreversible.
Checklist
- Create Collection Control Overview domain overview page
- Document Collection Control Audit Log —
GET /v2/clients/\{client_id\}/collection-control/audit-log - Add sidebar entries for new Collection Control pages
- Update Clients Overview with
collection_modefield onClientDTO - Update Get Client with
collection_modefield - Document Update Client —
PATCH /v2/clients/\{client_id\} - Update Clients Overview endpoint table with Update Client
- Update Users Overview with
statusfield onUserRefDTO - Update Users Overview with updated
UserSummaryDTO (total, archived_users, deleted_users) - Define BulkUserControlResult in Users overview
- Update List Users with
statusfilter and sort option —GET /v2/clients/\{client_id\}/users - Update Get User with
statusfield in response - Update User Summary with expanded response shape
- Update Update User with
statusfield and transition rules —PATCH /v2/clients/\{client_id\}/users/\{user_id\} - Document Bulk Archive Users —
POST /v2/clients/\{client_id\}/users/archive - Document Bulk Activate Users —
POST /v2/clients/\{client_id\}/users/activate - Document Bulk Delete Users —
POST /v2/clients/\{client_id\}/users/delete - Update Users Overview endpoint table with bulk user control endpoints
- Update Devices Overview with
collection_state,log_shipping_enabled,effective_modefields onDeviceDTO - Update Devices Overview with Effective Mode Computation section
- Define BulkDeviceControlResult in Devices overview
- Update List Devices with
collection_stateandeffective_modefilter/sort —GET /v2/clients/\{client_id\}/devices - Update Get Device with collection control fields in response
- Document Update Device Controls —
PATCH /v2/clients/\{client_id\}/devices/\{device_id\} - Document Bulk Enable Devices —
POST /v2/clients/\{client_id\}/devices/enable - Document Bulk Disable Devices —
POST /v2/clients/\{client_id\}/devices/disable - Document Bulk Delete Devices —
POST /v2/clients/\{client_id\}/devices/delete - Update Devices Overview endpoint table with bulk device control endpoints