feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
version: "0.1"
|
|
|
|
|
description: >
|
|
|
|
|
Canonical LDAP Schema for KeyCape / NetKingdom IAM Profile.
|
|
|
|
|
Expresses the canonical identity model in LDAP terms.
|
|
|
|
|
Portable across LLDAP, OpenLDAP, 389DS, and Active Directory.
|
|
|
|
|
|
|
|
|
|
base_dn: "dc=netkingdom,dc=local"
|
|
|
|
|
|
|
|
|
|
organization_units:
|
|
|
|
|
users:
|
|
|
|
|
dn: "ou=users,dc=netkingdom,dc=local"
|
|
|
|
|
description: "User accounts"
|
|
|
|
|
object_classes:
|
|
|
|
|
required:
|
|
|
|
|
- inetOrgPerson
|
|
|
|
|
- organizationalPerson
|
|
|
|
|
- person
|
|
|
|
|
- top
|
|
|
|
|
attributes:
|
|
|
|
|
required:
|
|
|
|
|
- uid # canonical: username
|
|
|
|
|
- cn # canonical: displayName
|
|
|
|
|
- sn # canonical: surname (may be set to displayName if absent)
|
|
|
|
|
optional:
|
|
|
|
|
- mail # canonical: email
|
|
|
|
|
- memberOf # back-reference to group membership
|
|
|
|
|
forbidden: []
|
|
|
|
|
naming_attr: uid
|
|
|
|
|
examples:
|
|
|
|
|
- dn: "uid=alice,ou=users,dc=netkingdom,dc=local"
|
|
|
|
|
uid: alice
|
|
|
|
|
cn: "Alice Example"
|
|
|
|
|
sn: Example
|
|
|
|
|
mail: alice@example.com
|
|
|
|
|
|
|
|
|
|
groups:
|
|
|
|
|
dn: "ou=groups,dc=netkingdom,dc=local"
|
|
|
|
|
description: "User groups"
|
|
|
|
|
object_classes:
|
|
|
|
|
required:
|
|
|
|
|
- groupOfNames
|
|
|
|
|
- top
|
|
|
|
|
attributes:
|
|
|
|
|
required:
|
|
|
|
|
- cn # canonical: name
|
|
|
|
|
- member # list of member DNs
|
|
|
|
|
optional:
|
|
|
|
|
- description
|
|
|
|
|
forbidden: []
|
|
|
|
|
naming_attr: cn
|
|
|
|
|
examples:
|
|
|
|
|
- dn: "cn=admins,ou=groups,dc=netkingdom,dc=local"
|
|
|
|
|
cn: admins
|
|
|
|
|
member:
|
|
|
|
|
- "uid=alice,ou=users,dc=netkingdom,dc=local"
|
|
|
|
|
|
|
|
|
|
clients:
|
|
|
|
|
dn: "ou=clients,dc=netkingdom,dc=local"
|
|
|
|
|
description: "OIDC client registrations"
|
|
|
|
|
object_classes:
|
|
|
|
|
required:
|
|
|
|
|
- inetOrgPerson
|
|
|
|
|
- top
|
|
|
|
|
attributes:
|
|
|
|
|
required:
|
|
|
|
|
- uid # canonical: clientId
|
|
|
|
|
- cn # canonical: displayName
|
|
|
|
|
optional:
|
|
|
|
|
- description
|
|
|
|
|
forbidden: []
|
|
|
|
|
naming_attr: uid
|
|
|
|
|
|
|
|
|
|
validation_rules:
|
|
|
|
|
structural:
|
|
|
|
|
- name: valid_dn_structure
|
|
|
|
|
description: "All DNs must conform to the base_dn and OU layout above."
|
|
|
|
|
- name: required_attributes_present
|
|
|
|
|
description: "Every entry must carry all required attributes for its OU."
|
Make snapshot attribute validation enforce a real rule
Closes gap G06. checkNoUnknownAttributes was named for a rule it did not
implement: it rejected blank keys, which is not an allow-list, so a snapshot
carrying malformed or shadowing attribute names passed a check whose name said
otherwise. A rule that passes for the wrong reason is worse than an absent one,
because the report is read as evidence.
raw_attributes_well_formed requires each key to be a valid LDAP attribute
descriptor, forbids shadowing an attribute the canonical model owns (uid, cn,
mail) in any casing, and rejects keys differing only by case, which LDAP treats
as one attribute. spec/ldap-schema.yaml carries the same wording.
Two corrections to the gap's description rather than implementations of it. An
allow-list is not derivable: ldapAttributes is defined as what the canonical
model does not cover, so the schema cannot enumerate what may appear there. And
the reference constraint already existed -- checkValidGroupMemberships only
checks emptiness, but the semantic rule checkReferencedUsersExist resolves every
member against the user set.
Neutering the rule fails four of the five new tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-07 23:22:55 +02:00
|
|
|
- name: raw_attributes_well_formed
|
|
|
|
|
description: >
|
|
|
|
|
Raw ldapAttributes keys must be valid LDAP attribute descriptors, must
|
|
|
|
|
not shadow an attribute the canonical model owns (uid, cn, mail), and
|
|
|
|
|
must not repeat one attribute under different casing. This is not an
|
|
|
|
|
allow-list: ldapAttributes carries what the canonical model does not
|
|
|
|
|
name, so no permitted-name set can be derived from the schema.
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
- name: valid_group_memberships
|
|
|
|
|
description: "All member values must be non-empty valid DNs."
|
|
|
|
|
semantic:
|
|
|
|
|
- name: referenced_users_exist
|
|
|
|
|
description: "Every user ID referenced in group members must exist."
|
|
|
|
|
- name: no_cyclic_groups
|
|
|
|
|
description: "Groups may not contain other group IDs as members."
|
|
|
|
|
- name: usernames_unique
|
|
|
|
|
description: "The uid attribute must be unique across ou=users."
|
|
|
|
|
- name: email_format_valid
|
|
|
|
|
description: "mail, when present, must be a valid RFC 5322 address."
|