Files
llm/scripts/check_1c_write_plan_contract.py
T

380 lines
18 KiB
Python

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
def problem_codes(result: dict[str, Any]) -> set[str]:
return {str(problem.get("code") or "") for problem in result.get("problems") or [] if isinstance(problem, dict)}
def require(condition: bool, message: str, failures: list[str]) -> None:
if not condition:
failures.append(message)
def check_blocked_effective_form_path(failures: list[str]) -> None:
result = adapter_server.metadata_write(
{
"base_id": "upo_test",
"target": {"canonical_path": "Документ.Заказ.Форма.ФормаДокумента.КнопкаЗаписать"},
"mode": "plan",
"edits": [{"property": "Заголовок", "value": "Записать"}],
}
)
require(result.get("status") == "blocked", "effective form path must be blocked without concrete route", failures)
require(result.get("error") == "write_plan_required", "effective form path must return write_plan_required", failures)
require((result.get("next_resolution") or {}).get("method") == adapter_server.FORM_WRITE_TARGET_RESOLVE_METHOD, "form path must expose form write target resolver", failures)
hint = result.get("apply_payload_hint") if isinstance(result.get("apply_payload_hint"), dict) else {}
require(hint.get("ready_for_apply_method") is False, "selector-only form hint must not be ready for apply", failures)
def check_module_control_guard(failures: list[str]) -> None:
result = adapter_server.metadata_write(
{
"base_id": "upo_test",
"target": {
"canonical_path": "ОбщийМодуль.Интеграция.Отправить",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
},
"intent": {"operation": "replace_with_control", "new": "Сообщить(\"new\");"},
}
)
require(result.get("status") == "blocked", "replace_with_control without control fragment must be blocked", failures)
require(result.get("error") == "write_plan_blocked", "blocked control guard must return write_plan_blocked", failures)
require("missing_control_fragment" in problem_codes(result), "blocked control guard must expose missing_control_fragment", failures)
allowed = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"kind": "module",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
},
"intent": {"operation": "replace_with_control", "expected_old_contains": "old", "new": "new", "current_text": "prefix old suffix"},
}
)
require(allowed.get("allowed") is True, "replace_with_control with expected_old_contains must be allowed on concrete route", failures)
drift = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"kind": "module",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
},
"intent": {"operation": "replace_with_control", "control_fragment": "old", "new": "new", "current_text": "prefix changed suffix"},
}
)
require(drift.get("allowed") is False, "replace_with_control drift must be blocked when current_text is provided", failures)
require("control_fragment_drift" in problem_codes(drift), "replace_with_control drift must expose control_fragment_drift", failures)
def check_reference_identity(failures: list[str]) -> None:
module_plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {"kind": "module", "file_name": "object-guid__module-guid.0", "stream_index": 4},
"intent": {"operation": "replace", "old": "a", "new": "b"},
}
)
module_hint = ((module_plan.get("route") or {}).get("apply_payload_hint") or {}).get("payload") or {}
require(module_plan.get("allowed") is True, "module file_name concrete route must be allowed", failures)
require((module_plan.get("target") or {}).get("concrete_reference_field") == "file_name", "module file_name route must preserve concrete_reference_field", failures)
require(module_hint.get("file_name") == "object-guid__module-guid.0", "module hint must preserve file_name", failures)
require("module_ref" not in module_hint, "module file_name hint must not invent module_ref", failures)
form_plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {"kind": "form", "form_guid": "form-guid"},
"intent": {"operation": "property_change", "property": "Заголовок", "value": "Новый"},
}
)
form_hint = ((form_plan.get("route") or {}).get("apply_payload_hint") or {}).get("payload") or {}
require(form_plan.get("allowed") is True, "form_guid concrete route must be allowed", failures)
require((form_plan.get("target") or {}).get("concrete_reference_field") == "form_guid", "form_guid route must preserve concrete_reference_field", failures)
require(form_hint.get("form_guid") == "form-guid", "form hint must preserve form_guid", failures)
require("file_name" not in form_hint, "form_guid hint must not invent file_name", failures)
def check_reference_mismatch(failures: list[str]) -> None:
plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {"kind": "module", "form_guid": "form-guid"},
"intent": {"operation": "replace", "old": "a", "new": "b"},
}
)
require(plan.get("allowed") is False, "module target with form_guid must be blocked", failures)
require("concrete_reference_kind_mismatch" in problem_codes(plan), "module target with form_guid must expose concrete_reference_kind_mismatch", failures)
def check_provided_origin_evidence(failures: list[str]) -> None:
plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"canonical_path": "ОбщийМодуль.Интеграция.Отправить",
"origin": {"source": "configuration", "presentation": "Конфигурация", "status": "ok"},
},
"intent": {"operation": "replace", "old": "a", "new": "b"},
}
)
recommended = (plan.get("route") or {}).get("recommended_write") or {}
require((plan.get("origin_lookup") or {}).get("method") == "provided_origin_evidence", "planner must accept provided origin evidence", failures)
require(recommended.get("write_surface") == "base_saved_state", "provided configuration origin must recommend base_saved_state", failures)
require("write_route_required" in problem_codes(plan), "provided origin must still require concrete write route", failures)
cas_plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"canonical_path": "ОбщийМодуль.Интеграция.Отправить",
"origin": {"source": "cas_reference", "status": "owner_unresolved", "write_surface": "requires_owner_resolution"},
},
"intent": {"operation": "replace", "old": "a", "new": "b"},
}
)
cas_recommended = (cas_plan.get("route") or {}).get("recommended_write") or {}
require(cas_recommended.get("write_surface") == "blocked_unknown", "unresolved CAS origin must be blocked_unknown", failures)
require("blocked_unknown" in problem_codes(cas_plan), "unresolved CAS origin must expose blocked_unknown", failures)
def check_origin_ambiguity(failures: list[str]) -> None:
original_definition_find = adapter_server.metadata_definition_find
def fake_definition_find(payload: dict[str, Any]) -> dict[str, Any]:
return {
"schema": "onec_metadata_definition_find.v1",
"status": "ok",
"matches": [
{
"area": "object",
"kind": "Document",
"name": "Заказ",
"match_by": "synthetic_contract",
"location": {"presentation": "Документ.Заказ.Реквизит.КнопкаЗаписать"},
"origin": {"source": "configuration", "status": "ok"},
},
{
"area": "form",
"kind": "Document",
"name": "Заказ",
"match_by": "synthetic_contract",
"location": {"presentation": "Документ.Заказ.Форма.ФормаДокумента.КнопкаЗаписать"},
"origin": {"source": "configuration", "status": "ok"},
},
],
"counts": {"matches": 2},
}
adapter_server.metadata_definition_find = fake_definition_find
try:
plan = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {"canonical_path": "Документ.Заказ.Форма.ФормаДокумента.КнопкаЗаписать"},
"intent": {"operation": "property_change", "property": "Заголовок", "value": "Записать"},
}
)
finally:
adapter_server.metadata_definition_find = original_definition_find
require(plan.get("allowed") is False, "ambiguous origin plan must not be allowed", failures)
require("ambiguous_origin_matches" in problem_codes(plan), "ambiguous origin plan must expose ambiguous_origin_matches", failures)
ambiguity = next((problem for problem in plan.get("problems") or [] if isinstance(problem, dict) and problem.get("code") == "ambiguous_origin_matches"), {})
require(ambiguity.get("match_count") == 2, "ambiguous origin plan must expose match_count", failures)
def check_extension_action_evidence(failures: list[str]) -> None:
inferred = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"kind": "module",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"extension_action": {"status": "ok", "operation_class": "replace_with_control"},
},
"intent": {"control_fragment": "old", "new": "new"},
}
)
require(inferred.get("allowed") is True, "known extension action with guards must be allowed on concrete route", failures)
require((inferred.get("route") or {}).get("operation_class") == "replace_with_control", "planner must infer operation from extension_action", failures)
require((inferred.get("route") or {}).get("operation_inferred_from") == "extension_action", "planner must mark operation_inferred_from", failures)
unknown = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"kind": "module",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"extension_action": {"status": "unknown", "operation_class": "unknown_extension_action"},
},
"intent": {"new": "new"},
}
)
require(unknown.get("allowed") is False, "unknown extension action must block module write planning", failures)
require("extension_action_unknown" in problem_codes(unknown), "unknown extension action must expose extension_action_unknown", failures)
mismatch = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {
"kind": "module",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"extension_action": {"status": "ok", "operation_class": "insert_after"},
},
"intent": {"operation": "replace", "old": "old", "new": "new"},
}
)
require(mismatch.get("allowed") is False, "operation mismatch with extension action must be blocked", failures)
require("extension_action_operation_mismatch" in problem_codes(mismatch), "operation mismatch must expose extension_action_operation_mismatch", failures)
ambiguous = adapter_server.metadata_write_plan(
{
"base_id": "upo_test",
"target": {"kind": "module", "module_ref": "ConfigCASSave:common_module.0#stream:0"},
"extension_actions": [
{"status": "ok", "operation_class": "insert_before"},
{"status": "ok", "operation_class": "replace"},
],
"intent": {"operation": "replace", "old": "old", "new": "new"},
}
)
require(ambiguous.get("allowed") is False, "multiple extension actions must block module write planning", failures)
require("extension_action_ambiguous" in problem_codes(ambiguous), "multiple extension actions must expose extension_action_ambiguous", failures)
def check_apply_methods_use_write_plan_gate(failures: list[str]) -> None:
original_changes_propose = adapter_server.changes_propose
original_apply = adapter_server.storage_saved_state_apply_proposal
apply_calls: list[dict[str, Any]] = []
def fake_changes_propose(payload: dict[str, Any]) -> dict[str, Any]:
source = payload.get("source") if isinstance(payload.get("source"), dict) else {}
return {
"schema": "onec_change_proposal.v1",
"status": "accepted_for_review",
"source": {
"table": source.get("table") or "ConfigCASSave",
"file_name": source.get("file_name") or "common_module.0",
},
"original": {"sha1": "0" * 40, "bytes": 10},
"encoded": {"sha1": "1" * 40, "bytes": 11, "payload_hex": "00"},
"validation": {"status": "ok"},
}
def fake_apply(payload: dict[str, Any]) -> dict[str, Any]:
apply_calls.append(payload)
return {"schema": "onec_storage_saved_state_apply.v1", "status": "applied", "applied": True}
adapter_server.changes_propose = fake_changes_propose
adapter_server.storage_saved_state_apply_proposal = fake_apply
try:
blocked = adapter_server.metadata_module_write_apply(
{
"base_id": "upo_test",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"mode": "apply",
"allow_saved_state_write": True,
"allow_sql_saved_state_apply": True,
"operation": "replace_with_control",
"old": "old",
"new": "Сообщить(\"new\");",
}
)
require(blocked.get("status") == "blocked", "module apply must be blocked when write_plan rejects guards", failures)
require(blocked.get("error") == "write_plan_blocked", "module apply block must report write_plan_blocked", failures)
require("missing_control_fragment" in problem_codes(blocked), "module apply block must expose missing_control_fragment", failures)
require(not apply_calls, "blocked module apply must not call storage_saved_state_apply_proposal", failures)
drift_blocked = adapter_server.metadata_module_write_apply(
{
"base_id": "upo_test",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"mode": "apply",
"allow_saved_state_write": True,
"allow_sql_saved_state_apply": True,
"operation": "replace_with_control",
"control_fragment": "old",
"old": "old",
"current_text": "changed",
"new": "Сообщить(\"new\");",
}
)
require(drift_blocked.get("status") == "blocked", "module apply must be blocked when write_plan detects control drift", failures)
require(drift_blocked.get("error") == "write_plan_blocked", "module apply drift block must report write_plan_blocked", failures)
require("control_fragment_drift" in problem_codes(drift_blocked), "module apply drift block must expose control_fragment_drift", failures)
require(not apply_calls, "drift-blocked module apply must not call storage_saved_state_apply_proposal", failures)
planned = adapter_server.metadata_module_write_apply(
{
"base_id": "upo_test",
"module_ref": "ConfigCASSave:common_module.0#stream:0",
"mode": "plan",
"allow_saved_state_write": True,
"old": "old",
"new": "new",
}
)
require(planned.get("status") == "planned", "module plan with concrete route must remain planned", failures)
require((planned.get("write_plan") or {}).get("allowed") is True, "module plan must include an allowed write_plan", failures)
finally:
adapter_server.changes_propose = original_changes_propose
adapter_server.storage_saved_state_apply_proposal = original_apply
def run_checks() -> dict[str, Any]:
failures: list[str] = []
check_blocked_effective_form_path(failures)
check_module_control_guard(failures)
check_reference_identity(failures)
check_reference_mismatch(failures)
check_provided_origin_evidence(failures)
check_origin_ambiguity(failures)
check_extension_action_evidence(failures)
check_apply_methods_use_write_plan_gate(failures)
return {
"schema": "onec_write_plan_contract_check.v1",
"status": "ok" if not failures else "failed",
"failures": failures,
"checks": {
"blocked_effective_form_path": True,
"module_control_guard": True,
"reference_identity": True,
"reference_mismatch": True,
"provided_origin_evidence": True,
"origin_ambiguity": True,
"extension_action_evidence": True,
"apply_methods_use_write_plan_gate": True,
},
}
def main() -> int:
parser = argparse.ArgumentParser(description="Check local 1C metadata write-plan 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 write-plan contract status: ok")
return 0 if report["status"] == "ok" else 1
if __name__ == "__main__":
raise SystemExit(main())