diff --git a/plugins/1c/connector/adapter_1c_server.py b/plugins/1c/connector/adapter_1c_server.py index d2c26ab..9c7e75b 100644 --- a/plugins/1c/connector/adapter_1c_server.py +++ b/plugins/1c/connector/adapter_1c_server.py @@ -770,7 +770,7 @@ METHODS = [ {"name": "metadata.form.command_button.write", "transport": "POST /rpc", "description": "Plan/apply a form command workflow: optional BSL handler routine upsert plus form command and visible command button append. Recognizes CommonForm/top-level common forms and object-owned forms; prepares saved-state when needed. XML is analysis/learning input only, not the live adapter write transport."}, {"name": "metadata.form.command_button.verify", "transport": "POST /rpc", "description": "Read-only verification for a saved-state form command workflow: command, visible button, embedded handler routine, command-handler link, and button-command link. Accepts the same public form selectors as metadata.form.command_button.write."}, {"name": "metadata.module.write_apply", "transport": "POST /rpc", "description": "Orchestrate saved-state BSL module stream writes from module_ref/table/file_name: plan, apply, or apply_and_rollback. Requires explicit saved-state write/apply gates."}, - {"name": "metadata.write.plan", "transport": "POST /rpc", "description": "Read-only metadata write planner. Resolves full 1C paths or concrete saved-state references, reports layer/provenance requirements, and never applies changes."}, + {"name": "metadata.write.plan", "transport": "POST /rpc", "description": "Read-only name-first metadata write planner. For forms pass target.kind=form with extension/ref/form and element, command, or attribute plus edits; for modules reuse the public write_plan_target from metadata.saved_state.modules.search. The adapter resolves saved-state SQL handles internally, reports layer/provenance requirements, and never applies changes."}, {"name": "metadata.write.preflight", "transport": "POST /rpc", "description": "Read-only preflight for high-level writes. Combines metadata.write.plan with live saved-state verification and reports ready, needs_prepare, needs_resolution, or blocked before any write."}, {"name": "metadata.write.capabilities", "transport": "POST /rpc", "description": "Agent-facing matrix of what the adapter can read, plan, and write to the saved-state layer. SQL/storage details are hidden unless include_storage=true."}, {"name": "metadata.write", "transport": "POST /rpc", "description": "High-level metadata write orchestrator. Routes saved-state form, module, and scheduled-job schedule targets by public 1C names, builds reviewable proposals, and can apply with explicit saved-state SQL gates. Module writes support text, old/new, and routine_name/routine_text edits with expected_sha1/expected_text_sha1 guards."}, @@ -1033,6 +1033,14 @@ METHOD_INPUT_SCHEMAS["metadata.write"] = { "area": {"type": "string", "enum": ["object", "form", "module", "schedule"]}, "ref": {"type": "string", "description": "Public 1C object/member ref."}, "canonical_path": {"type": "string", "description": "Public 1C code or metadata path."}, + "extension": {"type": "string", "description": "Public extension name for an extension-owned form or module."}, + "form": {"type": "string", "description": "Exact 1C form name."}, + "element": {"type": "string", "description": "Exact 1C form element name."}, + "command": {"type": "string", "description": "Exact 1C form command name."}, + "attribute": {"type": "string", "description": "Exact 1C form attribute name."}, + "module": {"type": "string", "description": "Public module role/name returned by saved-state module search."}, + "qualified_name": {"type": "string", "description": "Public qualified form or module name returned by search."}, + "stream_ordinal": {"type": "integer", "minimum": 1, "description": "Public 1-based stream ordinal returned by saved-state module search."}, "property": {"type": "string"}, "operation": { "type": "string", @@ -1051,6 +1059,17 @@ METHOD_INPUT_SCHEMAS["metadata.write"] = { "extension": {"type": "string"}, "property": {"type": "string"}, "value": {}, + "edits": { + "type": "array", + "items": { + "type": "object", + "required": ["property", "value"], + "properties": { + "property": {"type": "string"}, + "value": {}, + }, + }, + }, "schedule": {"type": "object", "additionalProperties": True}, "template_member_ref": {"type": "string"}, "new_member_name": {"type": "string"}, @@ -1090,6 +1109,18 @@ METHOD_INPUT_SCHEMAS["metadata.write"] = { }, "mode": "plan", }, + { + "base_id": "upo_test", + "target": { + "kind": "form", + "extension": "test2", + "ref": "Catalog.test2", + "form": "t_Форма", + "command": "ЗаменаДомена", + }, + "edits": [{"property": "Заголовок", "value": "Замена домена"}], + "mode": "plan", + }, { "base_id": "upo_test", "target": { @@ -1101,6 +1132,78 @@ METHOD_INPUT_SCHEMAS["metadata.write"] = { }, ], } +METHOD_INPUT_SCHEMAS["metadata.write.plan"] = { + "type": "object", + "required": ["base_id", "target"], + "additionalProperties": True, + "properties": { + "base_id": {"type": "string", "description": "Adapter base identifier."}, + "target": { + "type": "object", + "required": ["kind"], + "additionalProperties": True, + "description": "Prefer a public name-first selector; storage handles are optional follow-up values resolved internally from names.", + "properties": { + "kind": {"type": "string", "enum": ["form", "module", "metadata"]}, + "ref": {"type": "string", "description": "Preferred public owner ref, for example Catalog.Номенклатура."}, + "extension": {"type": "string", "description": "Public extension name."}, + "form": {"type": "string", "description": "Exact 1C form name."}, + "element": {"type": "string", "description": "Exact form element name."}, + "command": {"type": "string", "description": "Exact form command name."}, + "attribute": {"type": "string", "description": "Exact form attribute name."}, + "module": {"type": "string", "description": "Public module role/name returned by metadata.saved_state.modules.search."}, + "qualified_name": {"type": "string", "description": "Public qualified name returned by saved-state search."}, + "stream_ordinal": {"type": "integer", "minimum": 1}, + "canonical_path": {"type": "string", "description": "Full public 1C metadata/code path when one is already known."}, + }, + }, + "edits": { + "type": "array", + "description": "Form property edits. Planning resolves the target but does not apply them.", + "items": { + "type": "object", + "required": ["property", "value"], + "properties": { + "property": {"type": "string", "description": "Public property presentation or canonical property name."}, + "value": {}, + }, + }, + }, + "intent": {"type": "object", "additionalProperties": True, "description": "Module or metadata operation intent."}, + "resolve_origin": {"type": "boolean", "description": "Resolve a canonical path origin when no saved-state selector is available."}, + "timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 300}, + }, + "examples": [ + { + "base_id": "upo_test", + "target": { + "kind": "form", + "extension": "test2", + "ref": "Catalog.test2", + "form": "t_Форма", + "command": "ЗаменаДомена", + }, + "edits": [{"property": "Заголовок", "value": "Замена домена"}], + "resolve_origin": False, + }, + { + "base_id": "upo_test", + "target": { + "kind": "module", + "ref": "Catalog.Номенклатура", + "module": "Модуль менеджера", + "stream_ordinal": 1, + }, + "intent": { + "operation": "replace_with_control", + "control_fragment": "Процедура Обновить()", + "new": "Процедура Обновить()\nКонецПроцедуры", + }, + "resolve_origin": False, + }, + ], +} +METHOD_INPUT_SCHEMAS["metadata.write.preflight"] = copy.deepcopy(METHOD_INPUT_SCHEMAS["metadata.write.plan"]) METHOD_INPUT_SCHEMAS["metadata.code_index.build"] = { "type": "object", "required": ["base_id"], diff --git a/tests/1c/test_payload_codec.py b/tests/1c/test_payload_codec.py index 31e02af..3afe423 100644 --- a/tests/1c/test_payload_codec.py +++ b/tests/1c/test_payload_codec.py @@ -21787,6 +21787,27 @@ def test_metadata_write_plan_reports_unresolved_public_form_selector(monkeypatch assert any(problem["code"] == "public_form_target_not_resolved" for problem in result["problems"]) +def test_metadata_write_plan_help_describes_name_first_form_and_module_targets() -> None: + methods = { + row["name"]: row + for row in adapter_server.call_method("help.methods", {})["methods"] + } + + for method_name in ("metadata.write.plan", "metadata.write.preflight"): + method = methods[method_name] + target_properties = method["input_schema"]["properties"]["target"]["properties"] + assert method["input_schema"]["required"] == ["base_id", "target"] + assert {"ref", "extension", "form", "element", "command", "attribute", "module", "qualified_name", "stream_ordinal"} <= set(target_properties) + assert method["input_schema"]["properties"]["edits"]["items"]["required"] == ["property", "value"] + examples = method["input_schema"]["examples"] + assert any((example.get("target") or {}).get("kind") == "form" and (example.get("target") or {}).get("ref") for example in examples) + assert any((example.get("target") or {}).get("kind") == "module" and (example.get("target") or {}).get("module") for example in examples) + + description = methods["metadata.write.plan"]["description"] + assert "name-first" in description + assert "metadata.saved_state.modules.search" in description + + def test_metadata_write_preflight_verifies_concrete_saved_state(monkeypatch: pytest.MonkeyPatch) -> None: def fake_diff(payload: dict[str, Any]) -> dict[str, Any]: assert payload["module_ref"] == "ConfigCASSave:object-guid__module-guid.0#stream:4"