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
+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)
}
}