Make the LLDAP export report its own completeness
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 31s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 31s
The exporter discovered groups by walking each user's memberships, so a group nobody belongs to never reached the snapshot, and a failed lookup was skipped by a `continue` under a comment claiming it was recorded in the incompatibility report. The run then emitted `result: "success"`. Add an optional `domain.GroupLister` capability and implement `ListGroups` on the LLDAP adapter as a direct group-subtree search, kept off `UserRepository` because the OIDC layer never enumerates the directory. Record `groupEnumeration` on every result and a `Complete()` predicate over it; abort rather than write a smaller snapshot when the enumeration fails; report a failed per-user lookup on the fallback path; emit `partial` telemetry and name the mode from the CLI. Reading the adapter to write this surfaced a defect the assessment had not listed: `LookupGroups` never populated `Group.Members`, and the exporter built every membership from that field, so against a real directory the `memberships` block was always empty while the fixture-backed tests passed. Memberships on the fallback path now come from the user/group pair actually observed. Sort users, groups and memberships so an unchanged directory exports identically. Closes G05 of the scope/intent assessment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WAsfsfQmDu4vcBhiMcmQp Assistant: claude-code Assistant-Model: opus Assistant-Process: 867844@bnt-lap001 Assistant-Session: 3d45905e-0016-4b49-b828-231406881f7b
This commit is contained in:
parent
7fe5bccc7c
commit
f7dd51b8d2
9 changed files with 538 additions and 43 deletions
100
workplans/KEY-WP-0018-export-completeness-evidence.md
Normal file
100
workplans/KEY-WP-0018-export-completeness-evidence.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
id: KEY-WP-0018
|
||||
type: workplan
|
||||
title: "Make the LLDAP export report its own completeness"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: export-completeness-evidence
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
---
|
||||
|
||||
Closes gap G05 of `history/2026-09-05-011726-scope-intent-assessment.md`.
|
||||
|
||||
`src/internal/migration/lldapexport/exporter.go` discovers groups by walking each
|
||||
user's memberships, so a group nobody belongs to is absent from the snapshot and
|
||||
nothing says so. A `LookupGroups` failure is skipped by a `continue` under a
|
||||
comment claiming it is recorded in the incompatibility report — it is not. The
|
||||
run then emits `result: "success"` and the CLI reports a clean export. Group
|
||||
order comes from map iteration, so two exports of an unchanged directory differ.
|
||||
|
||||
A second defect falls out of reading the adapter: `LDAPAdapter.LookupGroups`
|
||||
never populates `Group.Members`, and the exporter builds every membership from
|
||||
that field. Against a real LLDAP directory the `memberships` block is therefore
|
||||
always empty, while the fixture-based tests pass because the mock fills it in.
|
||||
|
||||
The unit of work is the evidence, not the enumeration: an export that cannot
|
||||
prove it saw the whole directory must say which one it is.
|
||||
|
||||
## Enumerate groups independently of membership
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T01
|
||||
status: done
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Add an optional `domain.GroupLister` capability (`ListGroups`) and implement it
|
||||
on `LDAPAdapter` with a direct group-subtree search that reads `member` and
|
||||
`uniqueMember`, so `Group.Members` is populated from the directory rather than
|
||||
left empty. Keep it optional rather than widening `UserRepository`: the OIDC
|
||||
layer has no use for it, and every existing implementation and test double would
|
||||
otherwise have to grow a method it never calls.
|
||||
|
||||
Added `domain.GroupLister` and `LDAPAdapter.ListGroups`, searching the group
|
||||
subtree for `groupOfNames`/`groupOfUniqueNames` and reading `member` with
|
||||
`uniqueMember` as the fallback attribute. Compile-time assertions on the adapter
|
||||
now state both contracts it satisfies. The adapter tests assert the filter is
|
||||
*not* a membership filter and that a member-less group survives the mapping,
|
||||
which is the property the whole task exists for.
|
||||
|
||||
## Report or fail on incomplete reads
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T02
|
||||
status: done
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Record on the result which enumeration actually ran — a complete directory
|
||||
enumeration or the membership-derived fallback — and treat a failed group
|
||||
enumeration as fatal rather than as a silent gap. In the fallback path, record
|
||||
each skipped user in the incompatibility report instead of discarding the error,
|
||||
and derive memberships from the discovered user/group pairs. Emit `partial`
|
||||
telemetry, and have the CLI say which enumeration produced the snapshot, so a
|
||||
degraded export is legible without reading the YAML.
|
||||
|
||||
`ExportResult` gained `groupEnumeration` — written unconditionally, so nobody has
|
||||
to infer completeness from an absent field — and a `Complete()` predicate that
|
||||
requires both a directory enumeration and an empty report. The fallback path
|
||||
records its own incompleteness as a report entry before it starts, so the
|
||||
snapshot says so even when every user lookup succeeds.
|
||||
|
||||
The fallback also had to stop deriving memberships from `Group.Members`: the
|
||||
LLDAP adapter never populated that field, so against a real directory the
|
||||
`memberships` block was always empty while the fixture-backed tests passed. It
|
||||
now derives each membership from the user/group pair actually observed.
|
||||
|
||||
## Define ordering and test the failure modes
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T03
|
||||
status: done
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Sort users, groups and memberships on stable keys so an unchanged directory
|
||||
exports byte-identically. Cover the cases the assessment names and the current
|
||||
suite does not: an empty group, a group enumeration failure, a per-user lookup
|
||||
failure in the fallback path, and repeat-run determinism.
|
||||
|
||||
Sorted users and groups by ID and memberships by group then user. Four exporter
|
||||
tests and two adapter tests cover the named cases; the determinism test runs the
|
||||
export three times over deliberately unsorted input and compares the full
|
||||
ordering, rather than asserting on a single sorted field.
|
||||
|
||||
Recorded in `SCOPE.md` and in G05's status what this does and does not establish:
|
||||
completeness evidence for the user/group/membership surface, not for credential
|
||||
migration, which stays under G03.
|
||||
Loading…
Add table
Add a link
Reference in a new issue