feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package saas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type InitialConsentSelection struct {
|
||||
Types []string `json:"types"`
|
||||
Confirmed bool `json:"confirmed"`
|
||||
}
|
||||
|
||||
var ErrInitialConsentInvalid = errors.New("initial consent selection is invalid or unconfirmed")
|
||||
|
||||
var initialConsentNotices = map[string]string{
|
||||
ConsentTranscriptStorage: "保存后续聊天的文字记录,便于查看聊天内容。",
|
||||
ConsentRawAudioStorage: "保存后续聊天的原始录音;播放、下载仍需单独权限,设备支持录音上传时才能生成录音。",
|
||||
ConsentStoryExtraction: "从后续聊天文字整理故事和摘要,需要同时开启字幕保存。",
|
||||
ConsentPersonalMemory: "从后续聊天文字整理名字、偏好等记忆,并用于下次聊天;生成新记忆需要同时开启字幕保存。",
|
||||
ConsentFamilySharing: "允许按后续独立分享规则向家庭成员分享内容;本项不会自动分享全部聊天或录音。",
|
||||
}
|
||||
|
||||
func ValidateInitialConsents(selection InitialConsentSelection) error {
|
||||
if len(selection.Types) > 0 && !selection.Confirmed {
|
||||
return ErrInitialConsentInvalid
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, kind := range selection.Types {
|
||||
if _, ok := initialConsentNotices[kind]; !ok || seen[kind] {
|
||||
return ErrInitialConsentInvalid
|
||||
}
|
||||
seen[kind] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeSubjectMemory is called only from new-profile creation, never from
|
||||
// binding, profile edits, or reads, so a withdrawn permission stays withdrawn.
|
||||
func InitializeSubjectMemory(ctx context.Context, tx pgx.Tx, tenantID, subjectPublicID, actorID, requestID string, selection InitialConsentSelection) error {
|
||||
if err := ValidateInitialConsents(selection); err != nil {
|
||||
return err
|
||||
}
|
||||
var subjectID, policy, actorPublicID string
|
||||
if err := tx.QueryRow(ctx, `SELECT sp.id::text,t.policy_version,ua.public_id::text
|
||||
FROM subject_profile sp JOIN tenant t ON t.id=sp.tenant_id
|
||||
JOIN user_account ua ON ua.tenant_id=t.id AND ua.id=$3::uuid
|
||||
WHERE sp.public_id=$1::uuid AND sp.tenant_id=$2::uuid AND ua.status='active'
|
||||
FOR UPDATE OF sp`, subjectPublicID, tenantID, actorID).Scan(&subjectID, &policy, &actorPublicID); err != nil {
|
||||
return fmt.Errorf("resolve initial memory consent: %w", err)
|
||||
}
|
||||
for _, kind := range selection.Types {
|
||||
sum := sha256.Sum256([]byte(initialConsentNotices[kind] + "各项授权适用于后续新通话,可在数据与授权中分别撤回。"))
|
||||
var consentID string
|
||||
if err := tx.QueryRow(ctx, `INSERT INTO consent_record
|
||||
(tenant_id,subject_id,consent_type,scope,policy_version,notice_digest,actor_user_id,evidence_ref)
|
||||
VALUES($1::uuid,$2::uuid,$3,'{"scope":"future_conversations","source":"subject_creation_selection","notice_version":"creation-consents-v1"}'::jsonb,$4,$5,$6::uuid,$7)
|
||||
RETURNING public_id::text`, tenantID, subjectID, kind, policy, hex.EncodeToString(sum[:]), actorID, "subject-create:"+requestID).Scan(&consentID); err != nil {
|
||||
return fmt.Errorf("insert initial memory consent: %w", err)
|
||||
}
|
||||
if err := audit.NewRepository().Record(ctx, tx, audit.Event{
|
||||
TenantID: &tenantID, ActorType: "user", ActorPublicID: &actorPublicID,
|
||||
Action: "consent.grant", ResourceType: "consent", ResourceID: &consentID,
|
||||
RequestID: requestID, Result: "success", Detail: map[string]any{
|
||||
"subject_id": subjectPublicID, "consent_type": kind, "source": "subject_creation_selection", "notice_version": "creation-consents-v1",
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return createConsentSnapshot(ctx, tx, tenantID, subjectID)
|
||||
}
|
||||
Reference in New Issue
Block a user