Skip to content

fix(pyegeria): resolve OMVS audit mismatches -- broken helper calls, wrong body classes, tool hardening - #281

Merged
dwolfson merged 8 commits into
odpi:mainfrom
dwolfson:fix/omvs-template-and-classification-fixes
Aug 18, 2026
Merged

fix(pyegeria): resolve OMVS audit mismatches -- broken helper calls, wrong body classes, tool hardening#281
dwolfson merged 8 commits into
odpi:mainfrom
dwolfson:fix/omvs-template-and-classification-fixes

Conversation

@dwolfson

Copy link
Copy Markdown
Member

Summary

Continuation of the OMVS .http-ground-truth audit from PR #277. Fixes three
originally-uncommitted pieces lost to worktree cleanup, plus a full pass
through the remaining audit mismatches (47 -> 7, all 7 confirmed ground-truth
data-quality issues, not SDK bugs).

Most notable fixes

  • Guaranteed crash: schema_maker.delete_schema_type,
    delete_schema_attribute, subject_area.delete_subject_area, and
    time_keeper.delete_context_event all called
    _async_delete_element_body_request -- a method that does not exist
    anywhere in the codebase. Every call raised AttributeError before any
    request was sent.
  • Inverted operation: project_manager.clear_project_classification
    called the add-classification helper instead of the delete one --
    calling "clear" would have added a classification, not removed one.
  • Wrong HTTP method: my_profile.get_my_profile sent POST to a
    documented GET-only, bodyless endpoint.
  • Several wrong-request-body-class bugs where the SDK's validator silently
    discards the caller's properties and substitutes a default body of the
    wrong "class" (governance_officer, glossary_manager, actor_manager,
    valid_metadata).
  • scripts/omvs_audit.py hardening: 5 resolver gaps fixed (module-level
    base_path() helpers, local URL-root variables, query-string builders,
    name-vs-value root detection, union body-type comparison), each verified
    against the specific bug it was masking, not just that it goes quiet.

Verification

  • Full pyegeria/ compiles clean
  • All touched modules import cleanly
  • pytest tests/micro-tests/ passes
  • pytest tests/functional-tests/ --collect-only collects cleanly
  • scripts/omvs_audit.py: 652 OK / 7 mismatch (was 612 OK / 47 mismatch at
    the start of this branch)

Commits

8 commits, each independently verified and scoped to one investigation. See
individual commit messages for the ground-truth citation behind each fix.

🤖 Generated with Claude Code

…ody, arity, classification helper

These three fixes were made and verified in an isolated Agent worktree
(egeria-python-omvs-audit-followup) that the harness auto-cleaned once its
commits were pushed and PR odpi#277 merged. Because these three fixes were still
uncommitted at that point, they were lost with the directory rather than
merged. Reapplied here from scratch on top of PR odpi#277's tip (confirmed absent
via egeria-python-73, a peer session, before redoing).

1. governance_officer.get_governance_action_process_graph sent GetRequestBody
   where Egeria-api-governance-officer.http documents ResultsRequestBody.
   Added an opt-in body_model param to _async_get_guid_request (default
   unchanged: GetRequestBody) so this one call can send the documented body
   without touching the other 24 callers of that helper. Kept on
   _async_get_guid_request rather than switching to
   _async_get_results_body_request, because only this helper reads the
   singular "elementGraph" response key the graph endpoint returns.

2. Six *_from_template methods (actor_manager x3, data_discovery, subject_area,
   time_keeper) called _async_create_element_body_request instead of
   _async_create_element_from_template -- silently dropping the 8 fields only
   TemplateRequestBody has (template_guid, placeholder_property_values,
   replacement_properties, deep_copy, ...) via PyegeriaModel's extra='ignore'.
   The element was created with no template ever applied, no error raised.

   Also fixed 4 call sites (actor_manager x3, location_arena) passing an extra
   "POST" positional argument to _async_create_element_from_template, which
   only takes (url, body) -- confirmed this raises TypeError before any
   request is sent.

3. classification_explorer's clear_known_duplicate_classification and
   clear_consolidated_duplicate_classification annotated
   DeleteClassificationRequestBody but called _async_delete_relationship_request,
   whose validator only accepts DeleteRelationshipRequestBody or dict --
   passing the annotated type hits the validator's else branch, which returns
   None, so the guarded call is skipped and the clear silently never happens.
   Switched both to the dedicated _async_delete_classification_request helper
   (the other 12 clear_* methods in this file already used it correctly).

Verified: full pyegeria/ compiles, all touched modules import, zero
_async_create_element_from_template call sites now exceed its 2-param arity,
micro-tests pass, functional tests collect. Audit: 612 OK / 47 mismatch (was
605/54 on this base) -- governance-officer and the six from-template methods
now report 0 mismatches; classification-explorer down to the 6 unrelated
findings that predate this pass.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…ix broken get_metadata_element_history

metadata-expert reported 16 mismatches; 14 were a new variant of the tool
artifact already fixed for connection-maker/governance-officer.

Tooling (scripts/omvs_audit.py):
- Resolve module-level helper functions that just return the service root.
  metadata_expert.py (and governance_officer.py, solution_architect.py) builds
  URLs as f"{base_path(self, self.view_server)}/..." -- a plain function call,
  not a self.<attr> reference, so neither the by-name nor by-value root
  detection matched it. Added resolve_module_root_funcs(): any top-level
  function whose single return statement contains "/open-metadata/" is
  registered by name, and _flatten's Call handling checks that registry before
  falling through to the URL-wrapper/opaque-expression cases. The function's
  own arguments are irrelevant (they just interpolate platform_url/view_server,
  already collapsed to placeholders), so this resolves by name, not by call
  shape.

Endpoint fixes:
- get_metadata_element_history routed through _async_get_guid_request, whose
  default validates against GetRequestBody -- a Literal['GetRequestBody']
  discriminator on the "class" field. The method hand-builds a body with
  "class": "HistoryRequestBody" (matching Egeria-api-metadata-expert.http;
  no HistoryRequestBody model exists in pyegeria.models). Every call raised a
  pydantic ValidationError before any request was sent. Rewritten to send the
  raw dict via _async_make_request directly, mirroring
  _async_get_classification_history -- the sibling endpoint in this same file
  that already does this correctly.
- get_metadata_guid_by_unique_name annotated body as dict | FilterRequestBody,
  but actually sends "class": "UniqueNameRequestBody" (matching ground truth;
  again no backing pyegeria.models class exists). A caller following the
  annotation and passing an actual FilterRequestBody object would crash in
  body_slimmer(), whose body.items() call assumes a dict. Corrected the
  annotation to Optional[dict] with a comment, rather than importing a class
  that doesn't exist.

Audit now reports 628 OK / 31 mismatch (was 612 / 47).

Not fixed here (missing, not mismatch -- different bucket, deferred):
getAllRelatedMetadataElements and findRelationshipsBetweenMetadataElements
have no SDK method at all.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…l, detach helper direction

actor-manager reported 7 mismatches; 4 were real, fixed; 2 are a .http
ground-truth typo (see below); 1 is a stray .http parse artifact.

Fixes:
- add_security_group_membership / update_security_group_membership built their
  URL as .../security-group-membership/classify|reclassify (singular).
  Egeria-api-actor-manager.http documents .../security-group-memberships/
  (plural) consistently across classify, reclassify, and declassify;
  remove_all_security_group_memberships in this same file already used the
  plural form correctly. Also dropped a stray "url = url = (...)" double
  assignment in add_security_group_membership while touching that line.
- detach_asset_from_profile / detach_person_role_from_profile annotated their
  body as DeleteRelationshipRequestBody (correct, matches ground truth) but
  called _async_delete_element_request -- the inverse of the bug class fixed
  earlier in governance_officer/classification_explorer. Its validator treats
  a DeleteRelationshipRequestBody instance as "not provided" (isinstance check
  against DeleteElementRequestBody fails, falls to the else branch), silently
  discarding the caller's relationship-detach properties and sending a default
  DeleteElementRequestBody body instead. Switched both to
  _async_delete_relationship_request to match the annotation and the ground
  truth's /detach path shape.

Not changed -- ground-truth data-quality issues, not SDK bugs:
- updateActorRole / deleteActorRole: Egeria-api-actor-manager.http's URLs for
  these two are missing the {{actorRoleGUID}} path segment entirely
  (.../actor-roles/update, .../actor-roles/delete) while every sibling type in
  the same file (actor-profiles, user-identities) correctly uses
  .../{guid}/update and .../{guid}/delete. The SDK's {guid}/update /
  {guid}/delete shape is the one consistent with the rest of the API and is
  presumed correct; changing it to match would introduce a real bug to
  satisfy a typo in the ground truth.
- getActorRoleByGUID: the .http line for this endpoint ends in a stray
  `"})` copy/paste artifact (`.../retrieve"})`), which canon_path's cleanup
  can't fully strip without risking corruption of legitimately
  parameter-terminated paths elsewhere. Cosmetic parser noise, not a real
  finding.

Audit now reports 631 OK / 27 mismatch (was 628 / 31).

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…path segment

Six methods (get_semantic_assignees, get_source_elements,
get_elements_sourced_from, get_licensed_elements, get_licenses,
get_certified_elements) built URLs as
.../classification-explorer/glossaries/elements/... . Every one of these
operates on a generic Referenceable element (source/license/certification
relationships), not a glossary term, and
Egeria-api-classification-explorer.http confirms none of them include
"glossaries" in the path -- only get_meanings (glossaries/terms/...) actually
does, and was correctly left untouched.

Audit now reports 0 mismatches for classification-explorer (was 6).

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…ffix, resolve query-string helper

solution-architect reported 5 mismatches: 1 real duplicate-name bug, 3 tool
artifacts from a second variant of the base_path()-style false positive, 1
left flagged rather than force-fixed.

Tooling (scripts/omvs_audit.py):
- _flatten's ast.Name branch never consulted `roots` at all -- only the
  Attribute and Call branches did. A bare local variable referenced in an
  f-string (`f"{url}{possible_query_params}"`) always rendered as an opaque
  placeholder no matter what it held. Now checks `roots` first.
- Added QUERY_STRING_FUNCS + per-function local-variable resolution: methods
  assign a `query_string(...)`-built value to a local (e.g.
  `possible_query_params = query_string([("startFrom", ...), ...])`) before
  interpolating it into the URL. query_string() always returns "" or a
  "?key=value&..." suffix (solution_architect.py / collection_manager.py),
  safe to elide entirely since canon_path already splits on "?". Without this,
  every by-name/paginated endpoint using this pattern reported a false PATH
  mismatch (get_solution_roles_by_name, get_solution_components_by_name,
  get_solution_component_implementations all cleared by this alone).

Endpoint fix:
- get_design_patterns_by_name appended the search name directly onto the URL
  path (.../design-patterns/by-name/{name}) *and* sent it again in the request
  body via _async_get_name_request's FilterRequestBody -- the name was
  effectively sent twice, and the URL didn't match
  Egeria-api-solution-architect.http's undecorated .../by-name path. Removed
  the URL segment; the body-carried filter is Egeria's actual query mechanism
  for this endpoint (confirmed against ground truth).

Left flagged, not changed:
- detach_solution_linking_wire(component1_guid, component2_guid) builds
  .../wired-to/{}/detach, a path Egeria-api-solution-architect.http does not
  document at all -- only .../wires/{relationshipGUID}/detach exists, which
  detach_solution_linking_wire_by_guid (a sibling method, already correct)
  already implements. The two-component-GUID method has no relationship GUID
  to build the documented URL from, so this isn't a mechanical path fix;
  flagging for a follow-up decision (deprecate in favor of the _by_guid
  sibling, or confirm live whether the two-GUID form has a real backing
  endpoint under another path).

Audit now reports 642 OK / 17 mismatch (was 638 / 21).

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…nteed AttributeError on every call

schema_maker.delete_schema_type, schema_maker.delete_schema_attribute,
subject_area.delete_subject_area, and time_keeper.delete_context_event all
called self._async_delete_element_body_request(url, body). That method does
not exist anywhere in the codebase -- not on ServerClient, not on any base
class. Every call to any of these four methods raised AttributeError before
any request was sent. Found while investigating a "BODY sends
DeleteElementRequestBody != MetadataSourceRequestBody" audit finding for
delete_schema_type; the audit's body-class check doesn't (and can't) detect a
call to a name that isn't defined at all, since it only compares declared
types, so this was more broken than the audit reported.

Fix depends on what each method's ground truth (.http file) actually
documents as its request body:

- schema_maker.delete_schema_type / delete_schema_attribute: Egeria documents
  "class": "MetadataSourceRequestBody" for both (confirmed against
  Egeria-api-schema-maker.http). Routed to the existing, correct
  _async_metadata_source_body_request helper (validates MetadataSourceRequestBody
  via _metadata_source_request_adapter) and corrected the misleading
  DeleteElementRequestBody annotations on all 4 signatures (both async/sync
  pairs) to match.
- subject_area.delete_subject_area / time_keeper.delete_context_event: Egeria
  documents "class": "DeleteElementRequestBody" for both (confirmed against
  Egeria-api-subject-area.http / Egeria-api-time-keeper.http) -- these two
  already had the correct annotation, just the wrong helper name. Routed to
  the existing _async_delete_element_request helper instead.

Verified: full pyegeria/ compiles, all four touched modules import, zero
remaining references to the nonexistent helper name anywhere in the tree,
micro-tests pass, functional tests collect. Audit: schema-maker now 11 OK / 3
mismatch (was 10 / 4) -- the 3 remaining are Egeria-api-schema-maker.http
itself missing the {{schemaAttributeGUID}} path segment on
updateSchemaAttribute/deleteSchemaAttribute (every sibling type in the same
file correctly includes it) plus one stray '"}' copy-paste artifact on
getSchemaAttributeByGUID's line -- ground-truth data-quality issues, not SDK
bugs, left unchanged.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…the wrong body class

remove_is_abstract_concept, remove_is_context_definition, and
remove_activity_description all called _async_delete_classification_request
with a DeleteClassificationRequestBody annotation, but
Egeria-api-glossary-manager.http documents different classes per endpoint:

- clearTermAsAbstractConcept -> DeleteElementRequestBody
- clearTermAsActivity        -> DeleteRelationshipRequestBody
- clearTermAsContext         -> DeleteRelationshipRequestBody

Egeria's own API is inconsistent here -- other clear_* siblings in this same
file (glossary_as_taxonomy, is_data_value, term_as_question, is_prime_word,
is_modifier, is_class_word) genuinely do use DeleteClassificationRequestBody
and were correctly left untouched; the audit didn't flag them because they
already match ground truth. Fixed the 3 that don't, matching each to its
own documented class and helper (_async_delete_element_request /
_async_delete_relationship_request), both async and sync signatures.

Audit now reports glossary-manager at 0 mismatches (was 3). Overall: 646 OK /
13 mismatch.

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…audit resolver picks up local URL-root variables

Fixed the remaining audit mismatches down to 7 (were 13), all confirmed
against ground truth individually -- this batch skews toward more severe,
functionally-wrong-operation bugs than earlier ones.

Endpoint fixes:
- project_manager.clear_project_classification called
  _async_new_classification_request (the ADD-classification helper) instead
  of _async_delete_classification_request, despite its own URL
  (.../declassify) and docstring both documenting a remove operation.
  Calling "clear" would have added a ProjectClassification instead of
  removing one -- the opposite of what the method claims to do.
- valid_metadata.link_specification_property called
  _async_create_element_body_request (validates NewElementRequestBody)
  instead of _async_new_relationship_request (NewRelationshipRequestBody,
  confirmed via Egeria-api-valid-metadata.http) for an /attach relationship
  endpoint.
- my_profile.get_my_profile hand-rolled a POST to a GET-only, bodyless
  endpoint (Egeria-api-my-profile.http: plain GET, no request body -- the
  profile is derived from the bearer token). Every call would have hit the
  wrong HTTP method.
- actor_manager.delete_actor_role annotated + called the DeleteElementRequestBody
  path, but both ground truth and the method's own docstring sample document
  DeleteRelationshipRequestBody. Switched to _async_delete_relationship_request.
- data_designer.detach_specialized_data_value_specification used
  ".../specialized-data-value-specification-definition/..." (singular,
  "-definition" suffix); its attach sibling in the same file already uses the
  correct ".../specialized-data-value-specifications/..." (plural).
- lineage_linker.link_lineage used a made-up
  ".../elements/{}/{}/{}/attach" path with no model validation at all
  (raw dict passed straight to _async_make_request). A correct sibling,
  link_data_flow, already builds the real
  ".../from-elements/{}/via/{}/to-elements/{}/attach" shape -- matched it and
  routed through _async_new_relationship_request instead of an unvalidated
  raw POST.

Tooling (scripts/omvs_audit.py):
- Generalized the per-function local-variable root resolution (added for
  query_string() in the previous commit) to also catch a plain local
  assignment building a URL prefix inside a method body, e.g.
  `base = f"{self.platform_url}/servers/.../api/open-metadata/<service>"`
  (collection_manager.py). resolve_roots only sees self.<attr> assignments
  in __init__; this covers the same pattern scoped to a local variable.
  Cleared 1 more false mismatch (detach_associated_skill_set).

Not changed -- flagged as ambiguous, not mechanically fixable:
- detachSolutionLinkingWire (MISMATCH) and detachAllSolutionLinkingWire
  (MISSING) are two distinct, correctly-implemented Egeria endpoints
  (detach_solution_linking_wire and detach_solution_linking_wire_by_guid)
  whose ground-truth names are too similar for the audit's exact-name
  matching to disambiguate -- adding a NAME_OVERRIDES entry would just move
  the false mismatch from one ground-truth row to the other rather than
  resolve it. Both underlying SDK methods are already correct; this is a
  naming-convention decision for a human, not a bug.
- updateActorRole/deleteActorRole and updateSchemaAttribute/
  deleteSchemaAttribute PATH findings, and 2 stray '"}' artifacts (still
  ground-truth data-quality issues identified in earlier commits, unchanged).

Verified: full pyegeria/ compiles, all touched modules import, micro-tests
pass, functional tests collect. Audit: 652 OK / 7 mismatch (was 646 / 13).

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
@dwolfson
dwolfson merged commit ed48efb into odpi:main Aug 18, 2026
1 check passed
@dwolfson
dwolfson deleted the fix/omvs-template-and-classification-fixes branch August 19, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant