key-cape/workplans/KEY-WP-0021-snapshot-attribute-validation.md
tegwick ebc48e31ae chore(consistency): register KEY-WP-0021 [auto]
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:23:38 +02:00

101 lines
4.3 KiB
Markdown

---
id: KEY-WP-0021
type: workplan
title: "Make snapshot attribute validation enforce a real rule"
domain: infotech
repo: key-cape
status: finished
owner: claude
topic_slug: snapshot-attribute-validation
created: "2026-09-07"
updated: "2026-09-07"
state_hub_workstream_id: "733ad1a5-c4d8-5c2f-8608-c51bf1d5b17e"
---
Closes gap G06 of `history/2026-09-05-011726-scope-intent-assessment.md`:
`checkNoUnknownAttributes` is named for a rule it does not implement. It rejects
blank keys, which is not an allow-list, so a snapshot carrying arbitrary or
malformed LDAP attribute names passes a check whose name says otherwise. A rule
that passes for the wrong reason is worse than an absent one, because a report
listing `no_unknown_attributes: passed` is read as evidence.
One part of the assessment's description needs correcting rather than
implementing: it says group membership validation checks nonempty member IDs
rather than complete referenced identity existence. `checkValidGroupMemberships`
does only check emptiness, but the semantic rule `checkReferencedUsersExist`
already resolves every member against the user set and fails on an unknown one.
The reference constraint exists; it lives in a different rule than the one the
assessment looked at.
## Enforce real attribute constraints
```task
id: KEY-WP-0021-T01
status: done
priority: medium
state_hub_task_id: "e2921983-853c-501b-8dfc-158751dea093"
```
`ldapAttributes` is defined by the canonical model as raw attributes *not covered
by* the model, so an allow-list of permitted names cannot be derived from the
schema — the field exists precisely to carry what the schema does not name. Two
constraints are derivable and worth enforcing:
- the attribute name must be a syntactically valid LDAP attribute descriptor
(RFC 4512: a letter followed by letters, digits or hyphens, or a numeric OID),
since a name that cannot be written to a directory cannot round-trip; and
- it must not shadow a canonical field's documented LDAP mapping (`uid`, `cn`,
`mail`), because two sources for one value is how an export and a directory
come to disagree, and the model already owns those.
Attribute descriptors are case-insensitive in LDAP, so keys differing only by
case are a collision, not two attributes. Keep the blank-key check. Rename the
rule to describe what it actually enforces rather than leaving a name that
overpromises.
Implemented as `raw_attributes_well_formed`, replacing `no_unknown_attributes`
in both `validator.go` and `spec/ldap-schema.yaml`, with the schema entry stating
plainly that this is not an allow-list and why one cannot be derived.
## Prove each rule with an invalid snapshot
```task
id: KEY-WP-0021-T02
status: done
priority: medium
state_hub_task_id: "de4caf0a-39c6-5148-a986-db18a1308505"
```
The assessment asks for invalid-snapshot cases proving each stated rule. Add one
per constraint — malformed descriptor, shadowed canonical mapping, case-only
duplicate, blank key — plus passing cases for legitimate raw attributes and a
valid numeric OID, so the rule is shown to be selective rather than merely
strict.
Eleven malformed-descriptor cases, six canonical-mapping cases across casings, a
case-only duplicate, a message-content case, and five accepted-attribute cases.
Confirmed they test the rule rather than merely passing: making the check return
early fails four of the five tests.
## Reconcile the records
```task
id: KEY-WP-0021-T03
status: done
priority: medium
state_hub_task_id: "8a48c6cc-1e7e-5fd4-abfa-2b893c3d400d"
```
Update `SCOPE.md` and G06's status. State what the rule now enforces and, just as
importantly, what it still does not: this is not schema-derived validation of
every attribute against a directory schema, and the canonical model cannot
provide one for a field defined as everything the model does not cover. Correct
the assessment's reference-constraint premise rather than restating it.
`history/…-scope-intent-assessment.md` was being edited by a concurrent session
when this workplan was written; make this edit only against a clean tree.
Done against a clean tree after the other session committed. G06's status records
both corrections: no allow-list is derivable for a field defined as what the
model does not cover, and the reference constraint was already implemented in
`checkReferencedUsersExist` rather than missing.