Files
C-SSH/creation-cloud/migrations/0055_proxy_profile_encrypted_sync.sql

128 lines
6.0 KiB
SQL

-- 第三方代理线路只以客户端不透明密文参与统一账号 revision 流。
ALTER TABLE cloud_host_sync_states
ADD COLUMN minimum_sync_contract_version INTEGER NOT NULL DEFAULT 2
CHECK (minimum_sync_contract_version IN (2, 3));
CREATE TABLE cloud_proxy_profiles (
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
id UUID NOT NULL,
ciphertext BYTEA,
nonce BYTEA,
envelope_metadata JSONB,
source_device_id UUID NOT NULL,
revision BIGINT NOT NULL CHECK (revision > 0),
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (account_id, id),
FOREIGN KEY (account_id, source_device_id)
REFERENCES devices(account_id, id) ON DELETE RESTRICT,
CHECK (ciphertext IS NULL OR octet_length(ciphertext) <= 262144),
CHECK (nonce IS NULL OR octet_length(nonce) BETWEEN 1 AND 4096),
CHECK (envelope_metadata IS NULL OR (
jsonb_typeof(envelope_metadata) = 'object'
AND pg_column_size(envelope_metadata) <= 16384
)),
CHECK (
(is_deleted AND ciphertext IS NULL AND nonce IS NULL AND envelope_metadata IS NULL)
OR (NOT is_deleted AND ciphertext IS NOT NULL AND nonce IS NOT NULL
AND envelope_metadata IS NOT NULL)
)
);
CREATE INDEX cloud_proxy_profiles_revision_idx
ON cloud_proxy_profiles(account_id, revision, id);
CREATE INDEX cloud_proxy_profiles_tombstone_retention_idx
ON cloud_proxy_profiles(updated_at, account_id, revision, id) WHERE is_deleted;
CREATE TABLE cloud_proxy_profile_versions (
account_id UUID NOT NULL,
resource_id UUID NOT NULL,
revision BIGINT NOT NULL CHECK (revision > 0),
ciphertext BYTEA,
nonce BYTEA,
envelope_metadata JSONB,
source_device_id UUID NOT NULL,
is_deleted BOOLEAN NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (account_id, revision),
UNIQUE (account_id, resource_id, revision),
FOREIGN KEY (account_id, resource_id)
REFERENCES cloud_proxy_profiles(account_id, id) ON DELETE CASCADE,
FOREIGN KEY (account_id, source_device_id)
REFERENCES devices(account_id, id) ON DELETE RESTRICT,
CHECK (ciphertext IS NULL OR octet_length(ciphertext) <= 262144),
CHECK (nonce IS NULL OR octet_length(nonce) BETWEEN 1 AND 4096),
CHECK (envelope_metadata IS NULL OR (
jsonb_typeof(envelope_metadata) = 'object'
AND pg_column_size(envelope_metadata) <= 16384
)),
CHECK (
(is_deleted AND ciphertext IS NULL AND nonce IS NULL AND envelope_metadata IS NULL)
OR (NOT is_deleted AND ciphertext IS NOT NULL AND nonce IS NOT NULL
AND envelope_metadata IS NOT NULL)
)
);
CREATE INDEX cloud_proxy_profile_versions_pull_idx
ON cloud_proxy_profile_versions(account_id, resource_id, revision DESC);
CREATE INDEX cloud_proxy_profile_versions_retention_idx
ON cloud_proxy_profile_versions(recorded_at, account_id, revision);
ALTER TABLE cloud_sync_push_results
DROP CONSTRAINT cloud_sync_push_results_resource_kind_check,
ADD CONSTRAINT cloud_sync_push_results_resource_kind_check
CHECK (resource_kind IN ('host', 'ai_provider_account', 'proxy_profile'));
ALTER TABLE cloud_sync_resource_deliveries
DROP CONSTRAINT cloud_sync_resource_deliveries_resource_kind_check,
ADD CONSTRAINT cloud_sync_resource_deliveries_resource_kind_check
CHECK (resource_kind IN ('host', 'ai_provider_account', 'proxy_profile'));
ALTER TABLE cloud_sync_pull_decisions
DROP CONSTRAINT cloud_sync_pull_decisions_resource_kind_check,
ADD CONSTRAINT cloud_sync_pull_decisions_resource_kind_check
CHECK (resource_kind IN ('host', 'ai_provider_account', 'proxy_profile'));
ALTER TABLE cloud_sync_rekey_resource_results
DROP CONSTRAINT cloud_sync_rekey_resource_results_resource_kind_check,
ADD CONSTRAINT cloud_sync_rekey_resource_results_resource_kind_check
CHECK (resource_kind IN ('host', 'ai_provider_account', 'proxy_profile'));
ALTER TABLE cloud_data_protection_migration_results
DROP CONSTRAINT cloud_data_protection_migration_results_resource_kind_check,
ADD CONSTRAINT cloud_data_protection_migration_results_resource_kind_check
CHECK (resource_kind IN ('host', 'ai_provider_account', 'proxy_profile'));
ALTER TABLE audit_events
DROP CONSTRAINT audit_events_sync_rekey_v2_semantic_contract;
ALTER TABLE audit_events
ADD CONSTRAINT audit_events_sync_rekey_v2_semantic_contract CHECK (
action <> 'sync.encrypted_data_rekey_v2'
OR (
actor_account_id IS NOT NULL
AND resource_kind = 'sync_account'
AND resource_id = actor_account_id::TEXT
AND outcome = 'success'
AND details ?& ARRAY[
'mutation_id','device_id','changed_hosts','changed_ai_providers',
'result_revision','previous_sync_generation','sync_generation'
]
AND details - ARRAY[
'mutation_id','device_id','changed_hosts','changed_ai_providers',
'changed_proxy_profiles','result_revision',
'previous_sync_generation','sync_generation'
] = '{}'::jsonb
AND jsonb_typeof(details->'mutation_id') = 'string'
AND jsonb_typeof(details->'device_id') = 'string'
AND jsonb_typeof(details->'changed_hosts') = 'number'
AND jsonb_typeof(details->'changed_ai_providers') = 'number'
-- 已发布的二资源审计正文不可回写;仅兼容缺少新增计数的完整旧形状。
-- 新写入由同一 typed producer 始终提供 changed_proxy_profiles。
AND (NOT (details ? 'changed_proxy_profiles')
OR jsonb_typeof(details->'changed_proxy_profiles') = 'number')
AND jsonb_typeof(details->'result_revision') = 'number'
AND jsonb_typeof(details->'previous_sync_generation') = 'number'
AND jsonb_typeof(details->'sync_generation') = 'number'
AND (details->>'previous_sync_generation')::BIGINT > 0
AND (details->>'sync_generation')::BIGINT
= (details->>'previous_sync_generation')::BIGINT + 1
)
);