feat: SimpulseID credentials — schema-first domain layer on harbour-credentials#24
Merged
feat: SimpulseID credentials — schema-first domain layer on harbour-credentials#24
Conversation
Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: felix hoops <9974641+jfelixh@users.noreply.github.com>
Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
Signed-off-by: felix hoops <9974641+jfelixh@users.noreply.github.com>
Signed-off-by: felix hoops <9974641+jfelixh@users.noreply.github.com>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
RDFS inference materialises rdfs:label from schema:name (schema.org declares schema:name rdfs:subPropertyOf rdfs:label). Classes with sh:closed SHACL shapes must declare rdfs:label as an allowed property to prevent validation failures when an RDFS-aware validator is used. Add label attribute (slot_uri: rdfs:label) to ProgramEndpoint mixin, BaseMembershipProgram, EnvitedMembershipProgram, ProgramPublisher, and ProgramEcosystem — matching the pattern used in Gaia-X and Harbour schemas. Also add rdfs prefix and apply ruff format. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Rename publisher → programPublisher in simpulseid-core schema to eliminate a name collision with the gaia-x publisher slot. Both schemas define publisher with different from_schema URIs, causing LinkML's merge_dicts to raise ValueError during schema loading. The slot_uri remains sdo:publisher so OWL, SHACL, and JSON-LD output all use schema:publisher as the RDF property. DID fixtures are unaffected — they define their own inline context mapping. This removes the _context_gen_without_harbour monkey-patch that temporarily replaced linkml.utils.mergeutils.merge_dicts at runtime to suppress the conflict (-41 lines net). Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Merge 5 credential type definitions from simpulseid-credentials.yaml into simpulseid-core.yaml, eliminating the need for the filter_shacl_to_namespace() post-processing hack. Changes: - Move ParticipantCredential, AdministratorCredential, UserCredential, AscsBaseMembershipCredential, AscsEnvitedMembershipCredential into simpulseid-core.yaml - Delete simpulseid-credentials.yaml and its importmap entry - Replace filter_shacl_to_namespace() hack (~30 lines) with built-in exclude_imports=True flag on ShaclGenerator - Remove rdflib dependency from generate_artifacts.py - Fix pre-commit hooks to use venv python (python3 unavailable on Windows) SHACL output now includes 18 shapes (15 simpulseid: + 3 schema: for locally-defined classes with external class_uri). The 3 schema.org shapes validate inlined objects (ProgramPublisher, ProgramEcosystem, ProgramEndpoint) which was previously over-filtered. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Update all simpulseid examples and tests to use prefixed CURIEs for domain types, matching the harbour convention: - Bare 'ParticipantCredential' → 'simpulseid:ParticipantCredential' - Bare 'HarbourVerifiableCredential' → 'harbour:VerifiableCredential' - Bare 'CRSetEntry' → 'harbour:CRSetEntry' - etc. Convention: W3C terms stay bare, all domain types use prefixed CURIEs. Pin harbour-credentials to 18f0443 (class_uri + delegation namespace). Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
6c7ec0a to
b4f7b72
Compare
RDFS inference infers the parent type via rdfs:subClassOf, making the explicit harbour:Evidence entry redundant when a child type (harbour:CredentialEvidence) is already present. Also bumps harbour-credentials submodule pin to include: - Redundant type removal in harbour examples - SHACL test refactoring to use OMB ShaclValidator with RDFS inference - TypeScript DELEGATED_EVIDENCE_TYPES whitelist fix Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
- Bump harbour-credentials to 0ff4763 - serviceEndpoint: range uri (DID Core §5.4) - transaction_data: range JsonLiteral (OID4VP §8.4) - Add standards compliance rules to AGENTS.md and CLAUDE.md Resolves SHACL closed-shape violations caused by linkml:Any range triggering RDFS inference on IRI-valued endpoints. Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Updates submodule to 7a7e41e which fixes cross-runtime interop tests on Windows (temp file approach for yarn node). Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
- Add gitlab-ci-local usage instructions to AGENTS.md, CLAUDE.md, and .github/copilot-instructions.md - Document install requirements (npm, rsync, Docker/Podman) - Note corporate proxy limitation for Docker image pulls Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
- Makefile: add linkml submodule to _install_dev (single source of truth via OMB submodule pin) - Makefile: add REPO_ROOT variable, replace fragile ../../../../ paths with absolute references in validate targets - Makefile: fix ruff ordering in _format_default (check then format) - .pre-commit-config.yaml: fix jsonld-lint to include .jsonld files - .github/workflows/ci.yml: use make setup instead of make install dev - pyproject.toml: remove direct linkml git URL - submodules/harbour-credentials: bump with Makefile alignment fixes Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Test across OS: ubuntu-latest, macos-latest, windows-latest Matrix applied to: - test-python: 3 OS × 2 Python (3.12, 3.13) = 6 jobs - test-ts: 3 OS = 3 jobs - generate-validate and standards-and-syntax remain ubuntu-only Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- Enable core.longpaths on Windows before ALL recursive submodule checkouts (linkml snapshot files exceed 260-char Windows path limit) - Use --global instead of --system for git config (more reliable in CI) - TS test job: use shallow harbour-only checkout instead of recursive (TS tests don't need OMB/linkml chain, avoids long path issue) Closes #27 Signed-off-by: jdsika <carlo.van-driesten@bmw.de> Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
c5ec6f3 to
4002259
Compare
…ollision Membership program DIDs inlined hostingOrganization with @id + @type on the ASCS participant DID IRI, polluting it with rdf:type schema:Organization in the merged graph. The closed Organization shape rejected DID properties. SimpulseidParticipant class_uri (simpulseid:Participant) collided with gx:Participant in the JSON-LD context — the SHACL shape never fired on participant subjects. Changed to simpulseid:OrganizationParticipant. - Rewrite membership program DIDs: move metadata into serviceEndpoint, hostingOrganization as plain URI, remove non-standard root program property - Split Makefile validate into credential and DID fixture passes - Re-enable DID_FIXTURES in Makefile - Add DID fixture SHACL, structural invariant, cross-document IRI, and mutation-based negative SHACL tests Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update participant subject type in invalid test fixtures from the CURIE simpulseid:Participant to the bare class name SimpulseidParticipant, matching the valid credential pattern and resolving correctly to simpulseid:OrganizationParticipant via the generated context. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Pin ontology-management-base to v0.1.6 release in harbour-credentials (CI artifact generation verification, cross-platform test matrix, Makefile CI detection fixes). Sync both w3id.org forks with upstream master — namespace redirect rules merged upstream. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Extend the mutation-based SHACL test suite to cover audit findings: - Add User-missing-validFrom and Administrator-missing-givenName to parametrized mandatory field tests (nested harbour NaturalPerson shape) - Add TestEnumConstraints: verify sh:in constraint on SimpulseIdLegalForm rejects invalid values (InConstraintComponent) - Add TestProgramMetadataMutations: closed shape tests for AdministratorProgram and BaseMembershipProgram endpoints - Add program metadata integrity assertions: required fields per program type (policy docs for admin/user, governance docs for membership) - Parametrize evidence VP signing across all 5 credential types (was only Participant + BaseMembership) Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- Add cliff.toml for git-cliff changelog generation (required by cd-release.yml, was silently failing with continue-on-error) - Add npm cache for markdownlint-cli2 in lint-markdown job - Add yarn cache for TypeScript test job via setup-node cache param - Add dependabot.yml for automated pip and github-actions updates Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Refactor sign_examples.py to use harbour's delegation module for evidence VP nonce generation instead of a custom format. The nonce now follows the HARBOUR_DELEGATE challenge format: <random> HARBOUR_DELEGATE <sha256(transaction_data)> This makes evidence VPs parseable by parse_delegation_challenge() and aligns with EVES-009 §2 (challenge = hash of serialized message). The action type is 'credential.issue' (already registered in harbour's ACTION_LABELS) with the credential ID as transaction context. Also pins harbour-credentials with EVES-009 compliance improvements: revocation status warning and optional transaction freshness checks in verify_sd_jwt_vp(). Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Collaborator
Author
Audit resolution — 6 new commits pushedSubmodule pins
Test coverage gaps closed
CI/CD
EVES-009 compliance
Verification
|
- Move corepack enable before actions/setup-node so yarn 4 is available when setup-node resolves the yarn cache directory (yarn 1.x rejects packageManager: yarn@4.13.0) - Replace Unicode arrow in sign_examples.py print output with ASCII to avoid cp1252 encoding error on Windows CI runners Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update harbour-credentials submodule pointer to main (0a5205d) which includes the squash-merged Gaia-X Loire compliance, delegated signing, and did:ethr migration. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Switch submodule branch tracking from fix/simpulse-id-credentials to main now that the harbour PR has been merged. Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- Pin harbour-credentials to v1.0.0 release tag - Replace setup-node yarn cache with manual actions/cache after corepack enable (setup-node cache: yarn invokes yarn 1.x internally before corepack takes effect on Windows) - Fix remaining Unicode arrow in sign_examples.py for Windows cp1252 Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Schema-first SimpulseID credential and identity framework for the ENVITED-X Data Space, built as a domain layer on top of harbour-credentials. Implements EVES-008 (SimpulseID Credential and Identity Framework) with evidence protocol per EVES-009 (Evidence-Based Consent Using Verifiable Presentations).
91 files changed, 9,675 insertions, 797 deletions across 125 commits.
Specification compliance
HARBOUR_DELEGATEchallenge format (credential.issueaction)Design Decisions
Why a domain layer, not a standalone repo?
SimpulseID extends harbour-credentials rather than reimplementing credential infrastructure. Harbour provides the cryptographic signing/verification layer (ES256, SD-JWT, delegation), the W3C VC v2 base types, and the Gaia-X compliance model. SimpulseID adds only ENVITED-specific semantics: credential types, membership chains, program metadata, and legal form enums. This separation means harbour can serve other ecosystems (not just ENVITED), while SimpulseID stays focused on ASCS e.V. governance.
Why harbourCredential IRI reference instead of inline Gaia-X data?
Each SimpulseID credential subject (Participant, Administrator, User) carries a mandatory
harbourCredentialIRI pointing to a separate Harbour Gaia-X compliance credential. This avoids duplicating Gaia-X data (registration numbers, addresses, legal names) across every SimpulseID credential. The Harbour credential serves as the "baseline of trust" — if Gaia-X compliance is revoked, all SimpulseID credentials referencing it become invalid via the CRSet revocation mechanism. Membership credentials (AscsBaseMembership,AscsEnvitedMembership) do NOT carryharbourCredentialbecause they attest to a relationship, not an identity.Why OrganizationParticipant instead of Participant?
The LinkML schema originally used
class_uri: simpulseid:Participant, but the JSON-LD context generator emitted"SimpulseidParticipant": {"@id": "Participant"}— and the bare termParticipantwas already claimed bygx:Participantfrom the Gaia-X imports. This caused the SHACL shape to never fire on participant credential subjects (the type resolved to the wrong namespace). The fix was renaming tosimpulseid:OrganizationParticipant, which generates a unique context term. This is documented in the EVES-008 spec with an explanatory note.Why urn:uuid for membership credential subjects?
Membership credentials use
urn:uuid:identifiers instead of participant DIDs forcredentialSubject.id. If two membership credentials (base + ENVITED) shared a participant DID as their subject ID, loading both into an RDF graph would merge all properties onto a single node — conflating the base membership'shostingOrganizationwith the ENVITED membership'sbaseMembershipCredential. Theurn:uuid:pattern keeps each membership as an independent graph node. Theschema:memberproperty on the membership object points back to the participant DID.Why member vs memberOf (not interchangeable)?
schema:memberis used on the membership object, pointing TO the member:AscsBaseMembership.member = <BMW DID>.schema:memberOfis used on the person, pointing TO the organization they belong to:Administrator.memberOf = [<BMW DID>]. This follows schema.org semantics and is enforced by SHACL shapes — swapping them would produce validation failures.Why program metadata in DID service endpoints?
Program definitions (AdministratorProgram, UserProgram, BaseMembershipProgram, EnvitedMembershipProgram) are embedded as
serviceEndpointobjects insideProgramMetadataServiceentries on program DIDs. This is a DID Core §5.4 pattern: the program DID is externally controlled (via thecontrollerproperty pointing to the issuing participant DID) and serves its metadata through standard DID resolution. The alternative — separate JSON files or a centralized API — would break the decentralized resolution model.Why closed SHACL shapes with exclude_imports?
The SHACL generator uses
closed=True(unexpected properties triggerClosedConstraintComponent) andexclude_imports=True(only SimpulseID-defined classes get shapes). This means:sh:ignoredProperties (rdf:type)declaration allows JSON-LD@typeto coexist with closed shapesWhy HARBOUR_DELEGATE for evidence VPs?
Evidence VPs in SimpulseID use harbour's delegation module with the
credential.issueaction type. The nonce format is<random> HARBOUR_DELEGATE <SHA-256(TransactionData)>, where TransactionData includes the credential ID being consented to. This replaced an earlier custom format (<random> <hash>) that bypassed the delegation module and was not parseable byparse_delegation_challenge(). The HARBOUR_DELEGATE format aligns with EVES-009 §2 and makes evidence VPs verifiable through the standard delegation verification pipeline.Why signer DIDs vs resource DIDs?
Two distinct DID patterns serve different roles:
verificationMethodwith P-256JsonWebKey, referenced fromauthenticationandassertionMethod. No document-levelcontroller. These DIDs sign credentials and evidence VPs.controllerproperty pointing to the governing participant DID, no localverificationMethod. Authority is delegated via the controller chain, not local keys.This separation is enforced by integrity tests (
test_signer_did_has_verification_method,test_resource_did_has_controller).Changes by category
LinkML schemas (3 files, +560)
simpulseid-core.yaml— 5 credential types, 5 subject types, 6 program metadata types,SimpulseIdLegalFormenum (21 values across DE/US/UK jurisdictions)importmap.json— 70+ cross-directory import mappings (harbour → gaia-x → service-characteristics)harbourCredentialIRI on Participant/Administrator/User subjectsmember(required),baseMembershipCredential(required on ENVITED)Generated artifacts (1 file, +148)
simpulseid-core.owl.ttl)simpulseid-core.shacl.ttl) — closed shapes withsh:inenum enforcementsimpulseid-core.context.jsonld) withtype: @typealias injectionsrc/generate_artifacts.pywithexclude_imports=TruePython source (5 files, +466)
generate_artifacts.py— LinkML → OWL/SHACL/JSON-LD pipeline with importmap resolutionsign_examples.py— evidence VP signing withHARBOUR_DELEGATEchallenge format via delegation module (credential.issueaction)verify_signed_examples.py— JWT + evidence VP verification against DID keyringTests (13 files, +1,995)
test_shacl_failures.py— 30 mutation-based SHACL tests across 7 test classes:test_shacl_validation.py— 15 positive baselines (credentials + DIDs) + 7 invalid example regression teststest_examples_integrity.py— 30+ structural assertions: Gaia-X LegalPerson/NaturalPerson composition, DID key linkage (P-256 JsonWebKey in authentication + assertionMethod), cross-document IRI resolution (subject DIDs, memberOf, hostingOrganization, member, baseMembershipCredential, revocation registry), program metadata required fields, IRI type pollution regression guard (issue fix: DID document fixtures fail SHACL validation when loaded as data #28)test_evidence_proof.py— evidence VP signing and verification parametrized across all 5 credential types, full proof chain (evidence VP → outer VC), sign-verify fixture roundtripExamples (21 files, +992/−223)
HARBOUR_DELEGATEnonce format anddid:keyephemeral holdersDocumentation (17 files, +2,116)
artifacts/README.mddocumenting the generation pipeline and artifact structureWallet manifests (6 files, +525/−78)
CI/CD and build (12 files, +1,380/−17)
cliff.tomlfor git-cliff release changelog generationdependabot.ymlfor automated pip and github-actions dependency updatescd-release.ymlwith semantic versioning, pre-release detection, GitHub Pages docs deploymentcd-docs.ymlwith path-based triggering for documentation changesSubmodule pins
harbour-credentials: OMB v0.1.6 (CI artifact generation verification, cross-platform matrix), w3id.org at upstream master, EVES-009 compliance fixes (revocation warning, transaction freshness)w3id.org(top-level): upstream master (d004a453) with mergedreachhaven/harbourandascs-ev/simpulse-idnamespace redirect rulesTest plan
make storypasses (generate → sign → verify → validate)make test simpulseid— all SHACL + integrity + evidence tests green (156 passed)make lintpasses (pre-commit hooks green)git submodule status --recursiveclean after fresh clone