Skip to content

Emit CID service[] with lws:OpenIdProvider in WebID profiles (#320)#321

Merged
melvincarvalho merged 5 commits into
gh-pagesfrom
issue-320-cid-service-profile
Apr 23, 2026
Merged

Emit CID service[] with lws:OpenIdProvider in WebID profiles (#320)#321
melvincarvalho merged 5 commits into
gh-pagesfrom
issue-320-cid-service-profile

Conversation

@melvincarvalho
Copy link
Copy Markdown
Contributor

Summary

Adds a single CID 1.0 service[] entry to generated WebID profiles, mirroring the existing solid:oidcIssuer predicate. This lets LWS-aware verifiers establish trust against JSS-issued profiles per the LWS 1.0 OpenID Connect FPWD, which prescribes looking up service[type=lws:OpenIdProvider].serviceEndpoint to confirm the token's iss claim.

What changes

src/webid/profile.js — four small additions to generateProfileJsonLd:

  1. Two context namespace entries: cidhttps://www.w3.org/ns/cid/v1#, lwshttps://www.w3.org/ns/lws#.
  2. Two context term aliases: servicecid:service, serviceEndpointcid:serviceEndpoint.
  3. One service[] entry in the emitted profile: { id: '<doc>#oidc', type: 'lws:OpenIdProvider', serviceEndpoint: issuer }.
  4. Document URL derived from the WebID by stripping the #me fragment.

Properties

  • Zero removals. All existing WebID predicates (foaf:name, solid:oidcIssuer, pim:storage, ldp:inbox, etc.) remain unchanged — Mashlib, solid-client-authn, rdflib.js see the same profile as before.
  • Additive ~200 bytes per profile.
  • Existing pods unaffected unless regenerated; new pods get the CID service entry on first creation.
  • Scope conservative. Only the one service type that is normatively defined in the four LWS10 FPWDs (lws:OpenIdProvider). Storage, Inbox, and verificationMethod[] are explicitly out of scope — tracked for later once LWS defines those types or a concrete deployment asks.

Test plan

  • Three new tests in test/webid.test.js:
    • service[] array exists with an lws:OpenIdProvider entry
    • service.serviceEndpoint mirrors the existing oidcIssuer value
    • service.id is a fragment on the profile document URL
  • All existing WebID tests still pass (dual-write: legacy predicates still emitted and asserted).
  • Full suite green (443/443).

Part of #319 (LWS 1.0 Auth Suite alignment). Fixes #320.

Adds a single CID 1.0 service entry to generated WebID profiles,
mirroring the existing `solid:oidcIssuer` predicate. LWS-aware verifiers
can now establish trust against JSS-issued profiles by looking up
`service[type=lws:OpenIdProvider].serviceEndpoint` per the LWS 1.0
OpenID Connect FPWD.

Additive only — all existing WebID predicates remain unchanged, so
Mashlib, solid-client-authn, rdflib.js, etc. see the same profile
they see today. Storage/Inbox/verificationMethod are deferred until
LWS normatively defines those types.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a CID 1.0 service[] entry (typed lws:OpenIdProvider) to emitted WebID profile JSON-LD so LWS-aware verifiers can discover the issuer via serviceEndpoint, while keeping existing Solid predicates for backwards compatibility.

Changes:

  • Extend WebID profile @context with cid/lws namespaces and service/serviceEndpoint terms.
  • Emit a single service[] entry pointing to the OIDC issuer, using a fragment ID on the profile document URL.
  • Add tests asserting presence and correctness of the new service[] entry.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/webid/profile.js Adds CID/LWS context terms and emits a CID service[] entry mirroring oidcIssuer.
test/webid.test.js Adds coverage for the new CID service[] entry and its serviceEndpoint/id values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/webid/profile.js Outdated
Comment on lines +69 to +70
'id': `${docUrl}#oidc`,
'type': 'lws:OpenIdProvider',
Comment thread test/webid.test.js
Comment on lines +113 to +121
it('lws:OpenIdProvider service.serviceEndpoint mirrors oidcIssuer', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
const oidc = jsonLd.service.find((s) => s.type === 'lws:OpenIdProvider');
assert.strictEqual(
oidc.serviceEndpoint,
jsonLd.oidcIssuer,
'serviceEndpoint must equal the existing oidcIssuer value'
);
Comment thread test/webid.test.js Outdated
Comment on lines +124 to +130
it('lws:OpenIdProvider service.id is a fragment on the profile document', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
const oidc = jsonLd.service.find((s) => s.type === 'lws:OpenIdProvider');
const docUrl = jsonLd['@id'].split('#')[0];
assert.strictEqual(oidc.id, `${docUrl}#oidc`,
'service entry id should be `<profile-doc>#oidc`');
- Alias `id`/`type` to @id/@type in @context so nested service[] entries
  are interpreted as real JSON-LD node identifiers/types rather than
  plain properties. CID 1.0 and DID contexts do the same; LWS verifiers
  that expand JSON-LD would otherwise fail to read these entries.
- Tighten the two service-entry tests with explicit assertions on
  `jsonLd.service` being an array and `oidc` being truthy, so missing
  data produces clean assertion failures instead of TypeError.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates generated WebID profile JSON-LD to additionally emit a CID 1.0 service[] entry of type lws:OpenIdProvider, mirroring the existing solid:oidcIssuer, to align JSS profiles with the LWS 1.0 OpenID Connect discovery expectations.

Changes:

  • Extend the profile JSON-LD @context with cid/lws namespaces and CID service terms.
  • Emit a service array containing an lws:OpenIdProvider service object whose serviceEndpoint mirrors oidcIssuer.
  • Add tests asserting the service[] entry exists and is shaped as expected.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/webid/profile.js Adds CID/LWS context terms and emits the new service[] entry in generated profiles.
test/webid.test.js Adds assertions for presence/shape of the new CID service[] entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/webid/profile.js
Comment on lines +40 to +56
'cid': CID,
'lws': LWS,
// Alias `id` → @id and `type` → @type so nested nodes (like each
// `service[]` entry) are interpreted correctly by JSON-LD processors.
// CID 1.0 and DID contexts do the same.
'id': '@id',
'type': '@type',
'inbox': { '@id': 'ldp:inbox', '@type': '@id' },
'storage': { '@id': 'pim:storage', '@type': '@id' },
'oidcIssuer': { '@id': 'solid:oidcIssuer', '@type': '@id' },
'preferencesFile': { '@id': 'pim:preferencesFile', '@type': '@id' },
'publicTypeIndex': { '@id': 'solid:publicTypeIndex', '@type': '@id' },
'privateTypeIndex': { '@id': 'solid:privateTypeIndex', '@type': '@id' },
'isPrimaryTopicOf': { '@id': 'foaf:isPrimaryTopicOf', '@type': '@id' },
'mainEntityOfPage': { '@id': 'schema:mainEntityOfPage', '@type': '@id' }
'mainEntityOfPage': { '@id': 'schema:mainEntityOfPage', '@type': '@id' },
'service': { '@id': 'cid:service', '@container': '@set' },
'serviceEndpoint': { '@id': 'cid:serviceEndpoint', '@type': '@id' }
Comment thread test/webid.test.js
Comment on lines +105 to +135
it('should emit a CID service[] with an lws:OpenIdProvider entry', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
assert.ok(Array.isArray(jsonLd.service), 'profile should have a service array');
const oidc = jsonLd.service.find((s) => s.type === 'lws:OpenIdProvider');
assert.ok(oidc, 'service[] must include an lws:OpenIdProvider entry');
});

it('lws:OpenIdProvider service.serviceEndpoint mirrors oidcIssuer', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
assert.ok(Array.isArray(jsonLd.service), 'profile should have a service array');
const oidc = jsonLd.service.find((s) => s.type === 'lws:OpenIdProvider');
assert.ok(oidc, 'service[] must include an lws:OpenIdProvider entry');
assert.strictEqual(
oidc.serviceEndpoint,
jsonLd.oidcIssuer,
'serviceEndpoint must equal the existing oidcIssuer value'
);
});

it('lws:OpenIdProvider service.id is a fragment on the profile document', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
assert.ok(Array.isArray(jsonLd.service), 'profile should have a service array');
const oidc = jsonLd.service.find((s) => s.type === 'lws:OpenIdProvider');
assert.ok(oidc, 'service[] must include an lws:OpenIdProvider entry');
const docUrl = jsonLd['@id'].split('#')[0];
assert.strictEqual(oidc.id, `${docUrl}#oidc`,
'service entry id should be `<profile-doc>#oidc`');
});
Comment thread src/webid/profile.js
Comment on lines +72 to +78
'service': [
{
'id': `${docUrl}#oidc`,
'type': 'lws:OpenIdProvider',
'serviceEndpoint': issuer
}
]
Copilot spotted that the JSON-LD → Turtle converter dropped nested node
objects silently: property values that were objects with their own
@id/@type/properties (like a CID service[] entry) were collapsed to a
bare URI reference, losing the entry's type and serviceEndpoint. The
Turtle variant of a JSS profile therefore had no usable CID service
data — LWS verifiers asking for Turtle would see only dangling refs.

Three fixes:

- src/rdf/turtle.js — the main conversion loop now does a BFS over
  nodes, discovering nested node objects and emitting their triples as
  their own subjects.
- src/rdf/turtle.js — expandUri recursively expands context values that
  are themselves CURIEs (e.g. `cid:service` in a context term's @id),
  so prefixed identifiers resolve to full URIs instead of leaking as
  `<cid:service>` in the output.
- src/webid/profile.js — service entries use @id/@type directly and
  the id/type aliases added in the previous commit are removed. Direct
  JSON-LD keywords interoperate with the converter cleanly and match
  the custom-context style used elsewhere in this file.

Test plan:
- New Turtle-conneg regression test asserts the service predicate, the
  lws:OpenIdProvider type, the cid:serviceEndpoint predicate, and that
  the service entry appears as a subject (first-class node) in Turtle.
- Existing webid tests updated to check s['@type'] / s['@id'] on the
  nested service objects rather than the aliased keys.
- Full suite green (444/444).
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds LWS/CID-compatible service[] metadata to generated WebID profiles and updates JSON-LD→Turtle conversion so nested service nodes are preserved when conneg is enabled.

Changes:

  • Emit a CID service[] entry of type lws:OpenIdProvider in generated WebID profile JSON-LD (mirroring solid:oidcIssuer).
  • Extend JSON-LD→Turtle conversion to traverse and emit nested node objects (e.g., the service[] entry) and improve context term expansion.
  • Add JSON-LD and Turtle conneg tests asserting the CID/LWS service triples are present.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/webid/profile.js Adds CID/LWS prefixes and emits a CID service[] entry for OIDC trust discovery.
src/rdf/turtle.js Updates JSON-LD→quads conversion to include nested nodes and expands context CURIE mappings.
test/webid.test.js Adds assertions for CID service[] in JSON-LD and ensures Turtle conneg output retains nested service triples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rdf/turtle.js Outdated
Comment on lines +197 to +198
while (queue.length) {
const node = queue.shift();
Comment thread src/rdf/turtle.js Outdated
Comment on lines +200 to +202
const subjectUri = resolveUri(node['@id'], baseUri);
if (visited.has(subjectUri)) continue;
visited.add(subjectUri);
Comment thread src/rdf/turtle.js
Comment on lines +411 to 420
// Check if it's a term in context. A context value can itself be a
// CURIE (`cid:service`) that still needs prefix expansion, so recurse.
if (context[uri]) {
const expansion = context[uri];
if (typeof expansion === 'string') {
return expansion;
return expandUri(expansion, context);
}
if (expansion['@id']) {
return expansion['@id'];
return expandUri(expansion['@id'], context);
}
Three fixes to the JSON-LD → Turtle converter, all prompted by Copilot
review on #321:

- Replace queue.shift() (O(n) per step) with index-based iteration so
  the BFS is linear in the number of nodes, not quadratic.
- Drop the per-subject visited-set that was silently suppressing a
  second top-level document with the same @id. Duplicate RDF triples
  are harmless; losing data is not. Cycle protection now happens at
  the enqueue step via a WeakSet on object identity, which prevents
  the same nested object from being enqueued twice but still lets
  distinct top-level docs for the same subject both emit.
- Add cycle protection to expandUri. Without it a user-supplied
  context carrying a term-level loop (a → b → a) could cause
  unbounded recursion and a stack overflow on the server — a remote
  DoS once conneg is enabled on a pod.

New test/turtle.test.js covers:
- Cyclic context (a → b → a) does not hang
- Self-loop context (a → a) does not hang
- Duplicate top-level @id does not drop data
- Cyclical nested node graph does not hang

Full suite: 448/448.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds LWS/CID-aligned service[] metadata to generated WebID profiles and ensures Turtle conneg preserves nested service node triples, supporting LWS-aware issuer trust checks.

Changes:

  • Emit CID/LWS namespaces and a CID service[] entry (lws:OpenIdProvider) in generated WebID profiles.
  • Update JSON-LD → Turtle conversion to BFS-emit nested node objects (and add cycle-safety in URI expansion).
  • Add integration + unit tests covering the new profile shape and Turtle conversion regressions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/webid/profile.js Adds CID/LWS context terms and emits the new service[] entry in generated profiles.
src/rdf/turtle.js Ensures nested JSON-LD nodes (like service[] entries) are emitted as first-class Turtle subjects; adds cycle guard in context expansion.
test/webid.test.js Adds assertions for the new service[] entry and verifies it survives Turtle content negotiation.
test/turtle.test.js Adds unit tests for converter regressions (cycle safety, duplicate @id handling, cyclical nested references).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/webid/profile.js
Comment on lines +67 to +72
'service': [
{
'@id': `${docUrl}#oidc`,
'@type': 'lws:OpenIdProvider',
'serviceEndpoint': issuer
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — my issue-body example was inconsistent with the code. I've updated #320's Before/After to use @id/@type (matching what this PR actually emits) and added a short rationale:

Nested service entries use the JSON-LD keywords @id and @type directly. CID 1.0's own context URL (https://www.w3.org/ns/cid/v1) provides id/type aliases for authors who prefer the shorter form, but since this profile uses inline term definitions rather than a context URL, we stick with the keywords — they're JSON-LD-native and don't depend on an out-of-band context fetch. A verifier that runs a JSON-LD processor (the expected behaviour for CID-conformant tooling) expands both forms identically.

On the naive-plain-JSON-lookup concern specifically: a verifier that reads this doc as plain JSON would also see "@type": "lws:OpenIdProvider" — which is a CURIE that only expands to https://www.w3.org/ns/lws#OpenIdProvider via the context. So any verifier that doesn't run JSON-LD expansion is already broken regardless of id vs @id. Running a JSON-LD processor is the minimum bar for reading CID-conformant documents, and once that's in play both key shapes are equivalent.

Could you re-review now that the PR description / issue examples match the code?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds LWS/CID-aligned service[] data to generated WebID profiles and ensures the JSON-LD→Turtle conversion preserves nested service node triples (needed for LWS verifiers requesting Turtle).

Changes:

  • Emit a CID service[] entry with lws:OpenIdProvider in generated WebID profile JSON-LD.
  • Update JSON-LD→Turtle conversion to BFS-traverse nested node objects and guard against cyclic context expansion.
  • Add regression tests for the new WebID shape and Turtle conversion behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/webid/profile.js Adds CID/LWS context terms and emits the service[] entry referencing the issuer.
src/rdf/turtle.js Preserves nested nodes during conversion and adds cycle-safety for context expansion.
test/webid.test.js Adds assertions for service[] and verifies Turtle conneg retains nested service triples.
test/turtle.test.js Adds direct unit tests for converter regressions (cycles, duplicate @id, cyclical nested refs).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rdf/turtle.js
Comment on lines 423 to 428
if (uri.includes(':')) {
const [prefix, local] = uri.split(':', 2);
const ns = context[prefix] || COMMON_PREFIXES[prefix];
if (ns) {
return ns + local;
}
@melvincarvalho melvincarvalho requested a review from Copilot April 23, 2026 17:43
@melvincarvalho melvincarvalho merged commit 701ce15 into gh-pages Apr 23, 2026
2 checks passed
@melvincarvalho melvincarvalho deleted the issue-320-cid-service-profile branch April 23, 2026 17:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds LWS/CID-compatible OpenID Provider service discovery data to generated WebID profiles, and ensures RDF conneg (JSON-LD → Turtle) preserves nested CID service nodes needed by LWS verifiers.

Changes:

  • Emit a CID service[] entry (typed lws:OpenIdProvider) in generated WebID profile JSON-LD, mirroring solid:oidcIssuer.
  • Update the JSON-LD → Turtle converter to BFS-traverse and emit nested node objects (and harden expandUri against cyclic contexts).
  • Add regression/unit tests for the new profile shape and for Turtle conversion edge cases.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
test/webid.test.js Adds assertions for emitted CID service[] and verifies Turtle conneg retains nested service-node triples.
test/turtle.test.js Adds unit tests covering Turtle conversion regressions (nested nodes, cycles, duplicate @id, context pitfalls).
src/webid/profile.js Adds CID/LWS context terms and emits a single service[] entry for LWS OpenID Provider discovery.
src/rdf/turtle.js Ensures nested node objects are emitted as subjects in Turtle output and hardens URI expansion against cyclic contexts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

bourgeoa pushed a commit to bourgeoa/JavaScriptSolidServer that referenced this pull request Apr 28, 2026
…iptSolidServer#325)

Two related conneg bugs filed in JavaScriptSolidServer#325:

1. Q-weights ignored in the dispatch.
   src/handlers/resource.js used naive `acceptHeader.includes('text/turtle')`
   substring matching to decide between JSON-LD and Turtle. An Accept
   header of `application/ld+json, text/turtle;q=0.1` returned Turtle
   even though JSON-LD was the higher-q preference. Replaced five
   substring sites (handleGet container-with-index, container listing,
   resource HTML data island; handleHead container) with the existing
   q-aware `selectContentType` from src/rdf/conneg.js.

2. HEAD/GET divergence on containers without index.html.
   handleHead hard-coded `application/ld+json` for that case regardless
   of Accept, so HEAD reported a content-type that didn't match what
   GET would emit. The mismatch confused tooling that read HEAD
   content-type and then GET'd the body. handleHead now runs the same
   q-aware Accept logic so HEAD content-type tracks GET.

The auth-vs-anonymous mismatch in JavaScriptSolidServer#325 doesn't reproduce on the
current tip — likely fixed by the Turtle-converter work in JavaScriptSolidServer#321.
Both anon and auth paths now return identical content-type for the
same Accept.

Tests: new conneg.test.js block exercises q-weight respect, HEAD/GET
parity across four Accept shapes, and auth-vs-anon parity. 470/470.
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.

Align WebID profile emission with LWS 1.0 (dual-write CID service[])

2 participants