Skip to content

Sync/main from staging - #333

Merged
bourgeoa merged 89 commits into
mainfrom
sync/main-from-staging
Sep 28, 2026
Merged

bourgeoa merged 89 commits into
mainfrom
sync/main-from-staging

Conversation

@bourgeoa

Copy link
Copy Markdown
Contributor

No description provided.

bourgeoa and others added 30 commits May 20, 2026 19:15
- Convert `SessionWithLegacyEvents` imports to type-only imports to avoid runtime side effects when importing auth-related modules
- Prevent eager initialization of `authSession` via type-only usage in:
  - types.ts
  - SolidAuthnLogic.ts
  - solidLogic.ts
- Add focused tests for fetch bridge behavior in `solidLogicSingleton`:
  - use `window.fetch` when `credentials: omit`
  - fall back to `authFetch` when `session.fetch` is unavailable
- Keep migration compatibility behavior intact while improving import safety and regression coverage
Skip WebSession worker initialization for localhost/127.0.0.1 over http and use SessionCore with IndexedDB directly.

This prevents browser SecurityError noise from file:// worker resolution in local dev while keeping normal session behavior in other environments.
- session.ts: OIDC session factory (WebSession/SessionCore, database backends)
- events.ts: SessionEvents class (pure EventEmitter shim, zero side-effects)
- issuer.ts: issuer discovery via /.well-known/openid-configuration
- authSession.ts: login compatibility shim, legacy event wiring, authSession
  assembly; re-exports authSession and SessionWithLegacyEvents type

All existing imports continue working unchanged — authSession.ts is a
drop-in replacement with the same exports.
replace Inrupt by Uvdsl OIDC client
SharonStrats and others added 29 commits August 10, 2026 21:33
Prompt: add resource service regression tests and harden delete recursion

Co-authored-by: GPT-5.4 Mini <gpt-5.4-mini@openai.com>
Add functionality to support 3 dots menu
add a editable refresh to resource
Revert "add a edittable refresh to resource" — superseded by the session-transition metadata flag
The identity used to be assembled per call site from session fields, a cookie
fallback and a cleared mark, each with its own predicate; every new async path
(a slow restore, a late probe, a refocus) could reintroduce the same class of
bug in a different place.

The state now lives once per session in identityState.ts:

- sessionIsActive() is the single activity rule (an explicit false wins over a
  cached WebID);
- every observation (session event, token update, refocus resync, cookie
  probe result) is applied to one record, so a change is detected and reported
  once, whichever path noticed it;
- the record carries a version and the newest resync attempt, so an answer
  that belongs to an identity the session has since left — a restore started
  under Alice answering after Bob logged in — is dropped, and a caller that
  joins an attempt in flight is judged against the attempt's own baseline;
- transitions are derived from the previous/next snapshots
  (classifyTransition/identityReplaced), not from flags, so an unchanged
  identity costs no event.

The cookie fallback and the setTokenDetails watch are TEMPORARY: they exist
because uvdsl announces only isActive changes — not a WebID change that keeps
the session active, and not a cookie identity change at all. Both are isolated
here so they become a small deletion when the library reports identity changes
itself.

Nothing consumes the state yet: the next commit moves authSession,
SolidAuthnLogic and the fetch bridge onto it.

Tests: test/identityState.test.ts (15) — activity/predicate table, login and
logout (one replacement, no second one after the WebID is cleared), A->B while
active, an A->B that uvdsl never announces around setTokenDetails, cross-tab
clear reported once, joined resync, dropped stale answer, cookie identity
adopt/replace/clear, release semantics, refocus fan-out.
authSession, SolidAuthnLogic and the fetch bridge now use the per-session
identity state (identityState.ts) instead of assembling the identity per call
site:

- authSession subscribes once (events + the refocus resync) and publishes the
  derived `info` shape. 'logout' / 'sessionChange' / 'identityReplaced' come
  from the state; 'login' / 'sessionRestore' stay with checkUser, the only
  place that knows which path activated the session.
- SolidAuthnLogic keeps only what is its own: the redirect handling, the NSS
  cookie probe and saveUser. currentUser() asks the state for the effective
  identity, and the probe result goes through its subscription — a probe that
  answers after dispose(), or while the session took ownership, can no longer
  resurrect an identity. fallbackWebId, cookieBackedFallback,
  cookieProbeGeneration, the refocus watcher, probeCookieIdentity() and
  reportFallbackIdentityChange() are gone: one record replaces them.
- solidLogicSingleton decides the fetch credentials from the state, so a
  session that reports itself inactive (or was reported cleared) cannot send
  the previous identity's credentials.
- AuthnLogic gains the optional dispose() the state's lifetime needs; the
  legacy event vocabulary is exported and includes the identity events.

Tests: the fetch-bridge test fakes the session identity instead of the derived
`info` shape, whose assignment is ignored on purpose. Full suite green.
Replaces the smoke tests with the behaviours the identity state has to keep
for SolidAuthnLogic:

- currentUser(): the active session's WebID; logged out for an explicit
  isActive:false or info.isLoggedIn:false with a cached WebID; the legacy
  WebID-only shape accepted.
- checkUser(): sessionRestore/login announced once, from the path that
  activated the session; "No session to restore." is treated as logged out
  while a failure that nevertheless left the session active is rethrown; the
  NSS cookie probe recovers the WebID on *.localhost and reports it as a
  session change, is skipped when the session already has one, and a probe
  that answers after the session took the identity is ignored.
- refocus/dispose: the cookie identity is re-probed and its loss reported; a
  disposed instance stops probing (an in-flight probe result is dropped),
  while a second instance using the same session keeps the watcher.
- authSession.info: derived, explicit false reported, assignment ignored.

The tests subscribe a forwarder like authSession does in the app, so the
emitted list means the same thing as the legacy events consumers see.
A session transition changes whose credentials a request would carry, but
editable() reads responses that are not keyed by identity: a document fetched
anonymously (before a restore completed) or under a previous WebID keeps
answering for the old identity.

flagAuthorizationOnSessionTransitions() subscribes to the identity state — one
call per applied transition, whichever path noticed it — and marks every
recorded response out-of-date, so editable() answers "unknown" instead of the
previous identity's access. A failed invalidation is remembered as
refreshRequired, so the decision points repair instead of trusting a store
that could not be invalidated.

refreshDocumentAuthorization() / ensureDocumentAuthorization() /
loadAuthorizedDocument() force-refresh (force: true, clearPreviousData: true)
with the store's transition generation stamped around each attempt: an answer
overtaken by a transition is retried under the new identity and stays
"unknown" when it cannot be established — never the previous identity's
answer. loadAuthorizedDocument also generation-checks the load, because a
response begun before a transition can be recorded after it.

utilityLogic's followOrCreate* repair before reading (and fail closed with
NotEditableError), and solidLogic wires the invalidation where the session is
created.

identityState reports an applied transition through onTransition (once),
while onEvent still carries the individual legacy events: a transition that
carries two events must not make a consumer that only invalidates do its work
twice.

On rdflib 2.4.0 a plain load cannot repair a flagged document (the literal /
NamedNode mismatch, linkeddata/rdflib.js#427); from 2.4.1 load() matches the
literal, so checkEditable() heals too — the force path remains the
deterministic repair on both.

Tests: 12 for the store side (flag on change only, every transition path,
release, failed invalidation, definitive answers, repair, overtaken load,
failure modes).
rdflib 2.4.1 carries linkeddata/rdflib.js#871: load() matches the recorded
request by its URI literal and refetches a document whose recorded answers are
all flagged, so a plain load()/checkEditable() heals a flagged document again.
Our repair path (refreshDocumentAuthorization/ensureDocumentAuthorization/
loadAuthorizedDocument) is unchanged and stays the deterministic repair — it
also covers consumers still on rdflib 2.4.0.

Contract tests against the real UpdateManager/Fetcher:
- definitive -> unknown when flagged -> definitive after a fresh response;
- load() heals a flagged, already-loaded document on 2.4.1;
- refreshDocumentAuthorization() repairs on any version.

package.json / package-lock.json: rdflib ^2.4.0 -> ^2.4.1, lock edited to the
single dependency entry instead of regenerating it.
deliver() interleaved a subscriber's own events with its transition callback,
so which ran first depended on subscription order: in the app authSession
subscribes before the store, and a legacy 'logout' listener could therefore
read the store before it had been invalidated (measured in the e2e: a
synchronous read inside the listener still saw the previous identity's write
capability).

Every subscriber's onTransition now runs before any subscriber's onEvent, so
"the store is already invalidated when you hear the event" is a guarantee
rather than an accident.

Test: subscribers in the app's order observe ['transition', 'event', 'event'].
…efresh

rdflib 2.4.1 refetches a document whose recorded answers are all flagged
(linkeddata/rdflib.js#871) — exactly the state an identity transition leaves
behind — so the store side no longer forces a fetch by hand:

- forceRefresh() and refreshDocumentAuthorization() are gone; repairDocument()
  marks the responses out-of-date (including one recorded after the transition,
  which the transition's own flag could not have marked) and loads, retrying
  under a new identity and staying unknown if it keeps moving;
- a store that cannot invalidate is repaired through the same path, and
  ensureDocumentAuthorization() fails closed when the repair cannot be
  established — a store that cannot invalidate must not have its answers
  trusted;
- the module and its tests state the requirement plainly: rdflib >= 2.4.1.

Tests: the store-side suite models the flag/load contract instead of a refresh
callback; the contract test asserts that a decision point repairs a flagged
document through a load.
loadAuthorizedDocument() answers whether a document can be consumed; on a
store without fetcher.load it fell through to ensureDocumentAuthorization()
and could answer "yes" for a document it never loaded. It now fails like a
plain load() would.

createSolidLogic() keeps the unsubscribe that
flagAuthorizationOnSessionTransitions() returns and calls it from
authn.dispose(), so replacing a SolidLogic instance no longer leaves the old
store subscribed to the session (and retained by it).
watchTokenUpdates() only compared the identity when setTokenDetails()
resolved, so an update that applied the new identity and then rejected (a
failed persistence, say) left the transition unseen — the one thing the
wrapper exists to catch. The comparison now runs on success, on rejection
and on a synchronous throw; the failure itself is rethrown untouched.

resyncActionOf() promised the newest resync action but returned the first
subscriber that had one, i.e. the oldest. It keeps the last match instead,
so a second provider cannot make refocus behaviour depend on subscription
order.
… tests

invalidate() interpolated the caught value directly, which reads
"[object Object]" for a non-Error throw; String(error) matches the reload
warning in the same module.

The identityState tests now subscribe through a helper that keeps every
handle and releases it in afterEach: a first subscription attaches a
visibilitychange listener to the document, and leaving those behind lets a
later test's refocus run resyncs (and cookie probes) from a finished one.
@bourgeoa
bourgeoa merged commit 4bd9674 into main Sep 28, 2026
7 checks passed
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.

3 participants