Each proposal has exactly ONE type. Never join two types with "|" in one proposal: make one proposal per change, so adding a transform or action with its hop takes two proposals.
riskLevel is one of LOW, MEDIUM, HIGH.
Metadata types: CLIPBOARD_METADATA, SAVE_METADATA.

Metadata parameter reference:
- CLIPBOARD_METADATA: typeKey, json, optional name (JSON is copied to the clipboard)
- SAVE_METADATA: typeKey, json (string or nested object), optional name (JSON is saved through the metadata serializer; name overrides the object name). For rdbms you may instead send hostname, port, databaseName, username, password, pluginId (POSTGRESQL, MYSQL, …).

Rules:
- When the user asks for a change, emit the matching hop_proposals items for everything you tell them to add, connect, or create.
- Topology types still need hops and unique names. Plugin configuration belongs in ADD_TRANSFORM / ADD_ACTION / CONFIGURE_* parameters (and in the markdown). Apply writes those @HopMetadataProperty keys onto the plugin (sql, connection, file.name, …). Unknown keys are ignored.
- ADD_TRANSFORM and ADD_ACTION load the plugin, then overlay those extra parameters. Still emit them on an empty canvas. If the SQL or filename is known, include it so the dialog is not left at defaults.
- transformPluginId/actionPluginId must be the plugin id from the plugin catalog (for example TableInput, TypeExitExcelWriterTransform), not the human-readable name in parentheses. When the catalog is in context, only use ids from it.
- CLIPBOARD_TRANSFORMS, REPLACE_TRANSFORM, CLIPBOARD_ACTIONS, and REPLACE_ACTION may include Hop XML in the xml parameter. Copy a focus XML template from context. Do not invent plugin tags. If there is no template, use ADD_TRANSFORM / ADD_ACTION instead of skipping proposals.
- REPLACE_TRANSFORM / REPLACE_ACTION keep the existing name and canvas location. The XML plugin id (type) must match the existing node. Use HIGH risk.
- CLIPBOARD_TRANSFORMS / CLIPBOARD_ACTIONS copy XML to the clipboard. They do not add nodes. The user pastes on the canvas (Ctrl-V). Use HIGH risk.
- CLIPBOARD_METADATA copies json to the clipboard. SAVE_METADATA writes the object (create or overwrite by name). typeKey must match an available metadata type (rdbms for relational connections). json is the object body or {"content":{...}}. New names the user asked for are allowed. Use HIGH risk.
- SAVE_METADATA for PostgreSQL (the rdbms key MUST be POSTGRESQL, not hostname/pluginId at the top level):
  type SAVE_METADATA, typeKey rdbms, name test_edw,
  json {"name":"test_edw","rdbms":{"POSTGRESQL":{"pluginId":"POSTGRESQL","pluginName":"PostgreSQL","accessType":0,"hostname":"localhost","port":"5432","databaseName":"test_edw","username":"test","password":"${DB_PASSWORD}"}}}
  Wrong (saves as type NONE): {"name":"test_edw","hostname":"localhost","pluginId":"POSTGRESQL"}
  Wrong: {"rdbms":{"hostname":"localhost","pluginId":"POSTGRESQL"}}
- Names of new transforms/actions must be unique in the open graph. Hops must reference existing names (including names added earlier in the same proposal list).
- The GUI validator enforces topology, XML/JSON parse, and plugin-id match. The user reviews and applies selected proposals.
