Add community mesh registry API#103
Conversation
|
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. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (19)
🚧 Files skipped from review as they are similar to previous changes (11)
📝 WalkthroughWalkthroughIntroduces a validated community mesh registry with JSON Schema rules, semantic checks, immutable cached responses, HTTP endpoints, contributor documentation, fixtures, tests, and CI validation. ChangesCommunity Mesh Registry
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: 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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
8460ec6 to
2188fb3
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/routes/communityMeshes.ts (1)
13-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winHonor standard
If-None-Matchmatching.Exact equality misses valid validators such as weak tags, comma-separated tag lists, and
*, so compatible clients or proxies can receive200instead of304. 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.freezeonresponseis shallow — nestedcommunitiesentries stay mutable.
responseis a single module-level singleton reused across every request (getCommunityMeshes()/getCommunityMesh()return direct references).Object.freezeonly locks the top-level keys ofresponse; thecommunitiesarray and each parsedCommunityMeshobject 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
.github/pull_request_template.md.github/workflows/ci.ymlREADME.mddata/communityMeshes/_template.jsondocs/community-mesh-registry.mddocs/superpowers/plans/2026-07-13-community-mesh-registry.mddocs/superpowers/specs/2026-07-13-community-mesh-registry-design.mdpackage.jsonschemas/community-mesh.schema.jsonsrc/index.tssrc/lib/communityMeshes.tssrc/routes/communityMeshes.tssrc/routes/index.tssrc/scripts/validateCommunityMeshes.tstests/communityMeshes.routes.test.tstests/communityMeshes.schema.test.tstests/fixtures/communityMeshes/invalid-licensed-psk.jsontests/fixtures/communityMeshes/invalid-modem-union.jsontests/fixtures/communityMeshes/invalid-semantic/invalid-psk.jsontests/fixtures/communityMeshes/valid-public-custom.jsontests/helpers/http.ts
| 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. |
There was a problem hiding this comment.
📐 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.
| ```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": [] | ||
| } |
There was a problem hiding this comment.
🎯 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.
| "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 | ||
| }, |
There was a problem hiding this comment.
🔒 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: dropusername/passwordfrom themqttschema (or otherwise mark them explicitly out-of-scope for this public registry).src/lib/communityMeshes.ts#L129-L147: if credentials must remain supported, redactmqtt.username/mqtt.passwordwhen buildingresponse/servinggetCommunityMeshresults 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.
|
@thebentern Can you review? |
|
@coderabbitai review |
✅ Action performedReview finished.
|
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
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-meshespnpm test(10 tests passed)pnpm buildbiome ci(passes with two pre-existing warnings in unrelated files)Summary by CodeRabbit
New Features
Documentation
Bug Fixes