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
+60 -96
View File
@@ -1,22 +1,23 @@
package opsdevice
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"os"
"regexp"
"strings"
"testing"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/devicebinding"
"data-terminal-cloud/internal/identity"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestOperationsDeviceLifecycle(t *testing.T) {
func TestOperationsDeviceCreatesPhysicalConfirmIdentity(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not set")
@@ -31,90 +32,16 @@ func TestOperationsDeviceLifecycle(t *testing.T) {
if err != nil {
t.Fatal(err)
}
service, err := NewService(pool, resolver, audit.NewRepository())
if err != nil {
t.Fatal(err)
}
principal := integrationOperatorPrincipal(t, ctx, pool)
const tenantID = "10000000-0000-0000-0000-000000000010"
created, err := service.Create(ctx, principal, tenantID, "ops-device-integration-create", CreateInput{
Model: "integration-terminal", HardwareRevision: "rev-test",
HouseholdID: "10000000-0000-0000-0000-000000000211", SubjectID: "10000000-0000-0000-0000-000000000411",
VoiceRuntime: "volc_hardware_agent",
service, err := NewService(pool, resolver, audit.NewRepository(), Options{
OnboardingMasterKey: strings.Repeat("15", 32),
DeviceOrigin: "https://platform.example/onboarding/entry",
})
if err != nil {
t.Fatal(err)
}
deviceID := created.Device.ID
defer func() {
_, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE resource_id=$1::uuid`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_activation_transition WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM hardware_agent_device_binding WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_binding WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_credential WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_twin WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device WHERE public_id=$1::uuid`, deviceID)
}()
if len(created.Credential.Secret) != 64 || created.Credential.KeyVersion != 1 || created.Device.SubjectName == nil || *created.Device.SubjectName != "测试长者" ||
created.Device.ActivationState != "BOUND" ||
created.Device.VoiceRuntime != "volc_hardware_agent" || created.Device.HardwareAgentBotID != "" || created.Device.HardwareAgentTransport != "" || created.Device.HardwareAgentMinimumSDKVersion != "" || created.Device.HardwareAgentStatus != "" {
t.Fatalf("unexpected created result: %+v", created)
}
if created.Claim != nil {
t.Fatalf("assigned device unexpectedly received claim: %+v", created.Claim)
}
if _, err := service.IssueDeviceClaim(ctx, principal, tenantID, deviceID, "ops-device-integration-bound-claim"); !errors.Is(err, ErrClaimUnavailable) {
t.Fatalf("bound device claim error=%v", err)
}
resolved, err := resolver.Resolve(ctx, "device-secret://"+deviceID)
if err != nil || !bytes.Equal(resolved, []byte(created.Credential.Secret)) {
t.Fatalf("created credential did not resolve: err=%v", err)
}
model := "integration-terminal-v2"
status := "quarantined"
updated, err := service.Update(ctx, principal, tenantID, deviceID, "ops-device-integration-update", created.Device.Version, UpdateInput{Model: &model, Status: &status})
if err != nil || updated.Model != model || updated.Status != status || updated.Version != created.Device.Version+1 {
t.Fatalf("unexpected update: %+v err=%v", updated, err)
}
rotated, err := service.RotateCredential(ctx, principal, tenantID, deviceID, "ops-device-integration-rotate")
if err != nil || rotated.KeyVersion != 2 || rotated.Secret == created.Credential.Secret {
t.Fatalf("unexpected credential rotation: %+v err=%v", rotated, err)
}
resolved, err = resolver.Resolve(ctx, "device-secret://"+deviceID)
if err != nil || !bytes.Equal(resolved, []byte(rotated.Secret)) {
t.Fatalf("rotated credential did not resolve: err=%v", err)
}
retired, err := service.Delete(ctx, principal, tenantID, deviceID, "ops-device-integration-delete", updated.Version)
if err != nil || retired.Status != "revoked" || retired.ActivationState != "REVOKED" || retired.CredentialStatus != "revoked" || retired.HouseholdID != nil || retired.HardwareAgentStatus != "" {
t.Fatalf("unexpected delete result: %+v err=%v", retired, err)
}
if _, err := resolver.Resolve(ctx, "device-secret://"+deviceID); err == nil {
t.Fatal("retired device credential remained active")
}
}
func TestOperationsDeviceClaimIssueAndRotation(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not set")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, databaseURL)
if err != nil {
t.Fatal(err)
}
defer pool.Close()
resolver, err := deviceauth.NewDatabaseSecretResolver(pool, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
if err != nil {
t.Fatal(err)
}
service, err := NewService(pool, resolver, audit.NewRepository())
if err != nil {
t.Fatal(err)
}
principal := integrationOperatorPrincipal(t, ctx, pool)
const tenantID = "10000000-0000-0000-0000-000000000010"
created, err := service.Create(ctx, principal, tenantID, "ops-device-claim-integration-create", CreateInput{Model: "unbound-integration-terminal"})
created, err := service.Create(ctx, principal, tenantID, "ops-device-integration-create", CreateInput{Model: "ECHOEAR-S3"})
if err != nil {
t.Fatal(err)
}
@@ -122,29 +49,66 @@ func TestOperationsDeviceClaimIssueAndRotation(t *testing.T) {
defer func() {
_, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE resource_id=$1::uuid`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_activation_transition WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_claim WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_credential WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_onboarding_key WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_twin WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device WHERE public_id=$1::uuid`, deviceID)
}()
if created.Device.ActivationState != "CLAIMABLE" || created.Claim == nil || len(created.Claim.ClaimCode) != 19 || !created.Claim.ExpiresAt.After(created.Claim.IssuedAt) {
t.Fatalf("unexpected initial claim: %+v", created.Claim)
credential := created.Credential
if !regexp.MustCompile(`^KP01-[0-9A-F]{12}$`).MatchString(credential.FactoryID) || credential.FactoryID != created.Device.FactoryID || credential.Model != "ECHOEAR-S3" || credential.Origin != "https://platform.example" || credential.KeyVersion != 1 {
t.Fatalf("unexpected burn-in identity: %+v", credential)
}
firstDigest := sha256.Sum256([]byte(created.Claim.ClaimCode))
var storedDigest string
if err := pool.QueryRow(ctx, `SELECT token_digest FROM device_claim WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid) AND revoked_at IS NULL`, deviceID).Scan(&storedDigest); err != nil || storedDigest != hex.EncodeToString(firstDigest[:]) {
t.Fatalf("stored initial claim digest=%s err=%v", storedDigest, err)
key, err := hex.DecodeString(credential.DeviceKeyHex)
if err != nil || len(key) != 32 {
t.Fatalf("invalid device key: len=%d err=%v", len(key), err)
}
rotated, err := service.IssueDeviceClaim(ctx, principal, tenantID, deviceID, "ops-device-claim-integration-rotate")
if err != nil || rotated.ClaimCode == created.Claim.ClaimCode {
t.Fatalf("rotated claim=%+v err=%v", rotated, err)
if created.Device.EnrollmentMode != "physical_confirm" || created.Device.ActivationState != "CLAIMABLE" || created.Device.Bound || created.Device.CredentialStatus != "active" || created.Device.KeyVersion != 1 {
t.Fatalf("unexpected created device: %+v", created.Device)
}
var activeClaims, revokedClaims int
if err := pool.QueryRow(ctx, `SELECT count(*) FILTER (WHERE revoked_at IS NULL),count(*) FILTER (WHERE revoked_at IS NOT NULL) FROM device_claim WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID).Scan(&activeClaims, &revokedClaims); err != nil {
var released bool
var desiredConfig string
var activeKeys, legacyCredentials, claims, bindings int
if err := pool.QueryRow(ctx, `SELECT onboarding_released_at IS NOT NULL FROM device WHERE public_id=$1::uuid`, deviceID).Scan(&released); err != nil {
t.Fatal(err)
}
if activeClaims != 1 || revokedClaims != 1 {
t.Fatalf("claim counts active=%d revoked=%d", activeClaims, revokedClaims)
if err := pool.QueryRow(ctx, `SELECT desired_config::text FROM device_twin WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID).Scan(&desiredConfig); err != nil {
t.Fatal(err)
}
if err := pool.QueryRow(ctx, `SELECT
(SELECT count(*) FROM device_onboarding_key WHERE device_id=d.id AND status='ACTIVE'),
(SELECT count(*) FROM device_credential WHERE device_id=d.id),
(SELECT count(*) FROM device_claim WHERE device_id=d.id),
(SELECT count(*) FROM device_binding WHERE device_id=d.id AND unbound_at IS NULL)
FROM device d WHERE d.public_id=$1::uuid`, deviceID).Scan(&activeKeys, &legacyCredentials, &claims, &bindings); err != nil {
t.Fatal(err)
}
if !released || desiredConfig != "{}" || activeKeys != 1 || legacyCredentials != 0 || claims != 0 || bindings != 0 {
t.Fatalf("device was not created as direct BLE inventory: released=%v desired=%s active_keys=%d legacy_credentials=%d claims=%d bindings=%d", released, desiredConfig, activeKeys, legacyCredentials, claims, bindings)
}
if _, err := service.IssueDeviceClaim(ctx, principal, tenantID, deviceID, "ops-device-integration-legacy-claim"); !errors.Is(err, devicebinding.ErrConfirmationRequired) {
t.Fatalf("physical-confirm device accepted a legacy claim: %v", err)
}
rotated, err := service.RotateCredential(ctx, principal, tenantID, deviceID, "ops-device-integration-rotate")
if err != nil || rotated.KeyVersion != 2 || rotated.DeviceKeyHex == credential.DeviceKeyHex || rotated.FactoryID != credential.FactoryID {
t.Fatalf("unexpected credential rotation: %+v err=%v", rotated, err)
}
var activeVersion, revokedKeys int
if err := pool.QueryRow(ctx, `SELECT max(key_version) FILTER (WHERE status='ACTIVE'),count(*) FILTER (WHERE status='REVOKED') FROM device_onboarding_key WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID).Scan(&activeVersion, &revokedKeys); err != nil {
t.Fatal(err)
}
if activeVersion != 2 || revokedKeys != 1 {
t.Fatalf("unexpected rotated key state: active=%d revoked=%d", activeVersion, revokedKeys)
}
var version int64
if err := pool.QueryRow(ctx, `SELECT version FROM device WHERE public_id=$1::uuid`, deviceID).Scan(&version); err != nil {
t.Fatal(err)
}
retired, err := service.Delete(ctx, principal, tenantID, deviceID, "ops-device-integration-delete", version)
if err != nil || retired.Status != "revoked" || retired.ActivationState != "REVOKED" || retired.CredentialStatus != "missing" {
t.Fatalf("unexpected delete result: %+v err=%v", retired, err)
}
}