version: "0.1" description: > Canonical Identity Model for KeyCape / NetKingdom IAM Profile. This file is the reviewed contract for identity entities. The runtime authority is src/internal/domain/model.go: the server reads Go structs, not this YAML. The two are held together by an executable conformance check (src/internal/domain/conformance_test.go, KEY-WP-0017), which fails the build when a runtime field has no entry here or an entry here is not read by the runtime. So: change the Go model and this file together. A field declared here but deliberately not implemented must carry `runtime: false`, which is how the check tells a reserved field apart from a forgotten one. entities: User: description: "A person or service account in the identity directory." fields: id: type: string required: true description: "Stable internal identifier. Immutable after creation." username: type: string required: true description: "Unique login name. Maps to LDAP uid." displayName: type: string required: true description: "Human-readable full name. Maps to LDAP cn." email: type: string required: false format: email description: "Primary email address. Maps to LDAP mail." enabled: type: boolean required: true description: "Whether the account is active." tenant: type: string required: false description: > NetKingdom IAM Profile tenant claim for this user (e.g. tenant:friendly:binky). Absent falls back to the platform tenant rather than emitting an empty claim, since the profile requires a tenant on every token. groups: type: array items: type: string ref: Group.id description: "Group memberships by group ID." roles: type: array items: type: string ref: Role.id description: "Role assignments by role ID." mfaEnrollment: type: object ref: MFAEnrollment nullable: true description: "MFA enrollment record if the user has enrolled." ldapAttributes: type: object additionalProperties: true description: "Raw LDAP attributes not covered by the canonical model." Group: description: "A named collection of users." fields: id: type: string required: true description: "Stable internal identifier." name: type: string required: true description: "Unique group name. Maps to LDAP cn." description: type: string required: false description: "Human-readable description." members: type: array items: type: string ref: User.id description: "User IDs belonging to this group." Role: description: "A named permission set assigned to users." fields: id: type: string required: true description: "Stable internal identifier." name: type: string required: true description: "Unique role name." description: type: string required: false description: "Human-readable description." Client: description: "A registered OIDC client. Registration is static in v0.1." fields: clientId: type: string required: true description: "OAuth2 client_id." displayName: type: string required: true description: "Human-readable client name." redirectUris: type: array items: type: string format: uri required: false minItems: 1 description: > Allowed redirect URIs. Wildcards are NEVER permitted. Required for any client using the authorization_code grant; a client_credentials-only client registers none. allowedScopes: type: array items: type: string required: true description: "Scopes this client may request." grantTypes: type: array items: type: string enum: [authorization_code, client_credentials] required: true description: > Allowed OAuth2 grant types. An empty or absent value means authorization_code, which is what config validation assumes. clientType: type: string enum: [confidential, public] required: true description: "confidential = server-side app; public = SPA or native." secretRef: type: string nullable: true description: "Reference to the client secret (confidential clients only)." audience: type: string nullable: true description: > Static resource audience for access tokens. ID tokens keep the client audience. Absent means the access-token audience is the client ID. serviceSubject: type: string nullable: true description: > Subject claim for client_credentials tokens (e.g. service:secrets-engine). Required together with tenant for that grant. tenant: type: string nullable: true description: > Tenant claim emitted for this client's service tokens. Bound at registration and never influenced by request parameters; see docs/tenant-claim-contract.md. roles: type: array items: type: string description: "Role claims emitted for this client's service tokens." mfaRequired: type: boolean nullable: true description: > Per-client MFA requirement. Absent means the provider default (mandatory MFA); false lowers ordinary login to AAL1 while acr_values can still force step-up. registrationUrl: type: string format: uri nullable: true description: "Registration handoff target for unknown subjects." enrollmentUrl: type: string format: uri nullable: true description: "Factor-enrollment handoff target for unenrolled subjects." tokenProfile: type: string runtime: false description: > Reserved. Declared for future token configuration profiles; the runtime does not read it. environments: type: array items: type: string runtime: false description: > Reserved. Environments this client is registered for (e.g. prod, staging); the runtime does not read it. Membership: description: "Explicit link between a user and a group." fields: userId: type: string required: true ref: User.id groupId: type: string required: true ref: Group.id MFAEnrollment: description: "Records MFA enrollment state for a user via privacyIDEA." fields: userId: type: string required: true ref: User.id provider: type: string required: true enum: [privacyidea] description: "MFA provider. Only privacyidea is supported in v0.1." state: type: string required: true enum: [enabled, disabled, pending] description: "Current enrollment state." enrolledAt: type: string format: datetime description: "ISO 8601 timestamp of enrollment."