feat: establish standalone Device Core repository

This commit is contained in:
DCCONSTRUCTIONS
2026-08-21 11:51:21 +03:00
commit e0bac205d0
244 changed files with 51962 additions and 0 deletions
@@ -0,0 +1,99 @@
begin;
create table if not exists device_model_profiles (
profile_ref text primary key,
schema_version text not null,
vendor text not null,
model text not null,
device_type text not null,
protocol text not null,
profile jsonb not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists device_contours (
id uuid primary key,
owner_scope text not null,
name text not null,
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'suspended', 'retired')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (owner_scope, name)
);
create table if not exists device_discoveries (
id uuid primary key,
identifier_kind text not null,
identifier_digest text not null,
identifier_masked text not null,
model_profile_ref text not null references device_model_profiles(profile_ref),
protocol text not null,
lifecycle_state text not null default 'quarantine'
check (lifecycle_state in ('quarantine', 'claimed', 'rejected', 'expired')),
first_observed_at timestamptz not null,
last_observed_at timestamptz not null,
evidence jsonb not null,
claimed_device_id uuid,
claimed_at timestamptz,
claimed_by text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (identifier_kind, identifier_digest, model_profile_ref)
);
create index if not exists device_discoveries_state_last_seen_idx
on device_discoveries (lifecycle_state, last_observed_at desc);
create table if not exists device_instances (
id uuid primary key,
contour_id uuid not null references device_contours(id),
model_profile_ref text not null references device_model_profiles(profile_ref),
display_name text not null,
identifier_kind text not null,
identifier_digest text not null,
identifier_masked text not null,
credential_ref text,
lifecycle_state text not null default 'claimed'
check (lifecycle_state in ('claimed', 'online', 'offline', 'suspended', 'retired')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (identifier_kind, identifier_digest, model_profile_ref)
);
alter table device_discoveries
drop constraint if exists device_discoveries_claimed_device_fk;
alter table device_discoveries
add constraint device_discoveries_claimed_device_fk
foreign key (claimed_device_id) references device_instances(id);
create table if not exists device_bindings (
id uuid primary key,
contour_id uuid not null references device_contours(id),
target_kind text not null,
target_ref text not null,
capabilities text[] not null,
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'revoked')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (contour_id, target_kind, target_ref)
);
create table if not exists device_audit_events (
id uuid primary key,
event_type text not null,
actor_ref text not null,
contour_id uuid,
device_id uuid,
discovery_id uuid,
payload jsonb not null,
occurred_at timestamptz not null default now()
);
create index if not exists device_audit_events_device_time_idx
on device_audit_events (device_id, occurred_at desc);
commit;
@@ -0,0 +1,113 @@
begin;
create table if not exists device_owner_scopes (
id uuid primary key,
scope_kind text not null
check (scope_kind in ('company', 'personal')),
owner_ref text not null
check (length(btrim(owner_ref)) between 3 and 256),
display_name text not null
check (length(btrim(display_name)) between 1 and 160),
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'suspended', 'retired')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (scope_kind, owner_ref)
);
create table if not exists device_projects (
id uuid primary key,
owner_scope_id uuid not null references device_owner_scopes(id),
project_key text not null
check (project_key ~ '^[a-z][a-z0-9-]{1,62}$'),
name text not null
check (length(btrim(name)) between 1 and 160),
description text,
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'suspended', 'archived')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (owner_scope_id, project_key)
);
create index if not exists device_projects_owner_scope_idx
on device_projects (owner_scope_id, lifecycle_state, updated_at desc);
alter table device_instances
add column if not exists project_id uuid references device_projects(id);
create unique index if not exists device_instances_id_project_idx
on device_instances (id, project_id);
create table if not exists device_collections (
id uuid primary key,
project_id uuid not null references device_projects(id),
collection_key text not null
check (collection_key ~ '^[a-z][a-z0-9-]{1,62}$'),
name text not null
check (length(btrim(name)) between 1 and 160),
description text,
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'archived')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (project_id, collection_key),
unique (id, project_id)
);
create index if not exists device_collections_project_idx
on device_collections (project_id, lifecycle_state, updated_at desc);
create table if not exists device_collection_members (
collection_id uuid not null,
device_id uuid not null,
project_id uuid not null references device_projects(id),
added_by_ref text not null
check (length(btrim(added_by_ref)) between 3 and 256),
added_at timestamptz not null default now(),
primary key (collection_id, device_id),
foreign key (collection_id, project_id)
references device_collections(id, project_id),
foreign key (device_id, project_id)
references device_instances(id, project_id)
);
create index if not exists device_collection_members_device_idx
on device_collection_members (device_id, collection_id);
create table if not exists device_project_grants (
id uuid primary key,
project_id uuid not null references device_projects(id),
principal_kind text not null
check (principal_kind in ('user', 'group')),
principal_ref text not null
check (length(btrim(principal_ref)) between 3 and 256),
project_role text not null
check (project_role in ('viewer', 'operator', 'engineer', 'admin', 'owner')),
capability_allow text[] not null default '{}',
capability_deny text[] not null default '{}',
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'revoked')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (project_id, principal_kind, principal_ref),
check (not (capability_allow && capability_deny))
);
create index if not exists device_project_grants_principal_idx
on device_project_grants (
principal_kind,
principal_ref,
lifecycle_state,
project_id
);
commit;
@@ -0,0 +1,68 @@
begin;
do $$
begin
if not exists (
select 1
from pg_constraint
where conname = 'device_project_grants_owner_user_only'
and conrelid = 'device_project_grants'::regclass
) then
alter table device_project_grants
add constraint device_project_grants_owner_user_only
check (project_role <> 'owner' or principal_kind = 'user');
end if;
end
$$;
alter table device_audit_events
add column if not exists project_id uuid references device_projects(id);
create index if not exists device_audit_events_project_time_idx
on device_audit_events (project_id, occurred_at desc);
create table if not exists device_management_command_receipts (
id uuid primary key,
actor_ref text not null
check (length(btrim(actor_ref)) between 3 and 256),
command_kind text not null
check (command_kind in (
'owner_scope.ensure',
'project.ensure',
'collection.ensure',
'project_grant.upsert'
)),
idempotency_key text not null
check (length(idempotency_key) between 8 and 256),
request_digest text not null
check (request_digest ~ '^sha256:[a-f0-9]{64}$'),
lifecycle_state text not null default 'pending'
check (lifecycle_state in ('pending', 'completed')),
response_status integer
check (response_status between 200 and 599),
response_body jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
completed_at timestamptz,
unique (actor_ref, command_kind, idempotency_key),
check (
(
lifecycle_state = 'pending'
and response_status is null
and response_body is null
and completed_at is null
)
or
(
lifecycle_state = 'completed'
and response_status is not null
and response_body is not null
and completed_at is not null
)
)
);
create index if not exists device_management_receipts_created_idx
on device_management_command_receipts (created_at desc);
commit;
@@ -0,0 +1,208 @@
begin;
create table if not exists device_adapter_packages (
id uuid primary key,
package_key text not null
check (package_key ~ '^[a-z][a-z0-9-]{1,62}$'),
display_name text not null
check (length(btrim(display_name)) between 1 and 160),
publisher_ref text not null
check (length(btrim(publisher_ref)) between 3 and 256),
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'retired')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (package_key)
);
create table if not exists device_adapter_versions (
id uuid primary key,
adapter_package_id uuid not null references device_adapter_packages(id),
version text not null
check (version ~ '^[0-9]+\.[0-9]+\.[0-9]+([+-][A-Za-z0-9.-]+)?$'),
runtime_package_ref text not null
check (length(btrim(runtime_package_ref)) between 3 and 256),
content_digest text not null
check (content_digest ~ '^sha256:[a-f0-9]{64}$'),
contract_version text not null
check (contract_version ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$'),
capabilities text[] not null default '{}'
check (
cardinality(capabilities) <= 64
and array_position(capabilities, null) is null
),
lifecycle_state text not null default 'draft'
check (lifecycle_state in ('draft', 'active', 'retired')),
registered_by_ref text not null
check (length(btrim(registered_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (adapter_package_id, version),
unique (runtime_package_ref, content_digest),
unique (id, adapter_package_id)
);
alter table device_model_profiles
add column if not exists adapter_version_id uuid
references device_adapter_versions(id),
add column if not exists schema_artifact_ref text
check (
schema_artifact_ref is null
or length(btrim(schema_artifact_ref)) between 3 and 256
),
add column if not exists profile_digest text
check (
profile_digest is null
or profile_digest ~ '^sha256:[a-f0-9]{64}$'
),
add column if not exists capabilities text[] not null default '{}'
check (
cardinality(capabilities) <= 64
and array_position(capabilities, null) is null
),
add column if not exists lifecycle_state text not null default 'active'
check (lifecycle_state in ('draft', 'active', 'retired'));
create index if not exists device_model_profiles_adapter_version_idx
on device_model_profiles (adapter_version_id, lifecycle_state, updated_at desc);
create table if not exists device_edges (
id uuid primary key,
edge_key text not null
check (edge_key ~ '^[a-z][a-z0-9-]{1,62}$'),
display_name text not null
check (length(btrim(display_name)) between 1 and 160),
deployment_ref text
check (
deployment_ref is null
or length(btrim(deployment_ref)) between 3 and 256
),
lifecycle_state text not null default 'provisioning'
check (lifecycle_state in ('provisioning', 'active', 'suspended', 'retired')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (edge_key)
);
create table if not exists device_routes (
id uuid primary key,
project_id uuid not null references device_projects(id),
route_key text not null
check (route_key ~ '^[a-z][a-z0-9-]{1,62}$'),
display_name text not null
check (length(btrim(display_name)) between 1 and 160),
edge_id uuid not null references device_edges(id),
model_profile_ref text not null references device_model_profiles(profile_ref),
listener_ref text not null
check (length(btrim(listener_ref)) between 3 and 256),
protocol text not null
check (protocol ~ '^[A-Z][A-Z0-9_]{0,31}$'),
direction text not null default 'telemetry'
check (direction in ('telemetry', 'bidirectional')),
lifecycle_state text not null default 'draft'
check (lifecycle_state in ('draft', 'active', 'suspended', 'retired')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (project_id, route_key),
unique (id, project_id),
unique (id, edge_id, project_id),
unique (id, project_id, model_profile_ref)
);
create index if not exists device_routes_edge_state_idx
on device_routes (edge_id, lifecycle_state, updated_at desc);
create index if not exists device_routes_project_state_idx
on device_routes (project_id, lifecycle_state, updated_at desc);
create table if not exists device_sessions (
id uuid primary key,
session_ref text not null
check (length(btrim(session_ref)) between 3 and 256),
edge_id uuid not null,
project_id uuid not null,
route_id uuid not null,
device_id uuid,
protocol text not null
check (protocol ~ '^[A-Z][A-Z0-9_]{0,31}$'),
lifecycle_state text not null default 'connecting'
check (lifecycle_state in ('connecting', 'online', 'closing', 'closed', 'rejected')),
connected_at timestamptz not null,
last_seen_at timestamptz not null,
disconnected_at timestamptz,
close_reason_code text
check (
close_reason_code is null
or close_reason_code ~ '^[a-z][a-z0-9._-]{1,63}$'
),
frame_count bigint not null default 0 check (frame_count >= 0),
byte_count bigint not null default 0 check (byte_count >= 0),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (edge_id, session_ref),
foreign key (route_id, edge_id, project_id)
references device_routes(id, edge_id, project_id),
foreign key (device_id, project_id)
references device_instances(id, project_id),
check (last_seen_at >= connected_at),
check (
(lifecycle_state in ('connecting', 'online') and disconnected_at is null)
or
(lifecycle_state in ('closing', 'closed', 'rejected'))
)
);
create index if not exists device_sessions_route_state_seen_idx
on device_sessions (route_id, lifecycle_state, last_seen_at desc);
create index if not exists device_sessions_device_seen_idx
on device_sessions (device_id, last_seen_at desc)
where device_id is not null;
create table if not exists device_enrollment_intents (
id uuid primary key,
project_id uuid not null references device_projects(id),
enrollment_key text not null
check (enrollment_key ~ '^[a-z][a-z0-9-]{1,62}$'),
route_id uuid not null,
model_profile_ref text not null references device_model_profiles(profile_ref),
display_name text not null
check (length(btrim(display_name)) between 1 and 160),
expected_identifier_kind text not null
check (expected_identifier_kind ~ '^[a-z][a-z0-9._-]{1,31}$'),
expected_identifier_digest text not null
check (expected_identifier_digest ~ '^hmac-sha256:[a-f0-9]{64}$'),
expected_identifier_masked text not null
check (length(btrim(expected_identifier_masked)) between 4 and 64),
lifecycle_state text not null default 'pending'
check (lifecycle_state in ('pending', 'observed', 'claimed', 'cancelled', 'expired')),
expires_at timestamptz,
claimed_device_id uuid,
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (project_id, enrollment_key),
unique (
project_id,
expected_identifier_kind,
expected_identifier_digest,
model_profile_ref
),
foreign key (route_id, project_id, model_profile_ref)
references device_routes(id, project_id, model_profile_ref),
foreign key (claimed_device_id, project_id)
references device_instances(id, project_id),
check (expires_at is null or expires_at > created_at)
);
create index if not exists device_enrollment_intents_project_state_idx
on device_enrollment_intents (project_id, lifecycle_state, updated_at desc);
commit;
@@ -0,0 +1,21 @@
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'
));
commit;
@@ -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;
@@ -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;
@@ -0,0 +1,233 @@
begin;
do $$
begin
if not exists (
select 1 from pg_constraint
where conname = 'device_instances_direct_legacy_credential_check'
and conrelid = 'device_instances'::regclass
) then
alter table device_instances
add constraint device_instances_direct_legacy_credential_check
check (owner_scope_id is null or credential_ref is null)
not valid;
end if;
end
$$;
create table if not exists device_restricted_identifiers (
id uuid primary key,
device_id uuid not null references device_instances(id),
owner_scope_id uuid not null,
project_id uuid not null,
identifier_kind text not null
check (identifier_kind ~ '^[a-z][a-z0-9._:-]{1,63}$'),
identifier_digest text not null
check (identifier_digest ~ '^hmac-sha256:[a-f0-9]{64}$'),
identifier_masked text not null
check (
length(identifier_masked) between 5 and 128
and position('*' in identifier_masked) > 0
and identifier_masked !~ '[[:cntrl:]]'
and identifier_masked !~ '(^|[^0-9])[0-9]{15}([^0-9]|$)'
),
provenance_kind text not null
check (provenance_kind in ('claim', 'adapter_observation')),
is_primary boolean not null default false,
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'revoked')),
created_by_ref text not null
check (length(btrim(created_by_ref)) between 3 and 256),
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(),
foreign key (project_id, owner_scope_id)
references device_projects(id, owner_scope_id),
check (
(lifecycle_state = 'active' 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 unique index if not exists device_restricted_identifiers_active_identity_idx
on device_restricted_identifiers (identifier_kind, identifier_digest)
where lifecycle_state = 'active';
create unique index if not exists device_restricted_identifiers_primary_idx
on device_restricted_identifiers (device_id)
where lifecycle_state = 'active' and is_primary;
create index if not exists device_restricted_identifiers_device_idx
on device_restricted_identifiers (device_id, lifecycle_state, created_at);
create or replace function device_assert_identifier_current_owner()
returns trigger
language plpgsql
as $$
begin
if new.lifecycle_state = 'active' 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_identifier_ownership_mismatch';
end if;
return new;
end
$$;
drop trigger if exists device_restricted_identifiers_owner_guard
on device_restricted_identifiers;
create trigger device_restricted_identifiers_owner_guard
before insert or update of device_id, owner_scope_id, project_id, lifecycle_state
on device_restricted_identifiers
for each row
execute function device_assert_identifier_current_owner();
create or replace function device_assert_active_identifiers_follow_owner()
returns trigger
language plpgsql
as $$
begin
if exists (
select 1 from device_restricted_identifiers dri
where dri.device_id = new.id
and dri.lifecycle_state = 'active'
and (
dri.owner_scope_id is distinct from new.owner_scope_id
or dri.project_id is distinct from new.project_id
)
) then
raise foreign_key_violation using
message = 'device_active_identifier_ownership_mismatch';
end if;
return new;
end
$$;
drop trigger if exists device_instances_identifier_owner_guard
on device_instances;
create constraint trigger device_instances_identifier_owner_guard
after update
on device_instances
deferrable initially deferred
for each row
execute function device_assert_active_identifiers_follow_owner();
create table if not exists device_credential_bindings (
id uuid primary key,
device_id uuid not null references device_instances(id),
owner_scope_id uuid not null,
project_id uuid not null,
purpose text not null
check (purpose ~ '^[a-z][a-z0-9._-]{1,63}$'),
credential_owner text not null
check (credential_owner = 'ndc_l2_credentials'),
credential_ref text not null
check (credential_ref ~ '^ndc-credref:[A-Za-z0-9][A-Za-z0-9._:-]{7,240}$'),
lifecycle_state text not null default 'active'
check (lifecycle_state in ('active', 'revoked')),
bound_by_ref text not null
check (length(btrim(bound_by_ref)) between 3 and 256),
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(),
foreign key (project_id, owner_scope_id)
references device_projects(id, owner_scope_id),
check (
(lifecycle_state = 'active' 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 unique index if not exists device_credential_bindings_active_purpose_idx
on device_credential_bindings (device_id, purpose)
where lifecycle_state = 'active';
create index if not exists device_credential_bindings_project_state_idx
on device_credential_bindings (project_id, lifecycle_state, updated_at desc);
create or replace function device_assert_credential_binding_current_owner()
returns trigger
language plpgsql
as $$
begin
if new.lifecycle_state = 'active' 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_credential_binding_ownership_mismatch';
end if;
return new;
end
$$;
drop trigger if exists device_credential_bindings_owner_guard
on device_credential_bindings;
create trigger device_credential_bindings_owner_guard
before insert or update of device_id, owner_scope_id, project_id, lifecycle_state
on device_credential_bindings
for each row
execute function device_assert_credential_binding_current_owner();
create or replace function device_require_credential_revoke_before_transfer()
returns trigger
language plpgsql
as $$
begin
if exists (
select 1 from device_credential_bindings dcb
where dcb.device_id = old.id
and dcb.lifecycle_state = 'active'
) then
raise check_violation using
message = 'device_transfer_active_credential_binding';
end if;
return new;
end
$$;
drop trigger if exists device_instances_credential_transfer_guard
on device_instances;
create trigger device_instances_credential_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_credential_revoke_before_transfer();
commit;
@@ -0,0 +1,27 @@
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'
));
commit;
@@ -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;
@@ -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;
@@ -0,0 +1,66 @@
begin;
create table if not exists device_gateway_message_receipts (
id uuid primary key,
idempotency_key text not null
check (idempotency_key ~ '^sha256:[a-f0-9]{64}$'),
request_digest text not null
check (request_digest ~ '^sha256:[a-f0-9]{64}$'),
edge_ref text not null
check (length(btrim(edge_ref)) between 3 and 128),
adapter_ref text not null
check (adapter_ref ~ '^[a-z][a-z0-9-]{1,62}$'),
protocol_profile_ref text not null
references device_model_profiles(profile_ref),
protocol text not null
check (protocol ~ '^[A-Z][A-Z0-9_]{0,31}$'),
route_id uuid references device_routes(id),
project_id uuid references device_projects(id),
session_ref text not null
check (length(btrim(session_ref)) between 3 and 128),
message_ref text not null
check (length(btrim(message_ref)) between 3 and 128),
message_type text not null
check (message_type ~ '^[a-z][a-z0-9._-]{1,127}$'),
sequence bigint not null check (sequence > 0),
identifier_kind text not null
check (identifier_kind ~ '^[a-z][a-z0-9._:-]{1,63}$'),
identifier_digest text not null
check (identifier_digest ~ '^hmac-sha256:[a-f0-9]{64}$'),
identifier_masked text not null
check (length(identifier_masked) between 5 and 128),
payload_schema_ref text not null
check (length(btrim(payload_schema_ref)) between 3 and 128),
payload jsonb not null,
observed_at timestamptz not null,
accepted_at timestamptz not null default now(),
unique (idempotency_key),
unique (edge_ref, session_ref, message_ref),
foreign key (route_id, project_id)
references device_routes(id, project_id),
check (
(route_id is null and project_id is null)
or (route_id is not null and project_id is not null)
)
);
create index if not exists device_gateway_message_receipts_route_time_idx
on device_gateway_message_receipts (route_id, accepted_at desc)
where route_id is not null;
create index if not exists device_gateway_message_receipts_identity_time_idx
on device_gateway_message_receipts (
identifier_kind,
identifier_digest,
accepted_at desc
);
drop trigger if exists device_gateway_message_receipts_immutable_guard
on device_gateway_message_receipts;
create trigger device_gateway_message_receipts_immutable_guard
before update or delete or truncate
on device_gateway_message_receipts
for each statement
execute function device_reject_immutable_mutation();
commit;
@@ -0,0 +1,54 @@
begin;
alter table device_edges
add column if not exists channel_endpoint text,
add column if not exists channel_servername text,
add column if not exists channel_generation_ref text,
add column if not exists channel_trust_bundle_ref text,
add column if not exists channel_certificate_identities jsonb not null
default '[]'::jsonb,
add column if not exists channel_lifecycle_state text not null
default 'disabled';
do $$
begin
if not exists (
select 1 from pg_constraint
where conname = 'device_edges_channel_lifecycle_state_check'
) then
alter table device_edges add constraint device_edges_channel_lifecycle_state_check
check (channel_lifecycle_state in ('disabled', 'active', 'revoked'));
end if;
if not exists (
select 1 from pg_constraint
where conname = 'device_edges_channel_configuration_check'
) then
alter table device_edges add constraint device_edges_channel_configuration_check
check (
(
channel_lifecycle_state = 'disabled'
and channel_endpoint is null
and channel_servername is null
and channel_generation_ref is null
and channel_trust_bundle_ref is null
and channel_certificate_identities = '[]'::jsonb
)
or
(
channel_lifecycle_state in ('active', 'revoked')
and length(btrim(channel_endpoint)) between 12 and 256
and channel_servername ~ '^[A-Za-z0-9.-]{1,253}$'
and channel_generation_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
and channel_trust_bundle_ref ~ '^edge-trust:[a-z][a-z0-9-]{1,62}$'
and jsonb_typeof(channel_certificate_identities) = 'array'
and jsonb_array_length(channel_certificate_identities) between 1 and 2
)
);
end if;
end $$;
create index if not exists device_edges_active_channel_idx
on device_edges (channel_lifecycle_state, updated_at desc)
where channel_lifecycle_state = 'active';
commit;
@@ -0,0 +1,32 @@
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.update',
'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;
@@ -0,0 +1,20 @@
begin;
alter table device_instances
add column if not exists integration_device_id text;
alter table device_instances
drop constraint if exists device_instances_integration_device_id_check;
alter table device_instances
add constraint device_instances_integration_device_id_check
check (
integration_device_id is null
or (
char_length(integration_device_id) between 1 and 160
and integration_device_id = btrim(integration_device_id)
and integration_device_id !~ '[[:cntrl:]]'
)
);
commit;