Skip to content

feat(tra-922): MQTT topic routing — {org_slug}/ prefix + data-driven subscriptions#475

Open
mikestankavich wants to merge 9 commits into
mainfrom
feat/tra-922-mqtt-topic-org-slug-prefix
Open

feat(tra-922): MQTT topic routing — {org_slug}/ prefix + data-driven subscriptions#475
mikestankavich wants to merge 9 commits into
mainfrom
feat/tra-922-mqtt-topic-org-slug-prefix

Conversation

@mikestankavich

Copy link
Copy Markdown
Contributor

TRA-922 — MQTT topic routing: {org_slug}/-prefixed publish_topic + data-driven subscriptions

Closes the two problems surfaced 2026-06-04: the coarse MQTT_TOPIC filter silently constrained the "free-form" publish_topic, and cross-org topic collisions were latent under the per-org-only unique index.

Decisions (resolved with Mike)

  1. Slug-as-root topics. New publish_topics are forced to start with {org_slug}/ (organizations.identifier, globally unique → cross-org collision structurally impossible; sets up the {org_slug}/# ACL namespace for TRA-857). The trakrf.id/ root is dropped.
  2. Org slug is immutable (no rename path in code) → no cascade logic needed.
  3. Grandfather existing rows. No data migration; the prefix is enforced only on create and on an edit that changes the topic. Existing demo readers (trakrf.id/C4DEE229A176/reads, MK107, …) keep working untouched.
  4. Data-driven per-topic subscriptions (chosen over #-firehose + cache). A new topicroute registry owns both the in-memory publish_topic→route map (message routing) and the broker subscription set. The subscriber subscribes to exactly the registered topics — the broker filters, not the app — so MQTT_TOPIC retires entirely (no static filter to configure, no cloud chart hand-off needed), publish_topic stays truly free-form, and there's no negative cache.

What's here

  • Migration 000021 list_active_scan_topics() (SECURITY DEFINER) — enumerates every org's live mqtt topics for the registry; resolve_scan_topic kept as the message-path fallback.
  • internal/services/topicrouteRegistry (Reconcile / Lookup / Topics / SetManager); reconciles on boot, on scan-device CRUD, on a 5-min ticker, and on every MQTT OnConnect (bulk re-subscribe).
  • SubscriberOnConnect bulk-subscribes registry.Topics(); Subscribe/Unsubscribe apply CRUD deltas; handleMessage routes via the registry with a resolve_scan_topic fallback. MQTT_TOPIC removed from config + deploy/edge/.env.example.
  • Scan-device CRUD — enforces the {org_slug}/ prefix (400 on violation; web_ble exempt; unchanged-topic edits grandfathered) and reconciles the registry after each mutation.
  • /users/mecurrent_org.identifier added so the UI can prefill the prefix.
  • readerKeyFromTopic (backend + frontend) — relaxed the fixed trakrf.id/ root to any first segment so Live Reads keeps working for both new and grandfathered topics.
  • FrontendScanDeviceForm prefills {org_slug}/ on create + help text.

Tests (all green)

  • Unit: topicroute registry (add/remove/no-manager/snapshot), config (MQTT_TOPIC retired), readerKeyFromTopic (both schemes), ScanDeviceForm prefill.
  • Integration: list_active_scan_topics (mqtt-only, excludes web_ble/deleted), scan-device prefix validation (wrong/correct/grandfathered/no-identifier), /users/me identifier.
  • Full backend unit suite + integration for touched packages, just backend build, frontend typecheck/lint/test (1184 passing).

⚠️ Ops / infra note

  • The live edge box .env should drop MQTT_TOPIC — subscriptions are now data-driven. Grandfathered demo topics keep flowing regardless (the subscriber subscribes to them by exact string).
  • No cloud helm mqtt.topic change is required (the static filter is gone, not relaxed).

🤖 Generated with Claude Code

Mike Stankavich and others added 9 commits June 9, 2026 10:36
…riven MQTT subscriptions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Subscriber subscribes to exactly the registered publish_topics via the
topicroute registry (OnConnect bulk-subscribe + per-CRUD Subscribe/Unsubscribe
+ 5m reconcile ticker), instead of a static MQTT_TOPIC firehose. handleMessage
routes via the registry map with a resolve_scan_topic fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gistry on CRUD

Create/Update validate that an mqtt publish_topic starts with the caller org's
identifier (400 on violation); unchanged topics on edit are grandfathered;
web_ble devices are exempt. Successful CRUD reconciles the topic registry so the
subscriber tracks adds/removes inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… parse

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🚀 Preview Deployment Update

✅ This PR has been successfully merged into the preview branch.

The preview environment will update shortly at: https://app.preview.trakrf.id

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant