feat(device-core): add restricted identity references

This commit is contained in:
Codex
2026-08-10 18:31:30 +03:00
parent fceaca9546
commit 422ddb020f
20 changed files with 1413 additions and 7 deletions
@@ -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;