Close the adversarial review round: auth hardening, data fidelity against the platform context, backend grants and docker coherence

This commit is contained in:
Sylvain Duchesne
2026-10-10 09:54:53 +02:00
parent 13d9f179ef
commit 8dfc574346
42 changed files with 11138 additions and 235 deletions
@@ -9,7 +9,7 @@ Platform checkout: `/home/sylvain/projects/activitypods/activitypods` (never mod
## Ports
fuseki 3030 (admin/admin), mailcatcher 1080, redis 6379, backend 3000, frontend 5000, arena 4567, tripleadmin 3033.
pod provider 3000 (OpenID + `/auth` signup/login), Festipod backend 3003, Festipod app 5000, fuseki 3030 (admin/admin), mailcatcher 1080, redis 6390, arena 4567, tripleadmin 3033.
## Bring up
@@ -17,6 +17,7 @@ fuseki 3030 (admin/admin), mailcatcher 1080, redis 6379, backend 3000, frontend
2. Install once: `cd backend && COREPACK_ENABLE_AUTO_PIN=0 corepack yarn install --frozen-lockfile`, same in `frontend/`. Keep `COREPACK_ENABLE_AUTO_PIN=0`: corepack auto-pin rewrites `packageManager` in package.json. Note: the pod-provider README says `yarn start` for the frontend; only the `dev` script exists — use `BROWSER=none corepack yarn dev`.
3. Backend: `cd backend && corepack yarn start`. Wait for "ServiceBroker with ... service(s) started successfully" (~11 s).
4. Frontend: `cd frontend && BROWSER=none corepack yarn dev`. Verify: `GET http://localhost:5000/` → 200 and `GET http://localhost:3000/.well-known/config.js` → 200.
5. Festipod stack (this repo root): `cp backend/.env.example backend/.env` before `docker compose up`. The compose reads `backend/.env` as an optional `env_file`; on a fresh clone the file is gitignored and absent, so this copy is what feeds the backend its ActivityPods settings. With the coherent layout the app is served on :5000 and the Festipod backend on :3003.
## Account and resource flow (all VERIFIED 2026-10-09)
@@ -8,7 +8,7 @@ status: open
## Where the work lives
Branch **`refine-backends`**, worktree `/home/sylvain/projects/festipod/festipod/.claude/worktrees/refine-backends`, pushed to origin. Two commits: `e282384` (the seam carried onto this base) and `df1db79` (URL selection + corrections).
Branch **`refine-backends`**, worktree `/home/sylvain/projects/festipod/festipod/.claude/worktrees/refine-backends`, pushed to origin; the tip records the T04 tree close plus the adversarial-review round — `## Current gate state` below names the states by date and tip.
It is based on **`ng-eventually`** at `c7cda38` — the most advanced branch, and the reason this branch exists. An earlier attempt, **`festipod-refine`** (tip `352d1f1`, worktree `.claude/worktrees/festipod-64`), was built on `main`, which turned out to be far behind; the Refine work was transferred here and that branch is now only an archive. It holds the **only** worked implementation of a `@ng-org/refine-providers` backend (`src/shared/refine/providers/nextgraph.ts`, in its history up to the commit that withdrew it) — the reference for the shape of the next backend.
@@ -109,9 +109,11 @@ So both platforms count deposits rather than people, and the gap sits in **neith
- **The migration was delegated to a subagent** and verified independently afterwards: the five transferred files are byte-identical, the manifest merge preserved both pre-existing entries, and the gates were re-run by hand. Its one worry — that a missing local `node_modules` had made the baseline incomparable — was **unfounded** (pnpm had installed the full tree locally; 22 entries against the main checkout's 21). Its debt block was better than the specification it was given: it caught the `__FESTIPOD_BACKEND__` consequence and two branch-relative phrasings.
- **Moving base falsified three claims in the transferred code**, all corrected: the inbox the count comment points at is local here; the worked provider implementation is not in this branch's ancestry; and the `MeetingPoint` SHEX shape **does** exist here, so its binding stopped being optional (a backend that cannot carry one of the four entities cannot serve the app).
## Current gate state (2026-10-09, end of the T04 tree, tip `f8c2e3b`)
## Current gate state (2026-10-10, adversarial-review round closed, tip = the commit recording this state)
Verified by the orchestrator's own runs: `bunx tsc --noEmit` exit 0 ; `bun test` 86 pass / 0 fail ; the UI cucumber layer (`not @wip and not @humain and not @data and not @e2e and not @multibrowser and not @suspendu`) 125 scenarios / 0 failed ; smoke `@e2e-activitypods` 3/3 against the live local stack (pod provider :3000, festipod backend :3003) ; `bun run build.ts` ok ; `contracts.py check --integrity-only` clean. `lint` carries only the pre-existing ERROR (`polyfill-surface/usage_festipod.md` missing `## Needs`). The polyfill `@data`/`@e2e`/`@multibrowser` suites are suspended with the NextGraph workstream (`@suspendu` tag, code preserved, nothing deleted). The app at root now runs the ActivityPods variant: Refine providers for Pod CRUD, Solid-OIDC sign-in (picker + `/login`), Moleculer backend. Known follow-ups: app-install consent on the pod (join→collection loop + OIDC round-trip both `@suspendu` until then), `redirect_uri` alignment, cross-pod catalog provider, friction 5 (attendees member shape), refine-providers pin re-evaluation when npm publishes gitHead.
Verified by the orchestrator's own runs after the round's fixes: `bunx tsc --noEmit` exit 0 ; `bun test` 96 pass / 0 fail ; the UI cucumber layer 125 scenarios / 0 failed (54 fully implemented + 71 `Scénario non implémenté` placeholders that carry a skipped step by design) ; smoke `@e2e-activitypods` 3/3 against the live local stack (pod provider :3000, festipod backend :3003, app served on an ephemeral port) ; `bun run build.ts` ok ; `contracts.py check --integrity-only` clean.
The round: three adversarial passes (auth & security, data seam & backend, gates & ops); every confirmed finding was fixed in the commit recording this state. Fix themes — auth: `redirect` param validated (same-origin leading-slash only), callback re-entry guarded against StrictMode double-exchange, stale `?code=` URLs degrade to the picker instead of the fatal screen, `register_app` without a session falls back to the picker (was a permanent blank screen), ActivityPods logout wired through the provider, deep links survive login, clientId trailing slash normalized. Data fidelity: the meeting point's host is read from the Pod-stamped `dc:creator` (the `organizer` term is absent from the platform context and never round-tripped, so the Join lost its addressee), `location`/`city` are written as inline named nodes (`as:location` is `@type: @id`; a free-text string resolved into a garbage IRI), record ids are URL-encoded (absolute IRIs broke the router's `/events/:id` parse), participant resolution is keyed by actor URI (profiles were stored under their document URI and never matched), one delayed re-invalidation reconciles the count after join/leave, a Join/Leave without addressee throws instead of posting to no one, anonymous placeholder rows render on the event detail screen. Backend & ops: the attendees collection gets a public `acl:Read` grant in `after.createAndAttach` (the count is readable by everyone; anonymity lives in the profiles' own WAC), Join/Leave handlers retry while the collection is still attaching, `tsx` promoted to dependencies so the `--prod` image starts, compose `env_file` made optional with fuseki/redis services added and the frontend published on :5000 (the SEMAPPS_FRONT_URL default — fixes the shipped redirect_uri mismatch), `test:data` removed (zero matching scenarios since the suspension — a vacuous green), `strict: true` on both cucumber configs.
`contracts.py check` also reports, pre-existing: `polyfill-surface: provider moved: a33fb8a214 → a8d53010c2` — the pulled copy is behind the pinned ref.
@@ -123,6 +125,13 @@ Verified by the orchestrator's own runs: `bunx tsc --noEmit` exit 0 ; `bun test`
- The four provider contributions.
- Wiring the seam: DONE 2026-10-09 (T04 tree, tip `f8c2e3b`) — `FestipodDataContext` runs over Refine with the ActivityPods backend as the registered default; the join→collection loop is built but `@suspendu` until the app is installed on a pod (consent screen). Current state: `## Current gate state` above.
- Both usage declarations are named `usage_festipod.md`, so the bare stem is ambiguous as a wikilink — refer to either by folder path.
- Driving `getAppStatus()` is parked: the pinned contract documents the method but not the shape of the `AppStatus` result, so "stale" is not decidable without guessing. Belongs in the declaration's Frictions/Needs when the outward channel opens.
- The `activitypods-providers` reconstruction is stamped at the 0.1.5-era pin while the installed package is 0.2.0 — re-constructing the contract at the installed version is a pending decision (the source is read once at adoption time; a re-read is its own decision).
- The attendees collection is publicly readable by decision (count for everyone; member URIs are not secret; anonymity lives in the profiles' WAC). Revisit if a product rule ever hides membership.
- The dedup defi (same event + same place + same time) is confirmed absent across the seam; the 2026-10-03 brief marks it an open challenge — a product call, not a regression.
- The catalog service's discovery index is process memory: a backend restart empties it while the Pods still hold the events. Latent until the cross-pod catalog provider lands.
- `src/modules/home/**` screens still build raw `/events/${id}` paths; `parsePath` tolerates them, but they should adopt `eventPath`/`userPath` next time they are touched.
- The dockerized path is validated by `docker compose config` only — the backend image has never been built or started by the gates; one manual operator run is owed.
## Standing constraints