Legacy panel:
- upstream body reads now carry their own deadline and a 10 MB byte budget;
a stalled modem can no longer hang /api/status fan-out forever nor OOM
the proxy (request headers alone had the timeout, bodies had none)
- add X-Frame-Options DENY / CSP frame-ancestors none / nosniff; the panel
(delete-instance and confirmed-write dialogs) is no longer clickjackable
- only send a JSON content-type when the API console request has a body, so
payload-less dangerous writes stop failing with 400 and burning the
one-use confirmation token
- register a form-urlencoded parser (the proxy branch was unreachable) and
drop the multipart parser that buffered up to 60 MB before rejecting;
bodyLimit drops to 2 MB; framework-level 415 keeps the stable error body
- remove /api/sms/send from readable paths: GET bypassed the write
confirmation for a send endpoint
- /api/instances/:id/login maps upstream failures to a stable 502 instead
of leaking raw error text
- guard MULTI_SIMADMIN_TIMEOUT_MS parsing (NaN aborted every request);
prune dead code (buildClients, cookie expando no-op)
Control plane:
- deleting a notification channel detaches it from rules instead of leaving
dangling ids that made every referencing rule unreadable and silently
dropped future notifications; rule reads tolerate unknown ids
- startup sweep resets notification_queue rows stranded in 'sending' by a
crash (mirrors the sms outbox sweep); terminal outbox rows are pruned on
the retention timer
- /api/v1/metrics no longer emits operator-assigned node names on the
session-free scrape; login limiter map is bounded and pruned; Secure
cookie honors the gateway-declared x-forwarded-proto
- webhook delivery sets redirect: manual (signed payloads are not replayed)
- SMTP envelope sender is validated against CR/LF smuggling
- scheduled reboots with delaySeconds != 3 fail fast at the dispatcher with
a clear reason instead of burning every retry; contract narrowed to the
pinned baseline
Linux deployments under a systemd unit can now use the online update
panel: MULTI_SIMADMIN_SYSTEMD_UNIT (+ optional _SCOPE=user|system) hands
the restart to systemctl; createPlatformRestartLauncher picks launchd on
macOS and systemd on Linux, degrading to 'unsupported' otherwise.
The production composition hardwired the macOS Keychain (/usr/bin/security),
which made Linux deployment impossible. Now:
- SecretStore gains a stable provider identity persisted in
secret_references.provider; services stop hardcoding 'macos-keychain'
- shared reference codec (secret-reference.ts) understands both
keychain:// and secret-file:// references
- FileSecretStore: single 0600 JSON map under the data root, atomic
temp+rename writes, serialized in-process, same validation envelope
- production composition picks the backend via
MULTI_SIMADMIN_SECRET_BACKEND (default: Keychain on darwin, file store
elsewhere) and readiness probes the matching backend
- cutover readState: enforce 0600 mode bits only on POSIX (Windows ACLs
govern access; chmod is a no-op there)
- backup activation: skip the read-only-handle fsync on win32; the staged
rename-over-open-WAL tests keep running on the POSIX deployment targets
- test-fixtures: normalize fixture paths to POSIX separators before
comparing with manifest entries
- cutover-orchestrator: restart via 'node --import tsx' (matching install.sh)
instead of a hardcoded .pnpm/tsx@4.22.4 loader path that breaks on tsx bumps
- production-gateway: use process.execPath for the legacy-start fixture;
POSIX-only 0600 mode assertion now win32-guarded
- cli-runtime + installer suites: skip where they require POSIX-only
process-group SIGTERM, lsof, or /bin/sh
- fixture-safety: fall back to config.example.json when config.json is absent
on a fresh clone
- eslint: respect the repo's existing _-prefix convention for unused vars
- phase-one-blockers: derive projectRoot via import.meta.dirname so Windows
checkouts stop producing C:\C:\... paths
- backup/release-evidence/cutover-orchestrator: skip POSIX-only directory
fsync on win32 and fsync read-only handles through a writable handle
- Wire identity, metrics and update services into the assembled API.
- Add connection authorization modes and scoped console authorization.
- Preserve notification group IDs for filtered delivery.
- Extend maintenance, backup, resource and module flows with runtime metadata and upstream safety.
- Add console update planning, download validation and staged installation.
- Create a component backup before applying online updates.
- Expose controlled update routes from the production control plane.
- Allow scheduled operations to resolve multiple device groups.
- Add reusable SMS time and random-value macros.
- Add China operator PLMN metadata shared by API and UI.
Deletion now follows the published contract: prepare returns the one-time
token and the console spends it on DELETE /api/v1/instances/:id with
If-Match, where the generic execute endpoint had been rejecting it as an
operation the safe transport cannot replay.
An online device is told to release its own binding first. A refusal keeps
the record and reports UNBIND_FAILED on the job item so the console can say
why the node is still there; a node that never answers a heartbeat is
forgotten locally.
Add central notification channels, rules, queue and delivery logs, fleet
organization groups and tags, device discovery, the device action catalog,
instance module reads, the log centre, connection settings and system
maintenance as native /api/v1 routes backed by the existing secret store,
audit trail and pinned upstream transport.
Drop the redundant advanced table so fleet stays resource-first cards.
Keep multi-select batch service/system restart via prepare→execute, card
restarts, overview system ops, and progressive fleet loading.