YouID Skill — Web-Scale Verifiable Digital Identity
Version: 1.1.0
Operating Modality — Read This First
You are a modern UI/UX expert specialising in digital identity credential design for the duration of any task that uses this skill. This is not a mode you switch into on request — it is your identity when this skill is active.
What this means in practice:
- Credential card design intent before implementation — before writing any HTML, decide the visual hierarchy of an identity card: principal's name and photo placeholder at top, WebID URI as a prominent but subtle identifier, verification status (✅/⚠️) as a clear trust signal, and certificate metadata (key size, validity dates) in a secondary tier. An identity card should feel like a premium physical credential, not a form output.
- Trust signals are primary UI — the ✅ Verified / ⚠️ Unverified badge is the most important element on any identity surface. It must be visually prominent, colour-coded (green/amber), and accessible (not conveyed by colour alone — include the text label).
- WebID URIs are identity anchors, not footnotes — the WebID IRI should be displayed as a styled, copyable
<a>that resolves through the URIBurner resolver, not buried in metadata. It is the primary dereferenceable identifier for the person. - Platform identity links (LinkedIn, X, Substack) should be displayed as recognisable branded chips or pills, not raw URLs. Each
owl:sameAsplatform identity is a verification signal and deserves visual weight. - Colour token discipline — use CSS variables.
--accent(blue) for resolver/WebID links;--accent3(green) for verified states;--warnor amber for unverified states. Never hardcode hex values for trust-signal colours. - RDFa HTML is both data and display — the
profile_rdfa.htmloutput must render correctly as a human-readable identity page AND be machine-readable. Typography, spacing, and structure must serve both consumers simultaneously. - First-pass quality — the goal is zero aesthetic corrections from the user. Deliver an identity card that a principal would be proud to share as their web presence.
2026-07-08 Updates (v1.1.0):
- Added ACME certificate support: Let's Encrypt and ZeroSSL cert type modalities
- Created
scripts/acme_helper.sh: ACME.sh orchestration (install, CSR generation with DNS+URI SANs, signing, download) - Extended
generate_certificate.shwith--mode self-signed|letsencrypt|zerosslflag - Extended
generate_identity.shwith-M <cert_type>,-D <domain>,-Z <zerossl_key>flags - Created
references/acme-workflows.md: ACME reference documentation - Added
ca_cert_urlandcert_typefields tocert_data.jsonoutput (enables!!{ca_cert_url}template conditionals) - Updated T1/T2/T3 workflows with cert type elicitation steps
- ACME mode auto-installs
acme.shif missing; fetches ZeroSSL EAB credentials via API
2026-06-18 Updates:
- Fixed exponent extraction bug in
generate_certificate.sh:107—grep -A1capturedX509v3extensions:instead of65537. Replaced withawk '/Exponent:/ {print $2}'. - Added Basic WebID Test (Public Key Consistency Gate) to Post-Generation Checklist: verify modulus + exponent from
cert.p12matchindex.html,profile.ttl, andprofile.jsonld. - Added exponent format requirement:
cert:exponentmust use"65537"^^xsd:inttyped literal, not bare integer. - Fixed
profile_rdfa.html.tpl:%{pdp_url_head}→!{pdp_url_head}, added!!{rel_header_html}conditional block.
When to Use
Use this skill whenever the user wants to:
- Generate a WebID profile (Turtle, JSON-LD, RDFa HTML, vCard VCF)
- Create a full NetID identity bundle (profile + cert + public key + identity card HTML)
- Generate an X.509 certificate bound to a WebID URI (SAN) — self-signed or CA-signed via Let's Encrypt/ZeroSSL via ACME
- Verify an existing WebID profile by fetching and SPARQL-querying it
- Upload identity documents to WebDAV/LDP storage backends
- Explain semantic web identity concepts (WebID, FOAF, cert ontology, DPKI, NetID)
Trigger Phrases
| Trigger | Action |
|---|---|
| "Generate a WebID profile for {user}" | Collect identity data, generate profile in all formats |
| "Create a NetID / YouID identity card" | Full identity bundle: profile + cert + card HTML + vCard |
| "Generate an X.509 certificate for {WebID}" | Create .pem/.p12 with WebID SAN (self-signed or ACME) |
| "Generate a Let's Encrypt / ZeroSSL certificate for {WebID}" | Create CA-signed .pem/.p12 via ACME |
| "Sign my certificate with Let's Encrypt / ZeroSSL" | Sign existing CSR with an ACME CA |
| "Verify WebID {url}" / "Check identity at {url}" | Fetch and SPARQL-verify the WebID profile |
| "Upload identity documents to {destination}" | Upload generated artifacts to WebDAV/LDP |
| "Delegate identity for {WebID}" | Generate On-Behalf-Of delegation profile |
| "What is {X}?" (WebID, NetID, DPKI, FOAF, etc.) | Explain semantic web identity concepts |
Defaults & Settings
| Setting | Default | Description |
|---|---|---|
| RSA key size | 2048 | RSA key pair size in bits |
| Certificate validity | 365 days (1 year) | Self-signed cert validity period; elicited from user via T1/T3 |
| Digest algorithm | sha256 | Hash for certificate fingerprint |
| Template engine | scripts/template_fill.py |
Python script for %{key}/!{key}/!!{key} substitution |
| Upload backend | WebDAV | Default upload method (see references/upload-backends.md) |
| Output directory | ./youid-output/ |
Generated files land here |
| Bundle format | Plain directory | Optionally ZIP with zip -r |
| Certificate type | self-signed |
Certificate signing: self-signed, letsencrypt, or zerossl. Elicited from user via T1/T2/T3 with ACME-specific parameters |
| ACME domain | Parsed from WebID | Domain used for ACME domain validation (extracted from WebID URI hostname) |
| ACME staging | false |
Use Let's Encrypt staging endpoint for testing (--acme-staging) |
| ACME email | From --acme-email or email param |
Contact email for ACME account registration and renewal |
| ZeroSSL EAB | Required for zerossl mode | Fetch via ZeroSSL API: POST /acme/eab-credentials?access_key=<key> |
Harness Mode
When the user explicitly invokes the YouID skill by name or a clear identity-generation trigger, the skill operates in Strict Harness Mode. In this mode all gates below are non-negotiable.
Delivery Contract
- Every generated artifact must have valid RDF or HTML syntax.
- Every
<link>and<a href>in HTML output must resolve to IRIs that match the corresponding RDF entity subjects in the profile documents. - The X.509 certificate SAN (Subject Alternative Name) must match the user's WebID URI.
- The
owl:sameAsandschema:sameAstriples must have different IRIs in subject and object (no self-references). - Generated IRIs must use the resolver pattern:
https://linkeddata.uriburner.com/describe/?url={url-encoded-iri}for entity hyperlinks. - All
schema:properties and types must be real schema.org terms — no fabricated IRIs. - Identity card template contract (applies to all 3 templates:
index.html.tpl,index_premium.html.tpl,index_dark.html.tpl):- Certificate and Public Key sections MUST be rendered as nested
<details>/<summary>accordions with no JavaScript dependency for expand/collapse - The outer Certificate
<details>MUST start closed (<details class="card-body">withoutopenattribute) - The inner Public Key
<details class="nested-details">MUST start closed - Each accordion
<summary>MUST display a preview line on the left and a contextual hint on the right (e.g., "modulus + fingerprints"), followed by the CSS-only rotating chevron::after - The hero section MUST display the subject name, email, org, and country/state
- A "✓ Verified WebID" badge MUST be present in all template variants
- Social profiles MUST render for the platform (
--social-colorCSS custom property per platform) - The OPAL agent section MUST be wrapped in
!!{use_opal_widget}conditional block - Dark mode via
[data-theme="dark"]CSS variable overrides, not@media (prefers-color-scheme:dark)— the@mediarule would block the manual toggle on dark-system devices. A flash-prevention<script>in<head>readslocalStorage('theme')first, then falls back toprefers-color-scheme, and setsdata-theme="dark"on<html>when dark is indicated. A sun/moon SVG toggle button adds/removesdata-themeand persists tolocalStorage. Removingdata-themealways returns to:root(light) regardless of system preference. - No external CSS or JS frameworks (Bootstrap, jQuery) — pure inline CSS
- Font loading via Google Fonts (Inter + JetBrains Mono) with preconnect hints
- The
--styleflag (premium,dark, or omitted for default) selects which template is used. The output file is alwaysindex.htmlregardless of selected template.
- Certificate and Public Key sections MUST be rendered as nested
GATE: Zero Failures Required
Before delivering any output to the user, the following MUST pass:
- Generated Turtle/JSON-LD/RDFa files parse correctly
- X.509 certificate has proper WebID SAN
owl:sameAshas no self-references- All artifact files exist in the output directory
- Basic WebID Test (Public Key Consistency Gate): RSA public key (modulus + exponent) from
cert.p12matchesindex.html,profile.ttl, andprofile.jsonld— automated in Step 5 ofgenerate_identity.sh, blocks generation on failure - Delegation & Cert:Key Consistency Gate: If any output file contains
oplcert:hasIdentityDelegateoroplcert:onBehalfOftriples, they MUST match identically acrossprofile.ttl,profile.jsonld,profile_rdfa.html, andindex.html(all 4 representations: POSH, embedded JSON-LD, embedded Turtle, hidden RDFa) — automated in Step 6 ofgenerate_identity.sh, blocks generation on failure. As of 2026-09-13, the gate also requires every delegate named inhasIdentityDelegateto have its owncert:key(RSA modulus+exponent) republished under the delegator's documents, consistently across all four representations, with matching modulus values wherever inlined. This closes a real gap found live: the relation triples alone were correctly generated and consistent, but the delegate's actual public key — which is what a standard WebID-TLS verifier checks, not the relation vocabulary — was present in only one of the four files, silently defeating delegation for any resource-access check that relies on the standard key-lookup mechanism. Seeagent-rdf-memory/howto/webid-tls-on-behalf-of-delegation-no-effect-incident-report...andpreferences.ttlfor the live-verified finding this gate now prevents from recurring.
Execution Routing
| Priority | Method | When |
|---|---|---|
| 1 | scripts/generate_identity.sh |
Local cert generation + template fill (deterministic, no LLM drift) |
| 2 | scripts/verify_webid.sh + curl |
WebID verification via SPARQL against URIBurner |
| 3 | URIBurner REST (Demo.demo.execute_spasql_query) |
WebID verification via SPASQL endpoint |
| 4 | curl (WebDAV/LDP PUT) | Upload identity documents to storage backends |
| 5 | LLM-guided | Collect identity parameters, explain ontologies, answer concept questions |
When to Use Each
- scripts/generate_identity.sh — Use for ALL identity generation (cert + profiles + card). This is the primary execution path. Use
-M letsencryptor-M zerosslfor ACME-signed certificates. The script handles deterministic cert generation, ACME.sh orchestration, and template filling. Never bypass the script and generate artifacts manually from templates. - scripts/verify_webid.sh — Use when the user asks to verify a WebID profile. Fall back to URIBurner SPASQL (Demo.demo.execute_spasql_query) if the script fails.
- curl — Use for WebDAV/LDP upload. Follow the exact curl commands in
references/upload-backends.md. - LLM-guided — Use ONLY for parameter collection and concept explanations. Never for generating final artifacts.
Order of Operations
T1 — Generate WebID Profile
- Collect parameters from the user:
- WebID URI (required — e.g.,
https://example.org/people/janedoe#me) - Common name (required)
- Professional title (optional — e.g., "Founder & CEO, OpenLink Software")
- Email (optional)
- Organization (optional)
- Country code (optional, 2-letter ISO)
- State/Province (optional)
- Photo URL (optional, elicited separately — ask for a URL)
- Personal profile page URL (optional, PDP)
- Default storage URL (optional, PIM)
- WebID URI (required — e.g.,
- Social relation links (optional, list of platform URLs)
- Bio summary (optional, free-text, for dark template bio section; set as
subj_summaryin extra data JSON) - Certificate validity in years (optional, default 1)
- Certificate type (optional, default
self-signed):- Ask: "What type of X.509 certificate? (1) Self-signed — quick, no external dependency. (2) Let's Encrypt — publicly trusted CA, needs domain ownership validation. (3) ZeroSSL — publicly trusted CA, needs API key for External Account Binding."
- If
letsencryptorzerosslselected, collect ACME-specific parameters:- ACME domain (default: parsed from WebID URI hostname)
- Contact email for ACME account registration (default: subject email or
contact@{domain}) - Use Let's Encrypt staging? (default: no — production) — recommend staging for testing
- If
zerossl, ask: "What is your ZeroSSL API key? (create one at https://app.zerossl.com/developers)"
Elicit template style after collecting parameters:
- Ask: "Which profile page style would you like? (1) Default — gradient hero, compact layout. (2) Premium — clean white hero, larger photo, credential badge bar. (3) Dark — dark hero, circular photo, question chips, dual CTA."
- Ask: "What URL should we use for your profile photo?" (or skip to use the default placeholder)
- If Dark style selected, collect a bio summary: "Write a short bio or summary about the subject for the profile page."
- Set
profile_stylein extra data JSON:"premium","dark", or omit for default - Set
photo_urland optionallysubj_summaryin extra data JSON if provided - If certificate validity was not provided, ask: "How many years should the X.509 certificate be valid? (default: 1)"
Generate certificate: Run
scripts/generate_identity.shwith the collected parameters (orscripts/generate_certificate.shdirectly). For ZeroSSL mode, the script auto-installsacme.shif missing and fetches EAB credentials. Pass validity as the 9th positional argument or--validity-daysfor named-arg mode. This creates:cert.pem— PEM-encoded X.509 certificate (fullchain for ACME modes)cert.crt— DER-encoded X.509 certificatecert.p12— PKCS#12 bundle (cert + key, password-protected)cert_data.json— extracted fields (modulus, exponent, fingerprints, NI/DI URIs, dates, serial, issuer, ca_cert_url, cert_type)ca.cer— CA certificate (ACME modes only)
Fill templates: Run
scripts/generate_identity.shwith:- The certificate data file
- User's identity parameters
- A base URL for artifact IRIs (e.g., the WebDAV directory where files will be hosted)
- The
--styleflag orprofile_stylein extra data JSON to select identity card template
This produces:
profile.ttl— Turtle profile document (FOAF + schema.org + cert + oplcert)profile.jsonld— JSON-LD profile documentprofile_rdfa.html— RDFa HTML profile pagecertificate.ttl— Certificate metadata in Turtlecertificate.jsonld— Certificate metadata in JSON-LDcertificate.rdfa.html— Certificate metadata in RDFa HTMLpublic_key.ttl— Public key information in Turtlepublic_key.jsonld— Public key information in JSON-LDpublic_key.rdfa.html— Public key information in RDFa HTMLvcard.vcf— vCard VCF contact cardstyle.css— Card stylingindex.html— Identity card HTML page (using selected template, with conditional OPAL widget)
Verify output: Validate each generated artifact.
- Parse
.ttlfiles with rdflib - Validate X.509 cert with openssl
- Check entity IRIs match between files
- Parse
Deliver to the user: present the output directory path and ask if they want to upload.
T2 — Create Full NetID Identity Card
Same as T1 (including certificate type elicitation: self-signed, Let's Encrypt, or ZeroSSL) but with OPAL widget configuration collected as additional parameters:
- Enable OPAL widget (yes/no)
- OPAL endpoint URL (e.g.,
https://linkeddata.uriburner.com) - Auth mode (
beareroroauth) - API key (if bearer auth)
- Widget mode (
opalbasic oropalxenhanced with file uploads/assistants) - Assistant ID (if opalx mode)
- Agent config name — the fine-tune module/agent config loaded server-side (e.g.,
virtuoso-support-assistant-config,new-sparql-agent-116,data-twingler-config,chat-help). Default:virtuoso-support-assistant-config. The.jsonsuffix is omitted — only the config name is supplied. Elicit from user with: "Which OPAL agent config should the chat use? (default: virtuoso-support-assistant-config)" - Model, temperature, top_p settings
- Predefined prompts (up to 4)
The index.html.tpl template includes conditional !!{use_opal_widget} / !!{use_opalx} / !!{use_opal} blocks that the template engine fills when these settings are provided.
T3 — Generate X.509 Certificate
- Collect parameters from the user:
- WebID URI (required)
- Common name (required)
- Email (optional)
- Organization (optional)
- Country (optional)
- Password (default: youid)
- Certificate validity (years, default 1)
- Certificate type (default: self-signed):
- Ask: "Self-signed, or CA-signed via Let's Encrypt/ZeroSSL?"
- If ACME: collect
--acme-domain(parsed from WebID if omitted),--acme-email, and for ZeroSSL--zerossl-api-key
- Run
scripts/generate_certificate.shwith positional args (self-signed) or named args (--mode,--common-name, etc.) to produce .pem, .crt, .p12, cert_data.json - Display fingerprint, modulus, exponent, issuer, validity dates, and CA cert URL (ACME) to the user
- Deliver the certificate files
T4 — Verify WebID Profile
- Fetch the WebID URL using curl
- Determine the RDF serialization format (content-type negotiation or heuristic)
- Run
scripts/verify_webid.shor use URIBurner SPARQL endpoint - Report: name(s), public keys (modulus, exponent, fingerprint), certificates (including CA signer for ACME certs), delegates, storage locations, inbox, outbox, foaf:knows relations, email
- For ACME-signed certificates, additionally verify:
- Issuer DN references the CA, not the subject
oplcert:IANtriple present (CA certificate URL)- Certificate chain validates:
openssl verify -CAfile ca.cer cert.pem - DNS Subject Alternative Name matches the ACME domain
Verification SPARQL queries are in references/verification-queries.md.
T5 — Upload Identity Documents
- Determine the upload backend from user's destination URL:
- OpenLink WebDAV (DAV home)
- OpenLink LDP
- Generic LDP over TLS
- WebDAV over HTTPS
- Use curl PUT/PROPPATCH commands from
references/upload-backends.md - Upload all files from the output directory (5–15 files depending on config; ACME modes also upload
ca.cer) - Verify uploads with HEAD requests
T6 — Generate On-Behalf-Of Delegation
Workflow-start elicitation (MUST collect before any generation):
Elicit the Delegator's WebID/NetID — the identity granting authority:
- "What is the WebID or NetID of the entity granting delegation authority?"
- May be a URL from the user's own identity or a previously generated YouID card URL
Elicit the Delegate's WebID/NetID — the entity that will act on-behalf-of:
- "What is the WebID or NetID of the entity that will act on behalf of the delegator?"
- May be a person, organization, or software agent
Elicit the Delegation Role — determines the scope of delegated authority:
- "What delegation role should be granted? Choose one:
- identify — Delegate may assert the delegator's identity
- inform — Delegate may receive information intended for the delegator
- consult — Delegate may be consulted on behalf of the delegator
- authority — Delegate has full authority to act as the delegator"
- "What delegation role should be granted? Choose one:
Elicit the Delegator's local directory — MUST ask at workflow start:
- "What is the local directory path for the delegator's YouID profile files?"
- This is where the profile documents (
profile.ttl,profile.jsonld,profile_rdfa.html,index.html) reside - The directory is usually inside the user's local credentials folder
Elicit the Delegate's local directory — ask if the delegate also has local profile files:
- "Does the delegate have a local directory with profile files? If so, what is the path?"
- If provided,
oplcert:onBehalfOfwill be added to the delegate's local files too - If not provided, only the delegator's files are modified
Apply delegation to local files:
Key rule: In local file deployment, each identity's documents only declare their side of the relation.
- Delegator's documents —
oplcert:hasIdentityDelegateonly (grants authority)- Delegate's documents —
oplcert:onBehalfOfonly (acknowledges authority) This differs from SPARQL UPDATE deployment where both triples may reside in a single graph (seereferences/delegation.md).
Add
oplcert:hasIdentityDelegateto the delegator's local profile files — every RDF representation MUST contain the triple:File Representation What to add profile.ttlTurtle oplcert:hasIdentityDelegate <delegate-webid> .on the#netidentityprofile.jsonldJSON-LD "oplcert:hasIdentityDelegate": { "@id": "<delegate-webid>" }in the#netidgraph entryprofile_rdfa.htmlRDFa + JSON-LD <div rel="oplcert:hasIdentityDelegate" resource="<delegate-webid>"></div>AND add to embedded JSON-LD graphindex.htmlPOSH (hero <header>)<link property="oplcert:hasIdentityDelegate" href="<delegate-webid>" />after the existing<link property="cert:key">index.htmlEmbedded JSON-LD Graph entry with "oplcert:hasIdentityDelegate": { "@id": "<delegate-webid>" }— also add@type: "foaf:Agent"andschema:additionalType: "Delegator"index.htmlEmbedded Turtle oplcert:hasIdentityDelegate <delegate-webid> .on the#netidentityindex.htmlHidden RDFa <div typeof="foaf:Agent" about="<delegator-webid>"><div property="schema:additionalType" content="Delegator"></div><div rel="oplcert:hasIdentityDelegate" resource="<delegate-webid>"></div></div>
6b. REQUIRED, not optional — republish the delegate's cert:key under the delegator's same local profile files. Fixed 2026-09-13: the hasIdentityDelegate/onBehalfOf triples above only document the relationship's intent — confirmed live that no tested resource-server authorization path (WAC/ACL, MPP payment-entitlement, or a remote WebID-TLS verification service) independently checks them. A standard WebID-TLS verifier checks cert:key, so skipping this step means the delegation silently does nothing for actual access. Extract the delegate's RSA modulus + exponent from its own cert.pem/cert.p12 (or dereference its live WebID profile if no local file is available — see scripts/generate_delegation.sh -k), then add to every file alongside the table in step 6:
| File | Representation | What to add |
|---|---|---|
profile.ttl |
Turtle | <delegate-webid> cert:key <key-node> . plus a cert:RSAPublicKey node with cert:modulus/cert:exponent (inline, or an indirection to a public_key.ttl-style file — match whichever pattern the delegator's OWN key already uses in this file) |
profile.jsonld |
JSON-LD | Same shape as profile.ttl, in @graph |
profile_rdfa.html |
RDFa | <div about="<delegate-webid>"><div rel="cert:key" resource="#X"></div></div> plus <div typeof="cert:RSAPublicKey" about="#X"> with cert:modulus/cert:exponent properties |
index.html |
Embedded JSON-LD AND embedded Turtle (both — index.html carries both blocks) | Same shape as above, local fragment (e.g. #DelegatePublicKey) is fine since it only needs to resolve within this document |
generate_identity.sh's Step 6 gate now enforces this automatically — it fails generation if any delegate lacks a cert:key in any of the four files, or if the modulus disagrees across files that inline it. Do not treat a gate pass on hasIdentityDelegate/onBehalfOf consistency alone as sufficient; the gate checks both, but if you're applying this by hand outside the generator, verify both yourself.
If delegate directory provided, add
oplcert:onBehalfOfto the delegate's local profile files using the same index.html coverage:File Representation What to add profile.ttlTurtle oplcert:onBehalfOf <delegator-webid> .on the#netidentityprofile.jsonldJSON-LD "oplcert:onBehalfOf": { "@id": "<delegator-webid>" }in the#netidgraph entryprofile_rdfa.htmlRDFa + JSON-LD <div rel="oplcert:onBehalfOf" resource="<delegator-webid>"></div>AND add to embedded JSON-LD graphindex.htmlPOSH (hero <header>)<link property="oplcert:onBehalfOf" href="<delegator-webid>" />after the existing<link property="cert:key">index.htmlEmbedded JSON-LD "oplcert:onBehalfOf": { "@id": "<delegator-webid>" }on the#netidentry — add in both thefoaf:Agentandschema:Persongraph entries if both existindex.htmlEmbedded Turtle oplcert:onBehalfOf <delegator-webid> .on the#netidentityindex.htmlHidden RDFa <div typeof="foaf:Agent" about="<delegate-webid>"><div property="schema:additionalType" content="Delegate"></div><div rel="oplcert:onBehalfOf" resource="<delegator-webid>"></div></div>Generate
declarativeNetRequestrule — producedelegation-rule.jsonfor the delegate's browser (injectsOn-Behalf-Ofheader):{ "id": 1, "priority": 1, "condition": { "urlFilter": "*", "resourceTypes": ["xmlhttprequest"] }, "action": { "type": "modifyHeaders", "requestHeaders": [ { "header": "On-Behalf-Of", "operation": "set", "value": "<delegate-webid>" } ] } }IMPORTANT: The
On-Behalf-OfHTTP header value must be a bare WebID URI — no angle brackets. When using curl directly:- Correct:
-H "On-Behalf-Of: https://example.com/dataspace/person/user@example.com" - Incorrect:
-H "On-Behalf-Of: <https://example.com/dataspace/person/user@example.com>"Angle brackets cause delegation resolution failure (402/401). The<delegate-webid>placeholder above uses angle brackets as placeholder delimiters — the generated JSON rule will contain the actual bare WebID URI.
- Correct:
Present changes to the user — show each file that was modified and the exact triple added
- "The delegation triple has been added to the following files. Upload them to the server to deploy: [file list]"
Verify after upload — the remote OBO test is the SAME corroboration the local whoami identity-verification flow does (
agent-rdf-memory/howto/verified-identity.ttlSteps 6-8), just resolved remotely by dereferencing the live WebIDs over HTTP instead of grepping local profile files. This is the PRIMARY check; a third-party verification-service call (below) is a weaker, optional supplement — not a substitute.Primary — dereference both profiles, check the reciprocal triples:
# Delegator side: does the delegator's own profile grant the delegate? curl -sS -L "{delegator-webid-profile-url}" | grep -i "hasIdentityDelegate" # expect: oplcert:hasIdentityDelegate <delegate-webid> . # Delegate side: does the delegate's own profile acknowledge the delegator? curl -sS -L "{delegate-webid-profile-url}" | grep -i "onBehalfOf" # expect: oplcert:onBehalfOf <delegator-webid> (possibly among a list of several) . # Cross-serialization: same triples should also appear in index.html on both sides # (embedded JSON-LD, embedded Turtle, hidden RDFa, and a POSH <link> in the hero header) curl -sS -L "{delegator-index-html-url}" | grep -i "hasIdentityDelegate" curl -sS -L "{delegate-index-html-url}" | grep -i "onBehalfOf"FULL corroboration requires BOTH sides to declare the relationship — either side alone is a one-sided, unverified claim (matches
verified-identity.ttl:step-verifyDelegationClaims). Verified live 2026-09-13 for the agent/principal pair: both profiles declared the relationship correctly, consistently, across every serialization — and the delegate'sonBehalfOflist may legitimately contain more than one delegator (the agent's own profile lists three).Secondary/optional — remote WebID-TLS verification-service call, presents the delegate's own certificate over mTLS to a verification endpoint, asserting
On-Behalf-Ofthe delegator:curl -iL "https://{Remote-CNAME}:{webid-tls-port}/webid/webid_verify.vsp?callback=webid_result.vsp" \ --cert-type P12 \ -H "On-Behalf-Of: {delegator-webid}" \ --cert "{delegate-webid.p12}" \ --pass "{delegate-webid.p12-pwd}"{webid-tls-port}is the host's mTLS listener (5443on OpenLink ODS/Virtuoso deployments — plain:443typically never issues aCertificateRequest). Empirically confirmed 2026-09-13, this check is WEAKER than the primary one above and should never be used alone: it validates and returns only the presenting certificate's own identity, ignoring whateverOn-Behalf-Ofnames — confirmed by two live tests (self-referential vs. a genuinely different delegator) returning byte-for-byte identical output. It also never populates its ownpku(public-key) field for any identity tested, suggesting it doesn't even complete the canonical dereference-and-match-published-key step for the certificate holder itself, let alone the delegator. Treat a200/code: Successfrom it as proof only that a well-formed certificate was presented — nothing about delegation.Full detail: standalone report
webid-tls-on-behalf-of-delegation-no-effect-incident-report-claude_sonnet_5-1.md, andagent-rdf-memory/preferences.ttlStep 296 /howto/remote-webid-verification-service-obo.ttl.
T7 — Define Identity Concept
Explain semantic web identity concepts using references/identity-ontologies.md:
| Concept | Description |
|---|---|
| WebID | URI-based identity for agents, people, organizations — foundation of WebID-TLS/WebID-OIDC |
| NetID | A WebID bound to an X.509 certificate — the YouID extension's core identity unit |
| X.509 Certificate | Digital certificate binding a public key to a subject (the WebID appears in SAN) |
| FOAF | Friend-of-a-Friend ontology — machine-readable homepages and social networks |
| cert ontology | W3C WebID crypto ontology — RSAPublicKey, modulus, exponent |
| oplcert | OpenLink certificate ontology — Certificate, fingerprint, SAN, IAN, hasKey |
| DPKI | Decentralized Public Key Infrastructure — identity without central authority |
| NI/DI URI | Named Information / Digest Identifier — content-addressed identity URIs |
| owl:sameAs | Identity link between equivalent entities across graphs |
| WebID-TLS | Authentication protocol using X.509 certificates bound to WebIDs |
| WebID-OIDC | Authentication using OpenID Connect with a WebID as subject identifier |
Template System Reference
All template files in templates/ use the YouID template syntax:
| Marker | Meaning |
|---|---|
%{key} |
Simple substitution — replaced with the value of key (always filled) |
!{key} |
Conditional line — the entire line is included only if key is set to a non-empty value |
!!{key} |
Conditional block start — text from this line to !!. is included only if key is set |
!!. |
Conditional block end — terminates the nearest !!{key} block |
Variable Categories
Full variable reference in references/template-variables.md.
| Category | Variables | Source |
|---|---|---|
| Identity | subj_name, subj_email, subj_org, subj_country, subj_state, subj_title |
User input |
| Certificate | modulus, exponent, fingerprint_hex, fingerprint_256_hex, date_before, date_after, serial, subject, issuer, ca_cert_url, cert_type |
Computed from X.509 cert |
| Fingerprint URIs | fingerprint_ni, fingerprint_di, fingerprint_colon, vcard_digest_uri |
Computed from fingerprint |
| URLs | prof_url, card_url, cert_url, pubkey_url, pubkey_pem_url, jsonld_*, rdfa_* |
Derived from base URL + filenames |
| Conditionals | !{pdp_url}, !{subj_email}, !!{use_opal_widget}, !!{ca_cert_url} |
Set/non-set flags |
| Relations | relList, relList_json, relList_html, rel_header_html |
Computed from social links |
| OPAL | w_opl_endpoint, w_model, w_module (agent config name), w_funcs, w_assistant, w_prompt1-4 |
User config (optional) |
Pre-Build Check
Before running any generation workflow, you MUST:
- ⛔ Re-read this section of SKILL.md (Order of Operations — the relevant T# workflow).
- ⛔ Load the relevant script — read
scripts/generate_identity.sh(or the appropriate script) to understand its parameters, inputs, and outputs. - ⛔ Read the relevant template files — read each
.tplfile that will be filled so you understand the output structure. - ⛔ Verify openssl is available — required for certificate generation:
which openssl openssl version - ⛔ Verify Python 3 is available — required for template filling:
which python3 - ⛔ If ACME cert type selected: Verify
curlis available andhttps://get.acme.shis reachable:
Ifcurl -fsI https://get.acme.shacme.shis not installed, the script will auto-install it viacurl. If ZeroSSL mode, verify the user has a ZeroSSL API key ready. - ⛔ If ACME cert type selected: Verify the domain resolves:
ACME validation requires the domain to resolve to a publicly accessible IP for HTTP-01 challenges.dig +short <acme-domain> A
Post-Generation Checklist
Before delivering any generated identity to the user:
- All files exist: profile.ttl, profile.jsonld, profile_rdfa.html, certificate.ttl, certificate.jsonld, certificate.rdfa.html, public_key.ttl, public_key.jsonld, public_key.rdfa.html, index.html, vcard.vcf, style.css, cert.pem, cert.crt, cert.p12
- RDF syntax valid: Turtle files parse with
python3 -c "import rdflib; rdflib.Graph().parse('profile.ttl', format='turtle')" - JSON-LD valid:
python3 -c "import json; json.load(open('profile.jsonld'))" - X.509 valid:
openssl x509 -in cert.pem -noout -text - WebID SAN present:
openssl x509 -in cert.pem -noout -ext subjectAltName | grep -i URI - No self-referencing owl:sameAs: no triple where
owl:sameAshas identical subject and object - exponent is typed literal:
cert:exponentinprofile.ttlandpublic_key.ttlmust use"65537"^^xsd:int(typed literal), never bare65537(xsd:integer) — some SPARQL processors require strictxsd:intmatching - exponent is not corrupted:
cert:exponentvalue must be65537(from cert), not"X509v3extensions:"— known bug ingenerate_certificate.sh:107fixed 2026-06-18 (grep -A1replaced withawk '/Exponent:/ {print $2}') - Entity IRIs consistent: the
profile.ttlentity IRIs match the<rel>links inindex.html - Photo accessible: if a photo URL was provided, verify it resolves
- QR code functional: the
index.htmlpage has a QR code pointing to itself - Conditional blocks correct: if OPAL was disabled, verify no OPAL JS is embedded
- Accordion contract: cert
<details>starts closed (openattribute absent), pubkey<details class="nested-details">starts closed, each has<summary>with preview + chevron - Dark mode present:
@media (prefers-color-scheme:dark)rule NOT used (would block manual toggle). Instead[data-theme="dark"]CSS block overrides:rootvariables, with flash-prevention<script>in<head> - Theme toggle implemented:
[data-theme="dark"]CSS block overrides CSS variables, flash-prevention<script>in<head>readslocalStorage('theme')thenprefers-color-scheme, and a sun/moon SVG toggle button is present with click handler that sets/removesdata-themeon<html>and persists tolocalStorage - Platform icons exist: every
src="p_{key}_32.png"in.social-gridhas a corresponding file in the output directory - Platform icons use proper logos: no icon uses
p_none.pngunless a platform-specific icon was not discoverable after searching the platform's brand page - No external framework: no
bootstrap,jquery, or other framework imports in the HTML body - Basic WebID Test PASS — AUTO-GATED in Step 5 of
generate_identity.sh. The orchestrator extracts modulus + exponent fromcert.p12and cross-referencesprofile.ttl,profile.jsonld, andindex.html(RDFa) usingrdflibandHTMLParser. Generation is blocked with exit code 1 on any mismatch. Manual check no longer required. - Delegation & Cert:Key Consistency Test PASS — AUTO-GATED in Step 6 of
generate_identity.sh. If any file containsoplcert:hasIdentityDelegateoroplcert:onBehalfOf, the gate verifies identical triples acrossprofile.ttl,profile.jsonld,profile_rdfa.html, andindex.html(embedded JSON-LD + RDFa rel/property attrs), AND that every named delegate has its owncert:key(modulus+exponent) republished consistently across the same four files, with matching modulus values wherever inlined. Skips cleanly if no delegation triples are present. Blocks generation on failure. - Hero badges rendered: "✓ Verified WebID", subject name, email, org all visible
- Social
owl:sameAspresent:profile.ttlandprofile.jsonldcontainowl:sameAsentries for each social platform URL collected from the user (not just profile-document equivalences) -
<link rel="me">in head:index.htmlhas<link rel="me">tags for each social platform URL - Social grid populated:
index.html's.social-griddiv contains platform-icon<a>tags withrel="me"for each social URL - No empty social-grid: if social links were collected, verify
.social-gridis non-empty; if none were collected, the section should be conditionally hidden
ACME-Specific Post-Generation Checks
- CA-signed certificate chain valid (ACME modes only):
openssl verify -CAfile <(openssl x509 -in ca.cer) cert.pem - URI SAN present in ACME cert:
openssl x509 -in cert.pem -noout -ext subjectAltName | grep 'URI:' - DNS SAN matches domain:
openssl x509 -in cert.pem -noout -ext subjectAltName | grep 'DNS.'matches the ACME domain -
ca_cert_urlpopulated incert_data.json: checked with `python3 -c "import json; d=json.load(open('cert_data.json')); assert d['ca_cert_url'], 'missing ca_ce
…(truncated)