feat: expand companion platform, device controls, and memoir workflows
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:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+124
View File
@@ -0,0 +1,124 @@
package onboarding
import (
"fmt"
"strings"
"testing"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/saas"
"github.com/google/uuid"
)
func TestProfileCreationRollsBackWhenMemoryConsentFails(t *testing.T) {
f := newFixture(t)
name := "test_initial_memory_" + strings.ReplaceAll(uuid.NewString(), "-", "")
// Fail the second grant for this synthetic tenant to verify that the first
// grant, the new household, profile, audit records and replay result all roll back.
_, err := f.s.pool.Exec(f.ctx, fmt.Sprintf(`CREATE FUNCTION %s() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.tenant_id='%s'::uuid AND NEW.consent_type='personalized_memory' THEN RAISE EXCEPTION 'synthetic consent failure'; END IF; RETURN NEW; END $$;
CREATE TRIGGER %s BEFORE INSERT ON consent_record FOR EACH ROW EXECUTE FUNCTION %s();`, name, f.tenant, name, name))
must(t, err)
t.Cleanup(func() {
_, _ = f.s.pool.Exec(f.ctx, fmt.Sprintf("DROP TRIGGER IF EXISTS %s ON consent_record; DROP FUNCTION IF EXISTS %s();", name, name))
})
in := ProfileInput{HouseholdName: "授权失败家庭", Name: "授权失败老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentTranscriptStorage, saas.ConsentPersonalMemory}, Confirmed: true}}
requestID := uuid.NewString()
_, err = f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in)
if err == nil {
t.Fatal("profile creation succeeded despite failed consent")
}
var profiles, households, audits int
must(t, f.s.pool.QueryRow(f.ctx, `SELECT
(SELECT count(*) FROM subject_profile WHERE tenant_id=$1::uuid AND preferred_name=$2),
(SELECT count(*) FROM household WHERE tenant_id=$1::uuid AND name=$3),
(SELECT count(*) FROM audit_event WHERE request_id=$4)`, f.tenant, in.Name, in.HouseholdName, requestID).Scan(&profiles, &households, &audits))
if profiles != 0 || households != 0 || audits != 0 {
t.Fatalf("partial creation survived: profiles=%d households=%d audits=%d", profiles, households, audits)
}
_, err = f.s.pool.Exec(f.ctx, fmt.Sprintf("DROP TRIGGER %s ON consent_record; DROP FUNCTION %s();", name, name))
must(t, err)
_, err = f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in)
must(t, err)
}
func TestProfileCreationIncludesMemoryConsentAndReplay(t *testing.T) {
f := newFixture(t)
in := ProfileInput{HouseholdName: "选项授权测试家庭", Name: "选项授权测试老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentTranscriptStorage, saas.ConsentPersonalMemory}, Confirmed: true}}
requestID := uuid.NewString()
created, err := f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in)
must(t, err)
replayed, err := f.s.CreateProfile(f.ctx, f.web, requestID, requestJSON(in), in)
must(t, err)
if replayed["elder_id"] != created["elder_id"] {
t.Fatal("profile replay created another elder")
}
var count, audits int
var enabled bool
must(t, f.s.pool.QueryRow(f.ctx, `SELECT
(SELECT count(*) FROM consent_record c WHERE c.subject_id=sp.id),
(SELECT count(*) FROM audit_event WHERE request_id=$2 AND action='consent.grant'),
(SELECT effective_consents @> '{"voice_chat":true,"transcript_storage":true,"personalized_memory":true,"raw_audio_storage":false,"story_extraction":false,"family_sharing":false}'::jsonb AND jsonb_array_length(source_consent_ids)=2 FROM consent_snapshot WHERE subject_id=sp.id ORDER BY snapshot_version DESC LIMIT 1)
FROM subject_profile sp WHERE sp.public_id=$1::uuid`, created["elder_id"], requestID).Scan(&count, &audits, &enabled))
if count != 2 || audits != 2 || !enabled {
t.Fatalf("default consents count=%d audits=%d enabled=%v", count, audits, enabled)
}
}
func TestMemoryWithdrawalSurvivesDeviceBinding(t *testing.T) {
f := newFixture(t)
api := saas.NewService(f.s.pool, audit.NewRepository(), nil, nil)
consents, err := api.ListConsents(f.ctx, f.user, f.elder)
must(t, err)
for _, c := range consents {
_, err = api.WithdrawConsent(f.ctx, f.user, f.elder, c.ID, uuid.NewString())
must(t, err)
}
f.bound()
var enabled bool
must(t, f.s.pool.QueryRow(f.ctx, `SELECT call_memory_authorized(id) FROM subject_profile WHERE public_id=$1::uuid`, f.elder).Scan(&enabled))
if enabled {
t.Fatal("device binding restored withdrawn memory consent")
}
}
func TestCreationGrantsOnlyExplicitlySelectedPermissions(t *testing.T) {
f := newFixture(t)
for _, tc := range []struct {
name string
selected []string
}{
{"legacy", nil},
{"subtitle_only", []string{saas.ConsentTranscriptStorage}},
{"all", []string{saas.ConsentTranscriptStorage, saas.ConsentRawAudioStorage, saas.ConsentStoryExtraction, saas.ConsentPersonalMemory, saas.ConsentFamilySharing}},
} {
t.Run(tc.name, func(t *testing.T) {
in := ProfileInput{HouseholdName: "授权选择测试" + tc.name, Name: "测试老人", InitialConsents: saas.InitialConsentSelection{Types: tc.selected, Confirmed: len(tc.selected) > 0}}
created, err := f.s.CreateProfile(f.ctx, f.web, uuid.NewString(), requestJSON(in), in)
must(t, err)
var kinds []string
var sourceCount int
must(t, f.s.pool.QueryRow(f.ctx, `SELECT ARRAY(SELECT consent_type FROM consent_record WHERE subject_id=sp.id ORDER BY consent_type), (SELECT jsonb_array_length(source_consent_ids) FROM consent_snapshot WHERE subject_id=sp.id ORDER BY snapshot_version DESC LIMIT 1) FROM subject_profile sp WHERE public_id=$1::uuid`, created["elder_id"]).Scan(&kinds, &sourceCount))
if len(kinds) != len(tc.selected) || sourceCount != len(tc.selected) {
t.Fatalf("grants=%v sourceCount=%d wanted=%v", kinds, sourceCount, tc.selected)
}
for _, kind := range kinds {
found := false
for _, selected := range tc.selected {
found = found || kind == selected
}
if !found {
t.Fatalf("unselected permission granted: %s", kind)
}
}
})
}
bad := ProfileInput{HouseholdName: "未确认家庭", Name: "未确认老人", InitialConsents: saas.InitialConsentSelection{Types: []string{saas.ConsentPersonalMemory}}}
if _, err := f.s.CreateProfile(f.ctx, f.web, uuid.NewString(), requestJSON(bad), bad); err == nil {
t.Fatal("unconfirmed consent was accepted")
}
var count int
must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM household WHERE tenant_id=$1::uuid AND name=$2`, f.tenant, bad.HouseholdName).Scan(&count))
if count != 0 {
t.Fatal("unconfirmed selection left an orphan household")
}
}