feat(device-core): add device ownership lifecycle
This commit is contained in:
+326
@@ -0,0 +1,326 @@
|
||||
begin;
|
||||
|
||||
create unique index if not exists device_projects_id_owner_scope_idx
|
||||
on device_projects (id, owner_scope_id);
|
||||
|
||||
alter table device_instances
|
||||
alter column contour_id drop not null,
|
||||
add column if not exists owner_scope_id uuid references device_owner_scopes(id),
|
||||
add column if not exists device_key text
|
||||
check (
|
||||
device_key is null
|
||||
or device_key ~ '^[a-z][a-z0-9-]{1,62}$'
|
||||
);
|
||||
|
||||
create unique index if not exists device_instances_project_key_idx
|
||||
on device_instances (project_id, device_key)
|
||||
where device_key is not null;
|
||||
|
||||
create unique index if not exists device_instances_id_project_owner_idx
|
||||
on device_instances (id, project_id, owner_scope_id);
|
||||
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_instances_project_owner_fk'
|
||||
and conrelid = 'device_instances'::regclass
|
||||
) then
|
||||
alter table device_instances
|
||||
add constraint device_instances_project_owner_fk
|
||||
foreign key (project_id, owner_scope_id)
|
||||
references device_projects(id, owner_scope_id)
|
||||
not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_instances_ownership_mode_check'
|
||||
and conrelid = 'device_instances'::regclass
|
||||
) then
|
||||
alter table device_instances
|
||||
add constraint device_instances_ownership_mode_check
|
||||
check (
|
||||
(
|
||||
owner_scope_id is not null
|
||||
and project_id is not null
|
||||
)
|
||||
or
|
||||
(
|
||||
owner_scope_id is null
|
||||
and project_id is null
|
||||
and contour_id is not null
|
||||
)
|
||||
) not valid;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
alter table device_discoveries
|
||||
add column if not exists session_ref text
|
||||
check (
|
||||
session_ref is null
|
||||
or length(btrim(session_ref)) between 3 and 256
|
||||
),
|
||||
add column if not exists project_id uuid references device_projects(id),
|
||||
add column if not exists route_id uuid,
|
||||
add column if not exists enrollment_intent_id uuid,
|
||||
add column if not exists resolution_code text
|
||||
check (
|
||||
resolution_code is null
|
||||
or resolution_code ~ '^[a-z][a-z0-9._-]{1,63}$'
|
||||
),
|
||||
add column if not exists resolved_at timestamptz,
|
||||
add column if not exists resolved_by_ref text
|
||||
check (
|
||||
resolved_by_ref is null
|
||||
or length(btrim(resolved_by_ref)) between 3 and 256
|
||||
);
|
||||
|
||||
create unique index if not exists device_discoveries_id_project_idx
|
||||
on device_discoveries (id, project_id);
|
||||
|
||||
create index if not exists device_discoveries_route_state_seen_idx
|
||||
on device_discoveries (route_id, lifecycle_state, last_observed_at desc)
|
||||
where route_id is not null;
|
||||
|
||||
create unique index if not exists device_enrollment_intents_context_idx
|
||||
on device_enrollment_intents (
|
||||
id,
|
||||
project_id,
|
||||
route_id,
|
||||
model_profile_ref
|
||||
);
|
||||
|
||||
create unique index if not exists device_enrollment_intents_active_identity_idx
|
||||
on device_enrollment_intents (
|
||||
expected_identifier_kind,
|
||||
expected_identifier_digest,
|
||||
model_profile_ref
|
||||
)
|
||||
where lifecycle_state in ('pending', 'observed', 'claimed');
|
||||
|
||||
alter table device_enrollment_intents
|
||||
add column if not exists observed_discovery_id uuid,
|
||||
add column if not exists observed_at timestamptz,
|
||||
add column if not exists claimed_at timestamptz,
|
||||
add column if not exists resolution_code text
|
||||
check (
|
||||
resolution_code is null
|
||||
or resolution_code ~ '^[a-z][a-z0-9._-]{1,63}$'
|
||||
),
|
||||
add column if not exists resolved_at timestamptz,
|
||||
add column if not exists resolved_by_ref text
|
||||
check (
|
||||
resolved_by_ref is null
|
||||
or length(btrim(resolved_by_ref)) between 3 and 256
|
||||
);
|
||||
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_discoveries_route_context_fk'
|
||||
and conrelid = 'device_discoveries'::regclass
|
||||
) then
|
||||
alter table device_discoveries
|
||||
add constraint device_discoveries_route_context_fk
|
||||
foreign key (route_id, project_id, model_profile_ref)
|
||||
references device_routes(id, project_id, model_profile_ref)
|
||||
not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_discoveries_enrollment_context_fk'
|
||||
and conrelid = 'device_discoveries'::regclass
|
||||
) then
|
||||
alter table device_discoveries
|
||||
add constraint device_discoveries_enrollment_context_fk
|
||||
foreign key (
|
||||
enrollment_intent_id,
|
||||
project_id,
|
||||
route_id,
|
||||
model_profile_ref
|
||||
) references device_enrollment_intents (
|
||||
id,
|
||||
project_id,
|
||||
route_id,
|
||||
model_profile_ref
|
||||
) not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_discoveries_route_context_check'
|
||||
and conrelid = 'device_discoveries'::regclass
|
||||
) then
|
||||
alter table device_discoveries
|
||||
add constraint device_discoveries_route_context_check
|
||||
check (
|
||||
(project_id is null and route_id is null)
|
||||
or
|
||||
(project_id is not null and route_id is not null)
|
||||
) not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_discoveries_enrollment_context_check'
|
||||
and conrelid = 'device_discoveries'::regclass
|
||||
) then
|
||||
alter table device_discoveries
|
||||
add constraint device_discoveries_enrollment_context_check
|
||||
check (
|
||||
enrollment_intent_id is null
|
||||
or (project_id is not null and route_id is not null)
|
||||
) not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_enrollment_observed_discovery_fk'
|
||||
and conrelid = 'device_enrollment_intents'::regclass
|
||||
) then
|
||||
alter table device_enrollment_intents
|
||||
add constraint device_enrollment_observed_discovery_fk
|
||||
foreign key (observed_discovery_id, project_id)
|
||||
references device_discoveries(id, project_id)
|
||||
not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_enrollment_lifecycle_evidence_check'
|
||||
and conrelid = 'device_enrollment_intents'::regclass
|
||||
) then
|
||||
alter table device_enrollment_intents
|
||||
add constraint device_enrollment_lifecycle_evidence_check
|
||||
check (
|
||||
lifecycle_state not in ('observed', 'claimed')
|
||||
or (observed_discovery_id is not null and observed_at is not null)
|
||||
) not valid;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
alter table device_sessions
|
||||
drop constraint if exists device_sessions_device_id_project_id_fkey;
|
||||
|
||||
alter table device_enrollment_intents
|
||||
drop constraint if exists device_enrollment_intents_claimed_device_id_project_id_fkey;
|
||||
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_sessions_device_id_fk'
|
||||
and conrelid = 'device_sessions'::regclass
|
||||
) then
|
||||
alter table device_sessions
|
||||
add constraint device_sessions_device_id_fk
|
||||
foreign key (device_id) references device_instances(id)
|
||||
not valid;
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'device_enrollment_claimed_device_id_fk'
|
||||
and conrelid = 'device_enrollment_intents'::regclass
|
||||
) then
|
||||
alter table device_enrollment_intents
|
||||
add constraint device_enrollment_claimed_device_id_fk
|
||||
foreign key (claimed_device_id) references device_instances(id)
|
||||
not valid;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
create or replace function device_assert_session_current_project()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if new.device_id is not null and not exists (
|
||||
select 1 from device_instances di
|
||||
where di.id = new.device_id
|
||||
and di.project_id = new.project_id
|
||||
) then
|
||||
raise foreign_key_violation using
|
||||
message = 'device_session_project_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_sessions_current_project_guard
|
||||
on device_sessions;
|
||||
|
||||
create trigger device_sessions_current_project_guard
|
||||
before insert or update of device_id, project_id
|
||||
on device_sessions
|
||||
for each row
|
||||
execute function device_assert_session_current_project();
|
||||
|
||||
create or replace function device_assert_enrollment_current_project()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
if new.claimed_device_id is not null and not exists (
|
||||
select 1 from device_instances di
|
||||
where di.id = new.claimed_device_id
|
||||
and di.project_id = new.project_id
|
||||
) then
|
||||
raise foreign_key_violation using
|
||||
message = 'device_enrollment_project_mismatch';
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop trigger if exists device_enrollment_current_project_guard
|
||||
on device_enrollment_intents;
|
||||
|
||||
create trigger device_enrollment_current_project_guard
|
||||
before insert or update of claimed_device_id, project_id
|
||||
on device_enrollment_intents
|
||||
for each row
|
||||
execute function device_assert_enrollment_current_project();
|
||||
|
||||
create table if not exists device_ownership_transitions (
|
||||
id uuid primary key,
|
||||
device_id uuid not null references device_instances(id),
|
||||
transition_kind text not null
|
||||
check (transition_kind in ('claim', 'transfer')),
|
||||
source_owner_scope_id uuid,
|
||||
source_project_id uuid,
|
||||
target_owner_scope_id uuid not null,
|
||||
target_project_id uuid not null,
|
||||
actor_ref text not null
|
||||
check (length(btrim(actor_ref)) between 3 and 256),
|
||||
occurred_at timestamptz not null default now(),
|
||||
foreign key (source_project_id, source_owner_scope_id)
|
||||
references device_projects(id, owner_scope_id),
|
||||
foreign key (target_project_id, target_owner_scope_id)
|
||||
references device_projects(id, owner_scope_id),
|
||||
check (
|
||||
(
|
||||
transition_kind = 'claim'
|
||||
and source_owner_scope_id is null
|
||||
and source_project_id is null
|
||||
)
|
||||
or
|
||||
(
|
||||
transition_kind = 'transfer'
|
||||
and source_owner_scope_id is not null
|
||||
and source_project_id is not null
|
||||
and (
|
||||
source_owner_scope_id <> target_owner_scope_id
|
||||
or source_project_id <> target_project_id
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create unique index if not exists device_ownership_single_claim_idx
|
||||
on device_ownership_transitions (device_id)
|
||||
where transition_kind = 'claim';
|
||||
|
||||
create index if not exists device_ownership_device_time_idx
|
||||
on device_ownership_transitions (device_id, occurred_at desc);
|
||||
|
||||
commit;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
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'
|
||||
));
|
||||
|
||||
commit;
|
||||
Reference in New Issue
Block a user