# tera-api Initial mock backend for TERA Core-X48 PRO management APIs. ## First user task Portable Go is installed under the repository `.tools/` directory. Run: ```powershell cd services\tera-api powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 test ./... powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 run ./cmd/tera-api powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-logd powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-hwd powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-seriald powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-serial-check powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-build.ps1 -Command tera-serial-line-check powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-linux-arm64-serial-termios.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-target-rootfs-staging.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-target-rootfs-staging.ps1 -InitSystem buildroot ``` For a same-origin Direct Mode attachment load against a deployed target, keep the web password in the process environment and run the dedicated client. The client sends the browser-required `Origin` header and `tera.direct.v1` subprotocol, consumes one-use stream tickets, keeps reading to service WebSocket heartbeats, validates one welcome frame for every attachment, checks the matching channel `session` logs, and closes every session during cleanup. With `-attachments 48`, every CH01-CH48 channel is covered once without sending serial payload; `192` covers four viewers per channel: ```powershell $env:TERA_TARGET_PASSWORD = '' powershell -NoProfile -ExecutionPolicy Bypass -File ..\..\scripts\use-go.ps1 run ./cmd/tera-target-session-load -base-url http://192.168.0.223:8083 -attachments 192 -hold-seconds 60 Remove-Item Env:\TERA_TARGET_PASSWORD ``` Then verify: ```powershell Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/system/status Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/system/services Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"username":"admin","password":"mock-admin-secret"}' http://127.0.0.1:8080/api/v1/auth/login Invoke-RestMethod -Method Post -Headers @{"Authorization"="Bearer "} http://127.0.0.1:8080/api/v1/auth/logout Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/me Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/tokens Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"name":"automation"}' http://127.0.0.1:8080/api/v1/auth/tokens Invoke-RestMethod -Method Delete -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/tokens/token_000001 Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/users Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"username":"fieldops","password":"fieldops-secret-1","role":"port_operator"}' http://127.0.0.1:8080/api/v1/auth/users Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":false}' http://127.0.0.1:8080/api/v1/auth/users/fieldops Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"password":"fieldops-secret-2"}' http://127.0.0.1:8080/api/v1/auth/users/fieldops/password Invoke-RestMethod -Method Delete -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/users/fieldops Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/groups Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/auth/permissions Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/status Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/export Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/time/settings Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"timezone":"Asia/Seoul","ntp_servers":["time1.example.local","time2.example.local"]}' http://127.0.0.1:8080/api/v1/time/settings Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/security/management-access Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"https_enabled":true,"https_port":8443,"ssh_enabled":true,"ssh_port":2222,"http_enabled":false,"http_port":80,"telnet_enabled":false,"telnet_port":23,"idle_timeout_minutes":30}' http://127.0.0.1:8080/api/v1/security/management-access Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"interfaces":[{"name":"Interface-1","address":"192.168.10.233/24","gateway":"192.168.10.1"}]}' http://127.0.0.1:8080/api/v1/network/running Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/ports Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/alarms/rules Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"mode":"Reverse Telnet"}' http://127.0.0.1:8080/api/v1/ports/CH01 Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":false}' http://127.0.0.1:8080/api/v1/alarms/rules/auth_failures Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/config/apply Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"browser_time":"2026-06-16T10:31:12+09:00","timezone":"Asia/Seoul"}' http://127.0.0.1:8080/api/v1/time/sync-browser Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/time/sync-ntp Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"target":"192.168.0.1"}' http://127.0.0.1:8080/api/v1/diagnostics/network-test Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"target":"8.8.8.8","count":4}' http://127.0.0.1:8080/api/v1/diagnostics/network-test Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/diagnostics/support-bundle Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/audit?actor=admin&order=desc&limit=100' Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/system?severity=warning&keyword=failover&from=2026-06-15T00:00:00Z&to=2026-06-17T00:00:00Z' Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/port?channel=CH01&keyword=LOGIN&actor=admin&from=2026-06-15T00:00:00Z&to=2026-06-17T00:00:00Z' Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} 'http://127.0.0.1:8080/api/v1/logs/links?kind=serial&resource=CH01' Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/policy Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/retention Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"port_days":180,"protected_usage_warning_pct":85}' http://127.0.0.1:8080/api/v1/logs/retention Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/logs/remote-syslog Invoke-RestMethod -Method Patch -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"enabled":true,"host":"syslog.local","port":1514,"protocol":"tcp","facility":"local4"}' http://127.0.0.1:8080/api/v1/logs/remote-syslog Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"log_type":"port","channel":"CH01"}' http://127.0.0.1:8080/api/v1/logs/export Invoke-WebRequest -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} -ContentType 'application/json' -Body '{"log_type":"port","channel":"CH01","actor":"admin","from":"2026-06-15T00:00:00Z","to":"2026-06-17T00:00:00Z"}' -OutFile tera-port-logs.jsonl http://127.0.0.1:8080/api/v1/logs/export/download Invoke-RestMethod -Method Post -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/ports/CH01/restart Invoke-RestMethod -Headers @{"X-Mock-Actor"="mock-admin"} http://127.0.0.1:8080/api/v1/operations ``` ## Current scope - Mock domain data with optional target-backed local service adapters and bounded file-backed state for host and rootfs verification. - In-memory `pending`/`running`/`persistent` config transaction state. - JSON request bodies are bounded by `TERA_API_MAX_REQUEST_BYTES` and must contain a single JSON value. Oversized bodies return `payload_too_large` with HTTP 413; malformed or trailing JSON returns `validation_failed`. - Audit events for user-verifiable command validation. By default these are in-memory. Set `TERA_API_AUDIT_LOG` to a JSON Lines file path to enable bounded file-backed prototype audit storage; set `TERA_API_AUDIT_MAX_EVENTS` to control the retained event count. - Config transaction state is in-memory by default. Set `TERA_API_CONFIG_STORE` to a JSON file path to retain prototype port, alarm, network, time, and log-retention `pending`/`running`/ `persistent` state across `tera-api` restarts. Prototype config export returns running, persistent, and pending port/alarm/network/time/log-retention/ management-access/remote-syslog state and requires `config.read`. - Operation state is in-memory by default. Set `TERA_API_OPERATION_STORE` to a JSON file path to retain prototype operation results across `tera-api` restarts. Operation result lookup and operation history listing require `operation.read`. - Prototype local auth users are in-memory by default. Set `TERA_API_AUTH_STORE` to a JSON file path to retain prototype users with nested password KDF records containing `algorithm`, `version`, `params`, `salt`, and `hash`. Local password hashing uses Argon2id by default; the record schema is versioned so a future FIPS-compatible `pbkdf2-hmac-sha256` mode can be added without changing the auth store shape. Local user creation and password reset enforce an eight-character minimum by default. `TERA_API_MIN_PASSWORD_LENGTH` may raise that policy up to 128 characters but cannot reduce it below eight. The target profile sets `TERA_API_AUTH_STORE=/var/lib/tera-api/auth.json` so provisioned users and disabled bootstrap accounts survive service restarts. - Prototype bearer sessions use crypto-random tokens, store token hashes in memory, expire after eight hours, and can be revoked through `POST /api/v1/auth/logout`. - Prototype API bearer tokens are available behind `auth.read`/`auth.write`. Raw token material is returned only on creation, stored by hash, and bounded by `TERA_API_AUTH_MAX_TOKENS` when configured. Token records are listed by opaque `token_id` without exposing raw token material and can be revoked by `DELETE /api/v1/auth/tokens/{token_id}`. - Prototype local login tracks repeated failures in memory and temporarily locks a normalized username after five failures for 15 minutes. Client responses remain generic; lock details are recorded only in the audit log. - Prototype local user, group, and permission read APIs are available behind `auth.read`. Prototype local user creation, enable/disable updates, and role updates, password resets/deletes are available behind `auth.write`. These responses intentionally exclude password KDF records, salts, hashes, session tokens, and token hashes. Deleting or demoting the last enabled administrator is rejected to avoid local lockout. Disabling, deleting, changing role, or resetting the password for a local user immediately revokes that user's existing bearer sessions and API bearer tokens. - Prototype audit log reads require `audit.read`; only `mock-admin` has this permission in the current mock policy. - System, structured port command/result, and serial/Ethernet link log search require `log.read`; audited log export and NDJSON download require `log.export`. `GET /api/v1/logs/policy` exposes the 365-day maximum, command/result and link capture, no-at-rest-encryption decision, and enabled bounded UART/Direct Mode payload capture with a 4-KiB per-record maximum. The production environment connects `tera-api` to the independent `tera-logd` Unix socket for authoritative reads/writes, retention, health, and filtered NDJSON export. - Mock log retention policy reads require `log.read`; updates require `config.write`, participate in the config transaction, and preserve protected logs across factory reset by policy. All retention values are limited to 1 through 365 days. - In explicit host-prototype mode, `TERA_API_SYSTEM_LOG_STORE`, `TERA_API_PORT_LOG_STORE`, and `TERA_API_LINK_LOG_STORE` enable bounded JSON Lines prototype persistence; `TERA_API_LOG_MAX_EVENTS` bounds each of those stores. Command and result fields are credential-redacted and size-bounded before persistence. A configured/enabled remote syslog target receives best-effort forwarded records through the existing alarm-delivery boundary. - Target packaging instead sets `TERA_API_LOG_SOCKET` to `/run/tera-logd/log-store.sock` and enables the production scheduler with `TERA_API_LOG_SCHEDULER_ENABLED=true`. It must not configure the prototype JSON Lines paths at the same time. - Mock remote syslog reads require `log.read`; updates require `config.write`, participate in the config transaction, and intentionally contain no shared secrets or credentials. - Mock alarm rule read/update endpoints are available for Web UI flow validation. Reads require `alarm.read`; updates are config transaction changes and require `alarm.write`. - Alarm delivery tests call the group-restricted `tera-alarmd` Unix socket in target mode. `tera-alarmd` persists a bounded retry queue atomically, restores queued/retry-pending work after restart, delivers local/web events, and sends configured remote syslog over UDP or TCP without a shell. SMTP and SNMP trap transports fail explicitly until protected credential storage is implemented; no placeholder secret material is embedded in configuration. - Mock serial channel inventory/detail reads require `port.read`. The current mock policy grants it to `mock-admin` and `mock-port-operator`. - Mock network state reads require `network.read`; only `mock-admin` has this permission in the current mock policy. Mock network interface address/gateway staging requires `network.write`, participates in the config transaction, and becomes visible in running state after config apply. - Mock dashboard system status reads require `system.read`; only `mock-admin` has this permission in the current mock policy. Mock service health reads use the same permission and report prototype logical service readiness. - Mock config transaction status lookup requires `config.read`; only `mock-admin` has this permission in the current mock policy. Mock config export uses the same permission and intentionally excludes auth, session, token, and password KDF material. - Browser/PC time sync requires `time.sync`, calls the group-restricted `tera-timed` Unix socket, returns an operation result, and records the final audit outcome. `tera-timed` validates the bounded RFC3339 time and IANA timezone, applies `/etc/localtime` transactionally, and restores the prior timezone if the Linux clock update fails. The in-process mock is used only when mock actors are explicitly enabled. - Mock time settings read/update is available for maintenance flow validation. Reads require `config.read`; updates require `config.write` and participate in the config transaction. - Mock management access policy read/update is available for security flow validation. Reads require `config.read`; updates require `config.write`, participate in the config transaction, and reject changes that disable every HTTPS, SSH, and serial-console recovery path. - Network-test and traceroute diagnostics require `diagnostics.run` and use a bounded asynchronous operation queue in target mode. The process adapter invokes only fixed absolute `ping`/`traceroute` executables without a shell, revalidates targets, enforces request timeouts, bounds combined output, and records accepted plus final audit events. Queue/worker counts are bounded and unfinished work is marked failed on restart or shutdown. When `TERA_API_SUPPORT_BUNDLE_DIR` is configured, support-bundle requests use the same bounded queue to create mode-`0600` ZIP artifacts atomically. Bundles include only system status, service health, redacted config export, bounded audit/system/port events, and operation metadata; auth users, password KDF records, bearer sessions, API token hashes, and secret material are excluded. Artifact count, total bytes, and per-section bytes are bounded. Authorized downloads use `GET /api/v1/diagnostics/support-bundles/{bundle_id}` with `no-store`, checksum, and attachment headers and create a download audit. - Green LED control is a real target hardware boundary, not mock serial data: `hardware.read` lists the CH01–CH48 PCF8574 mapping and `hardware.write` force/releases or pulses one green LED. `tera-api` stays unprivileged and calls the group-restricted `tera-hwd` Unix socket; `tera-hwd` alone writes the kernel `gpio-leds` brightness entries after the target DTB is active. - Serial line configuration is routed through the separate `tera-seriald` Unix-socket boundary. `tera-api` never opens `/dev/ttyWCH*`. The controller accepts only the validated `fet3568j-c-20260721-v1` mapping: CH01-CH24 use `/dev/ttyWCH4`-`/dev/ttyWCH27`, and CH25-CH48 use `/dev/ttyWCH32`-`/dev/ttyWCH55`. Raw nodes 0-3 and 28-31 are excluded as the unconnected direct CH384 UARTs. Missing or mismatched mapping evidence still fails closed. See `docs/sw-detail-design/serial-hardware-api-contract.md`. - Port updates now stage the bounded full UART profile (supported baud rate, 5-8 data bits, none/even/odd parity, one or two stop bits, and none/RTS-CTS flow control). Apply operations pass the complete profile to `tera-seriald`; readback mismatch or partial failure restores all opened channels. - Mock operation result lookup and operation history listing require `operation.read`; only `mock-admin` has this permission in the current mock policy. - Prototype `X-Mock-Actor` auth/RBAC skeleton for mutating commands and `GET /api/v1/auth/me` profile checks. Set `TERA_API_ALLOW_MOCK_ACTORS=false` to disable this header-based prototype bypass and require bearer session or API token authentication for protected routes; CORS preflight responses also stop advertising `X-Mock-Actor` when this bypass is disabled. - Firmware upload is streamed through the protected `tera-updated` Unix socket; `tera-api` does not own `/userdata/update` and cannot mutate partitions or the eMMC layout. The current updater accepts validation-only packages and rejects apply. Read-only diagnostics and explicitly configured remote syslog are the only new external network actions. The only implemented clock mutation crosses the narrow `tera-timed` local socket; HTTP handlers and the unprivileged `tera-api` process cannot set the system clock or write `/etc/localtime` directly. - No production login, session token, password hash, external auth secret, or TLS key storage exists in this prototype. File-backed audit/auth/config/ operation storage is prototype-only and is not a replacement for the production `tera-logd`, product persistent configuration database, protected secret storage, and operation journal paths. Prototype snapshot rewrites do use same-directory temporary files, file and directory sync on Linux, mode `0600`, atomic rename, and symbolic-link refusal so a crash cannot expose a partially written JSON destination. - API contract is documented in `docs/sw-detail-design/web-api-contract.md`. - Backend-to-target implementation and acceptance gates are documented in `docs/sw-detail-design/backend-target-test-gates.md`. - Green LED API and deployment details are documented in `docs/sw-detail-design/green-led-api-contract.md` and `target-bsp/docs/pcf8574-green-led-control.md`. - Architecture/FigJam guardrails are documented in `docs/sw-detail-design/architecture-figma-implementation-guardrails.md`. ## Next implementation steps 1. Replace prototype auth/RBAC storage with production session identity and protected token storage. Direct Mode already reuses the common permission and audit path and must not introduce a second auth store. 2. Add protected SMTP/SNMP credential storage before enabling those `tera-alarmd` transports. 3. Replace the remaining prototype config and operation stores with product persistence; authoritative log persistence is now owned by `tera-logd` but still requires target Stage 4 application and soak evidence. 4. Keep update/recovery and GPT/eMMC changes disabled until their separate device-side safety and rollback gates are explicitly approved.