From 8d266ebbb1681415b7e3e093a7347eabcb180c06 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Thu, 1 Oct 2026 08:41:15 -0500 Subject: [PATCH 1/2] feat(dr-egeria): default editing commands to forLineage=true; Solution Component deployment status Requested by Mandy Chessell. forLineage defaults (Promise support) - Egeria only returns Promise-classified elements (and Memento, logically deleted, elements) to requests with forLineage=true. Setting For Lineage on a command did not help: it only reached the request body, while the existence check, name lookups and fetch-by-GUID always used false -- so e.g. Update Solution Component on a promised component missed it and was rewritten into a Create. - pyegeria: new lineage_visible() context manager. Inside it, every JSON request body sent by any client (both BasePlatformClient and BaseServerClient request paths) goes out with forLineage=true, including bodies SDK helpers build with it false. - Dr.Egeria: editing commands run inside lineage_visible(True); report commands (View/List/Run) default to false. An explicit For Lineage value wins, except Declassify Promise, which always uses true. A Memento element found by an editing command is treated as not found, so a Create for a deleted element's name still creates rather than updating the deleted one. Solution Component (0730) - Create Solution Component gains Deployment Status / User Defined Deployment Status, sent as deploymentStatus / userDefinedDeploymentStatus. Shared attribute definitions kept identical in the Curation and Solution Architect families (global attribute namespace). - Fix "compoent(s)" typos in Link Solution Components / Link SubComponent. Verified against a live server: a promised Solution Component was found and updated in place by Update Solution Component with no For Lineage attribute (same GUID, no duplicate), deploymentStatus stored, report-style find hid it by default and showed it inside lineage_visible(). Signed-off-by: Dan Wolfson --- docs/dr_egeria_manual.md | 17 ++ .../commands_curation_compact.json | 8 +- .../commands_solution_architect_compact.json | 57 ++++- md_processing/data/generated_format_sets.json | 216 ++++++++++++++++++ md_processing/v2/curation.py | 8 +- md_processing/v2/processors.py | 56 ++++- md_processing/v2/solution_architect.py | 2 + pyegeria/__init__.py | 2 + pyegeria/core/_base_platform_client.py | 46 ++++ pyegeria/core/_base_server_client.py | 2 + pyegeria/view/base_report_formats.py | 4 +- .../advanced/Curation/Classify_Promise.md | 4 +- .../Create_Solution_Component.md | 18 ++ .../Link_Solution_Components.md | 2 +- .../Solution Architect/Link_SubComponent.md | 2 +- .../basic/Curation/Classify_Promise.md | 4 +- .../Create_Solution_Component.md | 18 ++ .../Link_Solution_Components.md | 2 +- .../Solution Architect/Link_SubComponent.md | 2 +- tests/micro-tests/test_curation_promise.py | 25 +- tests/micro-tests/test_for_lineage_default.py | 118 ++++++++++ 21 files changed, 579 insertions(+), 34 deletions(-) create mode 100644 tests/micro-tests/test_for_lineage_default.py diff --git a/docs/dr_egeria_manual.md b/docs/dr_egeria_manual.md index c3591632..f3a0956e 100644 --- a/docs/dr_egeria_manual.md +++ b/docs/dr_egeria_manual.md @@ -226,6 +226,22 @@ Several "advanced" attributes available on every Create/Update command are baked - **`Anchor ID` / `Anchor Scope ID`** — **Create-time only, by design.** Anchoring in Egeria is create-time-only plumbing; there is no supported way to change an element's anchor after creation. Setting these on an `Update` command has no effect. +### For Lineage: Promise and Memento elements + +Egeria hides two kinds of element from ordinary requests and returns them only when a request sets `forLineage=true`: + +- **Promise** (0010): a placeholder for a real-world resource that has not been delivered yet (see `Classify Promise` / `Declassify Promise` in the Curation family). +- **Memento**: an element that has been logically deleted but is kept so lineage stays complete. + +Dr.Egeria's defaults (added 2026-10-01): + +- **Editing commands** (Create, Update, Link, Classify, ...) send every request with `forLineage=true`, so a promised element can be found, updated and linked without adding `For Lineage` to each command. This covers the existence check, reference lookups, fetches and the write itself. +- **Report commands** (`View`, `List`, `Run`) send `forLineage=false`, so promised and deleted elements stay out of reports. +- An explicit `For Lineage` attribute (Advanced templates) overrides either default. The one exception is `Declassify Promise`, which always uses `forLineage=true`, since its target is otherwise invisible. +- A **Memento** element found by an editing command is treated as not found. A `Create` for a deleted element's name therefore still creates a new element, as it did before, instead of updating the deleted one. + +In the SDK, `with pyegeria.lineage_visible():` applies the same `forLineage=true` default to every request a pyegeria client sends inside the block. + --- ## Supported Command Families @@ -242,6 +258,7 @@ Dr.Egeria organizes its commands into "families," each corresponding to a specif - **Solution Architect**: Manage solution blueprints, components, information supply chains, and design patterns (e.g., `Create Design Pattern`, `Link Nested Design Patterns`, `Link Specialized Design Patterns`, `Link Related Design Patterns`). - **Concept Models** (0571, added 2026-08-21): `Create Concept Bead`, `Create Concept Bead Attribute`, `Create Concept Bead Relationship`, and 8 relationship commands — `Link Concept Design`, `Link Concept Bead Relationship End`, `Link Typed By Concept Bead`, `Link Is A Concept Bead`, `Link Concept Bead Attribute Link`, `Link Concept Bead Extension`, `Link Solution Component Port`, `Link Solution Port Delegation` (`Detach`/`Unlink`/`Remove` variants auto-derived from each `Link` command). Routed through the existing generic `SolutionArchitectProcessor`/`SolutionLinkProcessor` (new `om_type` branches, no new processor classes needed) — auto-registered via `register_solution_architect_processors()`'s family-driven loop, same as every other Solution Architect command. - `Create Concept Model` (added 2026-08-21) creates the `ConceptModel` container `Link Concept Design` attaches elements to. No dedicated `/solution-architect/concept-models` REST endpoint exists yet, so this routes through the generic `Collection Manager` creation path instead — added `"Concept Model"` to `COLLECTION_SUBTYPES` (`md_processing_constants.py`), which per this repo's own convention is the entire wiring step (auto-routes to `CollectionManagerProcessor`'s existing generic-collection-subtype fallback, no bespoke pyegeria method needed). Live-verified end to end against a running 6.2-SNAPSHOT server: `Create Concept Model` → `Create Concept Bead` → `Link Concept Design`, with the `ConceptDesign` relationship confirmed present via direct fetch afterward. + - `Create Solution Component` carries `Deployment Status` and `User Defined Deployment Status` (0730 `deploymentStatus`/`userDefinedDeploymentStatus`, added 2026-10-01). - `Link Implemented By` (0737, added 2026-09-13): attaches a design object (Information Supply Chain, Solution Component, Governance Definition, ...) to its implementation via the `ImplementedBy` relationship — a cross-OMVS call to `governance_officer`'s `linkDesignToImplementation`/`detachDesignFromImplementation` endpoints (`Detach`/`Unlink`/`Remove` variants auto-derived, same mechanism as the Concept Model relationship commands above). Along the way, fixed a real latent bug: `_get_supply_chain_rel_elements()` read a nonexistent `implementedByList` field instead of the real `implementedBy` (confirmed against `AttributedMetadataElement.java` and a live element fetch) — this had silently made the `Implemented By` sync's as-is diff always see zero existing relationships. - **Correction, 2026-09-16:** the paragraph above's `Create Information Supply Chain`/`Create Solution Component` sync was itself wrong — `In Information Supply Chain` (on `Create Solution Component`) and `Implemented By` (on `Create Information Supply Chain`) were both syncing via `ImplementedBy` (the mechanism `Link Implemented By` legitimately uses), when `InformationSupplyChain` is a Collection subtype and these two attributes are ordinary collection membership. Reported live: 71 components each carrying `In Information Supply Chain` fanned each chain out to 143 `ImplementedBy` links, alongside the 47 correct `CollectionMembership`s from explicit `Add Member` blocks. Both sync points (`SolutionComponentProcessor._sync_all_rels`'s "Supply Chains" block and `SupplyChainProcessor._sync_rels`'s "Implemented By" block, `solution_architect.py`) now call `_async_add_to_collection`/`_async_remove_from_collection` with `CollectionMembershipProperties`, matching every other ISC membership sync in this file. Pre-existing bad `ImplementedBy` links created before this fix are not auto-cleaned — the as-is fetch now reads `collectionMembers`, not `implementedBy`, so a re-sync no longer sees or touches them; removing them is a separate one-off cleanup against affected servers. - **Lineage Linker** (added 2026-09-16): Create/update/detach the seven Lineage Linker OMVS relationship types (0750/0755/0770 — DataFlow, ControlFlow, ProcessCall, LineageMapping, DataMapping, UltimateSource, UltimateDestination) between two elements, e.g. `Link Data Flow`, `Update Process Call`, `Link Data Mapping`. One dedicated `Link`/`Update` command pair per type (each with only its own type's attributes — Guard/Mandatory Guard for `Control Flow`; Query/Query ID/Query Type for `Data Mapping`; One Way/Integration Style/Protocol/Frequency/Data Exchanged, reused from Solution Architect's `SolutionLinkingWire` attributes, for the rest), plus one shared `Unlink Lineage Relationship` command since detaching only needs the relationship's own GUID. Split from an earlier single generic command triple that used a `Lineage Relationship Type` selector attribute offering every type's attributes regardless of which type was picked. The split also surfaced and fixed a real gap: the pyegeria SDK's `DataFlowProperties`/`ProcessCallProperties`/`LineageMappingProperties`/`UltimateSourceProperties`/`UltimateDestinationProperties` (`pyegeria/omvs/lineage_linker.py`) were missing `oneWay`/`integrationStyle`/`protocol`/`frequency`/`dataExchanged` (and `ProcessCallProperties` was missing `lineNumber`) entirely — real fields on the Egeria DTOs (`DataLineageRelationshipProperties`/`ProcessCallProperties.java`) that had no home on the pydantic model, so a caller passing them would validate silently and have them dropped before serialization (the `PyegeriaModel` `extra='ignore'` gotcha — see the SDK section below). diff --git a/md_processing/data/compact_commands/commands_curation_compact.json b/md_processing/data/compact_commands/commands_curation_compact.json index e2fcca4d..0e014fd7 100644 --- a/md_processing/data/compact_commands/commands_curation_compact.json +++ b/md_processing/data/compact_commands/commands_curation_compact.json @@ -2862,7 +2862,7 @@ "OTHER" ], "existing_element": "", - "description": "Progress toward delivering the promised real-world digital resource/artifact - one of an enumerated set of values (DeploymentStatus).", + "description": "The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource.", "generated": false, "style": "Valid Value", "user_specified": true, @@ -2871,7 +2871,7 @@ "min_cardinality": 0, "max_cardinality": 1, "level": "Basic", - "Journal Entry": "Added 2026-09-30 -- Promise classification (0010), Classification Explorer setElementAsPromise." + "Journal Entry": "2026-10-01: shared definition -- used by Curation (Classify Promise) and Solution Architect (Solution Component, Egeria 0730 deploymentStatus/userDefinedDeploymentStatus). Keep identical in both files (global attribute namespace)." }, "User Defined Deployment Status": { "variable_name": "user_defined_deployment_status", @@ -2881,7 +2881,7 @@ "default_value": "", "valid_values": [], "existing_element": "", - "description": "Locally defined deployment status - used when Deployment Status is OTHER.", + "description": "A locally defined deployment status - only used when Deployment Status is OTHER.", "generated": false, "style": "Simple", "user_specified": true, @@ -2890,7 +2890,7 @@ "min_cardinality": 0, "max_cardinality": 1, "level": "Domain", - "Journal Entry": "Added 2026-09-30 -- Promise classification (0010), Classification Explorer setElementAsPromise." + "Journal Entry": "2026-10-01: shared definition -- used by Curation (Classify Promise) and Solution Architect (Solution Component, Egeria 0730 deploymentStatus/userDefinedDeploymentStatus). Keep identical in both files (global attribute namespace)." }, "Start Time": { "variable_name": "start_time", diff --git a/md_processing/data/compact_commands/commands_solution_architect_compact.json b/md_processing/data/compact_commands/commands_solution_architect_compact.json index 452c71a4..2886ab77 100644 --- a/md_processing/data/compact_commands/commands_solution_architect_compact.json +++ b/md_processing/data/compact_commands/commands_solution_architect_compact.json @@ -3168,6 +3168,55 @@ "max_cardinality": 1, "level": "Domain", "Journal Entry": "Added 2026-09-13 -- ImplementedBy relationship (0737) support, requested by the user: Solution Architect had no command for governance_officer's linkDesignToImplementation/detachDesignFromImplementation, and Create Information Supply Chain had no way to record implementation elements. Confirmed live 2026-09-13 that 'Link Information Supply Chain Child' already correctly uses CollectionMembership (not ImplementedBy) -- no fix needed there. Detach/Unlink/Attach/Add/Remove variants are auto-derived by build_command_variants() from this one 'Link' definition, matching every other Solution Architect link command -- no separate Detach compact entry needed." + }, + "Deployment Status": { + "variable_name": "deployment_status", + "inUpdate": true, + "attr_labels": "", + "examples": "", + "default_value": "", + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ], + "existing_element": "", + "description": "The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource.", + "generated": false, + "style": "Valid Value", + "user_specified": true, + "unique": false, + "input_required": false, + "min_cardinality": 0, + "max_cardinality": 1, + "level": "Basic", + "Journal Entry": "2026-10-01: shared definition -- used by Curation (Classify Promise) and Solution Architect (Solution Component, Egeria 0730 deploymentStatus/userDefinedDeploymentStatus). Keep identical in both files (global attribute namespace)." + }, + "User Defined Deployment Status": { + "variable_name": "user_defined_deployment_status", + "inUpdate": true, + "attr_labels": "", + "examples": "", + "default_value": "", + "valid_values": [], + "existing_element": "", + "description": "A locally defined deployment status - only used when Deployment Status is OTHER.", + "generated": false, + "style": "Simple", + "user_specified": true, + "unique": false, + "input_required": false, + "min_cardinality": 0, + "max_cardinality": 1, + "level": "Domain", + "Journal Entry": "2026-10-01: shared definition -- used by Curation (Classify Promise) and Solution Architect (Solution Component, Egeria 0730 deploymentStatus/userDefinedDeploymentStatus). Keep identical in both files (global attribute namespace)." } }, "bundles": { @@ -3333,7 +3382,9 @@ "In Solution Components", "Planned Deployed Implementation Type", "Solution Component Type", - "Solution SubComponents" + "Solution SubComponents", + "Deployment Status", + "User Defined Deployment Status" ] }, "Solution Role Base": { @@ -3478,7 +3529,7 @@ "qn_prefix": "", "alternate_names": [], "family": "Solution Architect", - "description": "Links or unlinks two solution compoents.", + "description": "Links or unlinks two solution components.", "verb": "Link", "upsert": false, "attach": false, @@ -3677,7 +3728,7 @@ "qn_prefix": "", "alternate_names": [], "family": "Solution Architect", - "description": "Links a child compoent to a parent component.", + "description": "Links a child component to a parent component.", "verb": "Link", "upsert": false, "attach": false, diff --git a/md_processing/data/generated_format_sets.json b/md_processing/data/generated_format_sets.json index 31905ca9..dfcf6958 100644 --- a/md_processing/data/generated_format_sets.json +++ b/md_processing/data/generated_format_sets.json @@ -230033,6 +230033,33 @@ "OTHER" ] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "Category", "key": "category", @@ -230172,6 +230199,33 @@ "OTHER" ] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "Category", "key": "category", @@ -230331,6 +230385,33 @@ "format": false, "detail_spec": null, "valid_values": [] + }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] } ], "columns": [ @@ -230478,6 +230559,33 @@ "format": false, "detail_spec": null, "valid_values": [] + }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] } ] } @@ -230853,6 +230961,33 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "User Defined Status", "key": "user_defined_status", @@ -231205,6 +231340,33 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "User Defined Status", "key": "user_defined_status", @@ -231554,6 +231716,33 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "User Defined Status", "key": "user_defined_status", @@ -231914,6 +232103,33 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deployment Status", + "key": "deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [ + "PROPOSED", + "UNDER_DEVELOPMENT", + "DEVELOPMENT_COMPLETE", + "APPROVED_FOR_DEPLOYMENT", + "REJECTED", + "STANDBY", + "ACTIVE", + "DISABLED", + "FAILED", + "OTHER" + ] + }, + { + "name": "User Defined Deployment Status", + "key": "user_defined_deployment_status", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "User Defined Status", "key": "user_defined_status", diff --git a/md_processing/v2/curation.py b/md_processing/v2/curation.py index 6a0b9b46..80a9bdaf 100644 --- a/md_processing/v2/curation.py +++ b/md_processing/v2/curation.py @@ -319,9 +319,13 @@ def supports_target_element_lookup(self) -> bool: async def fetch_as_is(self) -> Optional[Dict[str, Any]]: return None - def lookup_for_lineage(self) -> bool: + def effective_for_lineage(self) -> bool: + # Declassify Promise must always see its (hidden) target, even if the + # author explicitly set For Lineage to false. class_spec = CLASSIFICATION_METHODS.get(self.get_command_spec().get("OM_TYPE")) - return bool(class_spec and class_spec.declassify_for_lineage and self.command.verb == "Declassify") + if class_spec and class_spec.declassify_for_lineage and self.command.verb == "Declassify": + return True + return super().effective_for_lineage() async def apply_changes(self) -> str: verb = self.command.verb diff --git a/md_processing/v2/processors.py b/md_processing/v2/processors.py index 23dffeec..4a760e0e 100644 --- a/md_processing/v2/processors.py +++ b/md_processing/v2/processors.py @@ -9,14 +9,14 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional, Union from loguru import logger -from pyegeria import EgeriaTech, PyegeriaException, PyegeriaTimeoutException, NO_ELEMENTS_FOUND, print_basic_exception +from pyegeria import EgeriaTech, PyegeriaException, PyegeriaTimeoutException, NO_ELEMENTS_FOUND, print_basic_exception, lineage_visible from pyegeria.core.utils import make_format_set_name_from_type from pyegeria.view.base_report_formats import select_report_spec from pyegeria.view.output_formatter import generate_output, format_for_markdown_table, populate_columns_from_properties from md_processing.v2.extraction import DrECommand from md_processing.v2.parsing import AttributeFirstParser -from md_processing.md_processing_utils.md_processing_constants import get_command_spec, resolve_command_spec +from md_processing.md_processing_utils.md_processing_constants import get_command_spec, resolve_command_spec, VIEW_VERBS from md_processing.md_processing_utils.common_md_utils import ( update_element_dictionary, get_element_dictionary, is_present, find_key_with_value ) @@ -538,11 +538,44 @@ async def _sync_parent_relationship(self, guid: str, attributes: Dict[str, Any]) logger.error(f"Error syncing parent relationship for {guid}: {e}") self.add_related_result("Parent Relationship", guid=guid, status="failure", message=str(e)) + def effective_for_lineage(self) -> bool: + """Whether this command's requests are sent with forLineage=true. + + An explicit "For Lineage" value in the command wins. Otherwise editing + commands default to True, so elements classified as Promise (visible + only to lineage requests) can still be found and changed without the + author adding For Lineage to every command; report commands (View/ + List/Run) default to False. Read from the raw command text, not the + parsed attributes, because parsing itself already issues lookups. + """ + raw = self.command.attributes.get("For Lineage") if isinstance(self.command.attributes, dict) else None + if raw is not None and str(raw).strip(): + return str(raw).strip().lower() in {"true", "yes", "1", "on"} + return self.command.verb not in VIEW_VERBS + + @staticmethod + def _ignore_memento(element: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + """Treat a logically deleted (Memento-classified) element as not found. + + Editing commands run with forLineage=true so Promise-classified elements + are visible, but that also exposes Memento elements, which must not be + updated or turn a Create into an Update. + """ + if isinstance(element, dict) and (element.get("elementHeader") or {}).get("memento"): + logger.info(f"Ignoring logically deleted (Memento) element " + f"{element['elementHeader'].get('guid')} -- treating it as not found") + return None + return element + async def execute(self) -> Dict[str, Any]: """ Orchestrate the command execution flow. Returns a dictionary containing the output markdown and execution metadata. """ + with lineage_visible(self.effective_for_lineage()): + return await self._execute() + + async def _execute(self) -> Dict[str, Any]: directive = self.context.get("directive", "process") # 1. Parse attributes using the spec-agnostic parser @@ -612,7 +645,7 @@ async def execute(self) -> Dict[str, Any]: # 1b. Handle 'display' directive if directive == "display": - self.as_is_element = await self.fetch_as_is() + self.as_is_element = self._ignore_memento(await self.fetch_as_is()) if self.as_is_element: output = await self.render_result_markdown(self.as_is_element.get('elementHeader', {}).get('guid')) else: @@ -659,7 +692,7 @@ async def execute(self) -> Dict[str, Any]: # We do this BEFORE recording in planned_elements to avoid self-shadowing # (where an element sees itself in 'planned' and skips the Egeria lookup) if self.supports_target_element_lookup(): - self.as_is_element = await self.fetch_as_is() + self.as_is_element = self._ignore_memento(await self.fetch_as_is()) else: self.as_is_element = None @@ -1390,10 +1423,10 @@ async def fetch_element(self, guid: str, _max_timeout_retries: int = 2) -> Optio return None def lookup_for_lineage(self) -> bool: - """Whether reference lookups for this command should include elements only visible to - lineage requests (forLineage=true) -- e.g. Declassify Promise, whose target is hidden - from normal queries once promised. Overridden per processor; default False.""" - return False + """Whether this command's reference lookups include elements only visible to lineage + requests. Same as effective_for_lineage(); used to keep lineage and non-lineage + lookups in separate cache entries, since they can return different results.""" + return self.effective_for_lineage() async def resolve_element_guid(self, name_or_guid: str, tech_type: Optional[str] = None) -> Optional[str]: """ @@ -1486,8 +1519,9 @@ def _cache_if_not_found(key: tuple, value: Any) -> None: # Pass 1: Try WITH type constraint (fastest, avoids ambiguity) for_lineage = self.lookup_for_lineage() # Lineage lookups see a different element set, so they get their own cache keys. + # (The forLineage flag itself is applied to the lookup's request body by + # lineage_visible(), set around execute().) lineage_key = ("forLineage",) if for_lineage else () - lineage_kwargs = {"for_lineage": True} if for_lineage else {} pass1_key = (name_or_guid, tech_type or None, *lineage_key) cached1 = query_cache.get(pass1_key, _NOT_CACHED) if cached1 is _AMBIGUOUS: @@ -1499,7 +1533,7 @@ def _cache_if_not_found(key: tuple, value: Any) -> None: res = cached1 else: try: - res = await self.client.__async_get_guid__(qualified_name=name_or_guid, display_name=name_or_guid, property_name="displayName", tech_type=tech_type or None, **lineage_kwargs) + res = await self.client.__async_get_guid__(qualified_name=name_or_guid, display_name=name_or_guid, property_name="displayName", tech_type=tech_type or None) _cache_if_not_found(pass1_key, res) except PyegeriaException as e: # Catch multiple matches error @@ -1538,7 +1572,7 @@ def _cache_if_not_found(key: tuple, value: Any) -> None: res = cached2 else: try: - res = await self.client.__async_get_guid__(qualified_name=name_or_guid, display_name=name_or_guid, property_name="displayName", **lineage_kwargs) + res = await self.client.__async_get_guid__(qualified_name=name_or_guid, display_name=name_or_guid, property_name="displayName") _cache_if_not_found(pass2_key, res) except PyegeriaException as e: if "Multiple elements found" in str(e): diff --git a/md_processing/v2/solution_architect.py b/md_processing/v2/solution_architect.py index ba9f02da..20712dda 100644 --- a/md_processing/v2/solution_architect.py +++ b/md_processing/v2/solution_architect.py @@ -197,6 +197,8 @@ async def apply_changes(self) -> str: prop_body.update({ "solutionComponentType": attributes.get('Solution Component Type', {}).get('value'), "plannedDeployedImplementationType": attributes.get('Planned Deployed Implementation Type', {}).get('value'), + "deploymentStatus": attributes.get('Deployment Status', {}).get('value'), + "userDefinedDeploymentStatus": attributes.get('User Defined Deployment Status', {}).get('value'), "canonicalName": attributes.get('Canonical Name', {}).get('value'), }) diff --git a/pyegeria/__init__.py b/pyegeria/__init__.py index f72913d4..15c6a237 100644 --- a/pyegeria/__init__.py +++ b/pyegeria/__init__.py @@ -36,6 +36,7 @@ disable_warnings(InsecureRequestWarning) from pyegeria.core._globals import (GovernanceDomains) from pyegeria.core._server_client import ServerClient +from pyegeria.core._base_platform_client import lineage_visible from pyegeria.core._exceptions import ( PyegeriaException, PyegeriaAPIException, @@ -125,6 +126,7 @@ "EgeriaConfig", "EgeriaCat", "ServerClient", + "lineage_visible", # Exceptions "PyegeriaException", "PyegeriaAPIException", diff --git a/pyegeria/core/_base_platform_client.py b/pyegeria/core/_base_platform_client.py index 6135c2b4..1a022c15 100644 --- a/pyegeria/core/_base_platform_client.py +++ b/pyegeria/core/_base_platform_client.py @@ -12,6 +12,8 @@ import json import os import threading +from contextlib import contextmanager +from contextvars import ContextVar import httpcore import httpx @@ -33,6 +35,49 @@ _validate_url_path_safe, ) +# Elements classified as Promise (not yet delivered) or Memento (logically +# deleted) are only returned to requests with forLineage=true. Inside a +# `lineage_visible()` block, every JSON request body sent by any pyegeria client +# is sent with forLineage=true, including bodies built internally by SDK helpers +# (which otherwise default it to false). Dr.Egeria uses this so its editing +# commands can see Promise-classified elements without the author setting +# For Lineage on every command. +_for_lineage_default: ContextVar[bool] = ContextVar("pyegeria_for_lineage_default", default=False) + + +@contextmanager +def lineage_visible(enabled: bool = True): + """Send every request body in this block with forLineage=true (when enabled is True).""" + token = _for_lineage_default.set(enabled) + try: + yield + finally: + _for_lineage_default.reset(token) + + +def _apply_for_lineage_default(payload: str | dict | None) -> str | dict | None: + """Set forLineage=true on a request body when inside an enabled lineage_visible() block. + + Only touches JSON objects that are request bodies (a "class" ending in + "RequestBody") or that already carry a forLineage field, so payloads that + don't take the option are sent unchanged. + """ + if not _for_lineage_default.get() or payload is None: + return payload + body = payload + if isinstance(payload, str): + try: + body = json.loads(payload) + except ValueError: + return payload + if not isinstance(body, dict) or body.get("forLineage") is True: + return payload + body_class = body.get("class") + if "forLineage" not in body and not (isinstance(body_class, str) and body_class.endswith("RequestBody")): + return payload + body = {**body, "forLineage": True} + return json.dumps(body) if isinstance(payload, str) else body + ... @@ -546,6 +591,7 @@ async def _async_make_request( ) elif request_type == "POST": + payload = _apply_for_lineage_default(payload) if payload is None: response = await self.session.post( endpoint, headers=self.headers, timeout=timeout, params = params diff --git a/pyegeria/core/_base_server_client.py b/pyegeria/core/_base_server_client.py index ccab5180..000eb07b 100644 --- a/pyegeria/core/_base_server_client.py +++ b/pyegeria/core/_base_server_client.py @@ -25,6 +25,7 @@ PyegeriaNotFoundException, PyegeriaUnauthorizedException ) from pyegeria.core._globals import enable_ssl_check, max_paging_size +from pyegeria.core._base_platform_client import _apply_for_lineage_default from pyegeria.core._validators import ( validate_name, validate_server_name, @@ -550,6 +551,7 @@ async def _async_make_request( ) elif request_type == "POST": + payload = _apply_for_lineage_default(payload) if payload is None: response = await self.session.post( endpoint, headers=self.headers, timeout=timeout, params = params diff --git a/pyegeria/view/base_report_formats.py b/pyegeria/view/base_report_formats.py index 3db7ae63..14bb6ccf 100644 --- a/pyegeria/view/base_report_formats.py +++ b/pyegeria/view/base_report_formats.py @@ -337,8 +337,8 @@ 'Software-Archive-DrE-Basic': FormatSet(target_type='Software Archive', heading='Software-Archive-DrE-Basic Attributes', description='Auto-generated format for Software Archive (Create, Basic).', family='Collections', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Purpose', key='purpose')])], action=ActionParameter(function='CollectionManager.find_collections', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'], spec_params={'metadata_element_type': 'SoftwareArchive'})), 'Solution-Blueprint-DrE-Advanced': FormatSet(target_type='Solution Blueprint', heading='Solution-Blueprint-DrE-Advanced Attributes', description='Auto-generated format for Solution Blueprint (Create, Advanced).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Role List', key='role_list'), Column(name='Solution Components', key='solution_components'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Purpose', key='purpose'), Column(name='Role List', key='role_list'), Column(name='Solution Components', key='solution_components'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='SolutionArchitect.find_solution_blueprints', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Solution-Blueprint-DrE-Basic': FormatSet(target_type='Solution Blueprint', heading='Solution-Blueprint-DrE-Basic Attributes', description='Auto-generated format for Solution Blueprint (Create, Basic).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Role List', key='role_list'), Column(name='Solution Components', key='solution_components'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Purpose', key='purpose'), Column(name='Role List', key='role_list'), Column(name='Solution Components', key='solution_components')])], action=ActionParameter(function='SolutionArchitect.find_solution_blueprints', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), - 'Solution-Component-DrE-Advanced': FormatSet(target_type='Solution Component', heading='Solution-Component-DrE-Advanced Attributes', description='Auto-generated format for Solution Component (Create, Advanced).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Actors', key='actors'), Column(name='Canonical Name', key='canonical_name'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Actors', key='actors'), Column(name='Canonical Name', key='canonical_name'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='SolutionArchitect.find_solution_components', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), - 'Solution-Component-DrE-Basic': FormatSet(target_type='Solution Component', heading='Solution-Component-DrE-Basic Attributes', description='Auto-generated format for Solution Component (Create, Basic).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Actors', key='actors'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Actors', key='actors'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components')])], action=ActionParameter(function='SolutionArchitect.find_solution_components', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), + 'Solution-Component-DrE-Advanced': FormatSet(target_type='Solution Component', heading='Solution-Component-DrE-Advanced Attributes', description='Auto-generated format for Solution Component (Create, Advanced).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Actors', key='actors'), Column(name='Canonical Name', key='canonical_name'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Deployment Status', key='deployment_status', valid_values=['PROPOSED', 'UNDER_DEVELOPMENT', 'DEVELOPMENT_COMPLETE', 'APPROVED_FOR_DEPLOYMENT', 'REJECTED', 'STANDBY', 'ACTIVE', 'DISABLED', 'FAILED', 'OTHER']), Column(name='User Defined Deployment Status', key='user_defined_deployment_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Actors', key='actors'), Column(name='Canonical Name', key='canonical_name'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Deployment Status', key='deployment_status', valid_values=['PROPOSED', 'UNDER_DEVELOPMENT', 'DEVELOPMENT_COMPLETE', 'APPROVED_FOR_DEPLOYMENT', 'REJECTED', 'STANDBY', 'ACTIVE', 'DISABLED', 'FAILED', 'OTHER']), Column(name='User Defined Deployment Status', key='user_defined_deployment_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='SolutionArchitect.find_solution_components', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), + 'Solution-Component-DrE-Basic': FormatSet(target_type='Solution Component', heading='Solution-Component-DrE-Basic Attributes', description='Auto-generated format for Solution Component (Create, Basic).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Actors', key='actors'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Deployment Status', key='deployment_status', valid_values=['PROPOSED', 'UNDER_DEVELOPMENT', 'DEVELOPMENT_COMPLETE', 'APPROVED_FOR_DEPLOYMENT', 'REJECTED', 'STANDBY', 'ACTIVE', 'DISABLED', 'FAILED', 'OTHER']), Column(name='User Defined Deployment Status', key='user_defined_deployment_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Actors', key='actors'), Column(name='In Information Supply Chain', key='in_supply_chain'), Column(name='In Solution Blueprints', key='in_solution_blueprints'), Column(name='In Solution Components', key='in_components'), Column(name='Planned Deployed Implementation Type', key='planned_deployed_impl_type'), Column(name='Solution Component Type', key='solution_component_type'), Column(name='Solution SubComponents', key='solution_sub_components'), Column(name='Deployment Status', key='deployment_status', valid_values=['PROPOSED', 'UNDER_DEVELOPMENT', 'DEVELOPMENT_COMPLETE', 'APPROVED_FOR_DEPLOYMENT', 'REJECTED', 'STANDBY', 'ACTIVE', 'DISABLED', 'FAILED', 'OTHER']), Column(name='User Defined Deployment Status', key='user_defined_deployment_status')])], action=ActionParameter(function='SolutionArchitect.find_solution_components', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Solution-Role-DrE-Advanced': FormatSet(target_type='Solution Role', heading='Solution-Role-DrE-Advanced Attributes', description='Auto-generated format for Solution Role (Create, Advanced).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Role Domain Identifier', key='role_domain_identifier', valid_values=['All Domains', 'Data', 'Privacy', 'Security', 'IT Infrastructure', 'Software Development', 'Corporate', 'Asset Management', 'Other']), Column(name='Scope', key='scope'), Column(name='Title', key='title'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Role Domain Identifier', key='role_domain_identifier', valid_values=['All Domains', 'Data', 'Privacy', 'Security', 'IT Infrastructure', 'Software Development', 'Corporate', 'Asset Management', 'Other']), Column(name='Scope', key='scope'), Column(name='Title', key='title'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='SolutionArchitect.find_solution_roles', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Solution-Role-DrE-Basic': FormatSet(target_type='Solution Role', heading='Solution-Role-DrE-Basic Attributes', description='Auto-generated format for Solution Role (Create, Basic).', family='Solution Architect', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Role Domain Identifier', key='role_domain_identifier', valid_values=['All Domains', 'Data', 'Privacy', 'Security', 'IT Infrastructure', 'Software Development', 'Corporate', 'Asset Management', 'Other']), Column(name='Scope', key='scope'), Column(name='Title', key='title'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Role Domain Identifier', key='role_domain_identifier', valid_values=['All Domains', 'Data', 'Privacy', 'Security', 'IT Infrastructure', 'Software Development', 'Corporate', 'Asset Management', 'Other']), Column(name='Scope', key='scope'), Column(name='Title', key='title')])], action=ActionParameter(function='SolutionArchitect.find_solution_roles', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Study-Project-DrE-Advanced': FormatSet(target_type='Study Project', heading='Study-Project-DrE-Advanced Attributes', description='Auto-generated format for Study Project (Create, Advanced).', family='Projects', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Actual Completion Date', key='actual_completion_date'), Column(name='Actual Start Date', key='actual_start_date'), Column(name='Mission', key='mission'), Column(name='Planned Completion Date', key='planned_completion_date'), Column(name='Planned Start Date', key='planned_start_date'), Column(name='Priority', key='priority'), Column(name='Project Approach', key='approach'), Column(name='Project Health', key='project_health'), Column(name='Project Identifier', key='project_identifier'), Column(name='Project Management Style', key='management_style'), Column(name='Project Phase', key='project_phase'), Column(name='Project Results Usage', key='results_usage'), Column(name='Project Scope', key='project_scope'), Column(name='Project Type', key='project_type', valid_values=['Project', 'Campaign', 'Task', 'PersonalProject', 'StudyProject', 'Experiment']), Column(name='Purposes', key='purposes'), Column(name='Sub-Projects', key='sub_projects'), Column(name='Success Criteria', key='success_criteria'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Project Status', key='project_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Actual Completion Date', key='actual_completion_date'), Column(name='Actual Start Date', key='actual_start_date'), Column(name='Mission', key='mission'), Column(name='Planned Completion Date', key='planned_completion_date'), Column(name='Planned Start Date', key='planned_start_date'), Column(name='Priority', key='priority'), Column(name='Project Approach', key='approach'), Column(name='Project Health', key='project_health'), Column(name='Project Identifier', key='project_identifier'), Column(name='Project Management Style', key='management_style'), Column(name='Project Phase', key='project_phase'), Column(name='Project Results Usage', key='results_usage'), Column(name='Project Scope', key='project_scope'), Column(name='Project Status', key='project_status'), Column(name='Project Type', key='project_type', valid_values=['Project', 'Campaign', 'Task', 'PersonalProject', 'StudyProject', 'Experiment']), Column(name='Purposes', key='purposes'), Column(name='Sub-Projects', key='sub_projects'), Column(name='Success Criteria', key='success_criteria'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='ProjectManager.find_projects', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'], spec_params={'include_only_classified_elements': ['StudyProject']})), diff --git a/sample-data/templates/advanced/Curation/Classify_Promise.md b/sample-data/templates/advanced/Curation/Classify_Promise.md index 81fc8753..cea40e25 100644 --- a/sample-data/templates/advanced/Curation/Classify_Promise.md +++ b/sample-data/templates/advanced/Curation/Classify_Promise.md @@ -16,7 +16,7 @@ ___ > **Attribute Type**: Simple -> **Description**: Locally defined deployment status - used when Deployment Status is OTHER. +> **Description**: A locally defined deployment status - only used when Deployment Status is OTHER. ### Start Time @@ -64,7 +64,7 @@ ___ > **Attribute Type**: Valid Value -> **Description**: Progress toward delivering the promised real-world digital resource/artifact - one of an enumerated set of values (DeploymentStatus). +> **Description**: The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource. > **Valid Values**: PROPOSED,UNDER_DEVELOPMENT,DEVELOPMENT_COMPLETE,APPROVED_FOR_DEPLOYMENT,REJECTED,STANDBY,ACTIVE,DISABLED,FAILED,OTHER diff --git a/sample-data/templates/advanced/Solution Architect/Create_Solution_Component.md b/sample-data/templates/advanced/Solution Architect/Create_Solution_Component.md index cbed10c4..3069e84a 100644 --- a/sample-data/templates/advanced/Solution Architect/Create_Solution_Component.md +++ b/sample-data/templates/advanced/Solution Architect/Create_Solution_Component.md @@ -69,6 +69,14 @@ ___ > **Description**: Solution sub-components of this component. In current approach the parent does not specify sub-components; components specify their parents instead. +### User Defined Deployment Status +> **Input Required**: False + +> **Attribute Type**: Simple + +> **Description**: A locally defined deployment status - only used when Deployment Status is OTHER. + + ### Journal Entry > **Input Required**: False @@ -169,6 +177,16 @@ ___ > **Description**: The authors. +### Deployment Status +> **Input Required**: False + +> **Attribute Type**: Valid Value + +> **Description**: The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource. + +> **Valid Values**: PROPOSED,UNDER_DEVELOPMENT,DEVELOPMENT_COMPLETE,APPROVED_FOR_DEPLOYMENT,REJECTED,STANDBY,ACTIVE,DISABLED,FAILED,OTHER + + ### Effective From > **Input Required**: False diff --git a/sample-data/templates/advanced/Solution Architect/Link_Solution_Components.md b/sample-data/templates/advanced/Solution Architect/Link_Solution_Components.md index a234a5d1..62334a2d 100644 --- a/sample-data/templates/advanced/Solution Architect/Link_Solution_Components.md +++ b/sample-data/templates/advanced/Solution Architect/Link_Solution_Components.md @@ -1,7 +1,7 @@ ___ ## Link Solution Components -> Links or unlinks two solution compoents. +> Links or unlinks two solution components. ### Component1 > **Input Required**: True diff --git a/sample-data/templates/advanced/Solution Architect/Link_SubComponent.md b/sample-data/templates/advanced/Solution Architect/Link_SubComponent.md index 26ad73ee..9e5ca1dc 100644 --- a/sample-data/templates/advanced/Solution Architect/Link_SubComponent.md +++ b/sample-data/templates/advanced/Solution Architect/Link_SubComponent.md @@ -1,7 +1,7 @@ ___ ## Link SubComponent -> Links a child compoent to a parent component. +> Links a child component to a parent component. ### Component1 > **Input Required**: True diff --git a/sample-data/templates/basic/Curation/Classify_Promise.md b/sample-data/templates/basic/Curation/Classify_Promise.md index 66edbac1..10b274f3 100644 --- a/sample-data/templates/basic/Curation/Classify_Promise.md +++ b/sample-data/templates/basic/Curation/Classify_Promise.md @@ -16,7 +16,7 @@ ___ > **Attribute Type**: Simple -> **Description**: Locally defined deployment status - used when Deployment Status is OTHER. +> **Description**: A locally defined deployment status - only used when Deployment Status is OTHER. ### Start Time @@ -64,7 +64,7 @@ ___ > **Attribute Type**: Valid Value -> **Description**: Progress toward delivering the promised real-world digital resource/artifact - one of an enumerated set of values (DeploymentStatus). +> **Description**: The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource. > **Valid Values**: PROPOSED,UNDER_DEVELOPMENT,DEVELOPMENT_COMPLETE,APPROVED_FOR_DEPLOYMENT,REJECTED,STANDBY,ACTIVE,DISABLED,FAILED,OTHER diff --git a/sample-data/templates/basic/Solution Architect/Create_Solution_Component.md b/sample-data/templates/basic/Solution Architect/Create_Solution_Component.md index 501822aa..3441d646 100644 --- a/sample-data/templates/basic/Solution Architect/Create_Solution_Component.md +++ b/sample-data/templates/basic/Solution Architect/Create_Solution_Component.md @@ -69,6 +69,14 @@ ___ > **Description**: Solution sub-components of this component. In current approach the parent does not specify sub-components; components specify their parents instead. +### User Defined Deployment Status +> **Input Required**: False + +> **Attribute Type**: Simple + +> **Description**: A locally defined deployment status - only used when Deployment Status is OTHER. + + ### Journal Entry > **Input Required**: False @@ -169,4 +177,14 @@ ___ > **Description**: The authors. +### Deployment Status +> **Input Required**: False + +> **Attribute Type**: Valid Value + +> **Description**: The deployment status of the element - one of an enumerated set of values (DeploymentStatus). On a Promise it tracks progress toward delivering the promised resource. + +> **Valid Values**: PROPOSED,UNDER_DEVELOPMENT,DEVELOPMENT_COMPLETE,APPROVED_FOR_DEPLOYMENT,REJECTED,STANDBY,ACTIVE,DISABLED,FAILED,OTHER + + ___ diff --git a/sample-data/templates/basic/Solution Architect/Link_Solution_Components.md b/sample-data/templates/basic/Solution Architect/Link_Solution_Components.md index 974cf0df..a268705f 100644 --- a/sample-data/templates/basic/Solution Architect/Link_Solution_Components.md +++ b/sample-data/templates/basic/Solution Architect/Link_Solution_Components.md @@ -1,7 +1,7 @@ ___ ## Link Solution Components -> Links or unlinks two solution compoents. +> Links or unlinks two solution components. ### Component1 > **Input Required**: True diff --git a/sample-data/templates/basic/Solution Architect/Link_SubComponent.md b/sample-data/templates/basic/Solution Architect/Link_SubComponent.md index 80a14520..121be8ee 100644 --- a/sample-data/templates/basic/Solution Architect/Link_SubComponent.md +++ b/sample-data/templates/basic/Solution Architect/Link_SubComponent.md @@ -1,7 +1,7 @@ ___ ## Link SubComponent -> Links a child compoent to a parent component. +> Links a child component to a parent component. ### Component1 > **Input Required**: True diff --git a/tests/micro-tests/test_curation_promise.py b/tests/micro-tests/test_curation_promise.py index 19f2aaf3..8b78103e 100644 --- a/tests/micro-tests/test_curation_promise.py +++ b/tests/micro-tests/test_curation_promise.py @@ -3,15 +3,18 @@ CurationClassifyProcessor, with a fake client. A Promise-classified element is only visible to forLineage=true requests, so -Declassify Promise must (a) resolve its Target Element with for_lineage=True -and (b) send its clear request with forLineage=true. Classify Promise, whose -target is not yet hidden, must keep using a normal lookup. +Declassify Promise must (a) resolve its Target Element with forLineage=true +and (b) send its clear request with forLineage=true -- even if the author +explicitly wrote "For Lineage: false". Editing commands default to +forLineage=true anyway (lineage_visible() around execute()); the fake client +reads that flag the same way the real request layer applies it. """ from typing import Any, Dict, List import pytest from pyegeria import NO_ELEMENTS_FOUND +from pyegeria.core._base_platform_client import _for_lineage_default from md_processing.dr_egeria import register_curation_processors from md_processing.v2.dispatcher import V2Dispatcher @@ -47,6 +50,7 @@ async def __async_get_guid__(self, qualified_name=None, display_name=None, prope name = qualified_name or display_name if name != _TARGET: return NO_ELEMENTS_FOUND + for_lineage = for_lineage or _for_lineage_default.get() self.lookups.append(for_lineage) if self.promised and not for_lineage: return NO_ELEMENTS_FOUND @@ -80,7 +84,7 @@ async def test_classify_promise_sends_promise_properties(): assert body["properties"]["class"] == "PromiseProperties" assert body["properties"]["deploymentStatus"] == "UNDER_DEVELOPMENT" assert body["properties"]["dueTime"] == "2026-12-31T00:00:00" - assert True not in client.lookups # target not hidden yet: normal lookup only + assert client.lookups and all(client.lookups) # editing commands default to forLineage=true @pytest.mark.asyncio @@ -97,3 +101,16 @@ async def test_declassify_promise_uses_lineage_lookup_and_body(): assert body["class"] == "DeleteClassificationRequestBody" assert body["forLineage"] is True assert client.lookups and all(client.lookups) + + +@pytest.mark.asyncio +async def test_declassify_promise_ignores_explicit_for_lineage_false(): + client = _FakeClient(promised=True) + results = await _dispatcher(client).dispatch_batch( + [_cmd("Declassify", {"Target Element": _TARGET, "For Lineage": "false"})], + {"directive": "process"}, + ) + + assert results[0]["status"] == "success", results[0] + [(_, body)] = client.classification_manager.clear_calls + assert body["forLineage"] is True diff --git a/tests/micro-tests/test_for_lineage_default.py b/tests/micro-tests/test_for_lineage_default.py new file mode 100644 index 00000000..b58612f1 --- /dev/null +++ b/tests/micro-tests/test_for_lineage_default.py @@ -0,0 +1,118 @@ +""" +forLineage defaults for Dr.Egeria (see lineage_visible in +pyegeria/core/_base_platform_client.py and AsyncBaseCommandProcessor. +effective_for_lineage): + +- inside lineage_visible(), every JSON request body is sent with + forLineage=true -- including bodies SDK helpers build with it false; +- editing commands default to forLineage=true so Promise-classified elements + stay visible; report commands (View/List/Run) default to false; an explicit + "For Lineage" value always wins; +- a Memento (logically deleted) element found that way is treated as absent. +""" +import json + +import pytest + +from pyegeria import lineage_visible +from pyegeria.core._base_platform_client import _apply_for_lineage_default +from pyegeria.omvs.asset_catalog import AssetCatalog + +from md_processing.v2.extraction import DrECommand +from md_processing.v2.processors import AsyncBaseCommandProcessor + + +# ---------------------------------------------------------------- request layer + +def test_bodies_untouched_outside_lineage_visible(): + body = {"class": "GetRequestBody", "forLineage": False} + assert _apply_for_lineage_default(body) is body + + +@pytest.mark.parametrize("body", [ + {"class": "GetRequestBody", "forLineage": False}, # SDK helper default + {"class": "UpdateElementRequestBody"}, # request body without the field + {"class": "FindPropertyNameProperties", "forLineage": False}, # name lookup body +]) +def test_request_bodies_get_for_lineage_true(body): + with lineage_visible(): + assert _apply_for_lineage_default(body)["forLineage"] is True + assert json.loads(_apply_for_lineage_default(json.dumps(body)))["forLineage"] is True + + +@pytest.mark.parametrize("payload", [{"class": "SomeProperties", "name": "x"}, {"name": "x"}, "not json"]) +def test_other_payloads_unchanged(payload): + with lineage_visible(): + assert _apply_for_lineage_default(payload) == payload + + +def test_lineage_visible_false_leaves_bodies_alone(): + with lineage_visible(False): + body = {"class": "GetRequestBody", "forLineage": False} + assert _apply_for_lineage_default(body) is body + + +class _Response: + status_code = 200 + + def raise_for_status(self): + pass + + def json(self): + return {"class": "VoidResponse", "relatedHTTPCode": 200} + + +class _Session: + def __init__(self): + self.sent = [] + + async def post(self, endpoint, **kwargs): + payload = kwargs.get("json", kwargs.get("content")) + self.sent.append(json.loads(payload) if isinstance(payload, str) else payload) + return _Response() + + +@pytest.mark.asyncio +async def test_post_path_applies_default(): + session = _Session() + client = AssetCatalog("view-server", "https://localhost:9443", "user", "pwd") + client.session = session + + await client._async_make_request("POST", "https://x/y", '{"class": "GetRequestBody", "forLineage": false}') + with lineage_visible(): + await client._async_make_request("POST", "https://x/y", '{"class": "GetRequestBody", "forLineage": false}') + + assert [b["forLineage"] for b in session.sent] == [False, True] + + +# ---------------------------------------------------------------- Dr.Egeria defaults + +class _Processor(AsyncBaseCommandProcessor): + async def apply_changes(self) -> str: + return "" + + +def _processor(verb: str, object_type: str, attributes: dict) -> _Processor: + return _Processor(object(), DrECommand(verb=verb, object_type=object_type, attributes=attributes, + raw_block=f"## {verb} {object_type}\n")) + + +@pytest.mark.parametrize("verb, object_type, attributes, expected", [ + ("Update", "Solution Component", {}, True), # editing: default true + ("Create", "Solution Component", {}, True), + ("Link", "Solution Components", {}, True), + ("View", "Report", {}, False), # reports: default false + ("Update", "Solution Component", {"For Lineage": "false"}, False), # explicit wins + ("View", "Report", {"For Lineage": "true"}, True), + ("Update", "Solution Component", {"For Lineage": ""}, True), # blank = not set +]) +def test_effective_for_lineage(verb, object_type, attributes, expected): + assert _processor(verb, object_type, attributes).effective_for_lineage() is expected + + +def test_memento_element_treated_as_absent(): + memento = {"elementHeader": {"guid": "g1", "memento": {"typeName": "Memento"}}} + promise = {"elementHeader": {"guid": "g2", "promise": {"typeName": "Promise"}}} + assert AsyncBaseCommandProcessor._ignore_memento(memento) is None + assert AsyncBaseCommandProcessor._ignore_memento(promise) is promise + assert AsyncBaseCommandProcessor._ignore_memento(None) is None From 96c2eac0f92aa1100fc93b1391f418bc43fa0101 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Thu, 1 Oct 2026 08:44:45 -0500 Subject: [PATCH 2/2] test: don't depend on a live Egeria server in the AssetCatalog request-body tests AssetCatalog's constructor calls check_connection(), so these tests only passed on machines with an Egeria server at localhost:9443 -- CI has none, and main has failed since #402 added test_asset_lineage_for_lineage.py. Stub check_connection and use a port nothing listens on, so any real network call fails loudly instead of silently relying on a local server. Signed-off-by: Dan Wolfson --- tests/micro-tests/test_asset_lineage_for_lineage.py | 11 +++++++---- tests/micro-tests/test_for_lineage_default.py | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/micro-tests/test_asset_lineage_for_lineage.py b/tests/micro-tests/test_asset_lineage_for_lineage.py index 21e1e276..3259f4c3 100644 --- a/tests/micro-tests/test_asset_lineage_for_lineage.py +++ b/tests/micro-tests/test_asset_lineage_for_lineage.py @@ -15,8 +15,11 @@ def json(self): return {"element": {"mermaidGraph": "graph TD"}} -def _client_capturing(bodies: list) -> AssetCatalog: - client = AssetCatalog("view-server", "https://localhost:9443", "user", "pwd") +def _client_capturing(bodies: list, monkeypatch) -> AssetCatalog: + # No live server: skip the constructor's connection check, and use a port + # nothing listens on so any real network call would fail loudly. + monkeypatch.setattr(AssetCatalog, "check_connection", lambda self: "test") + client = AssetCatalog("view-server", "https://localhost:1", "user", "pwd") async def fake_make_request(method, url, body=None, *args, **kwargs): bodies.append(json.loads(body) if isinstance(body, str) else body) @@ -28,9 +31,9 @@ async def fake_make_request(method, url, body=None, *args, **kwargs): @pytest.mark.asyncio @pytest.mark.parametrize("kwargs, expected", [({}, None), ({"for_lineage": True}, True)]) -async def test_for_lineage_in_lineage_graph_body(kwargs, expected): +async def test_for_lineage_in_lineage_graph_body(kwargs, expected, monkeypatch): bodies: list = [] - client = _client_capturing(bodies) + client = _client_capturing(bodies, monkeypatch) await client._async_get_asset_lineage_graph_by_guid("guid-1", output_format="JSON", **kwargs) diff --git a/tests/micro-tests/test_for_lineage_default.py b/tests/micro-tests/test_for_lineage_default.py index b58612f1..a9551ec7 100644 --- a/tests/micro-tests/test_for_lineage_default.py +++ b/tests/micro-tests/test_for_lineage_default.py @@ -73,9 +73,12 @@ async def post(self, endpoint, **kwargs): @pytest.mark.asyncio -async def test_post_path_applies_default(): +async def test_post_path_applies_default(monkeypatch): + # No live server: skip the constructor's connection check, and use a port + # nothing listens on so any real network call would fail loudly. + monkeypatch.setattr(AssetCatalog, "check_connection", lambda self: "test") session = _Session() - client = AssetCatalog("view-server", "https://localhost:9443", "user", "pwd") + client = AssetCatalog("view-server", "https://localhost:1", "user", "pwd") client.session = session await client._async_make_request("POST", "https://x/y", '{"class": "GetRequestBody", "forLineage": false}')