from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "plugins" / "1c")) sys.path.insert(0, str(ROOT / "plugins" / "1c" / "connector")) import adapter_1c_server as adapter_server # noqa: E402 from parser.payload import compress_payload # noqa: E402 def require(condition: bool, message: str, failures: list[str]) -> None: if not condition: failures.append(message) def run_override(action_evidence: dict[str, Any] | None) -> dict[str, Any]: adapter_server.metadata_object_modules = lambda payload: { "status": "ok", "object": {"kind": "Catalog", "name": "Номенклатура"}, "modules": [{"module_id": "ConfigCAS:ext-guid__module-guid.0", "name": "object"}], } selection = { "routine_name": "ПередЗаписью", "line_start": 1, "line_end": 3, "match_by": "routine_exact", } if action_evidence: selection.update(action_evidence) adapter_server.read_module = lambda payload: {"status": "ok", "selection": selection} return adapter_server.call_method( "metadata.resolve_overrides", { "base_id": "upo_test", "object_type": "Catalog", "object_name": "Номенклатура", "method_name": "ПередЗаписью", }, ) def run_base_override() -> dict[str, Any]: adapter_server.metadata_object_modules = lambda payload: { "status": "ok", "object": {"kind": "Catalog", "name": "Номенклатура"}, "modules": [{"module_id": "Config:object-module.0", "name": "object"}], } adapter_server.read_module = lambda payload: { "status": "ok", "selection": { "routine_name": "ПередЗаписью", "line_start": 1, "line_end": 3, "match_by": "routine_exact", }, } return adapter_server.call_method( "metadata.resolve_overrides", { "base_id": "upo_test", "object_type": "Catalog", "object_name": "Номенклатура", "method_name": "ПередЗаписью", }, ) def run_saved_state_search_with_object_name(params: dict[str, Any] | None = None) -> dict[str, Any]: module_text = "Процедура ПередЗаписью(Отказ)\nКонецПроцедуры".encode("utf-8") header = f"\r\n{len(module_text):08x} {len(module_text):08x} 7fffffff \r\n".encode("ascii") stored = compress_payload(b"prefix" + header + module_text, "raw_deflate") adapter_server.metadata_cache_lookup_row = lambda base_id, kind, name: { "guid": "owner-guid", "kind": kind, "kind_ru": "Справочник", "public_kind": "catalog", "name": name, "source": "base", } adapter_server.storage_files_list = lambda payload: { "status": "ok", "files": [{"FileName": "owner-guid__module-guid.0", "PartCount": 1, "Bytes": len(stored)}], } adapter_server.read_storage_file_bytes = lambda base_id, table, file_name, timeout_seconds=30: (stored, {"database": base_id}, None) request_payload = { "base_id": "upo_test", "tables": ["ConfigCASSave"], "object_type": "Catalog", "object_name": "Номенклатура", "query": "ПередЗаписью", "limit": 10, } request_payload.update(params or {}) return adapter_server.call_method( adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, request_payload, ) def first_action(result: dict[str, Any]) -> dict[str, Any]: chain = result.get("chain") if isinstance(result.get("chain"), list) else [] first = chain[0] if chain and isinstance(chain[0], dict) else {} action = first.get("extension_action") if isinstance(first.get("extension_action"), dict) else {} return action def run_checks() -> dict[str, Any]: failures: list[str] = [] unknown = run_override(None) unknown_action = first_action(unknown) unknown_evidence = unknown.get("write_plan_evidence") if isinstance(unknown.get("write_plan_evidence"), dict) else {} unknown_next = unknown_evidence.get("next_resolution") if isinstance(unknown_evidence.get("next_resolution"), dict) else {} unknown_next_params = unknown_next.get("params") if isinstance(unknown_next.get("params"), dict) else {} require(unknown.get("status") == "ok", "override chain with extension routine must resolve", failures) require(unknown_action.get("status") == "unknown", "extension routine without action evidence must be unknown", failures) require(unknown_action.get("operation_class") == "unknown_extension_action", "unknown action must not become replace", failures) require((unknown_evidence.get("target") or {}).get("extension_action") == unknown_action, "unknown action must be carried into write_plan_evidence target", failures) require("intent" not in unknown_evidence, "unknown action must not infer write intent", failures) require(unknown_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "write_plan_evidence must expose saved-state module resolver", failures) require(unknown_next_params.get("tables") == ["ConfigCASSave"], "extension write_plan_evidence must search ConfigCASSave", failures) controlled = run_override({"operation_class": "replace_with_control"}) controlled_action = first_action(controlled) controlled_evidence = controlled.get("write_plan_evidence") if isinstance(controlled.get("write_plan_evidence"), dict) else {} controlled_next = controlled_evidence.get("next_resolution") if isinstance(controlled_evidence.get("next_resolution"), dict) else {} controlled_next_params = controlled_next.get("params") if isinstance(controlled_next.get("params"), dict) else {} require(controlled_action.get("status") == "ok", "known extension action must be ok", failures) require(controlled_action.get("operation_class") == "replace_with_control", "replace_with_control must be preserved", failures) require(controlled_action.get("requires_control_fragment") is True, "replace_with_control must require control fragment", failures) require((controlled_evidence.get("target") or {}).get("extension_action") == controlled_action, "known action must be carried into write_plan_evidence target", failures) require((controlled_evidence.get("intent") or {}).get("operation") == "replace_with_control", "known action must infer write_plan_evidence intent", failures) require(controlled_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "known action evidence must expose saved-state module resolver", failures) require(controlled_next_params.get("query") == "ПередЗаписью", "known action resolver params must carry routine query", failures) before = run_override({"action": "вставить до"}) before_action = first_action(before) require(before_action.get("operation_class") == "insert_before", "Russian insert-before action must normalize", failures) base = run_base_override() base_action = first_action(base) base_evidence = base.get("write_plan_evidence") if isinstance(base.get("write_plan_evidence"), dict) else {} base_next = base_evidence.get("next_resolution") if isinstance(base_evidence.get("next_resolution"), dict) else {} base_next_params = base_next.get("params") if isinstance(base_next.get("params"), dict) else {} require(base_action.get("operation_class") == "base_definition", "base routine must be marked as base_definition", failures) require(base_action.get("requires_control_fragment") is False, "base routine must not require control fragment", failures) require(base_next_params.get("tables") == ["ConfigSave"], "base write_plan_evidence must search ConfigSave", failures) saved_state = run_saved_state_search_with_object_name(controlled_next_params) saved_state_owner = saved_state.get("owner_resolution") if isinstance(saved_state.get("owner_resolution"), dict) else {} saved_state_modules = saved_state.get("modules") if isinstance(saved_state.get("modules"), list) else [] saved_state_streams = saved_state_modules[0].get("streams") if saved_state_modules and isinstance(saved_state_modules[0], dict) and isinstance(saved_state_modules[0].get("streams"), list) else [] saved_state_target = saved_state_streams[0].get("write_plan_target") if saved_state_streams and isinstance(saved_state_streams[0], dict) and isinstance(saved_state_streams[0].get("write_plan_target"), dict) else {} require(saved_state.get("status") == "ok", "saved-state module search with object name must be accepted", failures) require(saved_state_owner.get("owner_guid") == "owner-guid", "saved-state module search must resolve object name to owner_guid", failures) require(bool(saved_state_modules), "saved-state module search with resolved owner must find module", failures) require(saved_state_target.get("module_ref") == "ConfigCASSave:owner-guid__module-guid.0#stream:0", "saved-state stream must expose write_plan_target.module_ref", failures) require(saved_state_target.get("expected_sha1"), "saved-state stream must expose write_plan_target.expected_sha1", failures) concrete_plan_target = { **(controlled_evidence.get("target") if isinstance(controlled_evidence.get("target"), dict) else {}), **saved_state_target, } concrete_plan_intent = { **(controlled_evidence.get("intent") if isinstance(controlled_evidence.get("intent"), dict) else {}), "control_fragment": "Процедура ПередЗаписью", "new": "Процедура ПередЗаписью(Отказ)\n\t// smoke\nКонецПроцедуры", } concrete_plan = adapter_server.call_method( adapter_server.METADATA_WRITE_PLAN_METHOD, { "base_id": "upo_test", "target": concrete_plan_target, "intent": concrete_plan_intent, "resolve_origin": False, }, ) concrete_route = concrete_plan.get("route") if isinstance(concrete_plan.get("route"), dict) else {} concrete_hint = concrete_route.get("apply_payload_hint") if isinstance(concrete_route.get("apply_payload_hint"), dict) else {} concrete_hint_payload = concrete_hint.get("payload") if isinstance(concrete_hint.get("payload"), dict) else {} require(concrete_plan.get("allowed") is True, "write_plan_evidence plus saved-state write_plan_target must produce an allowed concrete plan", failures) require(concrete_route.get("apply_method") == adapter_server.MODULE_WRITE_APPLY_METHOD, "concrete override write plan must route to module write apply", failures) require(concrete_route.get("operation_class") == "replace_with_control", "concrete override write plan must preserve extension operation class", failures) require(concrete_hint.get("ready_for_apply_method") is True, "concrete override write plan hint must be ready for apply method", failures) require(concrete_hint_payload.get("module_ref") == saved_state_target.get("module_ref"), "concrete override write plan hint must carry module_ref", failures) require(concrete_hint_payload.get("expected_sha1") == saved_state_target.get("expected_sha1"), "concrete override write plan hint must carry expected_sha1", failures) return { "schema": "onec_extension_action_contract_check.v1", "status": "ok" if not failures else "failed", "failures": failures, "checks": { "unknown_extension_action": "extension routine without action evidence stays unknown", "replace_with_control": "controlled replacement action requires control fragment", "russian_insert_before": "Russian action names normalize to stable classes", "base_definition": "base routines are not treated as extension actions", "write_plan_evidence": "override results include a ready metadata.write.plan evidence fragment", "write_plan_next_resolution": "write_plan_evidence points to the saved-state module resolver", "saved_state_module_name_selector": "saved-state module search resolves object_type/object_name to owner_guid", "saved_state_stream_write_plan_target": "saved-state module streams expose concrete metadata.write.plan target", "override_to_concrete_write_plan": "write_plan_evidence and write_plan_target compose into an allowed concrete module plan", }, } def main() -> int: parser = argparse.ArgumentParser(description="Check extension routine action contract invariants.") parser.add_argument("--print", action="store_true") args = parser.parse_args() report = run_checks() if args.print or report["status"] != "ok": print(json.dumps(report, ensure_ascii=False, indent=2)) else: print("1C extension action contract status: ok") return 0 if report["status"] == "ok" else 1 if __name__ == "__main__": raise SystemExit(main())