Skip to content

docs(fep): QuantPub rev 7 — FitPub format alignment (#905) - #1045

Merged
fiddur merged 2 commits into
developfrom
905-fep-quantpub-fitpub-alignment
Aug 21, 2026
Merged

docs(fep): QuantPub rev 7 — FitPub format alignment (#905)#1045
fiddur merged 2 commits into
developfrom
905-fep-quantpub-fitpub-alignment

Conversation

@fiddur

@fiddur fiddur commented Aug 21, 2026

Copy link
Copy Markdown
Owner

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 (route was shipped but undocumented).

How the formats compare

Structurally near-identical — great news for a shared FEP:

  • Both deliver a plain Note (HTML content, one-line stat summary, generated image attachment) for Mastodon compatibility.
  • Both reuse AS2 startTime/endTime natively on the Note.
  • Both use an out-of-band structured payload with an in-band same-host-only pointer: FitPub's fitpub: { "detailUri": … } (namespace https://fitpub.social/ns#) ≈ our quant:structuredUrl.
  • Both do SSRF-guarded, best-effort enrichment, only for followed actors.

Differences:

Aspect FitPub QuantPub
Detail payload Internal API DTO (/api/activities/{id}, fixed camelCase schema: totalDistance, metrics.averageHeartRate, simplifiedTrack GeoJSON, …) Purpose-built contract: open {key, value, unit} metric array, kind discriminator, snake_case
Detection Property presence (fitpub/fitpubDetailUri) + NodeInfo software identity (gates follows) Object-id convention + /.well-known/quantpub capability doc
Time series None (consumes scalars + track geometry only) Bucketed series, per-metric per-post opt-in
Followers-only auth HTTP-signature-verified GET Capability tokens
Non-public response 403 Forbidden 404 (normative: unshared ≡ nonexistent)
Typing Single "Note"; inbound code does (String) activity.get("type") → an array-valued type breaks it Dual-typed ["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 via include_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:

  1. New routes, not changed ones: /.well-known/quantpub + the §5/§6 endpoints sit alongside the untouched /api/activities/{id}.
  2. Extra props are ignored by old peers: quant:* + quant:structuredUrl can ride next to fitpub.detailUri on the same Note; un-upgraded FitPub instances see exactly the object they saw before.
  3. The one breaker was dual-typing: old FitPub inbound code casts type to String, 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-type tolerance a MUST for consumers.
  4. No id-layout change required: a publisher keeping its existing object ids uses quant:structuredUrl instead 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 ClassCastException path — array-valued type is well-formed AS2 that FitPub should tolerate.)

Spec changes in this PR (docs/fep/quantpub.md, rev 7)

  • §2/§3 — dual-typing MUST → RECOMMENDED; single-typed Note explicitly allowed for interop; consumers MUST tolerate string and array type.
  • §5 — documents the optional route field (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.
  • §7.1 (new) — "Coexistence and incremental adoption": the backward-compatible adoption path above, written for implementations with deployed install bases.
  • §9 — signed 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.
  • Prior art — FitPub entry expanded with its actual wire format (previously lumped with Open Pace as "product-shaped").
  • Security considerations — route identifiability (tracks start/end at home; mask before export, not in rendering).

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

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
fiddur marked this pull request as ready for review August 21, 2026 13:36

@fiddur fiddur left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🛑 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 simplifiedTrack is GeoJSON while §5's route mints 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 type on 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.

Comment thread docs/fep/quantpub.md Outdated
- §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 fiddur left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

✅ 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 route field matches the running code. packages/api-spec/src/schemas/feed-structured.ts defines route as an optional array of { lat, lon, t } (WGS84 + ISO 8601), and resolveStructuredContent (apps/backend/src/services/feed-structured.ts:230) only populates it when post.include_map is 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 (resolveStructuredRoutedownsampleRoutePoints(…, 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:structuredUrl only, and §5 discrimination is by kind / activity_type / quant:observationOf — so a single-typed Note genuinely loses nothing. Aurboda's own inbound enrichment (timeline-enrich.ts) never inspects type, so the new "consumers MUST tolerate both string and array forms" requirement holds for the reference implementation, while outbound withQuantType still 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 t isn'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.
  • route is 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 GeoJSON LineString in §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.

@fiddur
fiddur merged commit 775f73e into develop Aug 21, 2026
4 checks passed
@fiddur
fiddur deleted the 905-fep-quantpub-fitpub-alignment branch August 21, 2026 13:46
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