3.9 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| api-casing-and-federation-signature | API JSON casing map + the federation signature v2 (snake) migration |
|
Federation wire is snake_case "signature v2" since the 0.7.0 line (FS1–FS6 in
SPEC.md). It used to be camelCase-and-immovable (BF3, now superseded). The whole
federation surface — /.well-known/potato-mesh, GET /api/instances
(instances.rb normalize_instance_row), and the announcement payload
(self_instance.rb instance_announcement_payload) — now emits snake_case:
public_key (was publicKey/pubkey), last_update (was
lastUpdate/lastUpdateTime), is_private, contact_link, nodes_count,
meshcore_nodes_count, meshtastic_nodes_count, reticulum_nodes_count
(forward-compat stub, always 0), plus signature_algorithm, signed_payload,
signature_version. Single-token keys (id, domain, name, version,
channel, frequency, latitude, longitude, signature) unchanged. DB
columns (pubkey, last_update_time) stay internal, mapped at the wire boundary.
How the migration works (federation/signature.rb):
- One shared
canonical_signed_payload(fields)builds the snake canonical and stampssignature_versioninside the signed bytes (anti-downgrade). Used by both signers (instance announcement + well-known) — option U0. canonical_instance_payloadsigns the v2 (snake) form, now including ALL node counts (FS2 — no unsigned attribute remains).verify_instance_signaturetries v2 (snake) then v1 (camel) canonical → accept-both.canonical_instance_payload_v1is kept verbatim for the v1 path.- The break is one-way (FS5): old peers can't verify our v2 signature (stop
accepting us until they upgrade); we still accept their v1 → mixed fleet
converges.
config.federation_signature_version= 2. - Parsers read both casings:
crawl.rbremote_instance_attributes_from_payloadroutes/ingest.rbPOST/api/instances+federation/validation.rbvalidate_well_known_document(e.g.payload["public_key"] || payload["publicKey"]).
Signed counts are stored/served/relayed verbatim (decision (a)). Because the
crawl re-verifies relayed /api/instances entries' signatures over their counts
(crawl.rb), the old recompute-on-store would break relay re-verification when
the recomputed value diverged from the signed snapshot. So the recompute in
ingest.rb and crawl.rb is now a count-absent fallback only — present
(signed) counts are kept so the canonical re-builds identically downstream.
HTTP API casing map (0.7.0 line):
- Read collections (
/api/nodes,/api/messages,/api/positions,/api/telemetry,/api/traces,/api/neighbors) and/api/stats,/version: snake_case. POST /api/nodes/POST /api/instancesinput: accept camelCase AND snake (nil-awarepick_alias/a || b).- Federation wire: snake_case v2 (signed — see above).
Gotchas:
- Frontend
federation-page.jsreads/api/instancesdual (x.snake ?? x.camel) so it survives either casing;federation-instance-display.jsonly readsname/domain(casing-safe). - RSpec
stub_const(name, value)does NOT take a block —stub_const(...) do … endsilently skips the block body (vacuous test).app_spec.rbhad several such blocks (build_well_known_document+ the.self_instance_domain/.self_instance_registration_decision/.ensure_self_instance_record!describes); all now converted to inlinestub_constand genuinely run — no block-formstub_constremains in the specs. Use the value formstub_const("X", Class.new do … end)only when the block belongs toClass.new. - The Flutter app reads
/versionconfig keys (one consumer that breaks on a/versioncasing change); it does NOT consume the federation wire.
See web-app-local-run for running the server to curl these.