Compose name-first form write plans
This commit is contained in:
@@ -126,6 +126,54 @@ def first_write_plan_target_from_saved_state(result: dict[str, Any]) -> dict[str
|
||||
return None
|
||||
|
||||
|
||||
def first_public_form_write_plan_candidate(result: dict[str, Any]) -> dict[str, Any] | None:
|
||||
candidates: list[dict[str, Any]] = []
|
||||
for form in result.get("forms") or []:
|
||||
if not isinstance(form, dict):
|
||||
continue
|
||||
for match in form.get("matches") or []:
|
||||
if not isinstance(match, dict):
|
||||
continue
|
||||
selector = match.get("selector") if isinstance(match.get("selector"), dict) else {}
|
||||
if not selector.get("ref") or not selector.get("form"):
|
||||
continue
|
||||
if not any(selector.get(key) for key in ("command", "element", "attribute")):
|
||||
continue
|
||||
if any(key in selector for key in ("table", "file_name", "guid", "form_guid")):
|
||||
continue
|
||||
for prop in match.get("writable_properties") or []:
|
||||
if not isinstance(prop, dict) or prop.get("value_type") != "string":
|
||||
continue
|
||||
property_name = str(prop.get("presentation") or prop.get("property") or "").strip()
|
||||
if not property_name:
|
||||
continue
|
||||
old_value = str(prop.get("value") or "")
|
||||
normalized_property = str(prop.get("property") or "").strip().casefold()
|
||||
priority = 0 if normalized_property == "title" else (2 if normalized_property in {"id", "name"} else 1)
|
||||
candidates.append(
|
||||
{
|
||||
"priority": priority,
|
||||
"target": {
|
||||
"kind": "form",
|
||||
**{
|
||||
key: value
|
||||
for key, value in selector.items()
|
||||
if key in {"extension", "ref", "form", "command", "element", "attribute"} and value not in (None, "")
|
||||
},
|
||||
},
|
||||
"edit": {
|
||||
"property": property_name,
|
||||
"value": f"{old_value} [NAME-FIRST PLAN]",
|
||||
},
|
||||
}
|
||||
)
|
||||
if not candidates:
|
||||
return None
|
||||
candidate = sorted(candidates, key=lambda item: int(item.get("priority") or 0))[0]
|
||||
candidate.pop("priority", None)
|
||||
return candidate
|
||||
|
||||
|
||||
def live_coverage_from_steps(steps: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
def last_step(name: str) -> dict[str, Any]:
|
||||
for step in reversed(steps):
|
||||
@@ -136,6 +184,8 @@ def live_coverage_from_steps(steps: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
resolve = last_step("metadata.resolve_overrides")
|
||||
saved_state = last_step("metadata.saved_state.modules.search")
|
||||
write_plan = last_step("metadata.write.plan")
|
||||
form_search = last_step("metadata.saved_state.forms.search")
|
||||
form_write_plan = last_step("metadata.write.plan.form")
|
||||
skips = [
|
||||
{"step": str(step.get("name")), "status": step.get("status"), **({"reason": step.get("reason")} if step.get("reason") else {})}
|
||||
for step in steps
|
||||
@@ -160,6 +210,15 @@ def live_coverage_from_steps(steps: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"composed": write_plan.get("allowed") is True,
|
||||
"from_write_plan_target": bool(write_plan.get("from_write_plan_target")),
|
||||
},
|
||||
"form_write_plan_composition": {
|
||||
"attempted": bool(form_write_plan),
|
||||
"search_attempted": bool(form_search),
|
||||
"search_status": form_search.get("status"),
|
||||
"candidate": bool(form_search.get("write_plan_candidate")),
|
||||
"status": form_write_plan.get("status"),
|
||||
"composed": form_write_plan.get("allowed") is True,
|
||||
"name_first": bool(form_write_plan.get("name_first")),
|
||||
},
|
||||
"skips": skips,
|
||||
}
|
||||
|
||||
@@ -175,6 +234,18 @@ def live_write_plan_composition_required_issue(coverage: dict[str, Any]) -> dict
|
||||
}
|
||||
|
||||
|
||||
def live_form_write_plan_composition_required_issue(coverage: dict[str, Any]) -> dict[str, Any] | None:
|
||||
composition = coverage.get("form_write_plan_composition") if isinstance(coverage.get("form_write_plan_composition"), dict) else {}
|
||||
if composition.get("composed") is True and composition.get("name_first") is True:
|
||||
return None
|
||||
return {
|
||||
"code": "live_form_write_plan_composition_required",
|
||||
"status": composition.get("status"),
|
||||
"search_status": composition.get("search_status"),
|
||||
"candidate": composition.get("candidate"),
|
||||
}
|
||||
|
||||
|
||||
def live_object_summary(*sources: dict[str, Any] | None) -> dict[str, Any]:
|
||||
summary: dict[str, Any] = {}
|
||||
for source in sources:
|
||||
@@ -504,6 +575,7 @@ def build_live_report(
|
||||
transport: str = "rest",
|
||||
object_selector: dict[str, Any] | None = None,
|
||||
require_write_plan_composition: bool = False,
|
||||
require_form_write_plan_composition: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
steps: list[dict[str, Any]] = []
|
||||
issues: list[dict[str, Any]] = []
|
||||
@@ -856,11 +928,91 @@ def build_live_report(
|
||||
if code_read.get("status") not in {"ok", "summary"}:
|
||||
issues.append({"code": "live_code_read_failed", "status": code_read.get("status"), "diagnostics": code_read.get("diagnostics")})
|
||||
|
||||
if require_form_write_plan_composition:
|
||||
form_search = rpc_call(
|
||||
endpoint_url,
|
||||
"metadata.saved_state.forms.search",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"limit": 20,
|
||||
"scan_limit": 1000,
|
||||
"timeout_seconds": int(timeout),
|
||||
},
|
||||
timeout=timeout,
|
||||
transport=transport,
|
||||
session_id=session_id,
|
||||
)
|
||||
form_candidate = first_public_form_write_plan_candidate(form_search)
|
||||
steps.append(
|
||||
{
|
||||
"name": "metadata.saved_state.forms.search",
|
||||
"status": form_search.get("status"),
|
||||
"forms": int((form_search.get("counts") or {}).get("forms") or 0),
|
||||
"write_plan_candidate": bool(form_candidate),
|
||||
"include_storage": False,
|
||||
}
|
||||
)
|
||||
if form_search.get("status") != "ok":
|
||||
issues.append({"code": "live_saved_state_forms_search_failed", "status": form_search.get("status")})
|
||||
elif not form_candidate:
|
||||
steps.append(
|
||||
{
|
||||
"name": "metadata.write.plan.form",
|
||||
"status": "skipped_no_public_form_target",
|
||||
"allowed": False,
|
||||
"name_first": False,
|
||||
}
|
||||
)
|
||||
else:
|
||||
form_plan_payload = {
|
||||
"base_id": base_id,
|
||||
"target": form_candidate["target"],
|
||||
"edits": [form_candidate["edit"]],
|
||||
"resolve_origin": False,
|
||||
}
|
||||
form_plan = rpc_call(
|
||||
endpoint_url,
|
||||
"metadata.write.plan",
|
||||
form_plan_payload,
|
||||
timeout=timeout,
|
||||
transport=transport,
|
||||
session_id=session_id,
|
||||
)
|
||||
form_route = form_plan.get("route") if isinstance(form_plan.get("route"), dict) else {}
|
||||
form_hint = form_route.get("apply_payload_hint") if isinstance(form_route.get("apply_payload_hint"), dict) else {}
|
||||
form_name_resolution = form_route.get("name_resolution") if isinstance(form_route.get("name_resolution"), dict) else {}
|
||||
steps.append(
|
||||
{
|
||||
"name": "metadata.write.plan.form",
|
||||
"status": form_plan.get("status"),
|
||||
"allowed": form_plan.get("allowed"),
|
||||
"apply_method": form_route.get("apply_method"),
|
||||
"ready_for_apply_method": form_hint.get("ready_for_apply_method"),
|
||||
"name_resolution": form_name_resolution.get("status"),
|
||||
"name_first": not any(
|
||||
key in form_plan_payload["target"]
|
||||
for key in ("table", "file_name", "guid", "form_guid", "module_ref")
|
||||
),
|
||||
}
|
||||
)
|
||||
if form_plan.get("allowed") is not True:
|
||||
issues.append({"code": "live_form_write_plan_not_allowed", "status": form_plan.get("status"), "problems": form_plan.get("problems")})
|
||||
if form_route.get("apply_method") != "metadata.form.element.write_apply":
|
||||
issues.append({"code": "live_form_write_plan_apply_method_mismatch", "actual": form_route.get("apply_method")})
|
||||
if form_hint.get("ready_for_apply_method") is not True:
|
||||
issues.append({"code": "live_form_write_plan_hint_not_ready", "hint": form_hint})
|
||||
if form_name_resolution.get("status") != "resolved":
|
||||
issues.append({"code": "live_form_write_plan_name_resolution_missing", "name_resolution": form_name_resolution})
|
||||
|
||||
coverage = live_coverage_from_steps(steps)
|
||||
if require_write_plan_composition:
|
||||
strict_issue = live_write_plan_composition_required_issue(coverage)
|
||||
if strict_issue:
|
||||
issues.append(strict_issue)
|
||||
if require_form_write_plan_composition:
|
||||
strict_form_issue = live_form_write_plan_composition_required_issue(coverage)
|
||||
if strict_form_issue:
|
||||
issues.append(strict_form_issue)
|
||||
|
||||
return {
|
||||
"schema": "onec_mcp_selector_chain_live_smoke.v1",
|
||||
@@ -1037,6 +1189,7 @@ def main() -> int:
|
||||
parser.add_argument("--guid", help="Optional concrete metadata object GUID for deterministic --live checks.")
|
||||
parser.add_argument("--timeout", type=float, default=15.0, help="HTTP timeout for --live.")
|
||||
parser.add_argument("--require-write-plan-composition", action="store_true", help="Fail live smoke when saved-state lookup does not produce a concrete write_plan_target and composed metadata.write.plan.")
|
||||
parser.add_argument("--require-form-write-plan-composition", action="store_true", help="Fail live smoke unless a public saved-state form selector composes metadata.write.plan without storage identifiers.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.live and not args.base_id:
|
||||
@@ -1051,6 +1204,7 @@ def main() -> int:
|
||||
transport=args.transport,
|
||||
object_selector=object_selector or None,
|
||||
require_write_plan_composition=args.require_write_plan_composition,
|
||||
require_form_write_plan_composition=args.require_form_write_plan_composition,
|
||||
)
|
||||
else:
|
||||
report = build_report()
|
||||
|
||||
Reference in New Issue
Block a user