Identity Store Enterprise Ontology: Formalizing the Sealed Specification in OWL and SHACL
Table of Contents
1. What this is
A reviewable OWL/RDF ontology, with SHACL validation shapes, covering enterprise identities, accounts, credentials, systems, and (as an extension beyond the sealed spec's own scope) personal-data governance. The complete Turtle source is embedded below.
It is a metadata model, not a credential vault or an authorization engine. Raw passwords, tokens, OTP seeds, recovery codes, and personal-data values stay out of the RDF graph by design; protected values live in a vault or governed source, and the graph stores only opaque references and management metadata.
3. Model map
| Area | Main concepts | What it records |
|---|---|---|
| Owners and identities | Person, Agent, Role, Project, Environment, Site, Owner | Who owns a credential and which entity/owner class applies |
| Accounts | Identity, Account, Realm, Provider | Provider realm, account handle, and alternate identifier metadata |
| Credentials | CredentialEntry, Credential, CredentialKind, Effect, EntryState | Kind, holder, scope, issue/expiry, state, and safe vault reference |
| Reader isolation | Owner, authorizedReader, encryptionKeyId | Owner-specific readers and key identifiers |
| Personal data | PersonalDataRecord, DataSubject, PersonalDataCategory, Purpose | Governed data references, subject, category, classification, and purpose |
| Governance | LegalBasis, ConsentRecord, RetentionRule, AccessPolicy | Basis, consent evidence, retention, and permitted readers |
| Audit | AuditEvent and lifecycle subclasses | Actor, time, affected entry, and provider verification outcome |
4. Security and implementation notes
- Never put secret or personal-data values in RDF literals, logs, command-line arguments, checked-in files, or review output. Keep only opaque references.
- v1's per-owner reader separation (S1-S2) is represented here as metadata. The vault or key system enforces actual isolation; this graph only records metadata about it.
- Token names identify the holder, not the capability (v1 P5). Record capability in scope; classify effect by the worst capability the token permits (v1 E5).
- Rotation and revocation are complete only after provider-side
verification (v1's note on
Rotate=/=Revoke): record that result in a lifecycle audit event without recording the secret. - SHACL shapes cover metadata structure, token requirements, path structure, reader presence, and personal-data governance fields. They check shape only; they are no substitute for a live audit of actual access controls.
- The namespace below is
https://wal.sh/ns/identity-store-enterprise#, not a placeholderexample.orgnamespace – replaced before publication, as the ontology's own header requires of any adopter.
5. In practice: minting a role-scoped virtual key
The practical layout note already names the shape of this for several shared systems: the person holds an admin or master credential, and a role gets something narrower minted against it – a virtual key with an alias, a budget, and rate limits in the case of a self-hosted LLM gateway; a scoped service-account token in the case of a metrics system. This section walks that pattern end to end for a local model-gateway proxy, generalized rather than tied to one deployment, and maps the result onto the ontology above.
The onboarding flow, as it actually runs against a self-hosted OpenAI-compatible gateway with its own admin panel:
- The person holds the admin/master key. It authenticates against the
gateway's own admin API (discoverable at its
/openapi.json) and its browser admin panel. InCredentialEntryterms:credentialKind :token,effect :outward(it can mint and revoke every other key), owned byme/. - A role is minted a virtual key through the admin panel, not by
sharing the master key. The panel creates a new key with an alias
(which role, which purpose), a spend budget, and a requests/tokens-per-
minute limit – exactly the
key_alias=/=max_budget=/=rpm=/=tpmfields the layout note's shared-systems table already names for this class of gateway. In ontology terms, thisIssueoperation (v1 §7) produces a newCredentialEntrywithcredentialKind :token,entryNameset to the role's holder (not its purpose),scopeDescriptionrecording the budget and rate limits in the provider's own words,usedBypointing at the system that presents it, and aneffectclassification –stagedif the budget scopes the key to a non- production project,outwardif the role can reach anything that publishes or sends externally through that budget. v1's C1 refuses a token enrolled without one; this is the field the layout note's own shared-systems table leaves unassigned for all seven systems it lists, not just this one. - The value lands in a role-scoped entry, never the person's own. Per
v1's O4 ("no two owners share an entry"), the virtual key is filed
under
role/<name>/<gateway-realm>/_/token/<holder>, not alongside the admin key underme/. A file-per-entry store such as pass works for this, as does any system-agnostic equivalent that gives each owner its own encrypted boundary (v1 S1-S2) – the layout note's rules don't depend onpassspecifically, only on readers being separable per owner. - The record notes it without decrypting it. A
heldline with the key's due date and effect, mechanically derived from the entry's own fields (v1's I2-I3). - Rotation goes through the same panel, and isn't finished on write.
The admin panel revokes the old virtual key and issues a new one; the
rotation is complete once a call with the old key is refused by the
gateway, matching v1's note that
Rotate=/=Revokeare "finished when the provider has been asked with the old secret and has said no," not when the store changes.
This is deliberately the thin case: one gateway, one role, one key. The
ontology's CredentialEntry=/=Effect=/=EntryState properties are exactly
what step 2 needs to record, and nothing here required touching the
personal-data-governance half of the model at all – a useful sign that
the two halves of this ontology are doing genuinely separate jobs, as the
fidelity check above also found.
6. Embedded ontology source
The Turtle source below is the source of truth for the ontology.
Running org-babel-tangle on this block regenerates the adjacent
identity-store-enterprise-ontology.ttl file.
@prefix : <https://wal.sh/ns/identity-store-enterprise#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
<https://wal.sh/ns/identity-store-enterprise>
a owl:Ontology ;
owl:versionInfo "1.0.0" ;
dcterms:title "Enterprise Identity, Credential, and Personal Data Governance Ontology"@en ;
dcterms:description "An OWL vocabulary and SHACL validation shapes for modeling enterprise owners, identities, accounts, credentials, access scope, protected personal-data records, stewardship, retention, and audit metadata. It formalizes the sealed identity-store specification v1 (site/research/2026-identity-store-spec)."@en ;
dcterms:created "2026-10-11"^^xsd:date ;
rdfs:comment "Security boundary: credential secrets and raw personal-data values MUST remain in an approved vault or protected system, never as RDF literals. This graph stores only metadata and opaque references. OWL axioms express meaning; SHACL shapes express operational validation rules."@en .
#################################################################
# Core actors, owners, and systems
#################################################################
:Entity a owl:Class ; rdfs:label "entity"@en .
:Person a owl:Class ; rdfs:subClassOf :Entity ; rdfs:label "person"@en .
:Organization a owl:Class ; rdfs:subClassOf :Entity ; rdfs:label "organization"@en .
:Enterprise a owl:Class ; rdfs:subClassOf :Organization ; rdfs:label "enterprise"@en .
:Agent a owl:Class ; rdfs:subClassOf :Entity ; rdfs:label "agent identity"@en ; rdfs:comment "A named automated agent or delegated software identity; not necessarily a human."@en .
:Role a owl:Class ; rdfs:subClassOf :Entity ; rdfs:label "role identity"@en .
:Project a owl:Class ; rdfs:label "project"@en .
:Environment a owl:Class ; rdfs:label "environment"@en .
:Site a owl:Class ; rdfs:label "site or place"@en .
:System a owl:Class ; rdfs:subClassOf prov:SoftwareAgent ; rdfs:label "system"@en .
:Service a owl:Class ; rdfs:subClassOf :System ; rdfs:label "service"@en .
:Machine a owl:Class ; rdfs:subClassOf :System ; rdfs:label "machine"@en .
:Provider a owl:Class ; rdfs:subClassOf :Organization ; rdfs:label "identity or credential provider"@en .
:Realm a owl:Class ; rdfs:label "credential realm"@en ; rdfs:comment "The provider domain, host, network, or document context where a credential is honored."@en .
:Identity a owl:Class ; rdfs:label "identity"@en ; rdfs:comment "A person, agent, or role as recognized in an enterprise identity context."@en .
:Account a owl:Class ; rdfs:label "account"@en ; rdfs:comment "An account recognized by a realm; one identity may have accounts in many realms."@en .
:Owner a owl:Class ; rdfs:label "credential owner"@en ; rdfs:comment "The person, agent, role, project-environment, or site that owns a credential entry."@en .
:hasIdentity a owl:ObjectProperty ; rdfs:domain :Entity ; rdfs:range :Identity ; rdfs:label "has identity"@en .
:identityOf a owl:ObjectProperty ; owl:inverseOf :hasIdentity ; rdfs:domain :Identity ; rdfs:range :Entity ; rdfs:label "identity of"@en .
:hasAccount a owl:ObjectProperty ; rdfs:domain :Identity ; rdfs:range :Account ; rdfs:label "has account"@en .
:accountRealm a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :Account ; rdfs:range :Realm ; rdfs:label "account realm"@en .
:accountProvider a owl:ObjectProperty ; rdfs:domain :Account ; rdfs:range :Provider ; rdfs:label "account provider"@en .
:accountHandle a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :Account ; rdfs:range xsd:string ; rdfs:label "account handle"@en .
:alternateAccountIdentifier a owl:DatatypeProperty ; rdfs:domain :Account ; rdfs:range xsd:string ; rdfs:label "alternate account identifier"@en ; rdfs:comment "Optional provider-recognized email, ID, or alias. Apply personal-data controls if it identifies a person."@en .
:ownedBy a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :Owner ; rdfs:label "owned by"@en .
:ownerEntity a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :Owner ; rdfs:range :Entity ; rdfs:label "owner entity"@en .
:ownerProject a owl:ObjectProperty ; rdfs:domain :Owner ; rdfs:range :Project ; rdfs:label "owner project"@en .
:ownerEnvironment a owl:ObjectProperty ; rdfs:domain :Owner ; rdfs:range :Environment ; rdfs:label "owner environment"@en .
:ownerSite a owl:ObjectProperty ; rdfs:domain :Owner ; rdfs:range :Site ; rdfs:label "owner site"@en .
:ownerClass a owl:ObjectProperty ; rdfs:domain :Owner ; rdfs:range :OwnerClass ; rdfs:label "owner class"@en .
:OwnerClass a owl:Class ; rdfs:label "owner class value"@en .
:PersonOwner a :OwnerClass ; rdfs:label "person"@en .
:AgentOwner a :OwnerClass ; rdfs:label "agent"@en .
:RoleOwner a :OwnerClass ; rdfs:label "role"@en .
:ProjectOwner a :OwnerClass ; rdfs:label "project environment"@en .
:SiteOwner a :OwnerClass ; rdfs:label "site"@en .
#################################################################
# Credential taxonomy and safe metadata
#################################################################
:Credential a owl:Class ; rdfs:label "credential"@en .
:LoginCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "login credential"@en .
:OTPCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "one-time-password seed"@en ; rdfs:comment "Metadata for a shared TOTP seed. The otpauth URI/seed itself is never stored in this graph."@en .
:RecoveryCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "recovery credential set"@en ; rdfs:comment "Metadata for recovery codes. Individual codes remain in the vault and are not RDF resources or literals."@en .
:TokenCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "token credential"@en .
:KeyCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "key material credential"@en .
:PINCredential a owl:Class ; rdfs:subClassOf :Credential ; rdfs:label "personal PIN credential"@en .
:CredentialEntry a owl:Class ; rdfs:label "credential entry"@en ; rdfs:comment "A protected-store entry and its cleartext-safe metadata. Its record is a projection of the corresponding entry metadata."@en .
:SecretReference a owl:Class ; rdfs:label "opaque secret reference"@en ; rdfs:comment "A non-secret locator or vault object identifier. It must not resolve to secret material in this RDF graph."@en .
:CredentialKind a owl:Class ; rdfs:label "credential kind"@en .
:login a :CredentialKind ; rdfs:label "login"@en .
:otp a :CredentialKind ; rdfs:label "otp"@en .
:recovery a :CredentialKind ; rdfs:label "recovery"@en .
:token a :CredentialKind ; rdfs:label "token"@en .
:key a :CredentialKind ; rdfs:label "key"@en .
:pin a :CredentialKind ; rdfs:label "pin"@en .
:Effect a owl:Class ; rdfs:label "credential effect"@en .
:private a :Effect ; rdfs:label "private"@en ; rdfs:comment "Access stays within the holder or owner."@en .
:staged a :Effect ; rdfs:label "staged"@en ; rdfs:comment "Access can change a non-production or staged environment."@en .
:outward a :Effect ; rdfs:label "outward"@en ; rdfs:comment "Access can publish, send externally, or affect others outside the private boundary."@en .
:EntryState a owl:Class ; rdfs:label "entry state"@en .
:held a :EntryState ; rdfs:label "held"@en .
:wanted a :EntryState ; rdfs:label "wanted"@en .
:elsewhere a :EntryState ; rdfs:label "elsewhere"@en .
:dropped a :EntryState ; rdfs:label "dropped"@en .
:entryCredential a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :Credential ; rdfs:label "entry credential"@en .
:credentialKind a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :CredentialKind ; rdfs:label "credential kind"@en .
:secretReference a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :SecretReference ; rdfs:label "secret reference"@en .
:vaultLocator a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :SecretReference ; rdfs:range xsd:string ; rdfs:label "vault locator"@en ; rdfs:comment "Opaque reference only; do not put secret values, credentials in URLs, or readable PII here."@en .
:realmDomain a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :Realm ; rdfs:range xsd:string ; rdfs:label "realm domain"@en .
:realmKind a owl:DatatypeProperty ; rdfs:domain :Realm ; rdfs:range xsd:string ; rdfs:label "realm kind"@en ; rdfs:comment "One of domain, host, network, or doc; use a canonical registrable domain when applicable."@en .
:accountForEntry a owl:ObjectProperty ; rdfs:domain :CredentialEntry ; rdfs:range :Account ; rdfs:label "account for entry"@en .
:entryPath a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:string ; rdfs:label "canonical entry path"@en .
:entryName a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:string ; rdfs:label "entry name or token holder"@en ; rdfs:comment "For a token, this is its holder (machine or service), not its purpose or scope."@en .
:issuedDate a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:date ; rdfs:label "issued date"@en .
:expiryDate a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:date ; rdfs:label "expiry date"@en .
:neverExpires a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:boolean ; rdfs:label "never expires"@en .
:effect a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :Effect ; rdfs:label "effect"@en ; rdfs:comment "Worst capability the bearer can exercise, not typical use."@en .
:scopeDescription a owl:DatatypeProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:string ; rdfs:label "scope description"@en .
:usedBy a owl:ObjectProperty ; rdfs:domain :CredentialEntry ; rdfs:range :System ; rdfs:label "used by system"@en .
:credentialURL a owl:DatatypeProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:anyURI ; rdfs:label "credential management URL"@en .
:entryState a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :EntryState ; rdfs:label "entry state"@en .
:recordDueDate a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:date ; rdfs:label "record due date"@en ; rdfs:comment "Derived projection of credential expiry; do not maintain independently."@en .
:recordEffect a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range :Effect ; rdfs:label "record effect"@en ; rdfs:comment "Derived projection of credential effect; do not maintain independently."@en .
:droppedReason a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:string ; rdfs:label "dropped reason"@en .
:isFictional a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:boolean ; rdfs:label "fictional value marker"@en ; rdfs:comment "Use separate entries for fictional and real values."@en .
:encryptionKeyId a owl:DatatypeProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:string ; rdfs:label "encryption key identifier"@en .
:authorizedReader a owl:ObjectProperty ; rdfs:domain :Owner ; rdfs:range :Entity ; rdfs:label "authorized reader"@en ; rdfs:comment "Owner-specific decryption/read authorization. Different owners have separate reader sets unless explicitly granted."@en .
:providerRevocationVerified a owl:DatatypeProperty, owl:FunctionalProperty ; rdfs:domain :CredentialEntry ; rdfs:range xsd:boolean ; rdfs:label "provider revocation verified"@en .
:credentialHolder a owl:ObjectProperty ; rdfs:domain :CredentialEntry ; rdfs:range :System ; rdfs:label "credential holder"@en .
:hasPermissionScope a owl:ObjectProperty ; rdfs:domain :CredentialEntry ; rdfs:range :PermissionScope ; rdfs:label "has permission scope"@en .
:PermissionScope a owl:Class ; rdfs:label "permission scope"@en .
#################################################################
# Personal data governance (metadata, not payload values)
#################################################################
:PersonalDataRecord a owl:Class ; rdfs:label "personal data record metadata"@en ; rdfs:comment "A governed reference to personal information. Do not put the personal-data value in the RDF graph."@en .
:DataSubject a owl:Class ; rdfs:subClassOf :Person ; rdfs:label "data subject"@en .
:PersonalDataCategory a owl:Class ; rdfs:label "personal data category"@en .
:ProcessingActivity a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "processing activity"@en .
:Purpose a owl:Class ; rdfs:label "processing purpose"@en .
:LegalBasis a owl:Class ; rdfs:label "legal basis"@en .
:ConsentRecord a owl:Class ; rdfs:label "consent record"@en .
:RetentionRule a owl:Class ; rdfs:label "retention rule"@en .
:DataAsset a owl:Class ; rdfs:label "data asset or protected source"@en .
:DataController a owl:Class ; rdfs:subClassOf :Organization ; rdfs:label "data controller"@en .
:DataProcessor a owl:Class ; rdfs:subClassOf :Organization ; rdfs:label "data processor"@en .
:DataClassification a owl:Class ; rdfs:label "data classification"@en .
:ordinary a :DataClassification ; rdfs:label "ordinary personal data"@en .
:sensitive a :DataClassification ; rdfs:label "sensitive personal data"@en .
:restricted a :DataClassification ; rdfs:label "restricted personal data"@en .
:highRisk a :DataClassification ; rdfs:label "high-risk personal data"@en .
:personalDataReference a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :SecretReference ; rdfs:label "protected data reference"@en .
:subjectOfData a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :DataSubject ; rdfs:label "data subject"@en .
:dataCategory a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :PersonalDataCategory ; rdfs:label "data category"@en .
:dataClassification a owl:ObjectProperty, owl:FunctionalProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :DataClassification ; rdfs:label "data classification"@en .
:processedFor a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :Purpose ; rdfs:label "processed for purpose"@en .
:processedUnder a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :LegalBasis ; rdfs:label "legal basis used"@en .
:consentEvidence a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :ConsentRecord ; rdfs:label "consent evidence"@en .
:governedByRetention a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :RetentionRule ; rdfs:label "governed by retention rule"@en .
:partOfActivity a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :ProcessingActivity ; rdfs:label "part of processing activity"@en .
:heldIn a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :DataAsset ; rdfs:label "held in data asset"@en .
:controller a owl:ObjectProperty ; rdfs:domain :ProcessingActivity ; rdfs:range :DataController ; rdfs:label "controller"@en .
:processor a owl:ObjectProperty ; rdfs:domain :ProcessingActivity ; rdfs:range :DataProcessor ; rdfs:label "processor"@en .
:activityPurpose a owl:ObjectProperty ; rdfs:domain :ProcessingActivity ; rdfs:range :Purpose ; rdfs:label "activity purpose"@en .
:collectionDate a owl:DatatypeProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range xsd:date ; rdfs:label "collection date"@en .
:retentionEndDate a owl:DatatypeProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range xsd:date ; rdfs:label "retention end date"@en .
:retentionDuration a owl:DatatypeProperty ; rdfs:domain :RetentionRule ; rdfs:range xsd:duration ; rdfs:label "retention duration"@en .
:retentionTrigger a owl:DatatypeProperty ; rdfs:domain :RetentionRule ; rdfs:range xsd:string ; rdfs:label "retention trigger"@en .
:lawfulBasisCode a owl:DatatypeProperty ; rdfs:domain :LegalBasis ; rdfs:range xsd:string ; rdfs:label "lawful basis code"@en .
:consentStatus a owl:DatatypeProperty ; rdfs:domain :ConsentRecord ; rdfs:range xsd:string ; rdfs:label "consent status"@en .
:consentCapturedAt a owl:DatatypeProperty ; rdfs:domain :ConsentRecord ; rdfs:range xsd:dateTime ; rdfs:label "consent captured at"@en .
:consentWithdrawnAt a owl:DatatypeProperty ; rdfs:domain :ConsentRecord ; rdfs:range xsd:dateTime ; rdfs:label "consent withdrawn at"@en .
:processingRegion a owl:DatatypeProperty ; rdfs:domain :ProcessingActivity ; rdfs:range xsd:string ; rdfs:label "processing region"@en .
:purposeStatement a owl:DatatypeProperty ; rdfs:domain :Purpose ; rdfs:range xsd:string ; rdfs:label "purpose statement"@en .
:sourceSystem a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :System ; rdfs:label "source system"@en .
:accessPolicy a owl:ObjectProperty ; rdfs:domain :PersonalDataRecord ; rdfs:range :AccessPolicy ; rdfs:label "access policy"@en .
:AccessPolicy a owl:Class ; rdfs:label "access policy"@en .
:permittedReader a owl:ObjectProperty ; rdfs:domain :AccessPolicy ; rdfs:range :Entity ; rdfs:label "permitted reader"@en .
#################################################################
# Lifecycle and provenance
#################################################################
:AuditEvent a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "audit event"@en .
:Enrolment a owl:Class ; rdfs:subClassOf :AuditEvent ; rdfs:label "credential enrolment"@en .
:CredentialRead a owl:Class ; rdfs:subClassOf :AuditEvent ; rdfs:label "credential read event"@en .
:CredentialRotation a owl:Class ; rdfs:subClassOf :AuditEvent ; rdfs:label "credential rotation"@en .
:CredentialRevocation a owl:Class ; rdfs:subClassOf :AuditEvent ; rdfs:label "credential revocation"@en .
:RecoveryCodeSpend a owl:Class ; rdfs:subClassOf :AuditEvent ; rdfs:label "recovery code spend"@en .
:performedBy a owl:ObjectProperty ; rdfs:domain :AuditEvent ; rdfs:range :Entity ; rdfs:label "performed by"@en .
:affectedEntry a owl:ObjectProperty ; rdfs:domain :AuditEvent ; rdfs:range :CredentialEntry ; rdfs:label "affected credential entry"@en .
:eventTime a owl:DatatypeProperty ; rdfs:domain :AuditEvent ; rdfs:range xsd:dateTime ; rdfs:label "event time"@en .
:verificationOutcome a owl:DatatypeProperty ; rdfs:domain :AuditEvent ; rdfs:range xsd:string ; rdfs:label "verification outcome"@en .
:auditNote a owl:DatatypeProperty ; rdfs:domain :AuditEvent ; rdfs:range xsd:string ; rdfs:label "audit note"@en .
#################################################################
# OWL semantics and safeguards
#################################################################
:LoginCredential owl:disjointWith :OTPCredential, :RecoveryCredential, :TokenCredential, :KeyCredential, :PINCredential .
:OTPCredential owl:disjointWith :RecoveryCredential, :TokenCredential, :KeyCredential, :PINCredential .
:RecoveryCredential owl:disjointWith :TokenCredential, :KeyCredential, :PINCredential .
:TokenCredential owl:disjointWith :KeyCredential, :PINCredential .
:KeyCredential owl:disjointWith :PINCredential .
:private owl:differentFrom :staged, :outward .
:staged owl:differentFrom :outward .
:held owl:differentFrom :wanted, :elsewhere, :dropped .
:wanted owl:differentFrom :elsewhere, :dropped .
:elsewhere owl:differentFrom :dropped .
:login owl:differentFrom :otp, :recovery, :token, :key, :pin .
:otp owl:differentFrom :recovery, :token, :key, :pin .
:recovery owl:differentFrom :token, :key, :pin .
:token owl:differentFrom :key, :pin .
:key owl:differentFrom :pin .
:CredentialEntry rdfs:comment "Each entry has one credential; credential values and recovery codes are not represented here."@en .
#################################################################
# SHACL operational validation
#################################################################
:CommonPrefixes
sh:declare [ sh:prefix "" ; sh:namespace "https://wal.sh/ns/identity-store-enterprise#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "xsd" ; sh:namespace "http://www.w3.org/2001/XMLSchema#"^^xsd:anyURI ] .
:CredentialEntryShape a sh:NodeShape ;
sh:targetClass :CredentialEntry ;
sh:prefixes :CommonPrefixes ;
sh:property [ sh:path :entryPath ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:pattern "^(me|agent/[a-z0-9._@-]+|role/[a-z0-9._@-]+|proj/[a-z0-9._@-]+/[a-z0-9._@-]+|site/[a-z0-9._@-]+)/[a-z0-9._@-]+/[a-z0-9._@-]+/(login|otp|recovery|token|key|pin)(/[a-z0-9._@-]+)?$" ] ;
sh:property [ sh:path :ownedBy ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :Owner ] ;
sh:property [ sh:path :credentialKind ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :CredentialKind ] ;
sh:property [ sh:path :entryCredential ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :Credential ] ;
sh:property [ sh:path :issuedDate ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:date ] ;
sh:property [ sh:path :entryState ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :EntryState ] ;
sh:property [ sh:path :secretReference ; sh:maxCount 1 ; sh:class :SecretReference ] ;
sh:property [ sh:path :effect ; sh:maxCount 1 ; sh:class :Effect ] ;
sh:property [ sh:path :recordEffect ; sh:maxCount 1 ; sh:class :Effect ] ;
sh:property [ sh:path :recordDueDate ; sh:maxCount 1 ; sh:datatype xsd:date ] ;
sh:sparql [
sh:message "A held credential must have a protected secret reference; non-held entries must not claim one."@en ;
sh:select """SELECT $this WHERE { $this :entryState :held . FILTER NOT EXISTS { $this :secretReference ?ref } }"""
] ;
sh:sparql [
sh:message "A held entry's cleartext record effect must equal the credential effect projection."@en ;
sh:select """SELECT $this WHERE { $this :entryState :held . FILTER NOT EXISTS { $this :effect ?e ; :recordEffect ?e } }"""
] ;
sh:sparql [
sh:message "A held entry's record due date must match its expiry date, when expiry is date-based."@en ;
sh:select """SELECT $this WHERE { $this :entryState :held ; :expiryDate ?d . FILTER NOT EXISTS { $this :recordDueDate ?d } }"""
] ;
sh:sparql [
sh:message "A dropped entry must state why it was dropped."@en ;
sh:select """SELECT $this WHERE { $this :entryState :dropped . FILTER NOT EXISTS { $this :droppedReason ?why . FILTER (STRLEN(STR(?why)) > 0) } }"""
] ;
sh:sparql [
sh:message "Token entries require a holder name, expiry (date or never), effect, scope, and consuming system."@en ;
sh:select """SELECT $this WHERE { $this :credentialKind :token . FILTER (NOT EXISTS { $this :entryName ?holder } || (NOT EXISTS { $this :expiryDate ?d } && NOT EXISTS { $this :neverExpires true }) || NOT EXISTS { $this :effect ?e } || NOT EXISTS { $this :scopeDescription ?s } || NOT EXISTS { $this :usedBy ?u }) }"""
] ;
sh:sparql [
sh:message "Login entries require an effect classification."@en ;
sh:select """SELECT $this WHERE { $this :credentialKind :login . FILTER NOT EXISTS { $this :effect ?e } }"""
] ;
sh:sparql [
sh:message "Token entryName identifies its holder, not its scope; use scopeDescription for permitted actions."@en ;
sh:select """SELECT $this WHERE { $this :credentialKind :token ; :entryName ?n . FILTER (LCASE(STR(?n)) IN (\"publish\", \"write\", \"admin\", \"read\", \"push\", \"api\")) }"""
] ;
sh:sparql [
sh:message "Login, OTP, and recovery entries must not have an entry name."@en ;
sh:select """SELECT $this WHERE { $this :credentialKind ?k ; :entryName ?n . FILTER (?k IN (:login, :otp, :recovery)) }"""
] ;
sh:sparql [
sh:message "Token, key, and PIN entries may have a name; token entries require one."@en ;
sh:select """SELECT $this WHERE { $this :credentialKind :token . FILTER NOT EXISTS { $this :entryName ?n } }"""
] .
:OwnerShape a sh:NodeShape ;
sh:targetClass :Owner ;
sh:prefixes :CommonPrefixes ;
sh:property [ sh:path :ownerClass ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :OwnerClass ] ;
sh:sparql [
sh:message "Every owner must have at least one authorized reader."@en ;
sh:select """SELECT $this WHERE { FILTER NOT EXISTS { $this :authorizedReader ?reader } }"""
] ;
sh:sparql [
sh:message "Project owners identify both a project and an environment."@en ;
sh:select """SELECT $this WHERE { $this :ownerClass :ProjectOwner . FILTER (NOT EXISTS { $this :ownerProject ?p } || NOT EXISTS { $this :ownerEnvironment ?e }) }"""
] ;
sh:sparql [
sh:message "Site owners identify a site."@en ;
sh:select """SELECT $this WHERE { $this :ownerClass :SiteOwner . FILTER NOT EXISTS { $this :ownerSite ?site } }"""
] .
:PersonalDataRecordShape a sh:NodeShape ;
sh:targetClass :PersonalDataRecord ;
sh:prefixes :CommonPrefixes ;
sh:property [ sh:path :personalDataReference ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :SecretReference ] ;
sh:property [ sh:path :subjectOfData ; sh:minCount 1 ; sh:class :DataSubject ] ;
sh:property [ sh:path :dataCategory ; sh:minCount 1 ; sh:class :PersonalDataCategory ] ;
sh:property [ sh:path :dataClassification ; sh:minCount 1 ; sh:maxCount 1 ; sh:class :DataClassification ] ;
sh:property [ sh:path :processedFor ; sh:minCount 1 ; sh:class :Purpose ] ;
sh:property [ sh:path :processedUnder ; sh:minCount 1 ; sh:class :LegalBasis ] ;
sh:property [ sh:path :governedByRetention ; sh:minCount 1 ; sh:class :RetentionRule ] ;
sh:property [ sh:path :partOfActivity ; sh:minCount 1 ; sh:class :ProcessingActivity ] ;
sh:property [ sh:path :accessPolicy ; sh:minCount 1 ; sh:class :AccessPolicy ] ;
sh:property [ sh:path :retentionEndDate ; sh:maxCount 1 ; sh:datatype xsd:date ] ;
sh:property [ sh:path :collectionDate ; sh:maxCount 1 ; sh:datatype xsd:date ] .
:SecretReferenceShape a sh:NodeShape ;
sh:targetClass :SecretReference ;
sh:property [ sh:path :vaultLocator ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ] .
#################################################################
# Modeling guidance
#################################################################
[] a owl:AllDifferent ; owl:distinctMembers ( :private :staged :outward ) .
[] a owl:AllDifferent ; owl:distinctMembers ( :held :wanted :elsewhere :dropped ) .
[] a owl:AllDifferent ; owl:distinctMembers ( :login :otp :recovery :token :key :pin ) .
:PersonalDataRecord rdfs:comment "Recommended categories include account identifier, contact detail, employment attribute, device identifier, authentication metadata, and sensitive-data category as applicable. Define local category IRIs for the enterprise's policy. Store values only in the governed source, not in RDF."@en .
:CredentialEntry rdfs:comment "Path convention from the source specification: me/realm/handle/kind[/name], agent/name/realm/handle/kind[/name], role/name/realm/handle/kind[/name], proj/project/environment/realm/handle/kind[/name], site/name/realm/handle/kind[/name]. Token name is mandatory and is the holder; purpose belongs in scope."@en .
:CredentialEntry rdfs:comment "Rotation and revocation are complete only after the provider has been checked; log a CredentialRotation or CredentialRevocation event with verificationOutcome. Do not log secret values. Recovery-code spending is by code value inside the vault, never by list position."@en .