docs(fep): QuantPub rev 7 — FitPub format alignment (#905) - #1045
Conversation
Informed by reading the FitPub source (codeberg.org/fitpub/fitpub) for fitpub/fitpub#455: - §2/§3: dual-typing relaxed MUST→RECOMMENDED; consumers MUST tolerate array-valued type (deployed consumers cast type to a string) - §5: document the structured payload's optional route field (already shipped by Aurboda) with its opt-in and privacy semantics - §7.1 (new): coexistence and incremental-adoption path for existing implementations (FitPub's backward-compatibility constraint) - §9: signed GETs as an optional additional grant for followers-only payloads (FitPub's mechanism), tokens remain the baseline - Prior art: expanded FitPub entry with its actual wire format - Security: route-identifiability consideration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwoP1SqJhHgHiEEoEQtUjT
fiddur
left a comment
There was a problem hiding this comment.
🛑 Changes required
Docs-only revision of the QuantPub FEP draft: dual-typing softened from a hard requirement to RECOMMENDED (with an explicit array-vs-string type tolerance rule for consumers), a new §7.1 on incremental adoption, the optional route field in the §5 payload, HTTP-Signature fetches as an additive grant in §9, and an expanded FitPub prior-art entry.
I verified the two claims about running code that this revision adds, and both hold: route is produced by resolveStructuredContent in apps/backend/src/services/feed-structured.ts:228-235, gated on post.include_map exactly as the doc describes, and the wire shape matches feedStructuredRoutePointSchema (lat/lon/t, WGS84, ISO 8601, optional) in packages/api-spec/src/schemas/feed-structured.ts:72-105. The Implementations wording ("shared by attaching the route-map image, so track and image have the same audience") is accurate.
One thing genuinely needs fixing before merge: the new route bullet defers its opt-in rule to §8 ("Geography is its own explicit opt-in like a series (§8)"), but §8 — the normative privacy model — has no geography item, and its item 5 currently reads as forbidding what route serves ("implementations MUST NOT serve raw per-measurement timestamps on public endpoints", while route[].t is a per-GPS-fix timestamp on the public §5 endpoint). Inline comment has the detail; the fix is a §8 item plus a scope clause, not a rework.
Non-blocking — fold into a later PR, don't re-roll this one
- The unchanged §5 Authorization paragraph still says a followers-only post "resolves only with a valid capability token (§9)". With the new signed-fetch grant that "only" is now imprecise; "a capability token or a signed fetch (§9)" would match §9.
- The prior-art entry notes FitPub's
simplifiedTrackis GeoJSON while §5'sroutemints a{lat,lon,t}array. Given §1's stated preference for reusing existing vocabulary, a sentence of rationale (GeoJSON coordinates carry no per-point time) would pre-empt the first question a FEP reviewer asks. - The FitPub claims are specific and load-bearing (namespace IRI, DTO field names, signature-authorized detail
GET, 403 semantics) but unverifiable from this repo. A footnote pointing at the source that was inspected would help reviewers of the submitted FEP. - §2's added paragraph ends "Consumers MUST tolerate both the string and array forms of
typeon any received object:" and the colon then leads into a table of properties. Reads cleaner with that MUST placed after the table. - "existing fediverse software is known to mishandle array-valued
type" is unattributed, unlike the Prior-art bullet that names Mastodon's observed behaviour; naming the software would strengthen the justification for downgrading dual-typing.
- §8.5 scoped to series timestamps; new §8.6 makes geography its own
normative opt-in (route was contradicting the old §8.5 wording)
- §5 authorization mentions the signed-fetch grant, route bullet points
to §8.6 and explains why {lat,lon,t} instead of GeoJSON LineString
- §2: array-type mishandling attributed (FitPub inbound handler),
consumer MUST moved below the table, table lead-in restored
- Prior art: footnote naming the inspected FitPub sources
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CwoP1SqJhHgHiEEoEQtUjT
fiddur
left a comment
There was a problem hiding this comment.
✅ Approved
Docs-only change to the pre-submission QuantPub FEP draft. I read the whole document plus the surrounding docs and the Aurboda code the new text claims to describe; nothing here blocks.
What I verified rather than assumed:
- The new
routefield matches the running code.packages/api-spec/src/schemas/feed-structured.tsdefinesrouteas an optional array of{ lat, lon, t }(WGS84 + ISO 8601), andresolveStructuredContent(apps/backend/src/services/feed-structured.ts:230) only populates it whenpost.include_mapis set — exactly the "shared by attaching the route-map image, so track and image have the same audience" claim in §5 and in Implementations. Downsampling is real (resolveStructuredRoute→downsampleRoutePoints(…, MAX_ROUTE_POINTS)), so the Implementations bullet is not stale. - The geo-masking wording is honest. There is no home-zone masking anywhere in the repo, and the doc only ever says publishers SHOULD offer it (§8.6, Security considerations) — consistent with
docs/features/feed.md, which records "Route maps have no privacy trimming … masking is a planned follow-up". No overclaiming about Aurboda. - Cross-references resolve. §8 is now a six-item list, so the new §8.6 pointers from §5 and §8.5 are valid, as are the existing §8.3/§8.4 references from §9. The
[HTTP Signature]reference link is defined at the bottom, and its label wrapping across a newline still matches after CommonMark label normalisation. - The single-typing allowance doesn't break the rest of the spec. §7 detection keys on the object id / same-host
quant:structuredUrlonly, and §5 discrimination is bykind/activity_type/quant:observationOf— so a single-typedNotegenuinely loses nothing. Aurboda's own inbound enrichment (timeline-enrich.ts) never inspectstype, so the new "consumers MUST tolerate both string and array forms" requirement holds for the reference implementation, while outboundwithQuantTypestill dual-types (fine under RECOMMENDED).
One caveat on scope: the factual claims about FitPub's internals (the inbound handler casting type to a string, 403 on non-public objects, NodeInfo-based detection, the named Java classes) can't be checked from this sandbox — codeberg.org isn't reachable. They carry a dated source footnote, so I'm taking them as authored.
Non-blocking — fold into a later PR, don't re-roll this one
Please don't push fixes for these here; another review round costs more than they're worth. Batch them with the next revision.
- §8.5's parenthetical is now redundant. Once the MUST NOT is scoped to series timestamps, the route's per-fix
tisn't an "exception" to it — it is simply outside its scope, so "the one deliberate exception is…" reads as if the normative sentence still covered routes. routeis the only payload with no resolution floor. §6 makes the server-side bucket floor normative as a privacy mechanism (§8.5), yet the highest-resolution data in the spec gets only "downsampled by the publisher" plus a SHOULD in §8.6. Aurboda already caps at 500 points; a RECOMMENDED maximum point count or minimum inter-point spacing, mirroring §6's 5-second floor, would make the privacy model self-consistent for upstream reviewers.- "despite §1's preference for existing vocabulary" slightly overstates the tension. §1 scopes the reuse-existing-terms preference to JSON-LD extension terms and explicitly calls §4–§6 payload fields plain JSON contracts rather than JSON-LD, so choosing
{lat,lon,t}over a GeoJSONLineStringin §5 isn't really a departure from §1. - §7.1 sits under a Level-3 (consumer) section although its content is publisher-side adoption guidance; it may read better as its own top-level section.
docs/fep/README.md's QuantPub blurb still summarises the privacy model without the new normative geography opt-in — worth a line whenever that file is next touched.
Part of #905; companion to the cross-project discussion in fitpub/fitpub#455.
I cloned FitPub (
codeberg.org/fitpub/fitpub) and read its federation format from the source (ActivityPostProcessingService,ActivityPubContexts,RemoteActivityDetailsFetcher,docs/federation.md). This PR is QuantPub rev 7: the spec changes needed so an existing FitPub installation base can adopt QuantPub backward-compatibly, plus one spec omission found while comparing (routewas shipped but undocumented).How the formats compare
Structurally near-identical — great news for a shared FEP:
Note(HTML content, one-line stat summary, generated image attachment) for Mastodon compatibility.startTime/endTimenatively on the Note.fitpub: { "detailUri": … }(namespacehttps://fitpub.social/ns#) ≈ ourquant:structuredUrl.Differences:
/api/activities/{id}, fixed camelCase schema:totalDistance,metrics.averageHeartRate,simplifiedTrackGeoJSON, …){key, value, unit}metric array,kinddiscriminator, snake_casefitpub/fitpubDetailUri) + NodeInfo software identity (gates follows)/.well-known/quantpubcapability docGET403 Forbidden404(normative: unshared ≡ nonexistent)"Note"; inbound code does(String) activity.get("type")→ an array-valuedtypebreaks it["Note", "quant:Exercise"]On casing: no standards conflict exists. AS2/JSON-LD vocabulary is standardized lowerCamelCase and both projects follow it on the wire. QuantPub's snake_case appears only in the out-of-band HTTP payload — plain JSON where no fediverse standard applies; FitPub's camelCase payload is likewise just its internal DTO, not a designed contract.
On GPS: Aurboda's structured payload already ships a downsampled, timestamped
route(same audience as the route-map image, opt-in viainclude_map) — the FEP just never documented it. Fixed here.Is backward-compatible adoption possible for existing FitPub installs?
Yes. Everything in QuantPub is additive — with one spec relaxation this PR makes:
/.well-known/quantpub+ the §5/§6 endpoints sit alongside the untouched/api/activities/{id}.quant:*+quant:structuredUrlcan ride next tofitpub.detailUrion the same Note; un-upgraded FitPub instances see exactly the object they saw before.typetoString, so an upgraded FitPub emitting["Note", "quant:Exercise"]would break its own un-upgraded peers. Since §7 detection never keyed on the type anyway, rev 7 relaxes dual-typing to RECOMMENDED with an explicit staged path (start single-typed, add the second type once the install base tolerates arrays) and makes array-typetolerance a MUST for consumers.quant:structuredUrlinstead of the id convention.(Also worth raising in #455 regardless of QuantPub: Aurboda's dual-typed posts delivered to a FitPub follower today hit that
ClassCastExceptionpath — array-valuedtypeis well-formed AS2 that FitPub should tolerate.)Spec changes in this PR (
docs/fep/quantpub.md, rev 7)Noteexplicitly allowed for interop; consumers MUST tolerate string and arraytype.routefield (time-ordered{lat, lon, t}, WGS84, downsampled) with normative privacy semantics: geography is its own explicit opt-in, bound in Aurboda to attaching the route-map image so track and image share one audience.GETs adopted (from FitPub) as an optional additional grant for followers-only payloads; corrects the previous "signatures would never be exercised" over-claim (true for Mastodon-class media fetches, not for purpose-built peers); capability tokens remain the required baseline since Level 1 has no actor keys; failure still answers 404, never 403.No code changes: Aurboda keeps dual-typing (now RECOMMENDED) and already ships everything the spec additions describe.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CwoP1SqJhHgHiEEoEQtUjT