Declare what Festipod needs from ActivityPods, rather than guess it
The Refine seam asks four things of a backend, and the ActivityPods README answers two: how its providers are configured, and that a declared shape tree gets its own Data Registration and container on the Pod. It is silent on the record shape and on whether update replaces or patches — the two the seam cannot default, and the two that fail silently when wrong. So the declaration is written before adoption instead of after: the surface is marked as declared rather than measured, the domain conditions are stated at full strength, and the two unknowns sit in Needs as requests addressed to the provider. Reading their source to settle them is the one thing this project does not do. This branch had no contract manifest at all, so it gets one. It carries no consume entry, because neither targeted backend publishes an engagement to pull; the declaration anchors on the README's commit identity, resolved with ls-remote, which names a state without fetching any content.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
---
|
||||
type: usage
|
||||
summary: What Festipod consumes from @activitypods/refine-providers as one of its interchangeable Refine backends, and the conditions its own vocabulary and confidentiality model put on that backend
|
||||
against: 66616484b96fb2227cd8cdf02321aacfb4811365
|
||||
---
|
||||
|
||||
# usage_festipod — Festipod on `@activitypods/refine-providers`
|
||||
|
||||
Festipod is a mobile-first web application: users create **meeting points** grafted onto public events, and sign up to them. Its entire persistence goes through Refine, behind a **backend seam** that exists so the app can support several Refine backends and let the user switch between them at runtime. `@activitypods/refine-providers` is one of the two backends targeted; the other is a Refine provider over NextGraph plus its polyfill. The seam's own contract — what a backend must answer before it can be added — lives in code, in `src/shared/refine/providers/types.ts`.
|
||||
|
||||
**Nothing is wired yet, and that is what this declaration is for.** The package is not installed and there are no call sites, so the surface below is **declared, not measured**: it is what Festipod will call, derived from the backend seam's requirements and from the only public specification available, this repository's README. Two of the seam's four required answers are not in that README, and they are structural enough that guessing them would produce a backend that fails silently rather than loudly — so they are in `## Needs` instead, and adoption waits on them.
|
||||
|
||||
`against:` names the commit this repository's README resolved to on 2026-10-01, obtained with `git ls-remote` — an identity, not a pulled engagement. No engagement document is published for this interface, so nothing is pinned and nothing can drift; the pin is the best available anchor for *which* README these statements come from.
|
||||
|
||||
## Consumed surface
|
||||
|
||||
What Festipod intends to call, and nothing else. Anything the package offers that is absent here is free to evolve without us.
|
||||
|
||||
### Provider construction
|
||||
|
||||
- `authProvider({ clientId, redirectUri? })` — one call site, built once per backend selection. Solid-OIDC sign-in.
|
||||
- `dataProvider({ authProvider, resources, jsonContext })` — one call site, handed the `authProvider` above.
|
||||
- `liveProvider` — mounted so Refine can subscribe behind every query.
|
||||
|
||||
All three are built together, **per backend instance**, inside the seam's `build()`.
|
||||
|
||||
### Refine operations, through `@refinedev/core` v5 hooks
|
||||
|
||||
Festipod reaches the data provider only through Refine's hooks, never directly: `useList` (`getList`), `useCreate` (`create`), `useUpdate` (`update`), `useDelete` (`deleteOne`). `getOne` is reached by Refine's own cache plumbing rather than by an explicit app call.
|
||||
|
||||
The app mounts Refine with `liveMode: 'auto'` and `mutationMode: 'optimistic'`.
|
||||
|
||||
### Resources
|
||||
|
||||
Four, one per persisted entity: **events**, **meeting points**, **participations** and **user profiles**. Each is declared through `resources` with a `shapeTreeUri`.
|
||||
|
||||
### Not consumed
|
||||
|
||||
- The `types` fallback for a resource — Festipod intends `shapeTreeUri`, which the README presents as the preferred form.
|
||||
- `appStatusCheckInterval` — no need identified for driving the app-status poll ourselves.
|
||||
|
||||
## Constraints
|
||||
|
||||
What must hold beyond the call list, for this backend to serve the app.
|
||||
|
||||
1. **Festipod's own vocabulary, not ActivityStreams.** The four entities are `fp:Event`, `fp:MeetingPoint`, `fp:Participation` and `fp:UserProfile`, defined by SHEX shapes this repo already holds and generates ORM bindings from. They are domain types of this product, and none of them is an ActivityStreams type. A backend that forces records into an `as:` class cannot carry them.
|
||||
2. **Two identifiers must stay distinguishable.** The app needs, per record, the key `update` and `getOne` address it by *and* the id other records reference it by. These are not always the same value — on the other targeted backend the address is the document URI while a participation points at the subject IRI — so the seam carries both (`recordKey`, `domainId`) and collapsing them breaks every cross-entity reference without any error surfacing.
|
||||
3. **Update semantics must be known, not inferred.** A backend whose `update` replaces the resource requires the app to send the complete field set; one that patches requires only the changed fields, and sending a complete set would clobber a concurrent edit. The seam declares which it is (`updateReplaces`, `toPatch`) and behaves accordingly, so the wrong value silently drops data in one direction or the other.
|
||||
4. **Every participation must be readable by every user.** Festipod's participant count is the number of participations of an event, counted with no filtering. What is confidential is the **link between a participation and a person** — only a connection can read the profile a participation points at and recognise who it is — never the participation record itself. So `getList` on participations must return all of an event's participations regardless of who created them. A backend that scopes participations to their author cannot produce the count.
|
||||
5. **The count is never a stored, incremented field.** Reading a counter, adding one and writing it back derives each write from a read of the previous one and races itself through any data provider. This is measured, not theoretical, and it is why the app derives the count from the participation list at read time.
|
||||
6. **Changes must be pushed, not polled.** The app and its BDD suites wait for a change to arrive; neither has a polling loop, and adding one is ruled out by project doctrine. `liveMode: 'auto'` is mounted on that basis, so the live provider has to deliver a change to a subscribed query.
|
||||
7. **Per-instance configuration, no process-global state.** Two backends coexist in one page and the user switches between them at runtime; switching remounts Refine to discard the cache of the backend being left. Building this backend's providers must therefore have no global side effect that outlives the remount or collides with the other backend's providers.
|
||||
8. **Each entity carries a declared visibility** — public, protected or private, per the product's confidentiality model — and it is the backend that honours it. Refine has no notion of it, so the intent is declared at the seam and enforcement is the backend's.
|
||||
|
||||
## Frictions
|
||||
|
||||
none
|
||||
|
||||
## Needs
|
||||
|
||||
Two answers. Both are facts about this package that its README does not state, both are structural in Festipod's seam, and neither can be established from the outside without reading source — which this project does not do.
|
||||
|
||||
1. **Is a shape tree URI that Festipod hosts itself, over its own vocabulary, accepted at app registration?**
|
||||
|
||||
The README says a resource is best declared by `shapeTreeUri`, that the `types` fallback is *"resolved against the Data Registrations' shape trees"*, and that *"Each declared shape tree gets its own Data Registration (and LDP container) on the user's Pod once they grant access"*. What it does not say is whether the URI may point at a shape tree **Festipod publishes and controls**, or whether it must resolve to an already-published vocabulary known to the Pod.
|
||||
|
||||
This decides the whole adoption. Festipod's four entities are its own domain types, so if self-hosted shape trees are accepted it authors four of them from the SHEX shapes it already has, declares them, and each gets its container on the user's Pod — the mechanism as documented, used as designed. If they are not accepted, the app needs to know what the accepted path for an application-specific vocabulary is instead.
|
||||
|
||||
2. **What is the shape of a record returned by `getList`, and does `update` replace or patch the resource?**
|
||||
|
||||
Specifically: which field of a returned record is the key Refine addresses it by, which field is the id other records should reference, and whether the two are the same value. Then, for `update`: whether the values sent replace the resource or are merged into it.
|
||||
|
||||
These are constraints 2 and 3 above — the two the seam cannot default. Both fail quietly when wrong: the wrong identifier produces references that resolve to nothing with no error anywhere, and the wrong update semantics either drops every field a write omits or overwrites a concurrent edit. A worked example of a returned record and a one-line statement of the update semantics would be enough.
|
||||
Reference in New Issue
Block a user