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
+170
View File
@@ -0,0 +1,170 @@
// Controlled, explicitly approved migration of one legacy device. Never deletes history.
package main
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"regexp"
"strings"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/deviceactivation"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
func secret(name string) (string, error) {
if path := os.Getenv(name + "_FILE"); path != "" {
b, err := os.ReadFile(path)
return strings.TrimSpace(string(b)), err
}
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return "", fmt.Errorf("%s is not configured", name)
}
return value, nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
public := flag.String("device", "", "exact existing public UUID")
binding := flag.String("expected-binding", "", "exact current binding UUID, or none")
mac := flag.String("factory-mac", "", "verified hardware MAC, uppercase 12 hex")
output := flag.String("output", "", "new private credential JSON file")
confirmed := flag.Bool("confirm-unbind-and-migrate", false, "requires explicit owner authorization")
dryRun := flag.Bool("dry-run", false, "validate the complete transaction, then roll it back without exporting keys")
flag.Parse()
if !*confirmed || uuid.Validate(*public) != nil || (*binding != "none" && uuid.Validate(*binding) != nil) || !regexp.MustCompile(`^[0-9A-F]{12}$`).MatchString(*mac) || *output == "" {
return errors.New("exact device, expected binding, factory MAC, private output and explicit confirmation are required")
}
if _, err := os.Stat(*output); !errors.Is(err, os.ErrNotExist) {
return errors.New("credential output already exists or is inaccessible; inspect previous attempt before retry")
}
dsn, err := secret("DATABASE_URL")
if err != nil {
return err
}
masterHex, err := secret("ONBOARDING_MASTER_KEY")
if err != nil {
return err
}
master, err := hex.DecodeString(masterHex)
if err != nil || len(master) != 32 {
return errors.New("invalid onboarding master key")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
return err
}
defer pool.Close()
tx, err := pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
var id, tenant, mode, lifecycle, status string
err = tx.QueryRow(ctx, `SELECT id::text,tenant_id::text,enrollment_mode,activation_state,status FROM device WHERE public_id=$1::uuid FOR UPDATE`, *public).Scan(&id, &tenant, &mode, &lifecycle, &status)
if err != nil {
return err
}
if mode != "legacy" || status != "registered" || (lifecycle != "ACTIVE" && lifecycle != "BOUND" && lifecycle != "CLAIMABLE") {
return errors.New("device is not an eligible legacy identity; no changes made")
}
var current string
var busy, hasKey bool
err = tx.QueryRow(ctx, `SELECT COALESCE((SELECT id::text FROM device_binding WHERE device_id=$1::uuid AND unbound_at IS NULL),'none'),EXISTS(SELECT 1 FROM conversation WHERE device_id=$1::uuid AND status NOT IN ('COMPLETED','FAILED','EXPIRED')),EXISTS(SELECT 1 FROM device_onboarding_key WHERE device_id=$1::uuid)`, id).Scan(&current, &busy, &hasKey)
if err != nil {
return err
}
if current != *binding || busy || hasKey {
return errors.New("binding changed, conversation still running, or onboarding key already exists; no changes made")
}
requestID := uuid.NewString()
if current != "none" {
if _, err = tx.Exec(ctx, `UPDATE device_binding SET unbound_at=now(),reason='用户明确授权:迁移蓝牙配网,保留历史记录' WHERE id=$1::uuid AND unbound_at IS NULL`, current); err != nil {
return err
}
}
if _, err = deviceactivation.Apply(ctx, tx, deviceactivation.Input{DeviceID: id, TenantID: tenant, Target: deviceactivation.Claimable, Reason: "用户授权旧设备迁移蓝牙注册", Source: "ble-device-migrate", ActorType: "system", RequestID: requestID}); err != nil {
return err
}
for _, sql := range []string{
`UPDATE device_claim SET revoked_at=now() WHERE device_id=$1::uuid AND used_at IS NULL AND revoked_at IS NULL`,
`UPDATE hardware_agent_provider_credential SET status='SUSPENDED',updated_at=now() WHERE device_id=$1::uuid AND status='ACTIVE'`,
`UPDATE hardware_agent_device_binding SET status='DISABLED',updated_at=now() WHERE device_id=$1::uuid AND status='ACTIVE'`,
} {
if _, err = tx.Exec(ctx, sql, id); err != nil {
return err
}
}
factoryID := "KP01-" + *mac
sum := sha256.Sum256([]byte(factoryID))
_, err = tx.Exec(ctx, `UPDATE device SET enrollment_mode='physical_confirm',factory_device_id=$2,factory_mac=$3,model='ECHOEAR-S3',hardware_revision='V1.2',credential_ref=$4,serial_digest=$5,onboarding_released_at=now(),onboarding_boot_id=NULL,onboarding_revision=onboarding_revision+1,version=version+1,updated_at=now() WHERE id=$1::uuid`, id, factoryID, *mac, "onboarding://"+factoryID, hex.EncodeToString(sum[:]))
if err != nil {
return err
}
key := make([]byte, 32)
if _, err = rand.Read(key); err != nil {
return err
}
block, err := aes.NewCipher(master)
if err != nil {
return err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonce := make([]byte, aead.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return err
}
encrypted := aead.Seal(nonce, nonce, key, []byte("ONBOARDING-KEY1\n"+id+"\n1"))
_, err = tx.Exec(ctx, `INSERT INTO device_onboarding_key(device_id,tenant_id,key_version,ciphertext,status,activated_at) VALUES($1::uuid,$2::uuid,1,$3,'ACTIVE',now())`, id, tenant, encrypted)
if err != nil {
return err
}
if err = audit.NewRepository().Record(ctx, tx, audit.Event{TenantID: &tenant, ActorType: "system", Action: "device.ble.migrate", ResourceType: "device", ResourceID: public, RequestID: requestID, Result: "success", Detail: map[string]any{"factory_device_id": factoryID, "previous_binding_id": current, "user_approved_unbind": true, "history_preserved": true}}); err != nil {
return err
}
if *dryRun {
if err = tx.Rollback(ctx); err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"validated": true, "rolled_back": true, "device_id": factoryID})
}
// Persist private recovery material before committing the identity mutation.
f, err := os.OpenFile(*output, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
if err != nil {
return err
}
err = json.NewEncoder(f).Encode(map[string]any{"device_id": factoryID, "platform_device_id": *public, "model": "ECHOEAR-S3", "key_version": 1, "device_key_hex": hex.EncodeToString(key), "request_id": requestID})
if err == nil {
err = f.Sync()
}
closeErr := f.Close()
if err != nil {
return err
}
if closeErr != nil {
return closeErr
}
if err = tx.Commit(ctx); err != nil {
return fmt.Errorf("commit uncertain; retain private output and inspect device state: %w", err)
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"migrated": true, "device_id": factoryID, "platform_device_id": *public, "history_preserved": true, "request_id": requestID})
}
+201
View File
@@ -0,0 +1,201 @@
// call-memory-admin is an operator-only maintenance command. It requires direct
// database access; evidence references are audit attestations, not test results.
package main
import (
"context"
"data-terminal-cloud/internal/callmemory"
"encoding/json"
"flag"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"os"
"strings"
"time"
)
func main() {
if e := run(); e != nil {
fmt.Fprintln(os.Stderr, "call-memory-admin:", e)
os.Exit(1)
}
}
type profileFile struct {
ID string `json:"id"`
SDK string `json:"sdk_version"`
Bot string `json:"bot_id"`
Model string `json:"model_version"`
Transport string `json:"transport"`
Audio map[string]any `json:"audio_profile"`
Template map[string]any `json:"vendor_template"`
Mapping map[string]string `json:"field_mapping"`
Capabilities map[string]bool `json:"capabilities"`
Evidence map[string]string `json:"evidence"`
Schema json.RawMessage `json:"projection_schema"`
}
func run() error {
op := flag.String("operation", "", "import-profile, attest-call-stopped, attest-legacy-cleanup, attest-backup-cleanup, export-deletion-ledger, replay-deletion-ledger, retry-projection")
file := flag.String("file", "", "input JSON file (export writes to stdout)")
subject := flag.String("subject-id", "", "internal subject UUID")
session := flag.String("session-id", "", "business session UUID for attest-call-stopped")
evidence := flag.String("evidence", "", "immutable audit record reference")
publish := flag.Bool("publish", false, "publish profile after capability validation")
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
p, e := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if e != nil {
return fmt.Errorf("database configuration invalid")
}
defer p.Close()
bytes := func(v any) []byte { b, _ := json.Marshal(v); return b }
switch *op {
case "attest-call-stopped":
if _, e = uuid.Parse(*session); e != nil || strings.TrimSpace(*evidence) == "" {
return fmt.Errorf("session-id and evidence are required")
}
tx, e := p.Begin(ctx)
if e != nil {
return e
}
defer tx.Rollback(context.Background())
var sid string
var localOff, sdkStopped bool
if e = tx.QueryRow(ctx, `SELECT subject_id::text,local_audio_off,sdk_stopped FROM call_session WHERE id=$1::uuid FOR UPDATE`, *session).Scan(&sid, &localOff, &sdkStopped); e != nil {
return e
}
if !localOff || !sdkStopped {
return fmt.Errorf("device must first prove local audio off and SDK stopped")
}
if _, e = tx.Exec(ctx, `UPDATE call_session SET remote_state='STOPPED_OBSERVED' WHERE id=$1::uuid`, *session); e != nil {
return e
}
if _, e = tx.Exec(ctx, `INSERT INTO call_memory_operator_evidence(subject_id,call_session_id,operation,evidence_ref) VALUES($1::uuid,$2::uuid,'attest-call-stopped',$3)`, sid, *session, *evidence); e != nil {
return e
}
return tx.Commit(ctx)
case "import-profile":
raw, e := os.ReadFile(*file)
if e != nil {
return e
}
var in profileFile
if e = callmemory.StrictJSON(raw, &in); e != nil {
return e
}
profile := callmemory.Profile{ID: in.ID, SDKVersion: in.SDK, BotID: in.Bot, ModelVersion: in.Model, Transport: in.Transport, Audio: in.Audio, Template: in.Template, Mapping: in.Mapping, Capabilities: in.Capabilities, Evidence: in.Evidence}
if *publish {
if _, e = profile.Compile("OFF", "", "", "", "", "test"); e != nil {
return e
}
if profile.Capabilities["H03"] || profile.Capabilities["H12"] {
if e = profile.Check("NATIVE_VIKING"); e != nil {
return e
}
}
}
if len(in.Schema) == 0 {
in.Schema = json.RawMessage(`{}`)
}
_, e = p.Exec(ctx, `INSERT INTO call_provider_profile(id,protocol_version,sdk_version,bot_id,model_version,transport,audio_profile,vendor_template,field_mapping,capabilities,evidence,projection_schema,published) VALUES($1,'CALL-MEMORY-1.1',$2,$3,$4,$5,$6::jsonb,$7::jsonb,$8::jsonb,$9::jsonb,$10::jsonb,$11::jsonb,$12)`, in.ID, in.SDK, in.Bot, in.Model, in.Transport, bytes(in.Audio), bytes(in.Template), bytes(in.Mapping), bytes(in.Capabilities), bytes(in.Evidence), in.Schema, *publish)
if e != nil {
return fmt.Errorf("profile insert failed (existing published profiles are immutable)")
}
case "attest-legacy-cleanup", "attest-backup-cleanup", "retry-projection":
if _, e = uuid.Parse(*subject); e != nil || strings.TrimSpace(*evidence) == "" {
return fmt.Errorf("subject-id and evidence are required")
}
tx, e := p.Begin(ctx)
if e != nil {
return e
}
defer tx.Rollback(context.Background())
if *op == "attest-legacy-cleanup" {
// Requires proof of old namespace cleanup and no unresolved old runtime.
var active bool
if e = tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM conversation WHERE subject_id=$1::uuid AND runtime_type='volc_hardware_agent' AND status NOT IN ('COMPLETED','FAILED'))`, *subject).Scan(&active); e != nil {
return e
}
if active {
return fmt.Errorf("legacy call still unresolved")
}
_, e = tx.Exec(ctx, `UPDATE memory_user_state SET legacy_projection_pending=false,updated_at=now() WHERE subject_id=$1::uuid`, *subject)
} else if *op == "attest-backup-cleanup" {
_, e = tx.Exec(ctx, `UPDATE memory_deletion_ledger SET backup_cleanup_verified_at=now(),evidence_ref=$2 WHERE subject_id=$1::uuid AND backup_cleanup_verified_at IS NULL`, *subject, *evidence)
} else {
_, e = tx.Exec(ctx, `UPDATE memory_projection_job SET state='RECONCILING',last_error=NULL,next_attempt_at=now() WHERE subject_id=$1::uuid AND state='BLOCKED'`, *subject)
}
if e != nil {
return e
}
_, e = tx.Exec(ctx, `INSERT INTO call_memory_operator_evidence(subject_id,operation,evidence_ref) VALUES($1::uuid,$2,$3)`, *subject, *op, *evidence)
if e != nil {
return e
}
if e = tx.Commit(ctx); e != nil {
return e
}
case "export-deletion-ledger":
var data []byte
e = p.QueryRow(ctx, `SELECT COALESCE(jsonb_agg(jsonb_build_object('memory_id',memory_id,'subject_id',subject_id,'privacy_epoch',privacy_epoch,'scope',scope)),'[]'::jsonb) FROM memory_deletion_ledger`).Scan(&data)
if e != nil {
return e
}
fmt.Println(string(data))
return nil
case "replay-deletion-ledger":
raw, e := os.ReadFile(*file)
if e != nil {
return e
}
var entries []struct {
Memory string `json:"memory_id"`
Subject string `json:"subject_id"`
Epoch int64 `json:"privacy_epoch"`
Scope string `json:"scope"`
}
if e = callmemory.StrictJSON(raw, &entries); e != nil {
return e
}
tx, e := p.Begin(ctx)
if e != nil {
return e
}
defer tx.Rollback(context.Background())
for _, r := range entries {
if _, e = uuid.Parse(r.Memory); e != nil {
return e
}
if _, e = uuid.Parse(r.Subject); e != nil {
return e
}
if r.Epoch < 0 || (r.Scope != "MEMORY_AND_DERIVED" && r.Scope != "SOURCE_AND_DERIVED") {
return fmt.Errorf("invalid ledger")
}
// Restore only matching records. The normal deletion pipeline creates fresh
// projection work and revokes calls; external service stays offline until done.
_, e = tx.Exec(ctx, `UPDATE curated_memory SET status='PENDING_DELETE',proactive_policy='NEVER',version=version+1 WHERE id=$1::uuid AND subject_id=$2::uuid AND status NOT IN('PENDING_DELETE','DELETED')`, r.Memory, r.Subject)
if e != nil {
return e
}
_, e = tx.Exec(ctx, `INSERT INTO memory_deletion_ledger(memory_id,privacy_epoch,subject_id,scope) SELECT id,$3,subject_id,$4 FROM curated_memory WHERE id=$1::uuid AND subject_id=$2::uuid ON CONFLICT(memory_id,privacy_epoch) DO UPDATE SET scope=CASE WHEN excluded.scope='SOURCE_AND_DERIVED' THEN excluded.scope ELSE memory_deletion_ledger.scope END,backup_cleanup_verified_at=NULL`, r.Memory, r.Subject, r.Epoch, r.Scope)
if e != nil {
return e
}
_, e = tx.Exec(ctx, `INSERT INTO memory_mutation(id,tenant_id,subject_id,action,request_digest,state,targets,scope,memory_revision,privacy_epoch) SELECT gen_random_uuid(),cm.tenant_id,cm.subject_id,'DELETE','restore-ledger','PROPAGATING',jsonb_build_array(cm.public_id::text),$3,us.memory_revision,us.privacy_epoch FROM curated_memory cm JOIN memory_user_state us ON us.subject_id=cm.subject_id WHERE cm.id=$1::uuid AND cm.subject_id=$2::uuid AND NOT EXISTS(SELECT 1 FROM memory_mutation mm WHERE mm.subject_id=cm.subject_id AND mm.action='DELETE' AND mm.targets @> jsonb_build_array(cm.public_id::text) AND mm.state IN('PROPAGATING','BLOCKED'))`, r.Memory, r.Subject, r.Scope)
if e != nil {
return e
}
}
if e = tx.Commit(ctx); e != nil {
return e
}
default:
return fmt.Errorf("unknown operation; use -help")
}
fmt.Println("operation recorded")
return nil
}
+106
View File
@@ -0,0 +1,106 @@
// Regenerates the CALL-MEMORY extension from the public Go request models.
package main
import (
"data-terminal-cloud/internal/callmemory"
"encoding/json"
"os"
"reflect"
"strings"
"time"
)
func schema(t reflect.Type) map[string]any {
if t.Kind() == reflect.Pointer {
return map[string]any{"anyOf": []any{schema(t.Elem()), map[string]any{"type": "null"}}}
}
if t == reflect.TypeOf(time.Time{}) {
return map[string]any{"type": "string", "format": "date-time"}
}
if t == reflect.TypeOf(json.RawMessage{}) {
return map[string]any{}
}
switch t.Kind() {
case reflect.String:
return map[string]any{"type": "string"}
case reflect.Bool:
return map[string]any{"type": "boolean"}
case reflect.Int, reflect.Int64:
return map[string]any{"type": "integer", "format": "int64", "minimum": 0}
case reflect.Slice:
return map[string]any{"type": "array", "items": schema(t.Elem())}
case reflect.Map:
return map[string]any{"type": "object", "additionalProperties": schema(t.Elem())}
case reflect.Struct:
props := map[string]any{}
required := []string{}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
tag := strings.Split(f.Tag.Get("json"), ",")
if tag[0] == "-" {
continue
}
if f.Anonymous {
nested := schema(f.Type)
for k, v := range nested["properties"].(map[string]any) {
props[k] = v
}
required = append(required, nested["required"].([]string)...)
continue
}
name := tag[0]
if name == "" {
name = f.Name
}
props[name] = schema(f.Type)
if !strings.Contains(f.Tag.Get("json"), "omitempty") {
required = append(required, name)
}
}
return map[string]any{"type": "object", "additionalProperties": false, "properties": props, "required": required}
default:
return map[string]any{}
}
}
func main() {
device := map[string]any{"warmup": callmemory.WarmupInput{}, "prepare": callmemory.PrepareInput{}, "events": callmemory.EventsInput{}, "heartbeat": callmemory.HeartbeatInput{}, "end": callmemory.EndInput{}, "status": callmemory.StatusInput{}, "command-acks": callmemory.AckInput{}}
memory := map[string]any{"query": callmemory.QueryInput{}, "mutations": callmemory.MutationInput{}, "mutations/resolve": callmemory.ResolveInput{}, "mutations/confirm": callmemory.ConfirmInput{}, "mutations/status": struct {
ID string `json:"mutation_id"`
}{}, "sync/status": struct {
ID string `json:"elder_id"`
}{}, "access/query": struct {
Elder string `json:"elder_id"`
}{}, "access/grant": callmemory.AccessInput{}}
paths := map[string]any{}
schemas := map[string]any{"Manifest": schema(reflect.TypeOf(callmemory.Manifest{}))}
for family, ops := range map[string]map[string]any{"device/calls": device, "memories": memory} {
for name, model := range ops {
key := strings.ReplaceAll(family+"/"+name, "/", "_")
security := "CustomerBearer"
description := "登录用户,独立记忆权限校验。正文上限 64 KiB,拒绝重复 JSON 键和未知字段。变更需提交稳定 mutation_id 与目标 record_version。"
parameters := []any{map[string]any{"in": "header", "name": "X-Request-ID", "required": family == "device/calls", "schema": map[string]any{"type": "string", "format": "uuid"}}}
if family == "device/calls" {
security = "DeviceHMAC"
description = "ONBOARDING-1.1 DEVREQ1 签名原始正文。nonce 5 分钟去重,时钟偏差不超过 120 秒。事件和 status 不续租;heartbeat 必须在启动 SDK 前成功,后续每 5 秒续租,绝对期限最长 45 秒。"
for _, header := range []string{"X-Device-ID", "X-Key-Version", "X-Timestamp-Ms", "X-Nonce"} {
parameters = append(parameters, map[string]any{"in": "header", "name": header, "required": true, "schema": map[string]any{"type": "string"}})
}
}
schemas[key] = schema(reflect.TypeOf(model))
responses := map[string]any{}
for _, status := range []string{"200", "400", "401", "403", "404", "409", "410", "413", "422", "429", "503"} {
responses[status] = map[string]any{"description": "CALL-MEMORY envelope; code/recovery_action are stable, data is present on success", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"$ref": "#/components/schemas/Envelope"}}}}
}
paths["/api/v1/"+family+"/"+name] = map[string]any{"post": map[string]any{"operationId": key, "description": description, "security": []any{map[string]any{security: []string{}}}, "parameters": parameters, "requestBody": map[string]any{"required": true, "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"$ref": "#/components/schemas/" + key}}}}, "responses": responses}}
}
}
schemas["Envelope"] = map[string]any{"type": "object", "required": []string{"ok", "code", "request_id", "server_time_ms"}, "properties": map[string]any{"ok": map[string]any{"type": "boolean"}, "code": map[string]any{"type": "string"}, "request_id": map[string]any{"type": "string"}, "server_time_ms": map[string]any{"type": "integer"}, "message": map[string]any{"type": "string"}, "recovery_action": map[string]any{"type": "string"}, "retry_after_ms": map[string]any{"type": "integer"}, "data": map[string]any{"type": "object", "additionalProperties": true}}}
doc := map[string]any{"openapi": "3.1.0", "info": map[string]any{"title": "CALL-MEMORY-1.1", "version": "1.1.0"}, "paths": paths, "components": map[string]any{"schemas": schemas, "securitySchemes": map[string]any{"DeviceHMAC": map[string]any{"type": "apiKey", "in": "header", "name": "Authorization", "description": "Device-HMAC-SHA256 <hex HMAC-SHA256 of DEVREQ1 canonical bytes>"}, "CustomerBearer": map[string]any{"type": "http", "scheme": "bearer"}}}}
b, e := json.MarshalIndent(doc, "", " ")
if e != nil {
panic(e)
}
if e = os.WriteFile("openapi/call-memory.yaml", append(b, '\n'), 0644); e != nil {
panic(e)
}
}
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"context"
"data-terminal-cloud/internal/onboarding"
"encoding/json"
"errors"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/redis/go-redis/v9"
"io"
"os"
"strings"
"time"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func secret(name string) (string, error) {
if file := os.Getenv(name + "_FILE"); file != "" {
b, e := os.ReadFile(file)
if e != nil {
return "", fmt.Errorf("read %s_FILE: %w", name, e)
}
return strings.TrimSpace(string(b)), nil
}
if v := os.Getenv(name); v != "" {
return v, nil
}
return "", fmt.Errorf("missing %s", name)
}
func run() error {
if len(os.Args) != 1 {
return errors.New("customer-phone-account reads provisioning JSON from standard input")
}
var in struct {
AccountKind string `json:"account_kind"`
UserID string `json:"user_id"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
Phone string `json:"phone_e164"`
Password string `json:"password"`
RequestID string `json:"request_id"`
}
raw, err := io.ReadAll(io.LimitReader(os.Stdin, 8193))
if err != nil {
return err
}
if err = onboarding.StrictJSON(raw, &in); err != nil {
return err
}
db, err := secret("DATABASE_URL")
if err != nil {
return err
}
master, err := secret("ONBOARDING_MASTER_KEY")
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, db)
if err != nil {
return errors.New("database configuration invalid")
}
defer pool.Close()
r := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDR")})
defer r.Close()
s, err := onboarding.NewService(pool, r, onboarding.Options{MasterKey: master, EntryURL: os.Getenv("ONBOARDING_ENTRY_URL")})
if err != nil {
return err
}
provision := s.ProvisionCustomerPhone
if in.AccountKind == "operator" {
provision = s.ProvisionOperatorPhone
} else if in.AccountKind != "" && in.AccountKind != "customer" {
return errors.New("invalid account_kind")
}
created, err := provision(ctx, in.UserID, in.Issuer, in.Subject, in.Phone, in.Password, in.RequestID)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"configured": true, "created": created, "user_id": in.UserID, "phone_masked": in.Phone[:len(in.Phone)-8] + "****" + in.Phone[len(in.Phone)-4:], "sms_verified": false})
}
+47
View File
@@ -0,0 +1,47 @@
// Backfill an existing elder using the same provisioner as POST /subjects.
package main
import (
"context"
"flag"
"fmt"
"os"
"data-terminal-cloud/internal/config"
"data-terminal-cloud/internal/database"
"data-terminal-cloud/internal/elderagent"
)
func main() {
subject := flag.String("subject", "", "existing elder public ID")
flag.Parse()
if *subject == "" {
fmt.Fprintln(os.Stderr, "--subject is required")
os.Exit(2)
}
cfg, err := config.Load("api")
if err != nil {
panic(err)
}
ctx := context.Background()
p, err := database.Open(ctx, cfg.DatabaseURL, "elder-agent")
if err != nil {
panic(err)
}
defer p.Close()
path := os.Getenv("DIRECT_CHAT_TEMPLATE_FILE")
if path == "" {
path = cfg.VolcVoiceChatTemplate
}
template, err := os.ReadFile(path)
if err != nil {
panic(err)
}
s := elderagent.Service{Pool: p, Provider: elderagent.NewClient(cfg.VolcAccessKeyID, cfg.VolcSecretAccessKey, cfg.VolcSessionToken, cfg.VolcRTCBaseURL, cfg.VolcRTCRegion).ForProduct(cfg.VolcHardwareInstanceID, cfg.VolcHardwareProductKey), Template: template, CallbackURL: cfg.SubtitleCallbackURL, CallbackSecret: []byte(cfg.SubtitleCallbackSecret)}
info, err := s.Ensure(ctx, *subject)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("subject=%s bot_id=%s status=%s\n", *subject, info.BotID, info.Status)
}
+105
View File
@@ -0,0 +1,105 @@
// Operator utility for an explicitly requested, attributed chat import.
// It uses the same permission, consent, extraction and memoir services as Web.
package main
import (
"context"
"data-terminal-cloud/internal/extraction"
"data-terminal-cloud/internal/identity"
"data-terminal-cloud/internal/memoir"
"data-terminal-cloud/internal/outbox"
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
"os"
"strings"
"time"
)
func secret(name string) string {
if file := os.Getenv(name + "_FILE"); file != "" {
b, e := os.ReadFile(file)
if e != nil {
panic("secret file unavailable")
}
return strings.TrimSpace(string(b))
}
return os.Getenv(name)
}
func main() {
if e := run(); e != nil {
fmt.Fprintln(os.Stderr, e)
os.Exit(1)
}
}
func run() error {
input := flag.String("input", "", "JSON file with issuer, account_subject, elder_id, import_id, title, transcript")
mode := flag.String("mode", "import", "import, extract, generate, inspect")
flag.Parse()
var in struct {
Issuer string `json:"issuer"`
Subject string `json:"account_subject"`
memoir.ImportInput
}
b, e := os.ReadFile(*input)
if e != nil {
return e
}
if e = json.Unmarshal(b, &in); e != nil {
return e
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
pool, e := pgxpool.New(ctx, secret("DATABASE_URL"))
if e != nil {
return errors.New("database unavailable")
}
defer pool.Close()
key := secret("ARK_API_KEY")
base := "https://ark.cn-beijing.volces.com/api/v3"
extractor, e := extraction.NewArkGenerator(base, key, "deepseek-v4-1-flash-260910", 90*time.Second)
if e != nil {
return e
}
editor, e := extraction.NewArkJSONGenerator(base, key, "doubao-seed-evolving", 180*time.Second, 65536)
if e != nil {
return e
}
s := &memoir.Service{Pool: pool, Generator: editor, Illustrator: memoir.NewIllustrator(base, key, "doubao-seedream-5-0-pro-260628"), Model: "doubao-seed-evolving"}
p := identity.Principal{Issuer: in.Issuer, Subject: in.Subject}
switch *mode {
case "import":
id, e := s.Import(ctx, p, in.ImportInput)
if e != nil {
return e
}
fmt.Println("import", id)
case "extract":
processor, e := extraction.NewProcessor(pool, extractor)
if e != nil {
return e
}
if e = processor.HandleImport(ctx, outbox.Event{AggregateID: in.ID}); e != nil {
return e
}
case "generate":
id, e := s.Enqueue(ctx, p, in.ElderID)
if e != nil {
return e
}
fmt.Println("job", id)
if e = s.Process(ctx, id); e != nil {
return e
}
case "inspect":
default:
return errors.New("unknown mode")
}
result, e := s.Query(ctx, p, in.ElderID)
if e != nil {
return e
}
return json.NewEncoder(os.Stdout).Encode(result)
}
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"data-terminal-cloud/internal/workflowbridge"
"log"
"net/http"
"os"
"strings"
"time"
)
func secret(name string) string {
if p := os.Getenv(name + "_FILE"); p != "" {
b, err := os.ReadFile(p)
if err != nil {
log.Fatal("secret file unavailable")
}
return strings.TrimSpace(string(b))
}
return strings.TrimSpace(os.Getenv(name))
}
func main() {
key, admin := secret("ARK_API_KEY"), secret("DIFY_BRIDGE_SECRET")
if key == "" || admin == "" {
log.Fatal("bridge credentials missing")
}
base := os.Getenv("ARK_BASE_URL")
if base == "" {
base = "https://ark.cn-beijing.volces.com/api/v3"
}
service, err := workflowbridge.NewArkService(admin, base, key)
if err != nil {
log.Fatal("model configuration invalid")
}
server := &http.Server{Addr: ":8188", Handler: service, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 30 * time.Second}
log.Fatal(server.ListenAndServe())
}