feat(users): resolve users by name, email or username wherever a user UUID was required - #141
feat(users): resolve users by name, email or username wherever a user UUID was required#141betty-pr-factory-app[bot] wants to merge 3 commits into
Conversation
…DEV-216562 Move pickMatch, namedRef and suggestionSuffix out of search_asset_keyword into a new shared pkg/tools/resolve package (following the pkg/tools/validation precedent) and extend user resolution to display names. clients.FindUsersByName returns every enabled user matching a name, sending nameSearchFields and includeDisabled explicitly instead of relying on the server defaults; the shared resolver keeps the existing precedence (UUID, exact email endpoint, exact username) and only then falls through to display-name candidates. An ambiguous name stays an error listing every candidate with its UUID; email addresses are never surfaced. New read-only get_user_id_by_name tool, registered without an experimental flag: the tools it feeds (create_assessment, edit_assessment, edit_asset, search_asset_keyword) are themselves ungated, so gating the resolver would ship the broken half-a-pair that TOOL_CONTRIBUTION_STANDARDS 1.1 warns about. Assumptions recorded: GROUP assignees still require a UUID (the user lookup cannot resolve group names), and search_asset_keyword's createdByFilter keeps its parameter-specific disambiguation wording via resolve.Hints. Co-authored-by: andrew.berkow@collibra.com <andrew.berkow@collibra.com> test(users): cover name resolution across the tools that take a user DEV-216562 Adds tests for the shared resolver (full name, ambiguity, not-found wording, UUID pass-through, email endpoint, username precedence, explicit nameSearchFields/includeDisabled) and for each consumer: edit_asset responsibilities, both assessment tools, search_asset_keyword's createdByFilter and data quality notification recipients. Corrects the /rest/2.0/users test double in edit_asset, which matched a three-field OR over username, first name and last name: under it no two-word name could ever match, so it encoded an API that does not exist. It now mirrors the endpoint's nameSearchFields, including the FIRSTNAME_LASTNAME and LASTNAME_FIRSTNAME concatenations. Co-authored-by: andrew.berkow@collibra.com <andrew.berkow@collibra.com> refactor(clients): drop FindUserByUsername now every caller resolves by name DEV-216562 Every call site went through the shared resolver or the notification recipient helper, both of which need the full candidate list from one request; keeping a second entry point that re-issues the same search and collapses it to one row would only invite drift. The exact-username scan it existed for stays, as exactUsernameMatch. Co-authored-by: andrew.berkow@collibra.com <andrew.berkow@collibra.com>
There was a problem hiding this comment.
Review
Reviewed at 9a9fa26 against main, with docs/TOOL_CONTRIBUTION_STANDARDS.md as the governing document. There were no findings from a previous round to re-judge (.pesto/inputs/review_findings/ was absent), so this is a first pass.
The shape of the change is right: one shared resolver, one precedence chain, every user-valued parameter routed through it, and a wide-but-mechanical move of pickMatch into pkg/tools/resolve following the pkg/tools/validation precedent. The tests are the strong part — they pin the precedence, the ambiguity contract and the "nothing is written" guarantee, and the correction to the /rest/2.0/users double in edit_asset fixes a mock that encoded an API which does not exist.
What I ran here (a constrained sandbox — no repo-wide runs, no -race, no lint):
go build ./... # clean
go vet ./pkg/tools/resolve/... ./pkg/tools/get_user_id_by_name/... \
./pkg/clients/... ./pkg/tools/edit_asset/... # clean
go test ./pkg/tools/resolve/... ./pkg/tools/get_user_id_by_name/... \
./pkg/tools/create_assessment/... ./pkg/tools/edit_assessment/... # ok
go test ./pkg/tools/edit_asset/... ./pkg/tools/search_asset_keyword/... \
./pkg/clients/... ./pkg/tools/ # ok
gofmt -l <every touched package> # no output
go test ./..., -race and golangci-lint were not run in this environment; CI is the check for those.
The four decisions, verified
get_user_id_by_nameregistered with no experimental flag (deviation from §3.1). Done as described.pkg/tools/register.go:102-108registers it outside everyIsExperimentalEnabledblock with the rationale in a comment citing §3.1 and §1.1, and the consumers it feeds are ungated in the same block.register_test.go:32-39asserts it is visible with an emptyServerToolConfig. Nothing was added tocmd/chip/experimental.go, which is correct — there is no flag to register. §5.3 annotations are set explicitly.- Ambiguity is an error carrying candidates, not a success payload. Done as described.
PickMatchreturns an error for more than one exact match;get_user_id_by_namereturnsOutput{}plus the error, andOutputhas nocandidatesfield to tempt a pick. The write paths abort before writing:edit_assessmentfails the op and skips the atomic PATCH,edit_assetreturns a per-op error and creates no responsibility (asserted byTestEditAsset_SetResponsibility_AmbiguousFullNameWritesNothing). The one exception — notification recipients falling toUnresolved— is appropriate for a best-effort list, but see finding 2 about telling the model which of the two failures it hit. - Email in, never out. Done as described for directory data:
resolve.Usercarries only ID/username/full name,toUserdrops the address, candidates are labelledusername:, and three tests assert no@in the error text. One boundary worth knowing rather than fixing:edit_assessmentechoes the caller's ownset_ownerinput intoresults[].value, andedit_assetechoes it intoresults[].userId, so an email the model itself supplied can come back in the payload. That is input echo, not disclosure, and it pre-dates this PR inedit_asset. pickMatchmove is a move. Confirmed. Old and new map 1:1 — same normalize-and-compare matching, same three outcomes, byte-identical message formats, same sort and max-15 truncation. The only semantic delta isparambecomingresolve.Hints, withfilterHints(param)reproducing the old "pass the UUID in<param>to disambiguate" wording verbatim. The other five filters are pure identifier renames, every pre-existingsearch_asset_keywordtest is unchanged in the diff, and they pass.
What should change before merge
1. edit_asset's tool description was left behind (§7.1, §7.3, §6.2). pkg/tools/edit_asset/tool.go:161 still reads "set_responsibility (… the user can be given as a UUID, username, or email)". The field tag, README.md and asset-edit/SKILL.md all learned about full names; the description — the text the model actually reads to choose the tool, and the surface §7 governs — did not.
2. The DQ job tools' notifyRecipients descriptions are now stale (§6.2, §7.1). This PR extended ResolveNotificationRecipients to display names, and create_dq_job/tool.go:353 and update_dq_job/tool.go:465 both call it, but their field tags still say "by username or email" (create_dq_job/tool.go:130, update_dq_job/tool.go:105). Two things go undocumented: full names now work, and an ambiguous full name lands in Unresolved indistinguishably from a typo — so the model is told "unresolved" with nothing to correct against (§6.3). findRecipientByName already knows which case it hit; carrying the reason through would be cheap.
3. A truncated user search can silently bind the wrong person. FindUsersByName sends limit=100 and listUsers discards page.Total (edit_asset_client.go:826-852) — editAssetUsersList parses Total and nobody reads it. Under the old code the only consumer was an exact-username scan and usernames are unique, so truncation could at worst yield a not-found. Now the same truncated window feeds display-name matching: if a substring search matches more than 100 users and exactly one of the first 100 has the exact full name, PickMatch reports a confident single match while a second holder sits outside the window, and edit_asset/edit_assessment write to that person. The probability is low — it needs a >100-hit substring and an exact-name collision across the boundary — but the outcome is precisely the silent wrong write the package comment promises never to make. Comparing Total against len(Results) and turning truncation into a self-correcting error ("more than 100 matches; narrow the name or pass the UUID") closes it.
4. includeDisabled=false is invisible to the model (§6.3, §7.5). A deactivated leaver can never resolve by name or username now, but userNotFoundHint lists only the accepted input forms and get_user_id_by_name's description doesn't mention enabled-only. The model will retry name variants forever. Worth one clause in the description and in the not-found hint. Related: the email path goes to /rest/2.0/users/email/{email}, which isn't filtered for enabled state in this code — so the same person may resolve by email but not by name. Whichever way that endpoint behaves, say so.
5. Nothing cites where the nameSearchFields contract came from (§8.2). Five enum values plus includeDisabled are hard-coded with a comment asserting they are "the endpoint's own defaults", and every test is a double that mirrors that assumption, so it cannot fail in CI. The string appears nowhere else in the repo, and neither the commit message nor the PR body names a spec or a deployment. If DGC rejects an unknown enum value, or wants a comma-separated list rather than repeated params, this turns username and email resolution — which works in production today through edit_asset — into a 400 on every call. §8.2 asks for the source and the version verified against, in the comment and the PR body. If that can't be obtained before merge, relying on the defaults the comment says these already are is the lower-risk shape.
Smaller points, take or leave
resolveAssignees(both assessment tools) resolves USER entries as it walks the list, so a bad GROUP UUID later in the list is only reported after the earlier lookups have gone out — §6.1 asks for validation before any network call. Nothing is written either way; this is request cost and error ordering.resolveUsersinsearch_asset_keyworddrops theresolving <label> "<value>":prefix its five sibling filters carry (§6.5). The value still appears in the resolver's own message.get_user_id_by_nameechoes a UUID back unverified, so on that path it returns nofullName/usernameto confirm against and will happily echo an asset UUID as auserId. Right for the mid-chain resolver; for the standalone lookup tool aGET /rest/2.0/users/{id}on the UUID path would make the confirmation story work. TheOutputtag documents the gap honestly, so this is a design call.notFoundError's "Valid users available: …" label is accurate for the enumerable sets (status, domain type) but overstates a fuzzy user search, where the list is up to 15 near-miss display names. "Closest matches:" would read truer.- A blank user reference errors with "a user is required" without naming which parameter —
ownerId,userId,assignees[2].idorname(§6.3).create_assessmentalready wraps withassignees[%d]:; the others don't.
Not raised
search_asset_keyword's own one-line description is well short of §7.1, but it is pre-existing and outside this change's scope — the createdByFilter tag it touched is compliant. Empty Permissions on the new tool matches every other tool needing no specific DGC permission. And the three-way split in how an ambiguous user is treated across the surfaces is justified by their different contracts and documented at both sites.
Review round 1. Behaviour:
- FindUsersByName now returns a UserSearch carrying the reported total, so a
truncated page is visible. Display-name reduction over a truncated window
could call a name unambiguous while a second holder sat outside the 100-row
page, which is the silent wrong write this design promises never to make;
the resolver now errors with the total and how to proceed, and notification
recipients land in the unresolved bucket. An exact username is unaffected —
usernames are unique, so the window cannot hide a second holder.
- RecipientResolution gains Ambiguous, the subset of Unresolved that matched
several accounts, and clients.UnresolvedRecipientsMessage renders the two
cases apart: "no active account" is simply wrong advice for a shared name.
Both DQ job tools use it.
- Assignee lists are fully validated before any user lookup (standards 6.1),
and edit_assessment's set_owner now reports the resolved UUID rather than
echoing the caller's input.
- createdByFilter failures are wrapped as "resolving user %q" like the five
sibling filters, and the not-found candidate list for users is labelled
"Closest matches" rather than claiming the near misses are the valid values.
Documentation, where code and prose had diverged:
- edit_asset's tool Description named only UUID/username/email for
set_responsibility; both DQ tools' notifyRecipients tags named only
username/email. Both now name the full-name form and what happens to a
shared name.
- The not-found hint, get_user_id_by_name's description and the README now
state that only enabled accounts are searched by name or username.
- userNameSearchFields cites its contract source (DGC Core REST API v2
documentation for GET /rest/2.0/users) and records honestly that it was NOT
verified against a live instance from this environment and that a
producing-service contract test is owed per standards 8.3.
Assumption recorded: get_user_id_by_name still echoes a UUID back without a
directory read, so AC-4 ("a UUID resolves with no user-lookup request issued")
continues to hold for every caller; the reviewer's optional suggestion to
verify UUIDs there was declined for that reason.
Co-authored-by: andrew.berkow@collibra.com <andrew.berkow@collibra.com>
There was a problem hiding this comment.
Review
Reviewed pesto/task-1b73776b (2 commits, b453e5b) against main (cfe51c9), with docs/TOOL_CONTRIBUTION_STANDARDS.md as the governing document.
This is good work. The shared pkg/tools/resolve package is the right shape for the problem, the "never pick a person for the model" invariant is enforced in code rather than in prose, and the test suite actually pins the behaviour that matters — precedence, ambiguity, truncation, and that no write happens when resolution fails. create_assessment and edit_assessment gained their first test files in the process, which is a bonus.
Two things need changing before merge, both small and localised. Everything else below is either a convention cleanup or a note confirming something was done as intended.
Verified here
go build ./... pass
go vet (resolve, get_user_id_by_name, clients, search_asset_keyword) pass
go test resolve, get_user_id_by_name, clients ok 0.018s / 0.012s / 0.061s
go test search_asset_keyword, edit_asset,
create_assessment, edit_assessment, pkg/tools ok 0.062s / 0.181s / 0.032s / 0.058s / 0.296s
Not verified: no Collibra instance was reachable from the review environment, so the /rest/2.0/users contract this change leans on — the meaning of total, the nameSearchFields enum, includeDisabled, and whether GET /rest/2.0/users/email/{email} also returns deactivated accounts — was not exercised against a live deployment. The PR says so itself in the package comment (pkg/clients/edit_asset_client.go:742-759) and flags the §8.3 contract test as owed; that is the right way to handle it, and I'm noting it rather than raising it.
1. get_user_id_by_name's description has no "when not to use it" — and it cannot resolve a user group (§7.1 point 3, §7.3, §6.3)
pkg/tools/get_user_id_by_name/tool.go:37-48
The description is strong on six of the seven §7.1 points — what it does, ordering, key parameter, what it returns, side effects, and four example questions including a vague one. What's missing is the boundary: it never says what the tool is not for, and never names the neighbour it will be confused with.
That matters concretely, because the tool resolves users only while every tool it feeds also takes groups:
create_assessment/edit_assessmentrequire aGROUPassignee's UUID, and nothing in CHIP resolves a group name.edit_asset'suserIdtag says the role is assigned to "the user (or user group)".
So a model asked "make the Data Stewards group the owner" will call get_user_id_by_name("Data Stewards") and get back userNotFoundHint, which lists four user forms and says nothing about groups being out of scope or where a group UUID comes from. It will most likely retry name variants.
Second half of the same gap: the description never mentions search_asset_keyword as the way to find a person this lookup can't resolve — even though pkg/tools/resolve/user.go:106 tells the model to do exactly that on a truncated search. §7.3 asks the description to stand alone.
Please add a clause to the Description saying it resolves users only, that a user group is not resolvable by name and must be given as its UUID, and that search_asset_keyword with resourceTypeFilters: ["User"] (or ["UserGroup"]) is where to go when this lookup can't help. And add the group carve-out to resolve.userNotFoundHint (pkg/tools/resolve/user.go:26) so the error is self-correcting too, not just the tool description — resourceTypeFilters does support both values (search_asset_keyword/tool.go:17), so the advice is real.
2. The truncation guard is only half-applied in dq_notifications (§6.5, §6.3)
pkg/clients/dq_notifications.go:224-245
resolve.userByName refuses to resolve by display name whenever the page is truncated, regardless of what happens to be inside the window (pkg/tools/resolve/user.go:104-108) — correct, and the reasoning in that comment is exactly right. findRecipientByName only reaches the same conclusion when it already found a display-name match:
if match != nil && search.Truncated {
return nil, true, nil
}When the page is truncated and no exact display-name match happens to fall inside the window, it returns (nil, false, nil). The recipient then lands in Unresolved but not Ambiguous, so UnresolvedRecipientsMessage tells the user:
These notification recipients have no active Collibra account: Smith.
which is not something we know — the window was truncated, so nothing was learned about accounts outside it. It also sends the user hunting for a typo instead of telling them to pass a username or email. Reachable for any recipient string that partial-matches more than userSearchLimit (100) accounts, i.e. a common surname.
The fix is to hoist the check above the display-name scan, right after the exactUsernameMatch early return (which is safe to keep first — usernames are unique, so truncation can't hide a second holder):
if u := exactUsernameMatch(search.Users, name); u != nil {
return u, false, nil
}
if search.Truncated {
return nil, true, nil
}…and drop the now-redundant trailing check. Worth a test mirroring TestUserRef_TruncatedSearchDoesNotResolveByName: a total=250 page whose users' FullName() doesn't equal the recipient, asserting it lands in Ambiguous. Since Ambiguous would then cover "too many matches to be sure" as well as "two people share it", the shared-name sentence in UnresolvedRecipientsMessage may want slightly broader wording.
Minor — happy for these to land in the same pass or not at all
Stepdown rule (AGENTS.md, cited from §-line 11 of the standards). New helpers were inserted ahead of pre-existing ones that the handler calls earlier. In create_assessment/tool.go the handler calls resolveTemplateID (85) → resolveOwnerID (92) → resolveAssignees (96) but they're defined 130 / 144 / 179; in edit_assessment/tool.go resolveAssessmentID is called first (156) and now defined at 341, behind the new resolveAssignees (309). Pure movement.
Leftover duplicate. resolve.SuggestionSuffix is now exported and the package doc justifies itself as "one small shared package rather than a copy per tool" — but pkg/tools/edit_asset/tool.go:33 still holds a byte-identical private copy with the same signature, in a package that already imports resolve. Its five call sites in operations.go could point at the shared one. (create_asset/tool.go:633 is a different two-arg variant — out of scope.)
The four deliberate decisions — all four done as described
No experimental flag on get_user_id_by_name. Registered outside every gate at register.go:100-108, with a comment naming §3.1 and the §1.1 half-a-pair reasoning. The premise holds: create_assessment, edit_assessment, edit_asset and search_asset_keyword are all registered unconditionally in the same run; the file's only gated block is ContextSpecificationsFeature. cmd/chip/experimental.go is untouched, which is right — §3.3 governs a flag that exists. register_test.go:31-40 asserts visibility with an empty config, and TestRegisterAll_AllToolsHaveProperAnnotations picks the new tool up automatically.
Ambiguity is an error, not a payload. ambiguousError returns a Go error naming every candidate as Name (id UUID, username: x); the handler returns Output{}, err and the test asserts out.UserID == "" alongside it. Every downstream site aborts before its write, and each is tested — TestCreateAssessment_AmbiguousOwnerCreatesNothing, TestEditAsset_SetResponsibility_AmbiguousFullNameWritesNothing (asserts nothing was created), TestCreatedByFilterAmbiguousNameReturnsCandidates, and edit_assessment's atomic phase-3 abort. dq_notifications is the one place ambiguity isn't an error, and that's correct for a surface that already returns needs_input — splitting Ambiguous out of Unresolved so the two get different advice is the right call there.
Email in, never out. Checked structurally rather than by reading intent: EmailAddress is referenced in exactly one place in non-test code, its own field declaration (edit_asset_client.go:721). Nothing reads it. resolve.User carries ID/UserName/FullName only, toUser drops the email explicitly, candidate lines carry username: and never an address, and Output has no email field — with two tests asserting the ambiguity error contains no @ at all. The only email that can surface is the caller's own input echoed back in a failure message, which is the model's own string and is what makes the error self-correcting under §6.3. Not a leak.
The pickMatch extraction is genuinely a move. Compared old and new side by side: normalize and SuggestionSuffix bodies are identical, pickMatch's two error branches are lifted verbatim into notFoundError/ambiguousError, and namedRef{id,name,ctx} → NamedRef{ID,Name,Ctx} accounts for most of the diff. The strings are unchanged for the five pre-existing filters: filterHints(param) supplies exactly the old "pass the UUID in %s to disambiguate", and with empty Candidates/NotFound hints notFoundError falls back to "Valid "+label+"s" and appends nothing. createdByFilter is the only branch actually rewritten, which is the point. Existing search_asset_keyword tests pass untouched.
One thing to be explicit about since it shows up in the moved code: the %ss pluralisation (Valid statuss, %d statuss share that name) came across with the move and is pre-existing on main. Not a defect of this PR and not something I'm asking you to fix here.
Note on the previous round
No previous-round findings file was handed to this node (only open_pr and push_branch inputs were present), so I couldn't check each earlier finding off against a list. I did review the remediation commit on its own merits: b453e5b is a real fix rather than a paper-over — it adds clients.UserSearch{Total, Truncated}, refuses to reduce by display name over a truncated page, deliberately keeps the unique-username path resolving over one, and lands two tests that fail without the change. Finding 2 above is precisely where that fix wasn't carried through to the second call site over the same search.
Review round 2. F1 — get_user_id_by_name had no "when NOT to use it" boundary (standards 7.1 point 3). It resolves individual users only, yet every tool it feeds also takes a user GROUP, so "make the Data Stewards group the owner" reached it and got back a hint listing only user forms. The description now says groups are not resolvable by name and must be given as a UUID, and points at search_asset_keyword (resourceTypeFilters ['User'] / ['UserGroup']) as the way to find whatever this lookup cannot resolve. The same carve-out is in resolve.userNotFoundHint, so the error is self-correcting on its own — the description is not always what the model is still holding. F2 — the truncation guard added last round was only half-applied at the notification call site. findRecipientByName treated a truncated page as ambiguous only when a display-name match happened to fall inside the window; with no match inside it, the recipient was reported as "no active Collibra account", which a truncated search cannot know and which sends the caller after a typo that does not exist. The check is now hoisted above the display-name scan, so a truncated page is never resolved by display name — the rule pkg/tools/resolve already follows. The exact-username early return stays above it: usernames are unique, so the window cannot hide a second holder. UnresolvedRecipientsMessage and the RecipientResolution doc now describe Ambiguous as "could not be pinned to one account", covering both the shared name and the too-broad search. F3 — stepdown order restored: the new helpers in create_assessment and edit_assessment had been inserted ahead of pre-existing helpers the handler calls earlier. Pure movement. F4 — deleted edit_asset's private suggestionSuffix, identical to the exported resolve.SuggestionSuffix in a package that already imports resolve, and pointed its five call sites at the shared one. create_asset's two-argument variant is left alone, being a different function and out of scope. Co-authored-by: andrew.berkow@collibra.com <andrew.berkow@collibra.com>
🎯 What does this PR do?
The assessment tools (
create_assessment,edit_assessment) demanded a user UUID andnothing else, while
edit_asset's responsibility operations have accepted a UUID, an emailor a username since they shipped. This gives every user-valued parameter the same latitude,
extends the shared resolver to cover a person's display name, and adds a small read-only
tool that returns a user's UUID from any of those forms.
The defect being fixed.
search_asset_keywordalready resolves five of its six filtersfrom names through
pickMatch, which returns one match, a self-correcting "not found" withsuggestions, or an ambiguity error listing every candidate with its id. Users never reached
it: the lookup called
FindUserByUsername, which collapsed to a single exact-username matchbefore a second candidate could exist. A display name therefore resolved to nothing.
What changed
pkg/tools/resolvepackage.pickMatch,namedRefandsuggestionSuffixmoveout of
search_asset_keywordinto a shared package, following thepkg/tools/validationprecedent.
search_asset_keywordimports them back for all six of its filters — that iswhy the diff is wide, but it is a move, not a rewrite: the matching, the message
formats and the three outcomes are unchanged. The one addition is
resolve.Hints, whichreplaces the old
paramargument so a caller with no filter parameter can word its own"how to disambiguate" instruction.
clients.FindUsersByNamereturns EVERY enabled user matching a name instead of one,and sends
nameSearchFields(USERNAME, FIRSTNAME, LASTNAME, FIRSTNAME_LASTNAME,LASTNAME_FIRSTNAME) and
includeDisabled=falseexplicitly rather than relying on theserver defaults.
FindUserByUsernameis removed: every caller now goes through the sharedresolver or the notification-recipient helper, both of which need the full candidate list
from a single request. The exact-username scan it existed for remains as
exactUsernameMatch.passes through with no request issued, a value containing
@goes to the exact emailendpoint, an exact username wins next, and only then does the value fall through to
display-name candidates. A candidate is presented as
First Lastwith its username asdisambiguating context and its UUID. Email addresses never appear in output — no CHIP
tool returns one today and this change does not start.
pickMatchdoes today, not asuccess payload with a
candidatesarray. The wording is new for a lookup with no filterparameter: "do NOT pick one — ask which person is meant, then call again with that user's
username or UUID". This matters because
get_user_id_by_nameruns mid-chain to fillanother call's parameter, and a model holding a list of plausible ids and a pending call
will pick one.
get_user_id_by_nametool (read-only): full name, username, email address orUUID in, UUID plus full name and username out.
create_assessment(owner, assignees),edit_assessment(
set_owner,set_assignees),edit_asset(set_responsibility,remove_responsibility),search_asset_keyword(createdByFilter) and data qualitynotification recipients. A GROUP assignee still requires a UUID — group names are not
resolvable through the user lookup.
/rest/2.0/usersdouble inpkg/tools/edit_asset/tool_test.gomatched a three-field OR over username, first name and last name. Under it no two-word name
could ever match, so it encoded an API that does not exist and a test written against it
would have passed while asserting the opposite of real behaviour. It now mirrors the
endpoint's
nameSearchFields, including both concatenated forms. The comment on the clientfunction was incomplete in the same way and is corrected.
Deliberate deviation from the contribution standards.
get_user_id_by_nameregisterswithout an experimental feature flag, which departs from
docs/TOOL_CONTRIBUTION_STANDARDS.md§3.1. The assessment tools it supports are themselvesungated (
pkg/tools/register.go), so a flagged resolver behind unflagged consumers is exactlythe broken-half-a-pair failure §1.1 warns about: a caller that advertises "give me a name" with
no tool available to turn one into a UUID. The rationale is repeated as a comment at the
registration site.
Impact Analysis
UUID still costs no lookup. What changes is that more inputs now succeed, plus two error
messages: an unresolvable user reads
no user matching "x" found. …rather thanno user found matching "x" …, and a shared name is now an ambiguity error rather than anot-found. Both remain per-operation errors in
edit_assetand abort the atomic PATCH inedit_assessment— nothing is written on a failed resolution.or display name costs the one
/rest/2.0/userssearch it already cost.the new tests;
search_asset_keyword's five other filters are untouched in behaviour.assessment tools' input schema) and fix(assessments): fixing assessment output schema #139 (assessment output schema). This work takes
priority and merges first, so both will need a rebase; the conflicts are in
create_assessment/tool.goandedit_assessment/tool.go, where owner/assignee validationis replaced by resolution.
go test ./...andgolangci-lint runboth pass.go test -racewasNOT run: the sandbox image has no C compiler and
CGO_ENABLED=0, so it exits with"-race requires cgo".
CONTRIBUTING.mddocuments-race; it needs a toolchain thisenvironment does not have, so CI is the check for it.
Checklist