diff --git a/backend/src/db/migrations/047_message_tags.sql b/backend/src/db/migrations/047_message_tags.sql new file mode 100644 index 0000000..49dc7e2 --- /dev/null +++ b/backend/src/db/migrations/047_message_tags.sql @@ -0,0 +1,51 @@ +-- 055: message_tags + tagger bookkeeping schema. +-- +-- The core backend reads message_tags for feed enrichment, packet detail and +-- the WebSocket initial state, but the table was previously created only by +-- tagger_worker.py's startup DDL -- which runs after a TYPESAFE_API_KEY gate +-- and exits before any DDL when the key is unset. Fresh stacks therefore had +-- no table: WS clients were closed with 1013 on connect and the CI smoke's +-- MQTT->WebSocket fanout check timed out. +-- +-- The schema now lives in the migration ledger so every deployment has it +-- independently of the optional worker; the worker's CREATE TABLE IF NOT +-- EXISTS stays as a harmless no-op. Prefix 055 deliberately leaves 052-054 +-- reserved for the owner packet sharing workstream. + +SET LOCAL lock_timeout = '30s'; +SET LOCAL statement_timeout = '0'; + +CREATE TABLE IF NOT EXISTS message_tags ( + packet_hash text PRIMARY KEY, + packet_time timestamptz NOT NULL, + tagged_at timestamptz NOT NULL DEFAULT now(), + model text, + latency_ms integer, + tags jsonb NOT NULL, + confidence jsonb +); + +CREATE TABLE IF NOT EXISTS tagger_state ( + id int PRIMARY KEY CHECK (id = 1), + cursor_time timestamptz, + seen bigint NOT NULL DEFAULT 0, + tagged bigint NOT NULL DEFAULT 0, + errors bigint NOT NULL DEFAULT 0, + http_429 bigint NOT NULL DEFAULT 0, + last_error text, + avg_latency_ms_50 real, + started_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz +); +INSERT INTO tagger_state (id) VALUES (1) ON CONFLICT DO NOTHING; +ALTER TABLE tagger_state ADD COLUMN IF NOT EXISTS retried bigint NOT NULL DEFAULT 0; +ALTER TABLE tagger_state ADD COLUMN IF NOT EXISTS given_up bigint NOT NULL DEFAULT 0; + +CREATE TABLE IF NOT EXISTS tagger_retry ( + packet_hash text PRIMARY KEY, + packet_time timestamptz NOT NULL, + attempts int NOT NULL DEFAULT 0, + last_error text, + first_failed_at timestamptz NOT NULL DEFAULT now(), + exhausted boolean NOT NULL DEFAULT false +); diff --git a/backend/src/ram/liveRead.ts b/backend/src/ram/liveRead.ts index c268b4d..786974f 100644 --- a/backend/src/ram/liveRead.ts +++ b/backend/src/ram/liveRead.ts @@ -149,6 +149,8 @@ function buildMergedRow(group: EntryWithTx[], includePayload: boolean, fields: ' rssi: input.rssi, snr: input.snr, summary, + tags: null, + tag_confidence: null, advert_count: input.advertCount, path_hashes: input.pathHashes, path_hash_size_bytes: input.pathHashSizeBytes, @@ -207,7 +209,7 @@ function augmentRow(row: RecentRow, group: EntryWithTx[], aliases: Map String(row['_representative_id'] ?? ''))))) { // Counts describe all observations; representative fields follow the same // SQL preference across both sources, including retained overlap. - const { observer_node_ids: _nodes, observer_iatas: _iatas, rx_count: _rx, tx_count: _tx, ...fields } = candidate; + const { observer_node_ids: _nodes, observer_iatas: _iatas, rx_count: _rx, tx_count: _tx, tags: _tags, tag_confidence: _tagConfidence, ...fields } = candidate; Object.assign(row, fields); } } diff --git a/docker-compose.yml b/docker-compose.yml index 22b7b78..6d5a01b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -971,7 +971,7 @@ services: volumes: - alert_receiver_data:/var/lib/meshcore-alerts healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/readyz >/dev/null"] + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null"] interval: 30s timeout: 5s retries: 5