Skip to content

Add community mesh registry API#103

Open
RCGV1 wants to merge 13 commits into
meshtastic:masterfrom
RCGV1:codex/community-mesh-registry
Open

Add community mesh registry API#103
RCGV1 wants to merge 13 commits into
meshtastic:masterfrom
RCGV1:codex/community-mesh-registry

Conversation

@RCGV1

@RCGV1 RCGV1 commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

Adds a versioned community mesh registry to the Meshtastic API so clients can offer nearby community radio configurations during setup. Community definitions are reviewed as one JSON file per community through the normal GitHub pull request workflow.

What changed

  • defines a GeoJSON-based JSON Schema for communities with one to three distinct radio profiles
  • supports preset and custom LoRa settings, licensed profiles, primary and optional channels, requirements, social links, and optional public MQTT configuration
  • validates schema rules plus semantic constraints such as closed GeoJSON rings, unique IDs, PSK encoding, channel byte limits, and licensed-mode encryption rules
  • serves the registry, individual records, and schema from versioned endpoints with cache headers and ETags
  • keeps discovery private by requiring clients to download the same registry and perform bounding-box and point-in-polygon matching locally; no location lookup endpoint is added
  • adds a contributor template, submission documentation, PR checklist, and CI validation

Client impact

Clients can fetch GET /v1/community-meshes, match GeoJSON coverage locally without disclosing a user location, offer separate radio configurations, prompt for optional channels, and collect a callsign locally before enabling a licensed profile.

Validation

  • pnpm validate:community-meshes
  • pnpm test (10 tests passed)
  • pnpm build
  • biome ci (passes with two pre-existing warnings in unrelated files)
  • explicit checks confirm the registry has no position-precision field and routes do not inspect location query parameters

Summary by CodeRabbit

  • New Features

    • Added a Community Mesh Registry API for browsing the full registry, retrieving individual mesh records, and accessing the JSON schema.
    • Added caching support with ETags for efficient registry updates.
    • Added privacy-preserving local discovery guidance for clients.
    • Added a template and validation rules for submitting community mesh records.
  • Documentation

    • Added README and contribution documentation covering registry usage, geometry, radio, channel, licensing, MQTT, and privacy requirements.
  • Bug Fixes

    • Expanded automated validation and test coverage for registry data and API behavior.

@CLAassistant

CLAassistant commented Jul 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Benjamin Faershtein seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c1391e79-2a64-429b-9f29-b4b6fb4a3bd5

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2847b and 25ea157.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .github/pull_request_template.md
  • .github/workflows/ci.yml
  • README.md
  • data/communityMeshes/_template.json
  • docs/community-mesh-registry.md
  • package.json
  • schemas/community-mesh.schema.json
  • src/index.ts
  • src/lib/communityMeshes.ts
  • src/routes/communityMeshes.ts
  • src/routes/index.ts
  • src/scripts/validateCommunityMeshes.ts
  • tests/communityMeshes.routes.test.ts
  • tests/communityMeshes.schema.test.ts
  • tests/fixtures/communityMeshes/invalid-licensed-psk.json
  • tests/fixtures/communityMeshes/invalid-modem-union.json
  • tests/fixtures/communityMeshes/invalid-semantic/invalid-psk.json
  • tests/fixtures/communityMeshes/valid-public-custom.json
  • tests/helpers/http.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • tests/fixtures/communityMeshes/invalid-semantic/invalid-psk.json
  • src/routes/index.ts
  • docs/community-mesh-registry.md
  • tests/helpers/http.ts
  • .github/pull_request_template.md
  • src/routes/communityMeshes.ts
  • package.json
  • src/index.ts
  • tests/fixtures/communityMeshes/invalid-modem-union.json
  • src/lib/communityMeshes.ts
  • schemas/community-mesh.schema.json

📝 Walkthrough

Walkthrough

Introduces a validated community mesh registry with JSON Schema rules, semantic checks, immutable cached responses, HTTP endpoints, contributor documentation, fixtures, tests, and CI validation.

Changes

Community Mesh Registry

Layer / File(s) Summary
Registry contract and schema
src/lib/communityMeshes.ts, schemas/community-mesh.schema.json, data/communityMeshes/_template.json
Defines registry interfaces, mesh metadata, GeoJSON coverage, radio/channel configuration, licensing rules, MQTT settings, requirements, and a contributor template.
Registry loading and semantic validation
src/lib/communityMeshes.ts, package.json, src/scripts/validateCommunityMeshes.ts
Loads JSON records, validates them with AJV and semantic checks, sorts and deep-freezes results, and generates registry JSON and ETags.
HTTP routes and application wiring
src/routes/*, src/index.ts
Adds registry, schema, and individual-community endpoints with caching and ETag handling, registers the routes, and permits the client origin through CORS.
Route and validation test coverage
tests/*
Adds HTTP route tests, schema and semantic validation tests, fixtures, and an ephemeral-server helper.
Contributor documentation and CI
.github/*, README.md, docs/community-mesh-registry.md
Documents registry consumption and submission requirements and runs registry validation and tests in CI.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: thebentern

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CommunityMeshRoutes
  participant communityMeshes
  Client->>CommunityMeshRoutes: Request registry or schema
  CommunityMeshRoutes->>communityMeshes: Read cached registry data
  communityMeshes-->>CommunityMeshRoutes: Return JSON and ETag
  CommunityMeshRoutes-->>Client: Return JSON or 304 response
Loading

Poem

A rabbit hops through meshes bright,
With frozen maps and checks just right.
ETags sparkle, routes bloom,
Tests keep errors from the room.
“Sniff!” says Bunny, “Ship the tune!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a community mesh registry API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@RCGV1 RCGV1 changed the title Add privacy-preserving community mesh registry API Add community mesh registry API Jul 13, 2026
@RCGV1 RCGV1 marked this pull request as ready for review July 13, 2026 23:02
@RCGV1 RCGV1 force-pushed the codex/community-mesh-registry branch from 8460ec6 to 2188fb3 Compare July 13, 2026 23:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/routes/communityMeshes.ts (1)

13-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Honor standard If-None-Match matching.

Exact equality misses valid validators such as weak tags, comma-separated tag lists, and *, so compatible clients or proxies can receive 200 instead of 304. Parse the header using HTTP validator semantics before comparing tags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/communityMeshes.ts` around lines 13 - 14, Update the conditional
handling in the community meshes route to parse If-None-Match according to HTTP
validator semantics, recognizing weak entity tags, comma-separated validators,
and the wildcard “*” before comparing against registryEtag. Return 304 when any
parsed validator matches, while preserving the existing response for
non-matches.
src/lib/communityMeshes.ts (1)

129-147: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Object.freeze on response is shallow — nested communities entries stay mutable.

response is a single module-level singleton reused across every request (getCommunityMeshes()/getCommunityMesh() return direct references). Object.freeze only locks the top-level keys of response; the communities array and each parsed CommunityMesh object are not frozen, so any future code that mutates a returned record (e.g. a route handler adding/stripping a field) would silently corrupt the shared cache for all subsequent requests with no error surfaced.

🛡️ Proposed fix: deep-freeze the registry once at load time
+const deepFreeze = <T>(value: T): T => {
+  if (value && typeof value === "object" && !Object.isFrozen(value)) {
+    Object.freeze(value);
+    for (const v of Object.values(value as Record<string, unknown>)) {
+      deepFreeze(v);
+    }
+  }
+  return value;
+};
+
 const communities = loadCommunityMeshes();
 const response: CommunityMeshesResponse = Object.freeze({
   apiVersion: "v1",
   schemaVersion: 1,
   generatedAt: new Date().toISOString(),
   sourceRevision: process.env.VERCEL_GIT_COMMIT_SHA ?? "local",
-  communities,
+  communities: deepFreeze(communities),
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/communityMeshes.ts` around lines 129 - 147, Deep-freeze the loaded
registry data before constructing the shared response so the communities array
and every nested CommunityMesh record cannot be mutated through
getCommunityMeshes or getCommunityMesh. Update the module-level initialization
around loadCommunityMeshes and response, using a reusable deep-freeze helper if
needed, while preserving the existing response shape and registryEtag behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-07-13-community-mesh-registry.md`:
- Around line 297-309: Align the Task 3 plan with the shipped
loadCommunityMeshes implementation: document that it returns CommunityMesh[] and
that the response envelope is constructed separately, or update the
implementation to return CommunityMeshesResponse. Ensure validation behavior
also matches the plan by aggregating all schema and semantic errors, including
the source filename, into one thrown error rather than failing on the first
issue.

In `@docs/superpowers/specs/2026-07-13-community-mesh-registry-design.md`:
- Around line 53-68: Update the example record’s meshProfiles field to include
at least one valid mesh profile conforming to the documented schema, while
preserving the surrounding example data.

In `@schemas/community-mesh.schema.json`:
- Around line 251-274: The public MQTT registry must not expose plaintext
credentials. In schemas/community-mesh.schema.json lines 251-274, remove
mqtt.username and mqtt.password or explicitly exclude them from the public
registry; in src/lib/communityMeshes.ts lines 129-147, if credentials remain
supported, redact those fields while constructing the response returned by
getCommunityMesh so res.json never receives the raw parsed values.

In `@src/routes/communityMeshes.ts`:
- Around line 11-16: Update the community meshes response and registryEtag
generation so the ETag hashes the exact serialized body returned by the route,
including generatedAt and sourceRevision alongside communities. Ensure the
If-None-Match comparison uses this complete-body ETag and 304 responses are only
returned when the full representation matches.

In `@tests/communityMeshes.schema.test.ts`:
- Around line 42-44: Update the records.forEach callback in the fixture writer
to use a block body and perform writeFileSync as a statement, preventing the
callback from implicitly returning its result while preserving the existing
file-writing behavior.

---

Nitpick comments:
In `@src/lib/communityMeshes.ts`:
- Around line 129-147: Deep-freeze the loaded registry data before constructing
the shared response so the communities array and every nested CommunityMesh
record cannot be mutated through getCommunityMeshes or getCommunityMesh. Update
the module-level initialization around loadCommunityMeshes and response, using a
reusable deep-freeze helper if needed, while preserving the existing response
shape and registryEtag behavior.

In `@src/routes/communityMeshes.ts`:
- Around line 13-14: Update the conditional handling in the community meshes
route to parse If-None-Match according to HTTP validator semantics, recognizing
weak entity tags, comma-separated validators, and the wildcard “*” before
comparing against registryEtag. Return 304 when any parsed validator matches,
while preserving the existing response for non-matches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 08e64b3a-3569-4bf6-9a22-f31a09bb7d48

📥 Commits

Reviewing files that changed from the base of the PR and between b712a07 and 4b2847b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • .github/pull_request_template.md
  • .github/workflows/ci.yml
  • README.md
  • data/communityMeshes/_template.json
  • docs/community-mesh-registry.md
  • docs/superpowers/plans/2026-07-13-community-mesh-registry.md
  • docs/superpowers/specs/2026-07-13-community-mesh-registry-design.md
  • package.json
  • schemas/community-mesh.schema.json
  • src/index.ts
  • src/lib/communityMeshes.ts
  • src/routes/communityMeshes.ts
  • src/routes/index.ts
  • src/scripts/validateCommunityMeshes.ts
  • tests/communityMeshes.routes.test.ts
  • tests/communityMeshes.schema.test.ts
  • tests/fixtures/communityMeshes/invalid-licensed-psk.json
  • tests/fixtures/communityMeshes/invalid-modem-union.json
  • tests/fixtures/communityMeshes/invalid-semantic/invalid-psk.json
  • tests/fixtures/communityMeshes/valid-public-custom.json
  • tests/helpers/http.ts

Comment on lines +297 to +309
export const loadCommunityMeshes = (directory = DATA_DIRECTORY): CommunityMeshesResponse => {
const communities = readdirSync(directory, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.startsWith("_"))
.map((entry) => JSON.parse(readFileSync(new URL(entry.name, directory), "utf8")) as unknown)
.map(validateCommunityMesh)
.sort((left, right) => left.id.localeCompare(right.id));

validateUniqueIds(communities);
return Object.freeze({ apiVersion: "v1", schemaVersion: 1, generatedAt: new Date().toISOString(), sourceRevision: process.env.VERCEL_GIT_COMMIT_SHA ?? "local", communities });
};
```

Define `fixtureDirectory = (name: string) => new URL(`../../tests/fixtures/communityMeshes/${name}/`, import.meta.url);` beside the test imports. Implement `validateCommunityMesh()` with Ajv against the committed schema, then semantic checks for: geometry ring closure and coordinate bounds; profile IDs; optional channel count/order; byte-length channel names; Base64 PSK byte lengths of 0, 1, 16, or 32; licensed profiles with only `null` PSKs; and `overrideFrequencyMHz` only on licensed profiles. Throw one error containing the filename and every validation error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the Task 3 plan aligned with the shipped loader.

The supplied src/lib/communityMeshes.ts implementation returns CommunityMesh[] and constructs the response envelope separately, while this plan declares that loadCommunityMeshes() returns CommunityMeshesResponse. It also throws immediately on semantic failures instead of aggregating every validation error as required here. Update the plan or implementation so the documented API and diagnostics match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-07-13-community-mesh-registry.md` around lines
297 - 309, Align the Task 3 plan with the shipped loadCommunityMeshes
implementation: document that it returns CommunityMesh[] and that the response
envelope is constructed separately, or update the implementation to return
CommunityMeshesResponse. Ensure validation behavior also matches the plan by
aggregating all schema and semantic errors, including the source filename, into
one thrown error rather than failing on the first issue.

Comment on lines +53 to +68
```json
{
"schemaVersion": 1,
"id": "portland-or",
"name": "Portland Mesh",
"description": "Community-operated Meshtastic coverage in Portland.",
"coverage": {
"type": "Polygon",
"coordinates": [[[-122.90, 45.40], [-122.40, 45.40], [-122.40, 45.70], [-122.90, 45.70], [-122.90, 45.40]]]
},
"links": [
{ "kind": "website", "url": "https://example.org" },
{ "kind": "discord", "url": "https://discord.gg/example" }
],
"meshProfiles": []
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the example schema-valid.

The example uses "meshProfiles": [], but the schema requires one to three profiles and the specification states that every community must define at least one. Readers copying this example will create a record rejected by validation; include one valid profile or explicitly mark the field as abbreviated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-13-community-mesh-registry-design.md` around
lines 53 - 68, Update the example record’s meshProfiles field to include at
least one valid mesh profile conforming to the documented schema, while
preserving the surrounding example data.

Comment on lines +251 to +274
"mqtt": {
"type": "object",
"required": [
"serverAddress",
"tlsEnabled",
"encryptionEnabled",
"jsonEnabled",
"proxyToClientEnabled",
"mapReportingEnabled"
],
"properties": {
"serverAddress": { "type": "string", "minLength": 1 },
"username": { "type": "string" },
"password": { "type": "string" },
"tlsEnabled": { "type": "boolean" },
"encryptionEnabled": { "type": "boolean" },
"jsonEnabled": { "type": "boolean" },
"rootTopic": { "type": "string" },
"proxyToClientEnabled": { "type": "boolean" },
"mapReportingEnabled": { "type": "boolean" },
"mapReportingIntervalSeconds": { "type": "integer", "minimum": 3600 }
},
"additionalProperties": false
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Plaintext MQTT credentials can flow straight through to public API responses. The schema's mqtt definition accepts raw username/password strings, and the loader/registry-building code never strips or redacts them before the (documented) public routes serve the full record via res.json(...) with public caching — one missing safeguard causes exposure at both sites.

  • schemas/community-mesh.schema.json#L251-L274: drop username/password from the mqtt schema (or otherwise mark them explicitly out-of-scope for this public registry).
  • src/lib/communityMeshes.ts#L129-L147: if credentials must remain supported, redact mqtt.username/mqtt.password when building response/serving getCommunityMesh results instead of passing the raw parsed record through.
📍 Affects 2 files
  • schemas/community-mesh.schema.json#L251-L274 (this comment)
  • src/lib/communityMeshes.ts#L129-L147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/community-mesh.schema.json` around lines 251 - 274, The public MQTT
registry must not expose plaintext credentials. In
schemas/community-mesh.schema.json lines 251-274, remove mqtt.username and
mqtt.password or explicitly exclude them from the public registry; in
src/lib/communityMeshes.ts lines 129-147, if credentials remain supported,
redact those fields while constructing the response returned by getCommunityMesh
so res.json never receives the raw parsed values.

Comment thread src/routes/communityMeshes.ts Outdated
Comment thread tests/communityMeshes.schema.test.ts Outdated
@RCGV1

RCGV1 commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@thebentern Can you review?

@RCGV1

RCGV1 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

2 participants