perf,feat(events),ops: third optimization pass

Overview cold start:
- InstanceResourceService persists every live fetch into status_snapshots
  (category 'resources') and serves a snapshot younger than 2 minutes
  before calling upstream, so the first fleet overview after a restart
  costs zero device requests; instance deletion cleans up via FK cascade

Bounded retention for the four fastest-growing tables (15-minute sweep):
- connection_logs: one row per probe per beat had no automatic cleanup
- audit_events: new pruneBefore (90d)
- operation_preparations: every prepare/retry attempt inserted a row,
  terminal rows now expire after 7 days
- notification_queue: terminal rows pruned after 30 days

SSE events now cover instance lifecycle:
- create/update emit instance envelopes from the routes; the two-phase
  delete emits a job envelope on every terminal transition plus an
  instance envelope when the node actually disappears, so fleet and
  instance views invalidate in real time

Linux ops:
- 'install.sh unit' writes systemd user units for API and gateway with
  Restart=on-failure, 0600 secret injection, and
  MULTI_SIMADMIN_SYSTEMD_UNIT wired so console self-update restarts via
  systemctl -- closing the self-update loop on Linux
This commit is contained in:
chick
2026-09-07 02:43:11 +08:00
parent c1be714ba4
commit 70e598208a
11 changed files with 362 additions and 6 deletions
@@ -1,5 +1,6 @@
import Database from 'better-sqlite3';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { EventEnvelope } from '../../application/events/event-journal.js';
import { buildApp } from '../../app.js';
import {
InstanceSessionStore,
@@ -78,13 +79,36 @@ const fixture = () => {
idFactory: () => `operation-${++id}`,
tokenFactory: () => Buffer.alloc(32, 11).toString('base64url'),
});
const events: Array<Record<string, unknown>> = [];
const onEvent = (envelope: EventEnvelope): void => {
events.push(envelope as unknown as Record<string, unknown>);
};
const app = buildApp({
registerRoutes: (scope) =>
registerInstanceRoutes(scope, { instances, connections, login, deletion }),
registerInstanceRoutes(scope, { instances, connections, login, deletion, onEvent }),
});
return { app, instances };
return { app, instances, events };
};
describe('instance route SSE events', () => {
it('emits an instance envelope when a node is created', async () => {
const { app, events } = fixture();
const created = await app.inject({
method: 'POST',
url: '/api/v1/instances',
payload: {
name: 'Alpha',
origin: 'http://192.168.1.10:8080',
password: { action: 'set', password: 'never-return-this' },
},
});
expect(created.statusCode).toBe(201);
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({ kind: 'instance', requestId: expect.any(String) });
expect(String(events[0]?.instanceId)).toBeTruthy();
});
});
describe('instance HTTP routes', () => {
it('creates, lists, gets, updates and deletes redacted instance resources', async () => {
const { app } = fixture();