Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/dr_egeria_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ Dr.Egeria organizes its commands into "families," each corresponding to a specif
- **Curation**: Apply classifications and relationships to *existing* Referenceable elements after the fact — this family creates no elements of its own, it curates ones created elsewhere. Every command names a `Target Element` (Reference Name) and either a classification level/status or a second element to relate to.
- **Classifications** (`Classify`/`Reclassify`/`Declassify`, or `Classify`/`Update`/`Declassify` for the three multi-value ones): Impact, Confidence, Confidentiality, Criticality, Retention, Ownership, Digital Resource Origin, Zone Membership, Security Tags, Data Scope, Governance Expectations, Governance Measurements, Known Duplicate, Consolidated Duplicate, and the 0438 naming-standards markers Class Word, Modifier, Prime Word (added 2026-08-09 once Egeria PR #9166 shipped the backing `glossary-manager` endpoints — routed through `GlossaryManager`, not `ClassificationExplorer`, unlike every other classification here). Also, added 2026-08-21 once an `omvs_audit.py` gap-closure pass shipped their backing REST endpoints (all confirmed against a live 6.2-SNAPSHOT server): the 10 governance-point classifications (0435: Control/Verification/Enforcement/Execution Point, Policy Administration/Decision/Enforcement/Information/Management/Retrieval Point — routed through `GovernanceOfficer`), 6 metamodel markers (0463: Incomplete, ObjectIdentifier, ReferenceData, MobileResource, InstanceMetadata, MetamodelInstance — routed through `ClassificationExplorer`), ProjectKind (routed through `ProjectManager`), CollectionKind (routed through `CollectionManager`), and a Data Sharing Agreement retrofit pair for classifying/declassifying an already-existing Agreement (routed through `DigitalBusiness` — `Create Data Sharing Agreement`, Digital Products family, already sets this at creation time via `initialClassifications`).
- **Relationships** (`Link`/`Unlink`, or `Attach`/`Update`/`Detach` for Search Keyword): Search Keyword, Semantic Assignment, Semantic Definition, Link Element To Scope (`ScopedBy`), Link Resource To Element (`ResourceList`), Link More Information, Peer Duplicate, Consolidated Duplicate Link.
- Investigation (0130 ProjectKind, routed through `ProjectManager`) and NamingStandardsVocabulary (0438, routed through `GlossaryManager`) — new Data Standards types from Egeria PR #9300, added 2026-09-08. **Not yet verified against a live server**: the type isn't deployed there yet (`validate_compact_specs` fails its OM_TYPE-against-live-server check for both until the server picks up the new archive), and this checkout's `.http` ground truth files haven't been updated for this PR either — re-verify (including `validate_compact_specs`) once both are current.
- No classification in this family remains genuinely unimplemented as of 2026-08-21 — Policy Management Point (previously blocked on a missing pyegeria SDK method) is now registered along with the 9 sibling governance-point classifications above.
- `Update Search Keyword`/`Detach Search Keyword` (fixed 2026-08-21) act on the `SearchKeyword` entity's own GUID via a new `Search Keyword GUID` attribute (`_async_update_search_keyword`/`_async_remove_search_keyword_from_element` both take only that GUID, not the `Target Element` the keyword is attached to — the `remove` method deletes the keyword entity itself despite its name). `Attach Search Keyword` (create + attach in one call) keeps its own `Target Element`-based bundle unchanged; `Update`/`Detach` each got their own dedicated bundle rather than sharing `Attach`'s, since `Target Element`/`Keyword` are separately mandatory attributes that don't apply to identifying an existing keyword by GUID. Live-verified end to end against a running 6.2-SNAPSHOT server, including a real bug this caught: `CurationLinkProcessor`/`CurationClassifyProcessor` never overrode `supports_target_element_lookup()`, so the base class's Create↔Update upsert-transition logic silently rewrote every `Update X` command in this family (including the pre-existing `Update Data Scope`/`Update Governance Expectations`/`Update Governance Measurements`) to `Create X` before it reached the processor's own verb-branching logic — for Search Keyword this meant `Update` silently deleted the entity instead of updating it. Both processors now override that method; see the commit fixing it for the full live-verification trail.
- Two related commands live in *other* families' compact specs (via the shared `Person Action Base` bundle), routed to those families' own processors rather than `CurationClassifyProcessor`/`CurationLinkProcessor`: `Create Meeting` (Project family, via `my_profile.create_meeting`) and `Create ToDo` (Actor Manager family, via `my_profile.create_my_todo`). `Create Review` (Feedback family, via `my_profile.create_review`) and `Create Note` are also `Person Action Base` commands but documented under **Feedback** below, since that's their family.
Expand Down
90 changes: 90 additions & 0 deletions md_processing/data/compact_commands/commands_curation_compact.json
Original file line number Diff line number Diff line change
Expand Up @@ -3302,6 +3302,20 @@
"own_attributes": [
"Search Keyword GUID"
]
},
"Investigation Base": {
"inherits": "Request Base",
"own_attributes": [
"Target Element",
"Additional Properties"
]
},
"Naming Standards Vocabulary Base": {
"inherits": "Request Base",
"own_attributes": [
"Target Element",
"Additional Properties"
]
}
},
"commands": {
Expand Down Expand Up @@ -5280,6 +5294,82 @@
"bundle": "Declassify Base",
"custom_attributes": [],
"Journal Entry": ""
},
"Classify Investigation": {
"display_name": "investigation_classification",
"qn_prefix": "Investigation",
"alternate_names": [],
"family": "Curation",
"description": "Classify an existing Project as an investigation that is being conducted to answer a question or seek out information (0130).",
"verb": "Classify",
"upsert": false,
"attach": false,
"level": "Basic",
"find_method": "",
"find_constraints": "",
"extra_find": "",
"extra_constraints": "",
"OM_TYPE": "Investigation",
"bundle": "Investigation Base",
"custom_attributes": [],
"Journal Entry": ""
},
"Declassify Investigation": {
"display_name": "investigation_classification",
"qn_prefix": "Investigation",
"alternate_names": [],
"family": "Curation",
"description": "Remove the Investigation classification from a Project.",
"verb": "Declassify",
"upsert": false,
"attach": false,
"level": "Basic",
"find_method": "",
"find_constraints": "",
"extra_find": "",
"extra_constraints": "",
"OM_TYPE": "Investigation",
"bundle": "Declassify Base",
"custom_attributes": [],
"Journal Entry": ""
},
"Classify Naming Standards Vocabulary": {
"display_name": "naming_standards_vocabulary_classification",
"qn_prefix": "NamingStandardsVocabulary",
"alternate_names": [],
"family": "Curation",
"description": "Classify an existing Glossary as a naming standards vocabulary -- a glossary that describes the terms used in naming standards (0438).",
"verb": "Classify",
"upsert": false,
"attach": false,
"level": "Basic",
"find_method": "",
"find_constraints": "",
"extra_find": "",
"extra_constraints": "",
"OM_TYPE": "NamingStandardsVocabulary",
"bundle": "Naming Standards Vocabulary Base",
"custom_attributes": [],
"Journal Entry": ""
},
"Declassify Naming Standards Vocabulary": {
"display_name": "naming_standards_vocabulary_classification",
"qn_prefix": "NamingStandardsVocabulary",
"alternate_names": [],
"family": "Curation",
"description": "Remove the NamingStandardsVocabulary classification from a Glossary.",
"verb": "Declassify",
"upsert": false,
"attach": false,
"level": "Basic",
"find_method": "",
"find_constraints": "",
"extra_find": "",
"extra_constraints": "",
"OM_TYPE": "NamingStandardsVocabulary",
"bundle": "Declassify Base",
"custom_attributes": [],
"Journal Entry": ""
}
}
}
14 changes: 14 additions & 0 deletions md_processing/v2/curation.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,18 @@ class ClassificationSpec:
"DataSharingAgreement": ClassificationSpec(
"_async_set_agreement_as_data_sharing_agreement", "_async_clear_agreement_as_data_sharing_agreement",
"DataSharingAgreementProperties"),
# Data Standards types (Egeria PR #9300) -- pure marker classifications, no
# custom properties. Investigation (0130 ProjectKind) routed through
# ProjectManager; NamingStandardsVocabulary (0438) routed through
# GlossaryManager (see CURATION_CLASSIFICATION_CLIENTS below). Not yet
# verified against a live server -- the type is not deployed there yet
# (confirmed with the user 2026-09-08); .http ground truth also not yet
# updated in this checkout.
"Investigation": ClassificationSpec(
"_async_set_project_as_investigation", "_async_clear_project_as_investigation", "InvestigationProperties"),
"NamingStandardsVocabulary": ClassificationSpec(
"_async_set_glossary_as_naming_standards_vocabulary", "_async_clear_glossary_as_naming_standards_vocabulary",
"NamingStandardsVocabularyProperties"),
}

# OM_TYPEs in CLASSIFICATION_METHODS whose set/clear methods live on a client other than
Expand All @@ -245,6 +257,8 @@ class ClassificationSpec:
"ProjectKind": "project_manager",
"CollectionKind": "collection_manager",
"DataSharingAgreement": "digital_business",
"Investigation": "project_manager",
"NamingStandardsVocabulary": "glossary_manager",
}


Expand Down
2 changes: 1 addition & 1 deletion pyegeria/core/_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def resolve_enum(enum_class: type[Enum], value: str | int) -> int | None:
MEMBERSHIP_STATUS = ["UNKNOWN", "DISCOVERED", "PROPOSED", "IMPORTED", "VALIDATED", "DEPRECATED", "OBSOLETE", "OTHER"]

RELATIONSHIP_TYPES = ["RelatedTerm", "Synonym", "Antonym", "PreferredTerm", "ReplacementTerm", "Translation", "IsA", "ValidValue"]
PROJECT_TYPES=["Project","Campaign","Task","PersonalProject","StudyProject","Experiment"]
PROJECT_TYPES=["Project","Campaign","Task","PersonalProject","StudyProject","Experiment","Investigation"]

TEMPLATE_GUIDS: dict[str, str] = {}
INTEGRATION_GUIDS: dict[str, str] = {}
Expand Down
16 changes: 16 additions & 0 deletions pyegeria/omvs/classification_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9916,6 +9916,10 @@ async def _async_add_data_scope(
"class" : "DataScopeProperties",
"dataCollectionStartTime" : "isoTimestamp",
"dataCollectionEndTime" : "isoTimestamp",
"dataValidityStartTime" : "isoTimestamp",
"dataValidityEndTime" : "isoTimestamp",
"dataCoverageStartTime" : "isoTimestamp",
"dataCoverageEndTime" : "isoTimestamp",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down Expand Up @@ -9979,6 +9983,10 @@ def add_data_scope(
"class" : "DataScopeProperties",
"dataCollectionStartTime" : "isoTimestamp",
"dataCollectionEndTime" : "isoTimestamp",
"dataValidityStartTime" : "isoTimestamp",
"dataValidityEndTime" : "isoTimestamp",
"dataCoverageStartTime" : "isoTimestamp",
"dataCoverageEndTime" : "isoTimestamp",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down Expand Up @@ -10044,6 +10052,10 @@ async def _async_update_data_scope(
"class" : "DataScopeProperties",
"dataCollectionStartTime" : "isoTimestamp",
"dataCollectionEndTime" : "isoTimestamp",
"dataValidityStartTime" : "isoTimestamp",
"dataValidityEndTime" : "isoTimestamp",
"dataCoverageStartTime" : "isoTimestamp",
"dataCoverageEndTime" : "isoTimestamp",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down Expand Up @@ -10113,6 +10125,10 @@ def update_data_scope(
"class" : "DataScopeProperties",
"dataCollectionStartTime" : "isoTimestamp",
"dataCollectionEndTime" : "isoTimestamp",
"dataValidityStartTime" : "isoTimestamp",
"dataValidityEndTime" : "isoTimestamp",
"dataCoverageStartTime" : "isoTimestamp",
"dataCoverageEndTime" : "isoTimestamp",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down
153 changes: 153 additions & 0 deletions pyegeria/omvs/glossary_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,159 @@ def clear_glossary_as_canonical(self, glossary_guid: str,
loop = asyncio.get_event_loop()
loop.run_until_complete(self._async_clear_glossary_as_canonical(glossary_guid, body))

@dynamic_catch
async def _async_set_glossary_as_naming_standards_vocabulary(self, glossary_guid: str,
body: Optional[dict | NewClassificationRequestBody] = None) -> None:
"""Classify a glossary to declare that it describes the terms used in naming standards. The terms in
this type of glossary are the name parts that are combined to form names that follow the organization's
naming standards. Async version.

Parameters
----------
glossary_guid : str
Unique identifier of the glossary to classify.
body : dict | NewClassificationRequestBody, optional
Full request body. If supplied, overrides other parameters.

Returns
-------
None

Raises
------
PyegeriaInvalidParameterException
PyegeriaAPIException
NotAuthorizedException

Notes
-----
Example body:
{
"class": "NewClassificationRequestBody",
"properties": {
"class": "NamingStandardsVocabularyProperties"
}
}
"""
url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/glossary-manager/glossaries/"
f"{glossary_guid}/is-naming-standards-vocabulary")
if body is None:
body = {
"class": "NewClassificationRequestBody",
"properties": {
"class": "NamingStandardsVocabularyProperties"
}
}
await self._async_new_classification_request(url, "NamingStandardsVocabularyProperties", body)
logger.info(f"Set glossary {glossary_guid} as naming standards vocabulary")

@dynamic_catch
def set_glossary_as_naming_standards_vocabulary(self, glossary_guid: str,
body: Optional[dict | NewClassificationRequestBody] = None) -> None:
"""Classify a glossary to declare that it describes the terms used in naming standards. The terms in
this type of glossary are the name parts that are combined to form names that follow the organization's
naming standards.

Parameters
----------
glossary_guid : str
Unique identifier of the glossary to classify.
body : dict | NewClassificationRequestBody, optional
Full request body. If supplied, overrides other parameters.

Returns
-------
None

Raises
------
PyegeriaInvalidParameterException
PyegeriaAPIException
NotAuthorizedException

Notes
-----
Example body:
{
"class": "NewClassificationRequestBody",
"properties": {
"class": "NamingStandardsVocabularyProperties"
}
}
"""
loop = asyncio.get_event_loop()
loop.run_until_complete(self._async_set_glossary_as_naming_standards_vocabulary(glossary_guid, body))

@dynamic_catch
async def _async_clear_glossary_as_naming_standards_vocabulary(self, glossary_guid: str,
body: Optional[dict | DeleteClassificationRequestBody] = None) -> None:
"""Remove the naming standards vocabulary classification from a glossary. Async version.

Parameters
----------
glossary_guid : str
Unique identifier of the glossary.
body : dict | DeleteClassificationRequestBody, optional
Request body with correlation properties.

Returns
-------
None

Raises
------
PyegeriaInvalidParameterException
PyegeriaAPIException
NotAuthorizedException

Notes
-----
Example body:
{
"class": "DeleteClassificationRequestBody",
"forLineage": false,
"forDuplicateProcessing": false
}
"""
url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/glossary-manager/glossaries/"
f"{glossary_guid}/is-naming-standards-vocabulary/remove")
await self._async_delete_classification_request(url, body)
logger.info(f"Cleared naming standards vocabulary classification from glossary {glossary_guid}")

@dynamic_catch
def clear_glossary_as_naming_standards_vocabulary(self, glossary_guid: str,
body: Optional[dict | DeleteClassificationRequestBody] = None) -> None:
"""Remove the naming standards vocabulary classification from a glossary.

Parameters
----------
glossary_guid : str
Unique identifier of the glossary.
body : dict | DeleteClassificationRequestBody, optional
Request body with correlation properties.

Returns
-------
None

Raises
------
PyegeriaInvalidParameterException
PyegeriaAPIException
NotAuthorizedException

Notes
-----
Example body:
{
"class": "DeleteClassificationRequestBody",
"forLineage": false,
"forDuplicateProcessing": false
}
"""
loop = asyncio.get_event_loop()
loop.run_until_complete(self._async_clear_glossary_as_naming_standards_vocabulary(glossary_guid, body))

#
# Terms
#
Expand Down
8 changes: 8 additions & 0 deletions pyegeria/omvs/governance_officer.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,10 @@ async def _async_create_data_lens(self, body: dict | NewElementRequestBody) -> s
"results": [],
"dataCollectionStartTime" : "{{$isoTimestamp}}",
"dataCollectionEndTime" : "{{$isoTimestamp}}",
"dataValidityStartTime" : "{{$isoTimestamp}}",
"dataValidityEndTime" : "{{$isoTimestamp}}",
"dataCoverageStartTime" : "{{$isoTimestamp}}",
"dataCoverageEndTime" : "{{$isoTimestamp}}",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down Expand Up @@ -690,6 +694,10 @@ def create_data_lens(self, body: dict | NewElementRequestBody) -> str:
"results": [],
"dataCollectionStartTime" : "{{$isoTimestamp}}",
"dataCollectionEndTime" : "{{$isoTimestamp}}",
"dataValidityStartTime" : "{{$isoTimestamp}}",
"dataValidityEndTime" : "{{$isoTimestamp}}",
"dataCoverageStartTime" : "{{$isoTimestamp}}",
"dataCoverageEndTime" : "{{$isoTimestamp}}",
"minLongitude" : 0,
"minLatitude" : 0,
"maxLongitude" : 0,
Expand Down
Loading
Loading