Skip to content

feat(harbour): schema-first Gaia-X Loire compliance, delegated signing, did:ethr#2

Merged
jdsika merged 78 commits into
mainfrom
fix/simpulse-id-credentials
Apr 4, 2026
Merged

feat(harbour): schema-first Gaia-X Loire compliance, delegated signing, did:ethr#2
jdsika merged 78 commits into
mainfrom
fix/simpulse-id-credentials

Conversation

@jdsika
Copy link
Copy Markdown
Contributor

@jdsika jdsika commented Feb 24, 2026

Summary

This branch transforms harbour-credentials from a basic VC signing library into a full credential infrastructure for the ENVITED-X Data Space. It adds Gaia-X Loire (25.11) compliance, an OID4VP-based delegated signing evidence protocol, and migrates from did:web to did:ethr on Base (ERC-1056).

141 files changed, 27,956 insertions, 2,153 deletions across 78 commits.


Design Decisions

Why did:ethr over did:web?

did:web ties DID resolution to DNS and HTTPS infrastructure — if a domain changes or a TLS certificate expires, all credentials become unresolvable. did:ethr anchors identity on-chain via ERC-1056, making DIDs independent of any single hosting provider. The trade-off is blockchain dependency, but Base (Ethereum L2) provides low-cost operations with Ethereum-grade finality. P-256 was chosen over secp256k1 because Gaia-X and the W3C VC ecosystem standardize on ES256 (P-256), and EUDI wallets require it.

Why OID4VP dual-hash binding instead of simple nonce = hash(message)?

EVES-009 specifies that the VP nonce should equal the message hash. Harbour instead uses the OID4VP standard's dual-binding approach: the VP nonce carries a random value for replay prevention, while the KB-JWT transaction_data_hashes array carries SHA-256(base64url(transaction_data)) for message binding. This was chosen because:

  1. Standard OID4VP wallets (EUDI, Sphereon, walt.id) expect KB-JWT binding, not a custom nonce format
  2. Separating replay prevention from message binding avoids conflating two distinct security properties
  3. Multiple evidence items in a single VP require an array of hashes — a single nonce field cannot bind to multiple messages

A review comment on EVES-009 suggests the spec be relaxed to accommodate both approaches.

Why delegation via evidence VPs, not DID capabilityDelegation?

The ENVITED use case requires proving that a specific user consented to a specific action (e.g., issuing a credential, executing a blockchain transaction). DID-level capabilityDelegation grants blanket authority — it cannot be scoped to individual transactions or carry consent evidence. The evidence VP approach binds consent to a deterministic challenge derived from the action, creating an independently verifiable audit trail. This follows the pattern described in EVES-009 §1.

Why SD-JWT-VC as the recommended format?

SD-JWT-VC (RFC 9901) supports selective disclosure — holders can redact claims before presenting credentials. This is critical for GDPR compliance: an administrator credential contains givenName, familyName, email, and address, but a service verifying membership only needs the memberOf reference. SD-JWT-VC lets the holder present only what's needed. The format also supports key binding (proving holder possession) and is the format mandated by the EUDI Architecture Reference Framework.

Why schema-first with LinkML?

All credential types, subject shapes, and property constraints are defined in LinkML YAML schemas. A single make generate produces OWL ontologies, SHACL validation shapes, and JSON-LD contexts. This eliminates drift between the data model and its machine-readable artifacts. The SHACL shapes use sh:closed true so unexpected properties are rejected, and exclude_imports=True ensures only harbour-defined types get shapes (Gaia-X base types are validated by upstream shapes).

Why Gaia-X composition over flat credentials?

Harbour credentials use a composition pattern: gx:LegalPerson and harbour.gx:NaturalPerson are inlined as blank nodes inside the credential subject's participant property, rather than flattened into the subject itself. This preserves the Gaia-X type hierarchy (a NaturalPerson IS-A gx:Participant) while allowing domain layers like SimpulseID to add their own properties (harbourCredential, legalForm, memberOf) without polluting the Gaia-X namespace.

Why CRSet revocation instead of X.509 CRL/OCSP?

Harbour uses a Credential Revocation Set (harbour:CRSetEntry) queryable via DID service endpoints, rather than X.509-style certificate revocation. CRSet entries are identified by <registry-did>/<credential-hash>, making revocation checks resolvable through the same DID infrastructure used for identity. This avoids introducing a separate PKI dependency.


Changes by category

LinkML schemas (8 files, +1,652/−458)

  • harbour-core-credential.yaml — W3C VC v2 infrastructure types (HarbourCredential, CRSet, Evidence, DIDDocument, VerificationMethod)
  • harbour-gx-credential.yaml — Gaia-X concrete types (LegalPerson, NaturalPerson, gxParticipant composition slot)
  • harbour-core-delegation.yaml — OID4VP transaction data, delegation challenge encoding, evidence binding
  • w3c-vc.yaml — thin shim projecting W3C VC v2 vocabulary terms into LinkML
  • importmap.json — 80+ cross-directory import mappings through the OMB submodule chain

Python source (15 files, +2,056/−493)

  • delegation.py — TransactionData creation/validation, challenge format (<nonce> HARBOUR_DELEGATE <hash>), SIWE-style consent display, OID4VP transaction_data parameter encoding
  • sd_jwt.py — SD-JWT-VC issuance with structured selective disclosure (RFC 9901 §6.2), flat and nested disclosure paths
  • kb_jwt.py — Key Binding JWT with sd_hash and transaction_data_hashes per OID4VP Appendix B.3.3
  • sd_jwt_vp.py — SD-JWT VP issuance/verification with evidence binding, revocation status warning, optional transaction freshness check
  • x509.py — X.509 self-signed certificate generation, chain validation, x5c header support
  • keys.py — did:ethr derivation (P-256 uncompressed → keccak256 → Ethereum address), did:key multibase encoding
  • credentials/example_signer.py — multi-key trust chain signing pipeline with role-based DID → kid mapping

TypeScript source (7 files, +1,046/−22)

  • Feature parity with Python: delegation.ts, sd-jwt.ts, kb-jwt.ts, sd-jwt-vp.ts, x509.ts, keys.ts, signer.ts
  • Uses jose library (WebCrypto API) for all crypto operations

Tests (34 files, +4,268/−437)

  • test_delegation.py (55 tests) — shared canonicalization vectors, challenge parse/verify, CLI, display rendering, validation edge cases
  • test_sd_jwt.py (28 tests) — RFC 9901 structured disclosure, partial/full/no disclosure, cnf binding, Ed25519 support
  • test_kb_jwt.py (18 tests) — transaction data hashing, sd_hash computation, wrong key/nonce/audience detection
  • test_sd_jwt_vp.py (23 tests) — full delegated consent flow, privacy-preserving audit scenario, multiple evidence items
  • test_shacl_failures.py — mutation-based SHACL tests for harbour credential shapes
  • 9 TypeScript test files mirroring Python test structure
  • Cross-runtime interop tests (Python signs → TypeScript verifies, vice versa)

Documentation (36 files, +16,213/−263)

  • 5 Architecture Decision Records: VC securing mechanism, dual runtime, canonicalization, key management, did:ethr migration
  • Credential lifecycle guide with mermaid swimlane diagrams
  • DID identity system overview
  • Getting started (installation + quickstart for Python and TypeScript)
  • API reference (Python via mkdocstrings, TypeScript via TypeDoc)
  • Specification reference copies (DID Core, SD-JWT, OID4VP, Gaia-X ICAM 25.11, VC-JOSE-COSE)

Examples (25 files, +1,805/−166)

  • did:ethr DID documents: signing service (with delegate key), trust anchor, legal person, natural person
  • Gaia-X credentials: LegalPerson and NaturalPerson with evidence VPs
  • Nested evidence example showing 2-level evidence chain

CI/CD and build (7 files, +733/−244)

  • Credential lifecycle story job (generate → sign → verify → SHACL validate)
  • OS × Python version matrix (ubuntu/macos/windows × 3.12/3.13)
  • Migrated from black/isort/flake8 to ruff
  • w3id artifact publishing in release workflow (OWL/SHACL/JSON-LD served via GitHub Pages)
  • OMB pinned to v0.1.6, w3id.org synced to upstream master (all namespace redirects merged)

Test plan

  • make test full passes (Python + TypeScript + interop)
  • make story passes (generate → sign → verify → validate)
  • make lint passes
  • CI matrix green (3 OS × 2 Python versions)

jdsika and others added 4 commits February 24, 2026 16:07
- Add delegation challenge encoding spec (v2.0) with OID4VP alignment
- Implement Python delegation module with TransactionData dataclass
- Add 48 unit tests for challenge creation, parsing, and validation
- Add did:web vs did:webs evaluation document
- Download reference specs (OID4VP, did:web, did:webs, KERI) for offline access
- Update documentation structure with guides and specs sections
- Add contributing guide and architecture overview

The delegated signing model enables users to authorize blockchain
transactions through any VC wallet, with a signing service executing
on their behalf. Challenge format uses hash-only approach for QR code
compatibility while maintaining full auditability.

BREAKING CHANGE: Evidence format changes from nonce-based to
proof.challenge with ABNF-defined structure.

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
… delegation modules

Restructure the evidence model for W3C-correct, OID4VP-aligned credentials
with three-layer privacy (public / authorized / full audit).

Schema changes (LinkML):
- Merge EmailVerification + IssuanceEvidence → CredentialEvidence
- Simplify DelegatedSignatureEvidence to use OID4VP transactionData
- Remove TransactionIntent class and legacy slots

Python:
- Refactor delegation.py TransactionData to OID4VP §8.4 fields
  (type, credential_ids, iat/exp as Unix timestamps, txn, nonce)
- Fix pre-existing SD-JWT VP test bugs (wrong parameter names, missing vct)

TypeScript:
- Add delegation.ts with full feature parity (challenge, hash, display)
- Add sd-jwt-vp.ts for SD-JWT VP issue/verify with evidence
- Add cross-runtime canonicalization interop tests (5 tests)

Examples:
- Add delegated-signing-receipt.json and consent-vp.json
- Update evidence types in existing examples

Housekeeping:
- Update CLAUDE.md module table and key imports for new modules
- Expose delegation and sd_jwt_vp in harbour __init__.py
- Use pathlib.Path in delegation.py CLI (per coding conventions)
- Fix duplicate section numbering in delegation spec

293 Python + 97 TypeScript + 11 interop tests — all passing.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- enforce delegation challenge derivation and verification from transaction_data
  (`<nonce> HARBOUR_DELEGATE <sha256(canonical(transaction_data))>`) in Python and TypeScript
- enforce KB-JWT transaction_data hash and audience bindings in SD-JWT VP flows
- add and align cross-runtime canonicalization vectors/tests for JSON, hashes, challenges,
  and OID4VP transaction_data parameter/hash handling
- standardize delegated signing examples/docs on OID4VP snake_case fields
  (`transaction_data`, `credential_ids`, `transaction_data_hashes*`, `asset_id`)
- migrate natural/legal person examples to did:webs identifiers and add did-webs DID document examples
- model EmailPass as credential evidence issued by a did:webs issuer DID
- extend SHACL validation pipeline with ontology loading probe and required ontology checks
  (`cs`, `cred`, `core`, `harbour`, `gx`) via ontology-management-base validation suite

Refs: #2
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
…s sole issuer

Refactor the credential issuance model so the Harbour Signing Service
is the sole issuer of ALL credentials (LegalPerson + NaturalPerson),
acting "on behalf of" an authorizing party. Evidence VPs now contain
authorization proof instead of empty key-ownership VPs.

Signing Service DID document:
- Add #key-2 (P-256) for capabilityDelegation (delegated txn signing)
- #key-1 remains assertionMethod (credential issuance)
- Both keys listed under authentication

Trust Anchor DID document:
- Add LinkedCredentialService endpoint pointing to self-signed credential
- Self-signed LegalPersonCredential (issuer == subject, root of trust)

Credential issuance chain:
- LegalPersonCredential: Trust Anchor authorizes org via VP containing
  its self-signed credential → Signing Service issues with VP as evidence
- NaturalPersonCredential: Org authorizes employee via VP containing
  its LegalPersonCredential (SD-JWT, PII redacted) → Signing Service
  issues with VP as evidence

Delete Harbour Credential Issuer:
- Remove harbour-credential-issuer.did.json (DID Eoc79uQyWN5vo9...)
- Merge issuer role into Signing Service (DID Er9_mnFstIFyj7...)
- Update all references in examples, docs, and tests

LinkML schema:
- Update CredentialEvidence description for authorization model
- Add LinkedCredentialService class to ServiceUnion
- Update gaiax-domain.yaml evidence comments for both credential types

Remove consent-vp.json:
- Redundant — the receipt credential embeds the consent VP as evidence
- The consent VP is an ephemeral artifact, not a persisted example

Fix vc+ld+jwt → vc+jwt in signer/verifier source code (both runtimes):
- VC-JOSE-COSE spec: "No +ld+ media types exist"
- signer.py/signer.ts: typ="vc+jwt" for VCs, typ="vp+jwt" for VPs
- verifier.py/verifier.ts: expected_typ updated to match
- All test assertions updated (sign, tamper, interop, example_signer)

Clean up legacy test data:
- Replace simpulse-id VCT URIs with Harbour namespace in SD-JWT and
  KB-JWT tests (Python + TypeScript)
- Replace ASCS/BMW sample claims with generic example data
- Fix did:web → did:webs in test_sd_jwt_vp.py

Docs audit — fix legacy references across all specs and ADRs:
- delegation-challenge-encoding.md: replace did:web with did:webs for
  Signing Service (5 occurrences), remove simpulse-id-credentials refs
- did-method-evaluation.md: update from SimpulseID to Harbour, rewrite
  section 7 for current did:webs implementation
- ADR-001: fix vc+ld+jwt → vc+jwt (VC-JOSE-COSE spec compliance),
  update did:web → did:webs for key resolution
- ADR-004: update ASCS/BMW examples to Harbour entities, did:web → did:webs
- architecture.md: update key resolution from did:web to did:webs
- ADR-001, ADR-004: remove backwards-compatibility and "legacy" language
  (this is v1 — no compatibility concerns exist)

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika changed the title feat(delegation): add delegated signing evidence spec and implementation feat(harbour): delegated signing, evidence model, and credential issuance refactoring Feb 26, 2026
The Cross-Runtime Interop CI job installed TypeScript dependencies but
never ran `yarn build`, so the compiled `dist/` output (delegation.js,
sd-jwt-vp.js) didn't exist. The 7 interop tests that import from
`./dist/delegation.js` failed with "Qualified path resolution failed".

CI fix:
- Add `yarn build` step to `test-interop` job after `yarn install`

Makefile fix:
- `make test-all` now runs `build-ts` before `test`, ensuring interop
  tests have fresh compiled output even from a clean state

Root cause: locally `dist/` persists across runs so the failure was
never observed. CI starts fresh on every run.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika self-assigned this Feb 26, 2026
@jdsika jdsika added the enhancement New feature or request label Feb 26, 2026
…ensions

Base examples are now harbour-only skeletons (no gxParticipant, no Gaia-X
context). Gaia-X domain extensions live in examples/gaiax/ with schema:name
as the authoritative name on the gxParticipant inner node (no duplicate
name on the outer credentialSubject).

LinkML schemas restructured: harbour.yaml contains only the abstract
HarbourCredential base class plus evidence types, CRSet, and DID document
structure. Concrete credential types (LegalPersonCredential,
NaturalPersonCredential) and participant types live in gaiax-domain.yaml.
ServiceOffering removed entirely.

Claim mappings split into MAPPINGS (base) + GAIAX_MAPPINGS registries with
context-aware get_mapping_for_vc() dispatch. Example signer auto-discovers
gaiax/ subdirectory and uses per-file output directories.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Inline the id slot and type comment from core.yaml into harbour.yaml,
eliminating a separate schema file that only defined one slot. Simplifies
the schema structure to two files: harbour.yaml (base) and
gaiax-domain.yaml (domain layer).

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
CI jobs now delegate to Makefile targets (make lint, make test, etc.)
instead of inlining shell commands. Added make test-interop target for
cross-runtime tests. Replaced all emoji in Makefile with ASCII text.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from 06736bd to 64a99c5 Compare February 26, 2026 13:31
lint-ts and test-ts were missing yarn install, causing CI failures
where dependencies hadn't been installed yet. Locally this worked
because make setup had already run yarn install. Now all TS targets
(build-ts, test-ts, lint-ts) consistently run corepack enable +
yarn install before execution. Removed redundant corepack enable
steps from CI workflow since Makefile handles it.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@flhps flhps self-requested a review February 26, 2026 17:18
flhps and others added 4 commits February 26, 2026 18:44
cd into OMB_SUBMODULE_DIR before invoking $(PYTHON), so the relative
path .venv/bin/python3 no longer resolves. Use $(abspath $(PYTHON))
to produce an absolute path, consistent with how submodule-setup
already passes PYTHON to sub-makes.

Signed-off-by: felix hoops <9974641+flhps@users.noreply.github.com>
Remove duplicate 'Run Tests' subsection from under Validating
Credentials. Consolidate into a single Testing section using make
targets throughout: make test-ts instead of yarn, make test-interop
instead of raw pytest. Note make build-ts as a prerequisite for
TypeScript and interop tests.

Signed-off-by: felix hoops <9974641+flhps@users.noreply.github.com>
…harbour-gx-credential

Clarify the separation between infrastructure types (HarbourCredential,
CRSet, Evidence, DID) and Gaia-X concrete types (LegalPersonCredential,
NaturalPersonCredential, gxParticipant) by renaming the schema files.

- linkml/harbour.yaml -> linkml/harbour-core-credential.yaml
- linkml/gaiax-domain.yaml -> linkml/harbour-gx-credential.yaml
- Makefile DOMAINS updated to match new filenames
- Test artifact paths and doc references updated
- Add BMW legal-person-credential example for IRI reference testing
- RDF namespace/prefix stays harbour: — no URI changes

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
In CI, PYTHON is a bare 'python3' command. $(abspath python3) wrongly
resolves to $CWD/python3 instead of using PATH. Introduce PYTHON_ABS
that uses $(shell which) in CI and $(abspath) locally.

Also update the required ontology check from harbour-core-credential to
harbour-gx-credential — the auto-discovery loads the gx ontology (which
transitively imports core) based on types found in the example data.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from a0a4006 to ce73098 Compare March 2, 2026 15:20
- Create gaiax/natural-person-credential-andreas.json (UUID b2c3d4e5)
- Create gaiax/natural-person-credential-max.json (UUID c3d4e5f6)
- Both use did:web:did.ascs.digital subject DIDs and BMW as memberOf
- Resolves broken harbourCredential references from simpulseid layer

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
@jdsika jdsika changed the title feat(harbour): delegated signing, evidence model, and credential issuance refactoring feat(harbour): delegated signing, evidence model, credential issuance refactoring, and SimpulseID examples Mar 6, 2026
jdsika added 3 commits March 6, 2026 16:06
…eKind

- Remove id from class slots: lists — identifier: true already maps to
  @id; listing in slots generated invalid sh:path harbour:id property
  shapes (7 classes: DIDDocument, TrustAnchorService,
  LinkedCredentialService, CRSetRevocationRegistryService,
  HarbourCredential, CRSetEntry, VerificationMethod)
- Change Evidence class_uri from cred:Evidence to harbour:Evidence to
  match subclass namespace and produce correct sh:class constraint
- Add HarbourShaclGenerator with cred:issuer nodeKind fix — W3C VC v2
  context defines issuer as @type: @id (IRI), not sh:Literal
- Replace gen-shacl/gen-owl/gen-jsonld-context CLI calls in Makefile
  with unified generate_artifacts.py script
- Fix CRSet status ID URIs: #fragment to /path (did:webs uses path
  separators for service sub-resources, not fragments)

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
…raints

- Add GitHub issue references (linkml/linkml#2914) to HarbourShaclGenerator
- Strip sh:class linkml:Any from generated SHACL (meta-type not in instance data)
- Add Gaia-X 25.11 compliance fields to harbour-gx-credential schema
- Add X.509 revocation checking stub

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
- Replace did:web with did:ethr using ERC-1056 on Base Sepolia (0x14a34)
- Add did:ethr DID document examples for all ecosystem entities
- Add ADR-005 documenting did:ethr migration rationale
- Add did:ethr method specification reference
- Update delegation challenge encoding for did:ethr format
- Update DID method evaluation with did:ethr comparison
- Update all examples and test fixtures to did:ethr identifiers
- Remove deprecated did:web examples and references

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
@jdsika jdsika changed the title feat(harbour): delegated signing, evidence model, credential issuance refactoring, and SimpulseID examples feat(harbour): delegated signing, SD-JWT-VP, did:ethr migration, and Gaia-X separation Mar 9, 2026
jdsika added 3 commits March 10, 2026 15:20
…nt tooling

LinkML schema improvements:
- Eliminate SHACL ghost properties (remove id from class slots lists)
- Fix Evidence class_uri to harbour:Evidence namespace
- Add HarbourShaclGenerator with cred:issuer nodeKind fix
- Clean up harbour-core-credential and harbour-gx-credential schemas
- Add w3c-vc.yaml schema for transitive imports

Artifact generation:
- Replace gen-shacl/gen-owl/gen-jsonld-context CLI calls with unified
  generate_artifacts.py script
- Add DomainContextGenerator to exclude W3C @Protected terms

Documentation:
- Add reference specs (did-core, oid4vp, vc-data-model-2.0, sd-jwt-rfc9901,
  gx-architecture-document-25.11)
- Convert did-web-method.txt to proper markdown
- Fix markdown lint violations across all docs (MD040, MD046, MD036, MD032)

Markdown lint tooling:
- Add .markdownlint-cli2.yaml config with tuned rules
- Add markdownlint-cli2 pre-commit hook (v0.17.2)
- Add lint-md and format-md Makefile targets
- Add lint-markdown CI job in GitHub Actions workflow

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
- Add docs/schema/credential-model.md with Mermaid class diagrams:
  credential type hierarchy, evidence inheritance, subject types,
  Gaia-X composition pattern, revocation infrastructure, DID model,
  artifact generation pipeline, and complete class map
- Expand docs/architecture.md from stub to full overview with component
  diagram, signing flow sequence diagram, and ADR-005 reference
- Add Schema section and ADR-005 to mkdocs.yml navigation
- Fix did-webs-spec.md ::: markers clashing with mkdocstrings
- Add site/ to .gitignore

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
Add did: https://www.w3.org/ns/did# to prefixes and replace 6 full URIs
with compact did: notation (controller, serviceEndpoint, DIDDocument,
service, verificationMethod, VerificationMethod).

Eliminates 'No namespace defined for URI' warnings during make generate.

Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
jdsika added 6 commits March 30, 2026 14:42
Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
The generate pipeline requires ASCS-eV/linkml fork features (deterministic
output, normalize-prefixes, xsd-anyuri-as-iri, exclude-external-imports).
Previously 'make install dev' ran 'pip install linkml' which installed
upstream PyPI linkml without these features, causing:

  TypeError: OwlSchemaGenerator.__init__() got an unexpected keyword
  argument 'deterministic'

Changes:
- Add LINKML_SUBMODULE_DIR variable pointing to OMB's linkml submodule
- Move linkml installation from _install_dev to _setup_submodules,
  installing from the fork submodule (editable mode)
- Remove standalone 'pip install linkml' from all targets
- Lint/test jobs no longer install linkml (not needed for those tasks)
- CI generate-validate job installs deps + submodules before generation

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
test_shacl_failures.py imports rdflib directly. Previously this was
transitively available via 'pip install linkml', but now that linkml
is only installed in the generate-validate job (from the fork submodule),
test jobs need rdflib declared explicitly.

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
JSON-LD expansion maps _comment to harbour:_comment triples, which
violate sh:closed shapes. Remove documentation-only _comment keys
from the nested evidence example.

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Set use_native_uris=False on OwlSchemaGenerator so OWL class IRIs
are derived from class_uri instead of default_prefix + ClassName.
This makes the rdfs:subClassOf hierarchy use the same IRIs as SHACL
sh:targetClass and JSON-LD @type, allowing RDFS inference to resolve
the type hierarchy directly.

Removes the _patch_owl_equivalences post-processing function (-51 lines)
that was copying rdfs:subClassOf triples and adding owl:equivalentClass
axioms to bridge the class_uri / native URI mismatch.

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Remove redundant 'Harbour' prefix from class_uris — the namespace
prefix already establishes context:
- harbour:HarbourCredential      → harbour:Credential
- harbour:HarbourPresentation    → harbour:Presentation
- (implicit) HarbourVerifiable*  → harbour:VerifiableCredential,
                                   harbour:VerifiablePresentation

Clean up delegation evidence naming:
- harbour:DelegatedSignatureEvidence → harbour:SignatureEvidence
  (stays in core where it is defined)
- harbour:DelegatedSigningReceipt    → harbour.delegate:SigningReceipt
  (receipt type only used in delegation examples)

Convention: W3C terms stay bare ('VerifiableCredential'), all domain
types use prefixed CURIEs ('harbour:VerifiableCredential'). Follows
the same pattern as harbour.gx:LegalPerson, harbour.gx:NaturalPerson.

Update all examples, tests (Python + TypeScript), and sd_jwt_vp.py
evidence type whitelist. LinkML class names unchanged (internal).

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from 18f0443 to f4ac3f5 Compare March 31, 2026 10:50
jdsika and others added 9 commits March 31, 2026 15:32
…redential arrays

- Remove harbour:Evidence from type arrays where a child type
  (CredentialEvidence, SignatureEvidence) is present; RDFS inference
  infers the parent via rdfs:subClassOf
- Remove harbour:VerifiableCredential from domain credential type
  arrays (LegalPerson, NaturalPerson, DelegatedSigningReceipt) where
  a domain-specific type already inherits all constraints from
  harbour:Credential
- Refactor test_shacl_failures.py to use the OMB ShaclValidator
  (with RDFS inference enabled) instead of calling pyshacl directly
  with inference=none
- Add harbour:SignatureEvidence to TypeScript DELEGATED_EVIDENCE_TYPES
  whitelist (was missing, causing 3 TS test failures)

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
- serviceEndpoint: range uri per DID Core §5.4
- transaction_data: range JsonLiteral per OID4VP §8.4
- Post-process JSON-LD contexts to use @JSON keyword
- Add standards compliance rules to AGENTS.md

The linkml:Any range caused SHACL closed-shape violations
when RDFS inference added rdf:type linkml:Any to IRI objects.
Replacing with spec-aligned ranges eliminates the issue.

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
On Windows, yarn.cmd invokes cmd.exe which mangles JS code
containing braces and parentheses passed via -e flag. Write
scripts to temp .mjs files instead, and resolve yarn via
shutil.which() for cross-platform subprocess compatibility.

Signed-off-by: jdsika <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@vdl.digital>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
- check_dev_setup: verify both linkml AND harbour are importable
  (was only checking linkml — missed harbour installation issues)
- _test_default: exclude tests/interop/ (requires TypeScript built;
  use 'make test full' for all including interop)
- Add check target: generate + validate (matches simpulseid pattern)
- Update all target: lint + check + test (was lint + test only)
- Add jsonld-lint and turtle-lint pre-commit hooks for JSON-LD and
  Turtle syntax validation (matching OMB and simpulseid patterns)

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from a3b0c52 to 132b8bd Compare April 2, 2026 19:37
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
- test-interop: 3 OS = 3 jobs
- generate-validate: 3 OS = 3 jobs

Lint jobs remain ubuntu-only (formatting is OS-independent).

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from d9059ea to 21c1b7e Compare April 2, 2026 19:49
New 'Credential Story' job runs the full generate → sign → verify →
SHACL validate pipeline across 3 OS (ubuntu/macos/windows).

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika force-pushed the fix/simpulse-id-credentials branch from f6fed47 to c836fdb Compare April 2, 2026 20:03
jdsika added 7 commits April 2, 2026 22:17
The verify_signed_examples.py script uses Unicode checkmarks (✓) in
print output. On Windows, the default cp1252 encoding cannot encode
these characters. Setting PYTHONIOENCODING=utf-8 fixes this.

OMB's validators use emojis too but run via pytest which handles
encoding internally. Story targets run scripts directly, so they
need the explicit encoding override.

Closes #5

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
The DIDDocument class only modeled 5 of 10 W3C DID Core properties.
The generated SHACL shape (sh:closed true) rejected spec-compliant
optional properties like capabilityDelegation.

Added per W3C DID Core (https://www.w3.org/TR/did-core/):
- keyAgreement (§5.3.3) → sec:keyAgreementMethod
- capabilityInvocation (§5.3.4) → sec:capabilityInvocationMethod
- capabilityDelegation (§5.3.5) → sec:capabilityDelegationMethod
- alsoKnownAs (§5.1.3) → as:alsoKnownAs

URI suffixes follow the DID Core JSON-LD context convention
(imports/did/did.context.jsonld) where verification relationships
expand to sec:*Method URIs.

Also fixes harbour-signing-service.did.json: replaced undefined
didcore:serviceEndpoint prefix with spec-compliant serviceEndpoint.

Signed-off-by: jdsika <carlo.van-driesten@bmw.de>
Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Pin ontology-management-base submodule to the v0.1.6 release which
includes CI artifact generation verification, cross-platform test
matrix, and Makefile CI detection fixes.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update w3id.org submodule to upstream master (ecada1d3). The
reachhaven/ and simpulse-id/ namespace redirect rules have been
merged upstream.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Standardize JSON-LD type declarations in DID service endpoints.
Inside serviceEndpoint objects, @type is needed for proper JSON-LD
expansion — plain type is only aliased at the DID document level
by the DID Core context.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
Update w3id.org fork to latest upstream master (d004a453). Both
reachhaven/harbour and ascs-ev/simpulse-id namespace redirect
rules are now merged in the public w3id.org repository.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
…tion

Per EVES-009 §5 security requirements:

- Log warning when credentialStatus is present but revocation check
  is not performed (caller responsibility per spec)
- Add optional check_transaction_freshness parameter to verify_sd_jwt_vp()
  that validates transaction data timestamps (iat, exp) via
  validate_transaction_data()
- Default off for backwards compatibility; callers opt in with
  check_transaction_freshness=True

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika jdsika merged commit 0a5205d into main Apr 4, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants