feat(device-core): add control resource ledger
This commit is contained in:
+610
@@ -0,0 +1,610 @@
|
||||
begin;
|
||||
|
||||
create table if not exists device_resource_bindings (
|
||||
id uuid primary key,
|
||||
owner_scope_id uuid not null,
|
||||
project_id uuid not null,
|
||||
binding_key text not null
|
||||
check (binding_key ~ '^[a-z][a-z0-9-]{1,62}$'),
|
||||
display_name text not null
|
||||
check (length(btrim(display_name)) between 1 and 160),
|
||||
source_kind text not null
|
||||
check (source_kind in ('device', 'collection')),
|
||||
device_id uuid references device_instances(id),
|
||||
collection_id uuid references device_collections(id),
|
||||
target_kind text not null
|
||||
check (target_kind ~ '^[a-z][a-z0-9._:-]{1,63}$'),
|
||||
target_ref text not null
|
||||
check (length(btrim(target_ref)) between 3 and 256),
|
||||
capabilities text[] not null default '{}'
|
||||
check (
|
||||
cardinality(capabilities) between 1 and 16
|
||||
and array_position(capabilities, null) is null
|
||||
),
|
||||
lifecycle_state text not null default 'pending_external_approval'
|
||||
check (lifecycle_state in ('pending_external_approval', 'active', 'revoked')),
|
||||
source_approved_by_ref text not null
|
||||
check (length(btrim(source_approved_by_ref)) between 3 and 256),
|
||||
source_approved_at timestamptz not null default now(),
|
||||
external_approval_ref text
|
||||
check (
|
||||
external_approval_ref is null
|
||||
or length(btrim(external_approval_ref)) between 3 and 256
|
||||
),
|
||||
external_approval_digest text
|
||||
check (
|
||||
external_approval_digest is null
|
||||
or external_approval_digest ~ '^sha256:[a-f0-9]{64}$'
|
||||
),
|
||||
external_approved_at timestamptz,
|
||||
revoked_at timestamptz,
|
||||
revoked_by_ref text
|
||||
check (
|
||||
revoked_by_ref is null
|
||||
or length(btrim(revoked_by_ref)) between 3 and 256
|
||||
),
|
||||
revocation_code text
|
||||
check (
|
||||
revocation_code is null
|
||||
or revocation_code ~ '^[a-z][a-z0-9._-]{1,63}$'
|
||||
),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (project_id, binding_key),
|
||||
foreign key (project_id, owner_scope_id)
|
||||
references device_projects(id, owner_scope_id),
|
||||
foreign key (collection_id, project_id)
|
||||
references device_collections(id, project_id),
|
||||
check (
|
||||
(source_kind = 'device' and device_id is not null and collection_id is null)
|
||||
or
|
||||
(source_kind = 'collection' and device_id is null and collection_id is not null)
|
||||
),
|
||||
check (
|
||||
(
|
||||
lifecycle_state = 'pending_external_approval'
|
||||
and external_approval_ref is null
|
||||
and external_approval_digest is null
|
||||
and external_approved_at is null
|
||||
and revoked_at is null
|
||||
and revoked_by_ref is null
|
||||
and revocation_code is null
|
||||
)
|
||||
or
|
||||
(
|
||||
lifecycle_state = 'active'
|
||||
and external_approval_ref is not null
|
||||
and external_approval_digest is not null
|
||||
and external_approved_at is not null
|
||||
and revoked_at is null
|
||||
and revoked_by_ref is null
|
||||
and revocation_code is null
|
||||
)
|
||||
or
|
||||
(
|
||||
lifecycle_state = 'revoked'
|
||||
and revoked_at is not null
|
||||
and revoked_by_ref is not null
|
||||
and revocation_code is not null
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create index if not exists device_resource_bindings_project_state_idx
|
||||
on device_resource_bindings (project_id, lifecycle_state, updated_at desc);
|
||||
|
||||
create index if not exists device_resource_bindings_device_state_idx
|
||||
on device_resource_bindings (device_id, lifecycle_state, updated_at desc)
|
||||
where device_id is not null;
|
||||
|
||||
create or replace function device_assert_binding_source_scope()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if new.source_kind = 'device' and not exists (
|
||||
select 1 from device_instances di
|
||||
where di.id = new.device_id
|
||||
and di.owner_scope_id = new.owner_scope_id
|
||||
and di.project_id = new.project_id
|
||||
) then
|
||||
raise foreign_key_violation using
|
||||
message = 'device_binding_source_scope_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_resource_bindings_source_guard
|
||||
on device_resource_bindings;
|
||||
|
||||
create trigger device_resource_bindings_source_guard
|
||||
before insert or update of owner_scope_id, project_id, source_kind, device_id, collection_id
|
||||
on device_resource_bindings
|
||||
for each row
|
||||
execute function device_assert_binding_source_scope();
|
||||
|
||||
create table if not exists device_configuration_revisions (
|
||||
id uuid primary key,
|
||||
owner_scope_id uuid not null,
|
||||
project_id uuid not null,
|
||||
device_id uuid not null references device_instances(id),
|
||||
revision_number bigint not null check (revision_number > 0),
|
||||
model_profile_ref text not null references device_model_profiles(profile_ref),
|
||||
schema_artifact_ref text not null
|
||||
check (length(btrim(schema_artifact_ref)) between 3 and 256),
|
||||
configuration_digest text not null
|
||||
check (configuration_digest ~ '^sha256:[a-f0-9]{64}$'),
|
||||
configuration jsonb not null
|
||||
check (
|
||||
jsonb_typeof(configuration) = 'object'
|
||||
and octet_length(configuration::text) <= 65536
|
||||
),
|
||||
change_summary text
|
||||
check (change_summary is null or length(change_summary) <= 1000),
|
||||
created_by_ref text not null
|
||||
check (length(btrim(created_by_ref)) between 3 and 256),
|
||||
created_at timestamptz not null default now(),
|
||||
unique (device_id, revision_number),
|
||||
unique (id, device_id, project_id),
|
||||
foreign key (project_id, owner_scope_id)
|
||||
references device_projects(id, owner_scope_id)
|
||||
);
|
||||
|
||||
create index if not exists device_configuration_revisions_project_idx
|
||||
on device_configuration_revisions (project_id, device_id, revision_number desc);
|
||||
|
||||
create or replace function device_assert_configuration_revision_scope()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1
|
||||
from device_instances di
|
||||
join device_model_profiles dmp
|
||||
on dmp.profile_ref = di.model_profile_ref
|
||||
where di.id = new.device_id
|
||||
and di.owner_scope_id = new.owner_scope_id
|
||||
and di.project_id = new.project_id
|
||||
and di.model_profile_ref = new.model_profile_ref
|
||||
and dmp.schema_artifact_ref = new.schema_artifact_ref
|
||||
and dmp.lifecycle_state = 'active'
|
||||
) then
|
||||
raise foreign_key_violation using
|
||||
message = 'device_configuration_revision_scope_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_configuration_revisions_scope_guard
|
||||
on device_configuration_revisions;
|
||||
|
||||
create trigger device_configuration_revisions_scope_guard
|
||||
before insert
|
||||
on device_configuration_revisions
|
||||
for each row
|
||||
execute function device_assert_configuration_revision_scope();
|
||||
|
||||
create table if not exists device_configuration_state (
|
||||
device_id uuid primary key,
|
||||
owner_scope_id uuid not null,
|
||||
project_id uuid not null,
|
||||
desired_revision_id uuid,
|
||||
applied_revision_id uuid,
|
||||
applied_at timestamptz,
|
||||
applied_by_ref text,
|
||||
updated_at timestamptz not null default now(),
|
||||
foreign key (device_id, project_id, owner_scope_id)
|
||||
references device_instances(id, project_id, owner_scope_id),
|
||||
foreign key (desired_revision_id, device_id, project_id)
|
||||
references device_configuration_revisions(id, device_id, project_id),
|
||||
foreign key (applied_revision_id, device_id, project_id)
|
||||
references device_configuration_revisions(id, device_id, project_id),
|
||||
check (desired_revision_id is not null or applied_revision_id is not null),
|
||||
check (
|
||||
(applied_revision_id is null and applied_at is null and applied_by_ref is null)
|
||||
or
|
||||
(applied_revision_id is not null and applied_at is not null and applied_by_ref is not null)
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists device_commands (
|
||||
id uuid primary key,
|
||||
owner_scope_id uuid not null,
|
||||
project_id uuid not null,
|
||||
device_id uuid not null references device_instances(id),
|
||||
command_key text not null
|
||||
check (command_key ~ '^[a-z][a-z0-9-]{1,62}$'),
|
||||
command_catalog_ref text not null
|
||||
check (length(btrim(command_catalog_ref)) between 3 and 256),
|
||||
command_type text not null
|
||||
check (command_type ~ '^[a-z][a-z0-9._:-]{1,63}$'),
|
||||
risk_class text not null
|
||||
check (risk_class in ('low', 'moderate', 'high', 'critical')),
|
||||
parameters_digest text not null
|
||||
check (parameters_digest ~ '^sha256:[a-f0-9]{64}$'),
|
||||
parameters_projection jsonb not null
|
||||
check (
|
||||
jsonb_typeof(parameters_projection) = 'object'
|
||||
and octet_length(parameters_projection::text) <= 16384
|
||||
),
|
||||
lifecycle_state text not null default 'draft'
|
||||
check (lifecycle_state in (
|
||||
'draft',
|
||||
'planned',
|
||||
'awaiting_confirmation',
|
||||
'queued',
|
||||
'dispatched',
|
||||
'acknowledged',
|
||||
'verified',
|
||||
'failed',
|
||||
'expired',
|
||||
'unknown'
|
||||
)),
|
||||
planned_by_ref text not null
|
||||
check (length(btrim(planned_by_ref)) between 3 and 256),
|
||||
planned_at timestamptz not null default now(),
|
||||
expires_at timestamptz not null,
|
||||
confirmed_by_ref text,
|
||||
confirmed_at timestamptz,
|
||||
dispatched_at timestamptz,
|
||||
transport_message_ref text
|
||||
check (
|
||||
transport_message_ref is null
|
||||
or length(btrim(transport_message_ref)) between 3 and 256
|
||||
),
|
||||
acknowledged_at timestamptz,
|
||||
terminal_at timestamptz,
|
||||
terminal_reason_code text
|
||||
check (
|
||||
terminal_reason_code is null
|
||||
or terminal_reason_code ~ '^[a-z][a-z0-9._-]{1,63}$'
|
||||
),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (project_id, command_key),
|
||||
unique (id, device_id, project_id),
|
||||
foreign key (project_id, owner_scope_id)
|
||||
references device_projects(id, owner_scope_id),
|
||||
check (expires_at > planned_at),
|
||||
check (
|
||||
(confirmed_at is null and confirmed_by_ref is null)
|
||||
or
|
||||
(confirmed_at is not null and confirmed_by_ref is not null)
|
||||
),
|
||||
check (
|
||||
(
|
||||
lifecycle_state in ('dispatched', 'acknowledged', 'verified', 'unknown')
|
||||
and dispatched_at is not null
|
||||
and transport_message_ref is not null
|
||||
)
|
||||
or lifecycle_state not in ('dispatched', 'acknowledged', 'verified', 'unknown')
|
||||
),
|
||||
check (
|
||||
(lifecycle_state in ('acknowledged', 'verified') and acknowledged_at is not null)
|
||||
or lifecycle_state not in ('acknowledged', 'verified')
|
||||
),
|
||||
check (
|
||||
(
|
||||
lifecycle_state in ('verified', 'failed', 'expired', 'unknown')
|
||||
and terminal_at is not null
|
||||
and terminal_reason_code is not null
|
||||
)
|
||||
or
|
||||
(
|
||||
lifecycle_state not in ('verified', 'failed', 'expired', 'unknown')
|
||||
and terminal_at is null
|
||||
and terminal_reason_code is null
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create index if not exists device_commands_project_state_idx
|
||||
on device_commands (project_id, lifecycle_state, updated_at desc);
|
||||
|
||||
create index if not exists device_commands_device_state_idx
|
||||
on device_commands (device_id, lifecycle_state, updated_at desc);
|
||||
|
||||
create table if not exists device_command_events (
|
||||
id uuid primary key,
|
||||
command_id uuid not null,
|
||||
device_id uuid not null,
|
||||
project_id uuid not null,
|
||||
sequence_number bigint not null check (sequence_number > 0),
|
||||
from_state text,
|
||||
to_state text not null
|
||||
check (to_state in (
|
||||
'draft',
|
||||
'planned',
|
||||
'awaiting_confirmation',
|
||||
'queued',
|
||||
'dispatched',
|
||||
'acknowledged',
|
||||
'verified',
|
||||
'failed',
|
||||
'expired',
|
||||
'unknown'
|
||||
)),
|
||||
actor_ref text not null
|
||||
check (length(btrim(actor_ref)) between 3 and 256),
|
||||
reason_code text not null
|
||||
check (reason_code ~ '^[a-z][a-z0-9._-]{1,63}$'),
|
||||
evidence_ref text
|
||||
check (
|
||||
evidence_ref is null
|
||||
or length(btrim(evidence_ref)) between 3 and 256
|
||||
),
|
||||
occurred_at timestamptz not null default now(),
|
||||
unique (command_id, sequence_number),
|
||||
foreign key (command_id, device_id, project_id)
|
||||
references device_commands(id, device_id, project_id),
|
||||
check (sequence_number = 1 or from_state is not null),
|
||||
check (sequence_number <> 1 or from_state is null),
|
||||
check (
|
||||
from_state is null
|
||||
or from_state in (
|
||||
'draft',
|
||||
'planned',
|
||||
'awaiting_confirmation',
|
||||
'queued',
|
||||
'dispatched',
|
||||
'acknowledged',
|
||||
'verified',
|
||||
'failed',
|
||||
'expired',
|
||||
'unknown'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create index if not exists device_command_events_command_idx
|
||||
on device_command_events (command_id, sequence_number);
|
||||
|
||||
create or replace function device_assert_command_scope()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from device_instances di
|
||||
where di.id = new.device_id
|
||||
and di.owner_scope_id = new.owner_scope_id
|
||||
and di.project_id = new.project_id
|
||||
) then
|
||||
raise foreign_key_violation using message = 'device_command_scope_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_commands_scope_guard on device_commands;
|
||||
|
||||
create trigger device_commands_scope_guard
|
||||
before insert
|
||||
on device_commands
|
||||
for each row
|
||||
execute function device_assert_command_scope();
|
||||
|
||||
create or replace function device_assert_command_event_sequence()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
previous_state text;
|
||||
begin
|
||||
if new.sequence_number = 1 then
|
||||
if new.from_state is not null or new.to_state <> 'draft' then
|
||||
raise check_violation using message = 'device_command_initial_event_invalid';
|
||||
end if;
|
||||
return new;
|
||||
end if;
|
||||
|
||||
select dce.to_state into previous_state
|
||||
from device_command_events dce
|
||||
where dce.command_id = new.command_id
|
||||
and dce.sequence_number = new.sequence_number - 1;
|
||||
|
||||
if previous_state is null or previous_state <> new.from_state then
|
||||
raise check_violation using message = 'device_command_event_sequence_invalid';
|
||||
end if;
|
||||
if not (
|
||||
(new.from_state = 'draft' and new.to_state in ('planned', 'expired'))
|
||||
or (new.from_state = 'planned' and new.to_state in ('awaiting_confirmation', 'queued', 'expired'))
|
||||
or (new.from_state = 'awaiting_confirmation' and new.to_state in ('queued', 'expired'))
|
||||
or (new.from_state = 'queued' and new.to_state in ('dispatched', 'failed', 'expired'))
|
||||
or (new.from_state = 'dispatched' and new.to_state in ('acknowledged', 'failed', 'unknown'))
|
||||
or (new.from_state = 'acknowledged' and new.to_state in ('verified', 'failed', 'unknown'))
|
||||
) then
|
||||
raise check_violation using message = 'device_command_event_transition_invalid';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_command_events_sequence_guard
|
||||
on device_command_events;
|
||||
|
||||
create trigger device_command_events_sequence_guard
|
||||
before insert
|
||||
on device_command_events
|
||||
for each row
|
||||
execute function device_assert_command_event_sequence();
|
||||
|
||||
create or replace function device_assert_command_transition()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if old.lifecycle_state = new.lifecycle_state then
|
||||
return new;
|
||||
end if;
|
||||
if not (
|
||||
(old.lifecycle_state = 'draft' and new.lifecycle_state in ('planned', 'expired'))
|
||||
or (old.lifecycle_state = 'planned' and new.lifecycle_state in ('awaiting_confirmation', 'queued', 'expired'))
|
||||
or (old.lifecycle_state = 'awaiting_confirmation' and new.lifecycle_state in ('queued', 'expired'))
|
||||
or (old.lifecycle_state = 'queued' and new.lifecycle_state in ('dispatched', 'failed', 'expired'))
|
||||
or (old.lifecycle_state = 'dispatched' and new.lifecycle_state in ('acknowledged', 'failed', 'unknown'))
|
||||
or (old.lifecycle_state = 'acknowledged' and new.lifecycle_state in ('verified', 'failed', 'unknown'))
|
||||
) then
|
||||
raise check_violation using message = 'device_command_transition_invalid';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_commands_transition_guard on device_commands;
|
||||
|
||||
create trigger device_commands_transition_guard
|
||||
before update of lifecycle_state
|
||||
on device_commands
|
||||
for each row
|
||||
execute function device_assert_command_transition();
|
||||
|
||||
create or replace function device_assert_command_event_projection()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
current_state text;
|
||||
latest_event_state text;
|
||||
begin
|
||||
select dc.lifecycle_state into current_state
|
||||
from device_commands dc
|
||||
where dc.id = new.id;
|
||||
|
||||
select dce.to_state into latest_event_state
|
||||
from device_command_events dce
|
||||
where dce.command_id = new.id
|
||||
order by dce.sequence_number desc
|
||||
limit 1;
|
||||
|
||||
if current_state is null or latest_event_state is distinct from current_state then
|
||||
raise check_violation using message = 'device_command_event_projection_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_commands_event_projection_guard on device_commands;
|
||||
|
||||
create constraint trigger device_commands_event_projection_guard
|
||||
after insert or update of lifecycle_state
|
||||
on device_commands
|
||||
deferrable initially deferred
|
||||
for each row
|
||||
execute function device_assert_command_event_projection();
|
||||
|
||||
create or replace function device_assert_command_current_projection()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
current_state text;
|
||||
latest_event_state text;
|
||||
begin
|
||||
select dc.lifecycle_state into current_state
|
||||
from device_commands dc
|
||||
where dc.id = new.command_id;
|
||||
|
||||
select dce.to_state into latest_event_state
|
||||
from device_command_events dce
|
||||
where dce.command_id = new.command_id
|
||||
order by dce.sequence_number desc
|
||||
limit 1;
|
||||
|
||||
if current_state is null or latest_event_state is distinct from current_state then
|
||||
raise check_violation using message = 'device_command_current_projection_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_command_events_current_projection_guard
|
||||
on device_command_events;
|
||||
|
||||
create constraint trigger device_command_events_current_projection_guard
|
||||
after insert
|
||||
on device_command_events
|
||||
deferrable initially deferred
|
||||
for each row
|
||||
execute function device_assert_command_current_projection();
|
||||
|
||||
create or replace function device_reject_immutable_mutation()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
raise check_violation using message = 'device_immutable_record_mutation_forbidden';
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_configuration_revisions_immutable_guard
|
||||
on device_configuration_revisions;
|
||||
create trigger device_configuration_revisions_immutable_guard
|
||||
before update or delete or truncate
|
||||
on device_configuration_revisions
|
||||
for each statement
|
||||
execute function device_reject_immutable_mutation();
|
||||
|
||||
drop trigger if exists device_command_events_immutable_guard
|
||||
on device_command_events;
|
||||
create trigger device_command_events_immutable_guard
|
||||
before update or delete or truncate
|
||||
on device_command_events
|
||||
for each statement
|
||||
execute function device_reject_immutable_mutation();
|
||||
|
||||
drop trigger if exists device_audit_events_immutable_guard
|
||||
on device_audit_events;
|
||||
create trigger device_audit_events_immutable_guard
|
||||
before update or delete or truncate
|
||||
on device_audit_events
|
||||
for each statement
|
||||
execute function device_reject_immutable_mutation();
|
||||
|
||||
create or replace function device_require_control_resources_clear_before_transfer()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if exists (
|
||||
select 1 from device_resource_bindings drb
|
||||
where drb.device_id = old.id
|
||||
and drb.lifecycle_state in ('pending_external_approval', 'active')
|
||||
) then
|
||||
raise check_violation using message = 'device_transfer_active_resource_binding';
|
||||
end if;
|
||||
if exists (
|
||||
select 1 from device_configuration_state dcs
|
||||
where dcs.device_id = old.id
|
||||
and dcs.applied_revision_id is not null
|
||||
) then
|
||||
raise check_violation using message = 'device_transfer_applied_configuration';
|
||||
end if;
|
||||
if exists (
|
||||
select 1 from device_commands dc
|
||||
where dc.device_id = old.id
|
||||
and dc.lifecycle_state not in ('verified', 'failed', 'expired', 'unknown')
|
||||
) then
|
||||
raise check_violation using message = 'device_transfer_nonterminal_command';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_instances_control_resource_transfer_guard
|
||||
on device_instances;
|
||||
|
||||
create trigger device_instances_control_resource_transfer_guard
|
||||
before update of owner_scope_id, project_id
|
||||
on device_instances
|
||||
for each row
|
||||
when (
|
||||
old.owner_scope_id is distinct from new.owner_scope_id
|
||||
or old.project_id is distinct from new.project_id
|
||||
)
|
||||
execute function device_require_control_resources_clear_before_transfer();
|
||||
|
||||
commit;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
begin;
|
||||
|
||||
alter table device_management_command_receipts
|
||||
drop constraint if exists device_management_command_receipts_command_kind_check;
|
||||
|
||||
alter table device_management_command_receipts
|
||||
add constraint device_management_command_receipts_command_kind_check
|
||||
check (command_kind in (
|
||||
'owner_scope.ensure',
|
||||
'project.ensure',
|
||||
'collection.ensure',
|
||||
'project_grant.upsert',
|
||||
'adapter_package.ensure',
|
||||
'adapter_version.register',
|
||||
'model_profile.register',
|
||||
'edge.ensure',
|
||||
'route.ensure',
|
||||
'enrollment_intent.ensure',
|
||||
'device.claim',
|
||||
'device.transfer',
|
||||
'discovery.reject',
|
||||
'discovery.expire',
|
||||
'device_credential_binding.upsert',
|
||||
'device_credential_binding.revoke',
|
||||
'device_binding.ensure',
|
||||
'device_binding.revoke',
|
||||
'device_configuration_revision.create',
|
||||
'device_configuration_desired.set'
|
||||
));
|
||||
|
||||
commit;
|
||||
Reference in New Issue
Block a user