from __future__ import annotations

import base64
from concurrent.futures import ThreadPoolExecutor, as_completed
from copy import deepcopy
from dataclasses import dataclass, field
import json
import os
import random
import re
import string
import tempfile
import time
from typing import Any, NoReturn, cast
from urllib.parse import quote_plus, unquote_plus
from uuid import uuid4

from ..backend_client import BackendRequestContext
from ..errors import QingflowApiError, backend_code_int, backend_code_value_int, is_auth_like_error
from ..json_types import JSONObject
from ..list_type_labels import RECORD_LIST_TYPE_LABELS, SYSTEM_VIEW_ID_TO_LIST_TYPE
from ..solution.build_assembly_store import BuildAssemblyStore, default_artifacts, default_manifest
from ..solution.compiler.chart_compiler import qingbi_workspace_visible_auth
from ..solution.compiler.form_compiler import build_question, default_form_payload, default_member_auth
from ..solution.compiler.icon_utils import encode_workspace_icon_with_defaults, workspace_icon_config
from ..solution.compiler.view_compiler import VIEW_TYPE_MAP
from ..solution.executor import _build_viewgraph_questions, _compact_dict, extract_field_map
from ..solution.spec_models import FieldType, FormLayoutRowSpec, FormLayoutSectionSpec, ViewSpec
from ..tools.app_tools import AppTools
from ..tools.custom_button_tools import CustomButtonTools
from ..tools.directory_tools import DirectoryTools
from ..tools.package_tools import PackageTools
from ..tools.portal_tools import PortalTools
from ..tools.qingbi_report_tools import QingbiReportTools
from ..tools.role_tools import RoleTools
from ..tools.solution_tools import SolutionTools
from ..tools.view_tools import ViewTools
from ..tools.workflow_tools import WorkflowTools
from . import workflow_spec as workflow_spec_api
from .button_style_catalog import button_style_catalog_payload
from .models import (
    AppChartsReadResponse,
    AppFieldsReadResponse,
    AppFlowReadResponse,
    ChartGetResponse,
    AppLayoutReadResponse,
    AppReadSummaryResponse,
    AppViewsReadResponse,
    AssociatedResourcesApplyRequest,
    AssociatedResourcePartialPatch,
    AssociatedResourceUpsertPatch,
    AssociatedResourceViewConfigPatch,
    ChartApplyRequest,
    ChartMetricPatch,
    ChartPartialPatch,
    ChartUpsertPatch,
    CustomButtonsApplyRequest,
    CustomButtonPartialPatch,
    CustomButtonViewButtonBindingPatch,
    CustomButtonViewConfigPatch,
    CustomButtonMatchRulePatch,
    CustomButtonPatch,
    CustomButtonRemovePatch,
    CustomButtonUpsertPatch,
    FieldMatchMappingPatch,
    FieldPatch,
    FieldRemovePatch,
    FieldSelector,
    FieldUpdatePatch,
    FlowPlanRequest,
    FlowAssigneePatch,
    LayoutApplyMode,
    LayoutPlanRequest,
    LayoutSectionPatch,
    LayoutPreset,
    PortalApplyRequest,
    PortalGetResponse,
    PortalListResponse,
    PortalReadSummaryResponse,
    PortalSectionPatch,
    PublicDepartmentScopeMode,
    PublicExternalVisibilityMode,
    PublicFieldType,
    PublicRelationMode,
    PublicChartType,
    PublicButtonPlacement,
    PublicButtonTriggerAction,
    PublicVisibilityMode,
    PublicViewType,
    PublicViewButtonConfigType,
    PublicViewButtonType,
    SchemaPlanRequest,
    VisibilityPatch,
    ViewAssociatedResourcesPatch,
    ViewActionButtonPatch,
    ViewButtonBindingPatch,
    ViewPartialPatch,
    ViewUpsertPatch,
    ViewFilterOperator,
    ViewGetResponse,
    ViewsPlanRequest,
    ViewsPreset,
    FlowPreset,
    FlowNodePermissionsPatch,
    normalize_public_condition_operator,
    public_view_action_button_payload,
    public_view_partial_payload,
    public_view_upsert_payload,
)

BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT = 50
CHART_APPLY_RECOMMENDED_UPSERT_BATCH_SIZE = 8
CUSTOM_BUTTON_VIEW_CONFIG_READBACK_RETRY_SECONDS = 0.4
PORTAL_INLINE_CHART_MAX_WORKERS = 10


QUESTION_TYPE_TO_FIELD_TYPE: dict[int, str] = {
    2: FieldType.text.value,
    3: FieldType.long_text.value,
    4: FieldType.date.value,
    5: FieldType.member.value,
    6: FieldType.email.value,
    7: FieldType.phone.value,
    8: FieldType.number.value,
    10: FieldType.boolean.value,
    11: FieldType.single_select.value,
    12: FieldType.multi_select.value,
    13: FieldType.attachment.value,
    20: FieldType.q_linker.value,
    26: FieldType.code_block.value,
    18: FieldType.subtable.value,
    21: FieldType.address.value,
    22: FieldType.department.value,
    25: FieldType.relation.value,
}

FIELD_TYPE_TO_QUESTION_TYPE: dict[str, int] = {
    FieldType.text.value: 2,
    FieldType.long_text.value: 3,
    FieldType.date.value: 4,
    FieldType.datetime.value: 4,
    FieldType.member.value: 5,
    FieldType.email.value: 6,
    FieldType.phone.value: 7,
    FieldType.number.value: 8,
    FieldType.amount.value: 8,
    FieldType.boolean.value: 10,
    FieldType.single_select.value: 11,
    FieldType.multi_select.value: 12,
    FieldType.attachment.value: 13,
    FieldType.q_linker.value: 20,
    FieldType.code_block.value: 26,
    FieldType.subtable.value: 18,
    FieldType.address.value: 21,
    FieldType.department.value: 22,
    FieldType.relation.value: 25,
}

INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
    FieldType.text.value,
    FieldType.long_text.value,
    FieldType.number.value,
    FieldType.amount.value,
    FieldType.date.value,
    FieldType.datetime.value,
    FieldType.single_select.value,
    FieldType.multi_select.value,
    FieldType.boolean.value,
)

MATCH_TYPE_ACCURACY = 1
MATCH_TYPE_QUESTION = 2
JUDGE_EQUAL = 0
JUDGE_UNEQUAL = 1
JUDGE_INCLUDE = 2
JUDGE_GREATER_OR_EQUAL = 5
JUDGE_LESS_OR_EQUAL = 7
JUDGE_EQUAL_ANY = 9
JUDGE_FUZZY_MATCH = 19
JUDGE_INCLUDE_ANY = 20
DEFAULT_TYPE_RELATION = 2
DEFAULT_TYPE_FORMULA = 3
RELATION_TYPE_Q_LINKER = 2
RELATION_TYPE_CODE_BLOCK = 3

INCLUDE_ANY_FLOW_FIELD_TYPES = {
    FieldType.multi_select.value,
    FieldType.member.value,
    FieldType.department.value,
}

QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES = {
    FieldType.address.value,
    FieldType.attachment.value,
    FieldType.code_block.value,
    FieldType.q_linker.value,
    FieldType.relation.value,
    FieldType.subtable.value,
}
QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES_PUBLIC = sorted(
    {
        "address",
        "attachment",
        "code_block",
        "q_linker",
        "relation",
        "subtable",
    }
)
QUERY_CONDITION_SUPPORTED_FIELD_TYPES_PUBLIC = sorted(
    {
        "text",
        "long_text",
        "number",
        "amount",
        "date",
        "datetime",
        "single_select",
        "multi_select",
        "phone",
        "email",
        "boolean",
        "member",
        "department",
    }
)
QUERY_CONDITION_FIX_HINT = (
    "Remove this field from query_conditions. query_conditions only configures the frontend query panel for supported query fields. "
    "Use filters for fixed saved filters, and use associated-resource match_mappings for current-record relation/report matching."
)

ASSOCIATED_RESOURCE_LIMIT_TYPE_ALL = 1
ASSOCIATED_RESOURCE_LIMIT_TYPE_SELECT = 2

STABLE_PUBLIC_FLOW_NODE_TYPES = {"start", "approve", "fill", "copy", "webhook", "end"}
DISABLED_PUBLIC_FLOW_NODE_TYPES = {"branch", "condition"}


@dataclass(slots=True)
class ResolvedApp:
    app_key: str
    app_name: str
    tag_ids: list[int]


@dataclass(slots=True)
class PermissionCheckOutcome:
    block: JSONObject | None = None
    warnings: list[dict[str, Any]] = field(default_factory=list)
    details: JSONObject = field(default_factory=dict)
    verification: JSONObject = field(default_factory=dict)


@dataclass(slots=True)
class RelationHydrationResult:
    fields: list[dict[str, Any]]
    permission_outcome: PermissionCheckOutcome | None = None
    degraded_expectations: list[dict[str, Any]] = field(default_factory=list)


@dataclass(slots=True)
class VisibilityResolutionError(Exception):
    error_code: str
    message: str
    details: JSONObject = field(default_factory=dict)

    def __str__(self) -> str:
        return self.message


class AiBuilderFacade:
    def __init__(
        self,
        *,
        apps: AppTools,
        buttons: CustomButtonTools,
        packages: PackageTools,
        views: ViewTools,
        workflows: WorkflowTools,
        portals: PortalTools,
        charts: QingbiReportTools,
        roles: RoleTools,
        directory: DirectoryTools,
        solutions: SolutionTools,
    ) -> None:
        self.apps = apps
        self.buttons = buttons
        self.packages = packages
        self.views = views
        self.workflows = workflows
        self.portals = portals
        self.charts = charts
        self.roles = roles
        self.directory = directory
        self.solutions = solutions

    def _with_backend_context(self, profile: str, handler, *, tool_name: str = "搭建后台上下文"):
        return self.apps._run(profile, lambda _session_profile, context: handler(context), tool_name=tool_name)

    def _read_app_base_raw_direct(self, *, profile: str, app_key: str) -> JSONObject:
        return self._with_backend_context(
            profile,
            lambda context: self.apps.backend.request("GET", context, f"/app/{app_key}/baseInfo"),
        )

    def flow_get_schema(self, *, profile: str, schema_version: str | None = None) -> JSONObject:
        normalized_args = {"schema_version": schema_version or workflow_spec_api.DEFAULT_SCHEMA_VERSION}
        try:
            payload = self._with_backend_context(
                profile,
                lambda context: workflow_spec_api.fetch_schema(
                    self.apps.backend,
                    context,
                    schema_version=schema_version,
                ),
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "FLOW_SPEC_SCHEMA_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={},
                suggested_next_call={"tool_name": "app_flow_get_schema", "arguments": {"profile": profile, **normalized_args}},
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read workflow spec schema",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {"schema": payload},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [],
            "verification": {"schema_loaded": True},
            "verified": True,
            "schema": payload,
        }

    def flow_get(
        self,
        *,
        profile: str,
        app_key: str,
        version_id: str | None = None,
    ) -> JSONObject:
        normalized_args = {"app_key": app_key, "version_id": version_id}
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="data_manage",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        try:
            payload = self._with_backend_context(
                profile,
                lambda context: workflow_spec_api.fetch_spec(
                    self.apps.backend,
                    context,
                    app_key=app_key,
                    version_id=version_id,
                ),
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "FLOW_SPEC_GET_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        response = {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read workflow spec",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": list(permission_outcome.warnings),
            "verification": {"spec_loaded": True},
            "verified": True,
            "app_key": app_key,
        }
        if isinstance(payload, dict):
            response.update(payload)
        else:
            response["result"] = payload
        return _apply_permission_outcomes(response, permission_outcome)

    def flow_apply(
        self,
        *,
        profile: str,
        app_key: str,
        spec: dict[str, Any],
        publish: bool = True,
        idempotency_key: str | None = None,
        schema_version: str | None = None,
    ) -> JSONObject:
        normalized_args = {
            "app_key": app_key,
            "spec": spec,
            "publish": publish,
            "idempotency_key": idempotency_key,
            "schema_version": schema_version or workflow_spec_api.DEFAULT_SCHEMA_VERSION,
        }
        apply_started_at = time.perf_counter()
        permission_ms = 0
        flow_spec_apply_ms = 0
        flow_spec_verify_ms = 0
        publish_ms = 0
        permission_outcomes: list[PermissionCheckOutcome] = []
        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        def attach_duration(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                flow_spec_apply_ms=flow_spec_apply_ms,
                flow_spec_verify_ms=flow_spec_verify_ms,
                publish_ms=publish_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        def finish(response: JSONObject) -> JSONObject:
            return finalize(attach_duration(response))

        def finish_with_publish(response: JSONObject) -> JSONObject:
            nonlocal publish_ms
            publish_started_at = time.perf_counter()
            published_response = self._append_publish_result(profile=profile, app_key=app_key, publish=publish, response=response)
            publish_ms += _duration_ms(publish_started_at)
            return finish(published_response)

        permission_started_at = time.perf_counter()
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="data_manage",
            normalized_args=normalized_args,
        )
        permission_ms += _duration_ms(permission_started_at)
        if permission_outcome.block is not None:
            return finish(permission_outcome.block)
        permission_outcomes.append(permission_outcome)

        if not isinstance(spec, dict) or not spec:
            return finish(
                _failed(
                    "FLOW_SPEC_REQUIRED",
                    "spec must be a non-empty WorkflowSpecDTO object",
                    normalized_args=normalized_args,
                    suggested_next_call={"tool_name": "app_flow_get_schema", "arguments": {"profile": profile}},
                )
            )
        apply_body = {
            "appKey": app_key,
            "idempotencyKey": idempotency_key or str(uuid4()),
            "schemaVersion": schema_version or workflow_spec_api.DEFAULT_SCHEMA_VERSION,
            "spec": spec,
        }
        try:
            flow_spec_apply_started_at = time.perf_counter()
            apply_result = self._with_backend_context(
                profile,
                lambda context: workflow_spec_api.post_apply(self.apps.backend, context, apply_body=apply_body),
            )
        except (QingflowApiError, RuntimeError) as error:
            flow_spec_apply_ms += _duration_ms(flow_spec_apply_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "FLOW_SPEC_APPLY_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key},
                    suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        flow_spec_apply_ms += _duration_ms(flow_spec_apply_started_at)
        if not isinstance(apply_result, dict):
            apply_result = {"result": apply_result}
        flow_spec_verify_started_at = time.perf_counter()
        verification, verify_warnings, verified = workflow_spec_api.verify_apply_response(
            input_spec=spec,
            apply_result=apply_result,
        )
        flow_spec_verify_ms += _duration_ms(flow_spec_verify_started_at)
        backend_status = str(apply_result.get("status") or "SUCCESS").upper()
        if backend_status not in {"SUCCESS", "OK"}:
            return finish(
                _failed(
                    "FLOW_SPEC_APPLY_FAILED",
                    str(apply_result.get("message") or "workflow spec apply failed"),
                    normalized_args=normalized_args,
                    details={"apply_result": apply_result},
                    suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        status = "success" if verified else "partial_success"
        response = {
            "status": status,
            "error_code": None if verified else "FLOW_SPEC_VERIFY_FAILED",
            "recoverable": not verified,
            "message": "applied workflow spec" if verified else "applied workflow spec; verification reported issues",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {
                "apply_result": apply_result,
                "appliedSpec": apply_result.get("appliedSpec"),
                "diffSummary": apply_result.get("diffSummary"),
                "semanticLint": apply_result.get("semanticLint"),
            },
            "request_id": None,
            "suggested_next_call": None if verified else {"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            "noop": False,
            "warnings": verify_warnings,
            "verification": verification,
            "verified": verified,
            "app_key": app_key,
            "current_version_id": apply_result.get("currentVersionId"),
            "write_executed": True,
            "write_succeeded": True,
            "safe_to_retry": False,
        }
        return finish_with_publish(response)

    def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
        requested = str(package_name or "").strip()
        if not requested:
            return _failed(
                "PACKAGE_NAME_REQUIRED",
                "package_name is required",
                suggested_next_call=None,
            )
        normalized_args = {"package_name": requested}
        try:
            listing = self.packages.package_list(profile=profile, trial_status="all", include_raw=False)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_RESOLVE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"package_name": requested},
                suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": "all"}},
            )
        items = listing.get("items") if isinstance(listing.get("items"), list) else []
        matches = [
            {
                "tag_id": item.get("tagId"),
                "tag_name": item.get("tagName"),
                "tag_icon": str(item.get("tagIcon") or "").strip() or None,
            }
            for item in items
            if isinstance(item, dict) and item.get("tagName") == requested and _coerce_positive_int(item.get("tagId")) is not None
        ]
        if not matches:
            return _failed(
                "PACKAGE_NOT_FOUND",
                f"package '{requested}' was not found",
                details={"package_name": requested, "matches": []},
                suggested_next_call=None,
            )
        if len(matches) > 1:
            return _failed(
                "AMBIGUOUS_PACKAGE",
                f"multiple packages matched '{requested}'",
                details={"package_name": requested, "matches": matches},
                suggested_next_call=None,
            )
        match = matches[0]
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "resolved package",
            "normalized_args": {"package_name": requested},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {},
            "tag_id": match["tag_id"],
            "tag_name": match["tag_name"],
            "tag_icon": match.get("tag_icon"),
            "match_mode": "exact",
        }

    def package_create(
        self,
        *,
        profile: str,
        package_name: str,
        icon: str | None = None,
        color: str | None = None,
        visibility: VisibilityPatch | None = None,
    ) -> JSONObject:
        requested = str(package_name or "").strip()
        normalized_args = {
            "package_name": requested,
            **({"icon": icon} if icon else {}),
            **({"color": color} if color else {}),
            **({"visibility": visibility.model_dump(mode="json")} if visibility is not None else {}),
        }
        if not requested:
            return _failed(
                "PACKAGE_NAME_REQUIRED",
                "package_name is required",
                normalized_args=normalized_args,
                suggested_next_call=None,
            )
        desired_tag_icon = encode_workspace_icon_with_defaults(
            icon=icon,
            color=color,
            title=requested,
            fallback_icon_name="files-folder",
        )
        try:
            desired_auth = self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
        except VisibilityResolutionError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details=error.details,
                suggested_next_call=None,
            )
        existing = self.package_resolve(profile=profile, package_name=requested)
        lookup_permission_blocked = None
        if existing.get("status") == "success":
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "package already exists",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": True,
                "verification": {"existing_package_reused": True},
                "tag_id": existing.get("tag_id"),
                "tag_name": existing.get("tag_name"),
                "tag_icon": existing.get("tag_icon"),
            }
        if existing.get("error_code") == "AMBIGUOUS_PACKAGE":
            return existing
        if existing.get("error_code") == "PACKAGE_RESOLVE_FAILED":
            existing_details = existing.get("details") if isinstance(existing.get("details"), dict) else {}
            existing_transport_error = (
                existing_details.get("transport_error") if isinstance(existing_details.get("transport_error"), dict) else {}
            )
            existing_error = QingflowApiError(
                category=str(existing_transport_error.get("category") or ""),
                message=str(existing.get("message") or ""),
                backend_code=existing.get("backend_code"),
                http_status=existing.get("http_status"),
                request_id=existing.get("request_id"),
            )
            if is_auth_like_error(existing_error) or backend_code_value_int(existing.get("backend_code")) not in {40002, 40027}:
                return existing
            lookup_permission_blocked = {
                "backend_code": existing.get("backend_code"),
                "http_status": existing.get("http_status"),
                "request_id": existing.get("request_id"),
            }
        elif existing.get("error_code") not in {"PACKAGE_NOT_FOUND"}:
            return existing
        try:
            created = self.packages.package_create(
                profile=profile,
                payload={"tagName": requested, "tagIcon": desired_tag_icon, "auth": desired_auth},
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_CREATE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={
                    "package_name": requested,
                    **({"lookup_permission_blocked": lookup_permission_blocked} if lookup_permission_blocked is not None else {}),
                },
                suggested_next_call={
                    "tool_name": "package_create",
                    "arguments": {
                        "profile": profile,
                        "package_name": requested,
                        **({"icon": icon} if icon else {}),
                        **({"color": color} if color else {}),
                    },
                },
            )
        result = created.get("result") if isinstance(created.get("result"), dict) else {}
        tag_id = _coerce_positive_int(result.get("tagId"))
        tag_name = str(result.get("tagName") or requested).strip() or requested
        tag_icon = str(result.get("tagIcon") or desired_tag_icon or "").strip() or None
        if tag_id is None:
            resolved = self.package_resolve(profile=profile, package_name=requested)
            if resolved.get("status") == "success":
                tag_id = _coerce_positive_int(resolved.get("tag_id"))
                tag_name = str(resolved.get("tag_name") or tag_name)
                tag_icon = str(resolved.get("tag_icon") or tag_icon or "").strip() or None
        verified = tag_id is not None
        return {
            "status": "success" if verified else "partial_success",
            "error_code": None,
            "recoverable": False,
            "message": "created package" if verified else "created package but could not verify tag id",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {"lookup_permission_blocked": lookup_permission_blocked} if lookup_permission_blocked is not None else {},
            "request_id": None,
            "suggested_next_call": None
            if verified
            else {"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": requested}},
            "noop": False,
            "verification": {"tag_id_verified": verified},
            "tag_id": tag_id,
            "tag_name": tag_name,
            "tag_icon": tag_icon,
            "visibility": _public_visibility_from_member_auth(desired_auth),
        }

    def package_get(self, *, profile: str, package_id: int | None = None, tag_id: int | None = None) -> JSONObject:
        effective_package_id = _coerce_positive_int(package_id if package_id is not None else tag_id)
        normalized_args = {"package_id": effective_package_id or package_id or tag_id}
        if effective_package_id is None:
            return _failed(
                "PACKAGE_ID_REQUIRED",
                "package_id must be positive",
                normalized_args=normalized_args,
                suggested_next_call=None,
            )
        base_result: JSONObject
        try:
            base_result = self.packages.package_get_base(profile=profile, tag_id=effective_package_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_GET_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"package_id": effective_package_id},
                suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": effective_package_id}},
            )

        detail_result: JSONObject | None = None
        detail_read_error: QingflowApiError | None = None
        try:
            detail_result = self.packages.package_get(profile=profile, tag_id=effective_package_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _is_optional_builder_lookup_error(api_error):
                detail_read_error = api_error
            else:
                return _failed_from_api_error(
                    "PACKAGE_GET_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"package_id": effective_package_id},
                    suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": effective_package_id}},
                )

        detail = detail_result.get("result") if isinstance(detail_result, dict) and isinstance(detail_result.get("result"), dict) else {}
        base = base_result.get("result") if isinstance(base_result.get("result"), dict) else {}
        summary = detail_result.get("summary") if isinstance(detail_result, dict) and isinstance(detail_result.get("summary"), dict) else {}
        source = detail if detail else base
        layout_tag_items = _select_package_layout_tag_items(detail=detail, base=base)
        warnings: list[JSONObject] = []
        if isinstance(detail_result, dict) and isinstance(detail_result.get("warnings"), list):
            warnings.extend(deepcopy(item) for item in detail_result.get("warnings", []) if isinstance(item, dict))
        if detail_read_error is not None:
            warnings.append(
                _warning(
                    "PACKAGE_DETAIL_READ_DEGRADED",
                    "package_get used baseInfo because the package detail endpoint was not readable",
                    backend_code=detail_read_error.backend_code,
                    http_status=detail_read_error.http_status,
                    request_id=detail_read_error.request_id,
                )
            )
        public_items = _public_package_items_from_tag_items(layout_tag_items)
        item_count = summary.get("itemCount")
        if not isinstance(item_count, int) or item_count < 0 or (item_count == 0 and public_items):
            item_count = len(public_items)
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read package",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {"package_exists": True},
            "verified": True,
            "package_id": _coerce_positive_int(source.get("tagId") or base.get("tagId")) or effective_package_id,
            "package_name": str(source.get("tagName") or base.get("tagName") or "").strip() or None,
            "icon": str(source.get("tagIcon") or base.get("tagIcon") or "").strip() or None,
            "publish_status": source.get("publishStatus") if source.get("publishStatus") is not None else base.get("publishStatus"),
            "item_count": item_count,
            "add_app_status": base.get("addAppStatus") if base.get("addAppStatus") is not None else summary.get("addAppStatus"),
            "edit_app_status": base.get("editAppStatus") if base.get("editAppStatus") is not None else summary.get("editAppStatus"),
            "del_app_status": base.get("delAppStatus") if base.get("delAppStatus") is not None else summary.get("delAppStatus"),
            "edit_tag_status": base.get("editTagStatus") if base.get("editTagStatus") is not None else summary.get("editTagStatus"),
            "visibility": _public_visibility_from_member_auth(base.get("auth") or source.get("auth")),
            "items": public_items,
        }

    def package_apply(
        self,
        *,
        profile: str,
        package_id: int | None = None,
        package_name: str | None = None,
        icon: str | None = None,
        color: str | None = None,
        visibility: VisibilityPatch | None = None,
        items: list[dict[str, Any]] | None = None,
        allow_detach: bool = False,
    ) -> JSONObject:
        requested_name = str(package_name or "").strip()
        normalized_args: JSONObject = {
            "package_id": package_id,
            **({"package_name": requested_name} if requested_name else {}),
            **({"icon": icon} if icon else {}),
            **({"color": color} if color else {}),
            **({"visibility": visibility.model_dump(mode="json")} if visibility is not None else {}),
            **({"items": deepcopy(items)} if items is not None else {}),
            "allow_detach": bool(allow_detach),
        }
        effective_package_id = _coerce_positive_int(package_id)
        created = False
        create_result: JSONObject | None = None
        update_result: JSONObject | None = None
        permission_outcomes: list[PermissionCheckOutcome] = []
        duration_started_at = time.perf_counter()
        duration_breakdown_ms: dict[str, int] = {
            "package_create_ms": 0,
            "package_update_ms": 0,
            "package_items_ms": 0,
            "package_readback_ms": 0,
        }

        def finish_duration(response: JSONObject) -> JSONObject:
            duration = dict(duration_breakdown_ms)
            duration["total_ms"] = int((time.perf_counter() - duration_started_at) * 1000)
            existing_duration = response.get("duration_breakdown_ms")
            if isinstance(existing_duration, dict):
                duration.update({str(key): value for key, value in existing_duration.items()})
                duration["total_ms"] = int((time.perf_counter() - duration_started_at) * 1000)
            response["duration_breakdown_ms"] = duration
            return response

        if effective_package_id is None:
            if not requested_name:
                return _failed(
                    "PACKAGE_NAME_REQUIRED",
                    "package_name is required when creating a package without package_id",
                    normalized_args=normalized_args,
                    suggested_next_call=None,
                )
            create_started_at = time.perf_counter()
            try:
                desired_tag_icon = encode_workspace_icon_with_defaults(
                    icon=icon,
                    color=color,
                    title=requested_name,
                    fallback_icon_name="files-folder",
                )
                desired_auth = self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
                created_payload = self.packages.package_create(
                    profile=profile,
                    payload={"tagName": requested_name, "tagIcon": desired_tag_icon, "auth": desired_auth},
                )
            except VisibilityResolutionError as error:
                duration_breakdown_ms["package_create_ms"] += int((time.perf_counter() - create_started_at) * 1000)
                return finish_duration(
                    _failed(
                        error.error_code,
                        error.message,
                        normalized_args=normalized_args,
                        details=error.details,
                        suggested_next_call=None,
                    )
                )
            except (QingflowApiError, RuntimeError) as error:
                duration_breakdown_ms["package_create_ms"] += int((time.perf_counter() - create_started_at) * 1000)
                api_error = _coerce_api_error(error)
                return finish_duration(
                    _failed_from_api_error(
                        "PACKAGE_CREATE_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details={"package_name": requested_name},
                        suggested_next_call={
                            "tool_name": "package_apply",
                            "arguments": {
                                "profile": profile,
                                "package_name": requested_name,
                                **({"icon": icon} if icon else {}),
                                **({"color": color} if color else {}),
                            },
                        },
                    )
                )
            duration_breakdown_ms["package_create_ms"] += int((time.perf_counter() - create_started_at) * 1000)
            result = created_payload.get("result") if isinstance(created_payload.get("result"), dict) else {}
            tag_id = _coerce_positive_int(result.get("tagId"))
            tag_name = str(result.get("tagName") or requested_name).strip() or requested_name
            tag_icon = str(result.get("tagIcon") or desired_tag_icon or "").strip() or None
            verified = tag_id is not None
            create_result = {
                "status": "success" if verified else "partial_success",
                "error_code": None if verified else "PACKAGE_CREATE_UNVERIFIED",
                "recoverable": not verified,
                "message": "created package" if verified else "created package but could not verify package_id",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {"direct_submit": True},
                "request_id": created_payload.get("request_id") if isinstance(created_payload, dict) else None,
                "suggested_next_call": None
                if verified
                else {"tool_name": "package_get", "arguments": {"profile": profile, "package_id": tag_id}},
                "noop": False,
                "warnings": [],
                "verification": {"tag_id_verified": verified, "direct_submit": True},
                "verified": verified,
                "tag_id": tag_id,
                "tag_name": tag_name,
                "tag_icon": tag_icon,
                "visibility": _public_visibility_from_member_auth(desired_auth),
                "write_executed": True,
                "write_succeeded": True,
                "safe_to_retry": False,
            }
            if create_result.get("status") not in {"success", "partial_success"}:
                return finish_duration(_publicize_package_apply_failure(create_result, profile=profile, normalized_args=normalized_args))
            effective_package_id = _coerce_positive_int(create_result.get("tag_id") or create_result.get("package_id"))
            if effective_package_id is None:
                return finish_duration(
                    _failed(
                        "PACKAGE_CREATE_UNVERIFIED",
                        "created package but could not verify package_id",
                        normalized_args=normalized_args,
                        details={"create_result": create_result},
                        suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
                    )
                )
            normalized_args["package_id"] = effective_package_id
            created = True

        metadata_requested = bool(requested_name or icon or color or visibility is not None)
        if metadata_requested and not created:
            edit_tag_outcome = self._guard_package_permission(
                profile=profile,
                tag_id=effective_package_id,
                required_permission="edit_tag",
                normalized_args=normalized_args,
            )
            if edit_tag_outcome.block is not None:
                return edit_tag_outcome.block
            permission_outcomes.append(edit_tag_outcome)
            update_started_at = time.perf_counter()
            update_result = self.package_update(
                profile=profile,
                tag_id=effective_package_id,
                package_name=requested_name or None,
                icon=icon,
                color=color,
                visibility=visibility,
            )
            duration_breakdown_ms["package_update_ms"] += int((time.perf_counter() - update_started_at) * 1000)
            if update_result.get("status") not in {"success", "partial_success"}:
                return finish_duration(_publicize_package_apply_failure(update_result, profile=profile, normalized_args=normalized_args))

        layout_result: JSONObject | None = None
        if items is not None:
            if not isinstance(items, list):
                return finish_duration(
                    _failed(
                        "PACKAGE_ITEMS_INVALID",
                        "items must be a list",
                        normalized_args=normalized_args,
                        suggested_next_call=None,
                    )
                )
            items_started_at = time.perf_counter()
            layout_result = self._apply_package_items(
                profile=profile,
                package_id=effective_package_id,
                items=items,
                allow_detach=allow_detach,
                normalized_args=normalized_args,
            )
            duration_breakdown_ms["package_items_ms"] += int((time.perf_counter() - items_started_at) * 1000)
            if layout_result.get("status") not in {"success", "partial_success"}:
                prior_package_write_executed = bool(
                    created
                    or (
                        metadata_requested
                        and isinstance(update_result, dict)
                        and update_result.get("status") in {"success", "partial_success"}
                        and not bool(update_result.get("noop"))
                    )
                )
                if prior_package_write_executed:
                    layout_details = layout_result.get("details") if isinstance(layout_result.get("details"), dict) else {}
                    partial = _post_write_readback_pending_result(
                        error_code=str(layout_result.get("error_code") or "PACKAGE_APPLY_PARTIAL"),
                        message="created or updated package, but package layout apply failed; read package before retrying",
                        normalized_args=normalized_args,
                        details={
                            "package_id": effective_package_id,
                            "layout_error_code": layout_result.get("error_code"),
                            "layout_result": layout_result,
                            **(
                                {"layout_write_error": layout_details.get("write_error")}
                                if isinstance(layout_details.get("write_error"), dict)
                                else {}
                            ),
                        },
                        suggested_next_call={
                            "tool_name": "package_get",
                            "arguments": {"profile": profile, "package_id": effective_package_id},
                        },
                        request_id=layout_result.get("request_id") if isinstance(layout_result.get("request_id"), str) else None,
                        backend_code=layout_result.get("backend_code"),
                        http_status=layout_result.get("http_status") if isinstance(layout_result.get("http_status"), int) else None,
                    )
                    partial["package_id"] = effective_package_id
                    partial["layout_failed"] = True
                    return finish_duration(_apply_permission_outcomes(partial, *permission_outcomes))
                return finish_duration(_apply_permission_outcomes(layout_result, *permission_outcomes))

        write_executed = bool(
            created
            or (
                metadata_requested
                and isinstance(update_result, dict)
                and update_result.get("status") in {"success", "partial_success"}
                and not bool(update_result.get("noop"))
            )
            or (
                items is not None
                and isinstance(layout_result, dict)
                and layout_result.get("status") in {"success", "partial_success"}
                and not bool(layout_result.get("noop"))
            )
        )
        if items is None and (created or (metadata_requested and isinstance(update_result, dict))):
            metadata_result = create_result if created else update_result
            metadata_verified = bool(metadata_result.get("verified")) if isinstance(metadata_result, dict) else False
            metadata_verification = metadata_result.get("verification") if isinstance(metadata_result, dict) and isinstance(metadata_result.get("verification"), dict) else {}
            response_verification: JSONObject = {
                "package_exists": True,
                "package_created": created,
                "layout_applied": False,
                "metadata_verified": metadata_verified,
                **deepcopy(metadata_verification),
            }
            response: JSONObject = {
                "status": "success" if metadata_verified else "partial_success",
                "error_code": None if metadata_verified else (metadata_result.get("error_code") if isinstance(metadata_result, dict) else "PACKAGE_READBACK_PENDING"),
                "recoverable": not metadata_verified,
                "message": "applied package" if metadata_verified else "applied package with unverified readback",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {
                    "metadata_result": metadata_result,
                    "final_readback_skipped": True,
                    "skip_reason": "metadata_result_already_verified" if metadata_verified else "metadata_result_is_partial",
                },
                "request_id": metadata_result.get("request_id") if isinstance(metadata_result, dict) else None,
                "suggested_next_call": None
                if metadata_verified
                else {"tool_name": "package_get", "arguments": {"profile": profile, "package_id": effective_package_id}},
                "noop": False,
                "warnings": deepcopy(metadata_result.get("warnings") or []) if isinstance(metadata_result, dict) and isinstance(metadata_result.get("warnings"), list) else [],
                "verification": response_verification,
                "verified": metadata_verified,
                "write_executed": write_executed,
                "write_succeeded": write_executed,
                "safe_to_retry": not write_executed,
            }
            if isinstance(metadata_result, dict):
                response.update(
                    {
                        key: deepcopy(value)
                        for key, value in metadata_result.items()
                        if key
                        not in {
                            "status",
                            "error_code",
                            "recoverable",
                            "message",
                            "normalized_args",
                            "missing_fields",
                            "allowed_values",
                            "details",
                            "request_id",
                            "suggested_next_call",
                            "noop",
                            "warnings",
                            "verification",
                            "verified",
                            "write_executed",
                            "write_succeeded",
                            "safe_to_retry",
                        }
                    }
                )
            return finish_duration(_apply_permission_outcomes(response, *permission_outcomes))

        layout_readback_skippable = (
            items is not None
            and not created
            and not metadata_requested
            and isinstance(layout_result, dict)
            and bool(layout_result.get("verified"))
            and isinstance(layout_result.get("details"), dict)
            and bool(layout_result["details"].get("current_read_skipped"))
        )
        if layout_readback_skippable:
            response: JSONObject = {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "applied package",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {
                    "layout_result": layout_result,
                    "final_readback_skipped": True,
                    "skip_reason": "layout_result_already_verified",
                },
                "request_id": layout_result.get("request_id") if isinstance(layout_result.get("request_id"), str) else None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": deepcopy(layout_result.get("warnings") or []) if isinstance(layout_result.get("warnings"), list) else [],
                "verification": {
                    "package_exists": True,
                    "package_created": False,
                    "layout_applied": True,
                    "layout_verified": True,
                    "metadata_verified": True,
                    "final_readback_skipped": True,
                },
                "verified": True,
                "package_id": effective_package_id,
                "write_executed": write_executed,
                "write_succeeded": write_executed,
                "safe_to_retry": not write_executed,
            }
            return finish_duration(_apply_permission_outcomes(response, *permission_outcomes))

        readback_started_at = time.perf_counter()
        verification = self.package_get(profile=profile, package_id=effective_package_id)
        duration_breakdown_ms["package_readback_ms"] += int((time.perf_counter() - readback_started_at) * 1000)
        if verification.get("status") != "success":
            if write_executed:
                return finish_duration(_apply_permission_outcomes(
                    _post_write_readback_pending_result(
                        error_code="PACKAGE_READBACK_PENDING",
                        message="applied package; final package readback is unavailable",
                        normalized_args=normalized_args,
                        details={"package_id": effective_package_id, "verification_result": verification},
                        suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": effective_package_id}},
                    ),
                    *permission_outcomes,
                ))
            return finish_duration(_apply_permission_outcomes(verification, *permission_outcomes))
        expected_visibility = None
        if visibility is not None:
            try:
                expected_visibility = _public_visibility_from_member_auth(
                    self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
                )
            except VisibilityResolutionError:
                expected_visibility = None
        metadata_verified = True
        if metadata_requested and update_result is not None:
            metadata_verified = bool(update_result.get("verified"))
        elif created and create_result is not None:
            metadata_verified = bool(create_result.get("verified"))
        layout_verified = True
        if items is not None and layout_result is not None:
            layout_verified = bool(layout_result.get("verified"))
        layout_error_code = (
            str(layout_result.get("error_code") or "").strip()
            if isinstance(layout_result, dict) and layout_result.get("error_code")
            else None
        )
        response_verification: JSONObject = {
            "package_exists": True,
            "package_created": created,
            "layout_applied": items is not None,
            "metadata_verified": metadata_verified,
            "layout_verified": layout_verified,
            "visibility_verified": None
            if expected_visibility is None
            else _visibility_matches_expected(verification.get("visibility"), expected_visibility),
        }
        if isinstance(update_result, dict):
            update_verification = update_result.get("verification")
            if isinstance(update_verification, dict):
                for key in ("package_name_verified", "package_icon_verified", "visibility_verified"):
                    if key in update_verification:
                        response_verification[key] = deepcopy(update_verification.get(key))
        response_verified = metadata_verified and layout_verified and response_verification.get("visibility_verified") is not False
        response_warnings = []
        if isinstance(layout_result, dict) and isinstance(layout_result.get("warnings"), list):
            response_warnings.extend(deepcopy(layout_result.get("warnings") or []))
        response: JSONObject = {
            "status": "success" if response_verified else "partial_success",
            "error_code": None if response_verified else layout_error_code,
            "recoverable": False,
            "message": "applied package" if response_verified else "applied package with unverified readback",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {
                **({"layout_result": layout_result} if layout_result is not None else {}),
                **(
                    {"layout_write_error": layout_result.get("details", {}).get("write_error")}
                    if isinstance(layout_result, dict)
                    and isinstance(layout_result.get("details"), dict)
                    and isinstance(layout_result.get("details", {}).get("write_error"), dict)
                    else {}
                ),
            },
            "request_id": None,
            "suggested_next_call": None,
            "noop": not (created or metadata_requested or items is not None),
            "warnings": response_warnings,
            "verification": response_verification,
            "verified": response_verified,
            "write_executed": write_executed,
            "write_succeeded": write_executed,
            "safe_to_retry": not write_executed,
            **{
                key: deepcopy(value)
                for key, value in verification.items()
                if key
                not in {
                    "status",
                    "error_code",
                    "recoverable",
                    "message",
                    "normalized_args",
                    "missing_fields",
                    "allowed_values",
                    "details",
                    "request_id",
                    "suggested_next_call",
                    "noop",
                    "warnings",
                    "verification",
                    "verified",
                }
            },
        }
        return finish_duration(_apply_permission_outcomes(response, *permission_outcomes))

    def package_update(
        self,
        *,
        profile: str,
        tag_id: int,
        package_name: str | None = None,
        icon: str | None = None,
        color: str | None = None,
        visibility: VisibilityPatch | None = None,
    ) -> JSONObject:
        normalized_args = {
            "tag_id": tag_id,
            **({"package_name": package_name} if str(package_name or "").strip() else {}),
            **({"icon": icon} if icon else {}),
            **({"color": color} if color else {}),
            **({"visibility": visibility.model_dump(mode="json")} if visibility is not None else {}),
        }
        if _coerce_positive_int(tag_id) is None:
            return _failed("TAG_ID_REQUIRED", "tag_id must be positive", normalized_args=normalized_args, suggested_next_call=None)
        try:
            current_base = self.packages.package_get_base(profile=profile, tag_id=tag_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_UPDATE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"tag_id": tag_id},
                suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "tag_id": tag_id}},
            )
        current: JSONObject | None = None
        detail_read_error: QingflowApiError | None = None
        try:
            current = self.packages.package_get(profile=profile, tag_id=tag_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _is_optional_builder_lookup_error(api_error):
                detail_read_error = api_error
            else:
                return _failed_from_api_error(
                    "PACKAGE_UPDATE_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"tag_id": tag_id},
                    suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "tag_id": tag_id}},
                )
        raw_current = current.get("result") if isinstance(current, dict) and isinstance(current.get("result"), dict) else {}
        raw_current_base = current_base.get("result") if isinstance(current_base.get("result"), dict) else {}
        warnings: list[JSONObject] = []
        if isinstance(current, dict) and isinstance(current.get("warnings"), list):
            warnings.extend(deepcopy(item) for item in current.get("warnings", []) if isinstance(item, dict))
        if detail_read_error is not None:
            warnings.append(
                _warning(
                    "PACKAGE_DETAIL_READ_DEGRADED",
                    "package_update used baseInfo because the package detail endpoint was not readable",
                    backend_code=detail_read_error.backend_code,
                    http_status=detail_read_error.http_status,
                    request_id=detail_read_error.request_id,
                )
            )
        current_name = str(raw_current.get("tagName") or raw_current_base.get("tagName") or "").strip() or None
        desired_name = str(package_name or current_name or "").strip() or current_name or "未命名应用包"
        desired_icon = encode_workspace_icon_with_defaults(
            icon=icon,
            color=color,
            title=desired_name,
            fallback_icon_name="files-folder",
            existing_icon=str(raw_current.get("tagIcon") or raw_current_base.get("tagIcon") or "").strip() or None,
        )
        try:
            desired_auth = (
                self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
                if visibility is not None
                else deepcopy(raw_current_base.get("auth") or raw_current.get("auth") or default_member_auth())
            )
        except VisibilityResolutionError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details=error.details,
                suggested_next_call=None,
            )
        try:
            update_result = self.packages.package_update(
                profile=profile,
                tag_id=tag_id,
                payload={"tagName": desired_name, "tagIcon": desired_icon, "auth": desired_auth},
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_UPDATE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"tag_id": tag_id},
                suggested_next_call={"tool_name": "package_update", "arguments": {"profile": profile, **normalized_args}},
            )
        verification = self.package_get(profile=profile, package_id=tag_id)
        if verification.get("status") != "success":
            return _post_write_readback_pending_result(
                error_code="PACKAGE_UPDATE_READBACK_PENDING",
                message="updated package; package readback is unavailable",
                normalized_args=normalized_args,
                details={"tag_id": tag_id, "package_id": tag_id, "verification_result": verification},
                suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": tag_id}},
            )
        package_name_verified = str(verification.get("package_name") or "").strip() == desired_name
        package_icon_verified = str(verification.get("icon") or "").strip() == desired_icon
        visibility_verified = _visibility_matches_expected(
            verification.get("visibility"),
            _public_visibility_from_member_auth(desired_auth),
        )
        verified = package_name_verified and package_icon_verified and visibility_verified
        return {
            "status": "success" if verified else "partial_success",
            "error_code": None,
            "recoverable": False,
            "message": "updated package" if verified else "updated package with unverified readback",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": update_result.get("request_id") if isinstance(update_result, dict) else None,
            "suggested_next_call": None if verified else {"tool_name": "package_get", "arguments": {"profile": profile, "package_id": tag_id}},
            "noop": False,
            "warnings": warnings,
            "verification": {
                "package_exists": True,
                "package_name_verified": package_name_verified,
                "package_icon_verified": package_icon_verified,
                "visibility_verified": visibility_verified,
            },
            "verified": verified,
            "write_executed": True,
            "safe_to_retry": False,
            **{
                key: deepcopy(value)
                for key, value in verification.items()
                if key
                not in {
                    "status",
                    "error_code",
                    "recoverable",
                    "message",
                    "normalized_args",
                    "missing_fields",
                    "allowed_values",
                    "details",
                    "request_id",
                    "suggested_next_call",
                    "noop",
                    "warnings",
                    "verification",
                    "verified",
                }
            },
        }

    def solution_install(
        self,
        *,
        profile: str,
        solution_key: str,
        being_copy_data: bool = True,
        solution_source: str = "solutionDetail",
    ) -> JSONObject:
        requested_solution_key = str(solution_key or "").strip()
        requested_solution_source = str(solution_source or "").strip() or "solutionDetail"
        normalized_args = {
            "solution_key": requested_solution_key,
            "being_copy_data": bool(being_copy_data),
            "solution_source": requested_solution_source,
        }
        if not requested_solution_key:
            return _failed(
                "SOLUTION_KEY_REQUIRED",
                "solution_key is required",
                normalized_args=normalized_args,
                suggested_next_call=None,
            )
        try:
            created = self.apps.app_create(
                profile=profile,
                payload={
                    "solutionKey": requested_solution_key,
                    "beingCopyData": bool(being_copy_data),
                    "solutionSource": requested_solution_source,
                },
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "SOLUTION_INSTALL_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"solution_key": requested_solution_key},
                suggested_next_call={"tool_name": "solution_install", "arguments": {"profile": profile, **normalized_args}},
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "installed solution",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": created.get("request_id"),
            "suggested_next_call": None,
            "noop": False,
            "verification": {},
            "write_executed": True,
            "safe_to_retry": False,
        }

    def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
        listed = self.packages.package_list(profile=profile, trial_status=trial_status, include_raw=False)
        raw_items = listed.get("items") if isinstance(listed.get("items"), list) else []
        items = [_publicize_package_list_item(item) for item in raw_items if isinstance(item, dict)]
        normalized_query = str(query or "").strip()
        if normalized_query:
            filtered_items = [item for item in items if _package_list_item_matches_query(item, normalized_query)]
        else:
            filtered_items = items
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "listed packages",
            "normalized_args": {"trial_status": trial_status, "query": normalized_query},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {},
            "trial_status": trial_status,
            "query": normalized_query,
            "items": filtered_items,
            "count": len(filtered_items),
            "matched_count": len(filtered_items),
            "unfiltered_count": len(items),
            "filter_mode": "local_packages",
            "source_shape": listed.get("source_shape"),
            "retried": bool(listed.get("retried", False)),
        }

    def _apply_package_items(
        self,
        *,
        profile: str,
        package_id: int,
        items: list[dict[str, Any]],
        allow_detach: bool,
        normalized_args: JSONObject,
    ) -> JSONObject:
        duplicate_resources = _find_duplicate_package_resources(items)
        if duplicate_resources:
            return _failed(
                "PACKAGE_LAYOUT_DUPLICATE_ITEM",
                "items contains duplicate apps or portals",
                normalized_args=normalized_args,
                details={"duplicates": [{"type": item[0], "id": item[1]} for item in duplicate_resources]},
                suggested_next_call=None,
            )

        desired_groups = _collect_public_package_group_specs(items)
        if allow_detach and not desired_groups:
            permission_outcome = self._guard_package_permission(
                profile=profile,
                tag_id=package_id,
                required_permission="edit_app",
                normalized_args=normalized_args,
            )
            if permission_outcome.block is not None:
                return permission_outcome.block
            try:
                backend_items = _backend_package_items_from_public_items(items, {})
            except ValueError as error:
                return _failed(
                    "PACKAGE_LAYOUT_INVALID",
                    str(error),
                    normalized_args=normalized_args,
                    details={"package_id": package_id},
                    suggested_next_call=None,
                )
            try:
                sort_result = self.packages.package_sort_items(profile=profile, tag_id=package_id, tag_items=backend_items)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                return _failed_from_api_error(
                    "PACKAGE_LAYOUT_SORT_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"package_id": package_id, "current_read_skipped": True},
                    suggested_next_call=None,
                )
            return _apply_permission_outcomes(
                {
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "applied package items",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {
                        "group_operations": [],
                        "sort_result": sort_result,
                        "current_read_skipped": True,
                    },
                    "request_id": sort_result.get("request_id") if isinstance(sort_result, dict) else None,
                    "suggested_next_call": None,
                    "noop": False,
                    "warnings": [],
                    "verification": {"layout_applied": True, "current_read_skipped": True},
                    "verified": True,
                    "package_id": package_id,
                },
                permission_outcome,
            )

        current_detail_result: JSONObject | None = None
        current_base_result: JSONObject | None = None
        detail_api_error: QingflowApiError | None = None
        try:
            current_detail_result = self.packages.package_get(profile=profile, tag_id=package_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as detail_error:
            detail_api_error = _coerce_api_error(detail_error)
            if not _is_optional_builder_lookup_error(detail_api_error):
                return _failed_from_api_error(
                    "PACKAGE_LAYOUT_READ_FAILED",
                    detail_api_error,
                    normalized_args=normalized_args,
                    details={"package_id": package_id},
                    suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
                )
        try:
            current_base_result = self.packages.package_get_base(profile=profile, tag_id=package_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as base_error:
            api_error = _coerce_api_error(base_error)
            if current_detail_result is None:
                return _failed_from_api_error(
                    "PACKAGE_LAYOUT_READ_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={
                        "package_id": package_id,
                        "detail_read_error": _transport_error_payload(detail_api_error),
                    },
                    suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
                )

        detail_raw = (
            current_detail_result.get("result")
            if isinstance(current_detail_result, dict) and isinstance(current_detail_result.get("result"), dict)
            else {}
        )
        base_raw = (
            current_base_result.get("result")
            if isinstance(current_base_result, dict) and isinstance(current_base_result.get("result"), dict)
            else {}
        )
        raw_tag_items = _select_package_layout_tag_items(detail=detail_raw, base=base_raw)
        if not isinstance(raw_tag_items, list):
            return _failed(
                "PACKAGE_LAYOUT_UNREADABLE",
                "package items could not be read safely",
                normalized_args=normalized_args,
                details={"package_id": package_id},
                suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
            )
        current_items = raw_tag_items

        current_group_specs = _collect_backend_package_group_specs(current_items)
        normalized_items, group_resolution_issues = _align_public_package_group_ids(items, current_group_specs=current_group_specs)
        if group_resolution_issues:
            return _failed(
                "PACKAGE_GROUP_AMBIGUOUS",
                "items contains group names that match multiple existing package groups; pass explicit group_id to disambiguate",
                normalized_args=normalized_args,
                details={"package_id": package_id, "issues": group_resolution_issues},
                suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
            )

        current_resources = _flatten_package_resource_identities(current_items, public=False)
        desired_resources = _flatten_package_resource_identities(normalized_items, public=True)
        missing_resources = sorted(current_resources - desired_resources)
        if missing_resources and not allow_detach:
            return _failed(
                "PACKAGE_LAYOUT_ITEMS_MISSING",
                "items omits existing apps or portals; pass allow_detach=true to remove them",
                normalized_args=normalized_args,
                details={"package_id": package_id, "missing_items": [{"type": item[0], "id": item[1]} for item in missing_resources]},
                suggested_next_call=None,
            )

        current_groups = {int(spec["group_id"]): str(spec["name"] or "").strip() for spec in current_group_specs}
        desired_groups = _collect_public_package_group_specs(normalized_items)
        desired_group_ids = {
            group_id for group_id in (_coerce_positive_int(group.get("group_id")) for group in desired_groups) if group_id is not None
        }
        deleted_group_ids = sorted(set(current_groups) - desired_group_ids)

        permission_outcomes: list[PermissionCheckOutcome] = []
        needs_group_create = any(_coerce_positive_int(group.get("group_id")) is None for group in desired_groups)
        needs_group_delete = bool(deleted_group_ids)
        # sortGroupUnderPackage is always called for a layout apply and is guarded by
        # backend MoveGroupAuth, which maps to package editAppStatus.
        needs_edit_app = True
        for required_permission in (
            (["add_app"] if needs_group_create else [])
            + (["edit_app"] if needs_edit_app else [])
            + (["delete_app"] if needs_group_delete else [])
        ):
            outcome = self._guard_package_permission(
                profile=profile,
                tag_id=package_id,
                required_permission=required_permission,
                normalized_args=normalized_args,
            )
            if outcome.block is not None:
                return outcome.block
            permission_outcomes.append(outcome)

        group_ids_by_path: dict[tuple[int, ...], int] = {}
        group_operations: list[JSONObject] = []
        for group in desired_groups:
            path = tuple(group.get("path") or ())
            group_id = _coerce_positive_int(group.get("group_id"))
            group_name = str(group.get("name") or "").strip()
            if not group_name:
                return _failed(
                    "PACKAGE_GROUP_NAME_REQUIRED",
                    "group items require name",
                    normalized_args=normalized_args,
                    details={"path": list(path)},
                    suggested_next_call=None,
                )
            if group_id is None:
                try:
                    created = self.packages.package_group_create(profile=profile, tag_id=package_id, group_name=group_name)
                except (QingflowApiError, RuntimeError) as error:
                    api_error = _coerce_api_error(error)
                    return _failed_from_api_error(
                        "PACKAGE_GROUP_CREATE_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details={"package_id": package_id, "group_name": group_name},
                        suggested_next_call=None,
                    )
                group_id = _extract_package_group_id(created)
                if group_id is None:
                    return _failed(
                        "PACKAGE_GROUP_CREATE_UNVERIFIED",
                        "created package group but could not read group_id",
                        normalized_args=normalized_args,
                        details={"package_id": package_id, "group_name": group_name, "create_result": created},
                        suggested_next_call={"tool_name": "package_get", "arguments": {"profile": profile, "package_id": package_id}},
                    )
                group_operations.append({"action": "create", "group_id": group_id, "name": group_name})
            elif current_groups.get(group_id) != group_name:
                try:
                    self.packages.package_group_update(profile=profile, tag_id=package_id, group_id=group_id, group_name=group_name)
                except (QingflowApiError, RuntimeError) as error:
                    api_error = _coerce_api_error(error)
                    return _failed_from_api_error(
                        "PACKAGE_GROUP_UPDATE_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details={"package_id": package_id, "group_id": group_id, "group_name": group_name},
                        suggested_next_call=None,
                    )
                group_operations.append({"action": "update", "group_id": group_id, "name": group_name})
            group_ids_by_path[path] = group_id

        try:
            backend_items = _backend_package_items_from_public_items(normalized_items, group_ids_by_path)
        except ValueError as error:
            return _failed(
                "PACKAGE_LAYOUT_INVALID",
                str(error),
                normalized_args=normalized_args,
                details={"package_id": package_id},
                suggested_next_call=None,
            )
        for group_id in deleted_group_ids:
            backend_items.append({"itemType": 3, "groupId": group_id, "title": current_groups.get(group_id) or "", "subItems": []})

        try:
            sort_result = self.packages.package_sort_items(profile=profile, tag_id=package_id, tag_items=backend_items)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PACKAGE_LAYOUT_SORT_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"package_id": package_id},
                suggested_next_call=None,
            )

        for group_id in deleted_group_ids:
            try:
                self.packages.package_group_delete(profile=profile, tag_id=package_id, group_id=group_id)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                return _apply_permission_outcomes(
                    {
                        "status": "partial_success",
                        "error_code": "PACKAGE_APPLY_PARTIAL",
                        "recoverable": True,
                        "message": "package layout sort was applied, but a later group delete failed",
                        "normalized_args": normalized_args,
                        "missing_fields": [],
                        "allowed_values": {},
                        "details": {
                            "package_id": package_id,
                            "group_id": group_id,
                            "group_operations": group_operations,
                            "sort_result": sort_result,
                            "write_error": {
                                "message": api_error.message,
                                "transport_error": _transport_error_payload(api_error),
                            },
                        },
                        "request_id": api_error.request_id,
                        "backend_code": api_error.backend_code,
                        "http_status": None if api_error.http_status == 404 else api_error.http_status,
                        "suggested_next_call": None,
                        "noop": False,
                        "warnings": [
                            _warning(
                                "PACKAGE_WRITE_INCOMPLETE_AFTER_PARTIAL_WRITE",
                                "package layout write was partially applied before a later write step failed; do not blindly repeat the same package apply",
                                **_transport_error_payload(api_error),
                            )
                        ],
                        "verification": {
                            "layout_applied": True,
                            "write_incomplete": True,
                            "metadata_unverified": True,
                        },
                        "verified": False,
                        "package_id": package_id,
                        "write_executed": True,
                        "write_succeeded": True,
                        "safe_to_retry": False,
                    },
                    *permission_outcomes,
                )
            group_operations.append({"action": "delete", "group_id": group_id})

        return _apply_permission_outcomes(
            {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "applied package items",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {"group_operations": group_operations, "sort_result": sort_result},
                "request_id": sort_result.get("request_id") if isinstance(sort_result, dict) else None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": [],
                "verification": {"layout_applied": True},
                "verified": True,
                "package_id": package_id,
            },
            *permission_outcomes,
        )

    def member_search(self, *, profile: str, query: str, page_num: int = 1, page_size: int = 20, contain_disable: bool = False) -> JSONObject:
        requested = str(query or "").strip()
        normalized_args = {
            "query": requested,
            "page_num": page_num,
            "page_size": page_size,
            "contain_disable": contain_disable,
        }
        if not requested:
            return _failed("MEMBER_QUERY_REQUIRED", "query is required", normalized_args=normalized_args, suggested_next_call=None)
        try:
            listed = self.directory.directory_list_internal_users(
                profile=profile,
                keyword=requested,
                dept_id=None,
                role_id=None,
                page_num=page_num,
                page_size=page_size,
                contain_disable=contain_disable,
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "MEMBER_SEARCH_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"query": requested},
                suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
            )
        if listed.get("status") == "failed" and listed.get("error_code") == "CONTACT_DIRECTORY_PERMISSION_DENIED":
            return _failed(
                "CONTACT_DIRECTORY_PERMISSION_DENIED",
                str(listed.get("message") or "Contact-directory management data is not readable for the current user."),
                normalized_args=normalized_args,
                details={
                    "query": requested,
                    "permission_boundary": "contact_directory",
                    "fix_hint": (
                        "This builder member lookup uses the contact-directory management route. "
                        "For record member/department field candidates, use record_member_candidates "
                        "or record_department_candidates instead."
                    ),
                },
                suggested_next_call=None,
                request_id=listed.get("request_id") if isinstance(listed.get("request_id"), str) else None,
                backend_code=listed.get("backend_code"),
                http_status=listed.get("http_status"),
            )
        items = []
        for item in _extract_directory_items(listed):
            uid = _coerce_positive_int(item.get("uid") or item.get("id"))
            if uid is None:
                continue
            items.append(
                {
                    "uid": uid,
                    "name": item.get("nickName") or item.get("name") or item.get("value"),
                    "email": item.get("email"),
                    "dept_name": item.get("deptName") or item.get("departName"),
                }
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "resolved members",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {"count": len(items)},
            "items": items,
        }

    def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
        requested = str(keyword or "").strip()
        normalized_args = {"keyword": requested, "page_num": page_num, "page_size": page_size}
        if not requested:
            return _failed("ROLE_QUERY_REQUIRED", "keyword is required", normalized_args=normalized_args, suggested_next_call=None)
        try:
            listed = self.roles.role_search(profile=profile, keyword=requested, page_num=page_num, page_size=page_size)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "ROLE_SEARCH_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"keyword": requested},
                suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
            )
        if listed.get("status") == "failed":
            permission_boundary = "contact_role" if listed.get("error_code") == "CONTACT_ROLE_PERMISSION_DENIED" else None
            return _failed(
                str(listed.get("error_code") or "ROLE_SEARCH_FAILED"),
                str(listed.get("message") or "role search failed"),
                normalized_args=normalized_args,
                details={
                    "keyword": requested,
                    "permission_boundary": permission_boundary,
                },
                suggested_next_call=None
                if permission_boundary
                else {"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
                backend_code=listed.get("backend_code"),
                request_id=listed.get("request_id"),
                http_status=listed.get("http_status"),
            )
        page = listed.get("page") if isinstance(listed.get("page"), dict) else {}
        raw_items = page.get("list") if isinstance(page.get("list"), list) else []
        items = []
        for item in raw_items:
            if not isinstance(item, dict):
                continue
            role_id = _coerce_positive_int(item.get("roleId") or item.get("id"))
            if role_id is None:
                continue
            items.append(
                {
                    "role_id": role_id,
                    "role_name": item.get("roleName") or item.get("name"),
                    "role_icon": item.get("roleIcon"),
                }
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "resolved roles",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {"count": len(items)},
            "items": items,
        }

    def role_create(
        self,
        *,
        profile: str,
        role_name: str,
        member_uids: list[int],
        member_emails: list[str],
        member_names: list[str],
        role_icon: str = "ex-user-outlined",
    ) -> JSONObject:
        normalized_args = {
            "role_name": str(role_name or "").strip(),
            "member_uids": [uid for uid in member_uids if isinstance(uid, int) and uid > 0],
            "member_emails": [str(email or "").strip() for email in member_emails if str(email or "").strip()],
            "member_names": [str(name or "").strip() for name in member_names if str(name or "").strip()],
            "role_icon": role_icon or "ex-user-outlined",
        }
        requested_name = normalized_args["role_name"]
        if not requested_name:
            return _failed("ROLE_NAME_REQUIRED", "role_name is required", normalized_args=normalized_args, suggested_next_call=None)
        existing = self.role_search(profile=profile, keyword=requested_name, page_num=1, page_size=50)
        if existing.get("status") == "success":
            exact = [item for item in existing.get("items", []) if isinstance(item, dict) and item.get("role_name") == requested_name]
            if len(exact) == 1:
                return {
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "role already exists",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {},
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": True,
                    "verification": {"existing_role_reused": True},
                    "role_id": exact[0]["role_id"],
                    "role_name": exact[0]["role_name"],
                    "role_icon": exact[0].get("role_icon"),
                    "write_executed": False,
                    "safe_to_retry": True,
                }
            if len(exact) > 1:
                return _failed(
                    "AMBIGUOUS_ROLE",
                    f"multiple roles matched '{requested_name}'",
                    normalized_args=normalized_args,
                    details={"matches": exact},
                    suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, "keyword": requested_name}},
                )
        resolved_members = self._resolve_member_references(
            profile=profile,
            member_uids=normalized_args["member_uids"],
            member_emails=normalized_args["member_emails"],
            member_names=normalized_args["member_names"],
        )
        if resolved_members["issues"]:
            first_issue = resolved_members["issues"][0]
            return _failed(
                "ROLE_MEMBERS_UNRESOLVED",
                "one or more role members could not be resolved",
                normalized_args=normalized_args,
                details={"issues": resolved_members["issues"]},
                suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, "query": first_issue.get("value") or ""}},
            )
        try:
            created = self.roles.role_create(
                profile=profile,
                payload={
                    "roleName": requested_name,
                    "roleIcon": normalized_args["role_icon"],
                    "users": resolved_members["member_uids"],
                },
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "ROLE_CREATE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"role_name": requested_name},
                suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
            )
        role_result = created.get("result") if isinstance(created.get("result"), dict) else {}
        role_id = _coerce_positive_int(role_result.get("roleId") or role_result.get("id"))
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "created role",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {"member_count": len(resolved_members["member_uids"])},
            "role_id": role_id,
            "role_name": requested_name,
            "role_icon": normalized_args["role_icon"],
            "write_executed": True,
            "safe_to_retry": False,
        }

    def _resolve_role_references(
        self,
        *,
        profile: str,
        role_ids: list[int],
        role_names: list[str],
    ) -> dict[str, Any]:
        issues: list[dict[str, Any]] = []
        resolved: list[dict[str, Any]] = []
        seen_ids: set[int] = set()
        for role_id in role_ids:
            normalized_role_id = _coerce_positive_int(role_id)
            if normalized_role_id is None or normalized_role_id in seen_ids:
                continue
            resolved.append(
                {
                    "roleId": normalized_role_id,
                    "roleName": str(normalized_role_id),
                    "roleIcon": "ex-user-outlined",
                    "beingFrontendConfig": True,
                }
            )
            seen_ids.add(normalized_role_id)
        for role_name in role_names:
            requested = str(role_name or "").strip()
            if not requested:
                continue
            matches_result = self.role_search(profile=profile, keyword=requested, page_num=1, page_size=50)
            if matches_result.get("status") != "success":
                issues.append(
                    {
                        "kind": "role",
                        "value": requested,
                        "error_code": matches_result.get("error_code") or "ROLE_SEARCH_FAILED",
                        "message": matches_result.get("message"),
                        "backend_code": matches_result.get("backend_code"),
                        "request_id": matches_result.get("request_id"),
                    }
                )
                continue
            items = matches_result.get("items", []) if matches_result.get("status") == "success" else []
            exact = [item for item in items if isinstance(item, dict) and item.get("role_name") == requested]
            if len(exact) != 1:
                issues.append(
                    {
                        "kind": "role",
                        "value": requested,
                        "error_code": "AMBIGUOUS_ROLE" if len(exact) > 1 else "ROLE_NOT_FOUND",
                        "matches": exact,
                    }
                )
                continue
            role_id = _coerce_positive_int(exact[0].get("role_id"))
            if role_id is None or role_id in seen_ids:
                continue
            resolved.append(
                {
                    "roleId": role_id,
                    "roleName": exact[0].get("role_name") or requested,
                    "roleIcon": exact[0].get("role_icon") or "ex-user-outlined",
                    "beingFrontendConfig": True,
                }
            )
            seen_ids.add(role_id)
        return {"role_entries": resolved, "issues": issues}

    def _resolve_member_references(
        self,
        *,
        profile: str,
        member_uids: list[int],
        member_emails: list[str],
        member_names: list[str],
    ) -> dict[str, Any]:
        issues: list[dict[str, Any]] = []
        resolved: list[dict[str, Any]] = []
        seen_uids: set[int] = set()

        def add_member(item: dict[str, Any], *, fallback_name: str | None = None) -> None:
            uid = _coerce_positive_int(item.get("uid") or item.get("id"))
            if uid is None or uid in seen_uids:
                return
            resolved.append(
                {
                    "uid": uid,
                    "name": item.get("nickName") or item.get("name") or fallback_name or str(uid),
                    "email": item.get("email"),
                }
            )
            seen_uids.add(uid)

        for uid in member_uids:
            normalized_uid = _coerce_positive_int(uid)
            if normalized_uid is not None and normalized_uid not in seen_uids:
                add_member({"uid": normalized_uid}, fallback_name=str(normalized_uid))

        for email in member_emails:
            requested = str(email or "").strip()
            if not requested:
                continue
            matches = self.member_search(profile=profile, query=requested, page_num=1, page_size=50, contain_disable=False)
            if matches.get("status") != "success":
                issues.append(
                    {
                        "kind": "member_email",
                        "value": requested,
                        "error_code": matches.get("error_code") or "MEMBER_SEARCH_FAILED",
                        "message": matches.get("message"),
                        "backend_code": matches.get("backend_code"),
                        "request_id": matches.get("request_id"),
                    }
                )
                continue
            items = matches.get("items", []) if matches.get("status") == "success" else []
            exact = [item for item in items if isinstance(item, dict) and str(item.get("email") or "").strip().lower() == requested.lower()]
            if len(exact) != 1:
                issues.append(
                    {
                        "kind": "member_email",
                        "value": requested,
                        "error_code": "AMBIGUOUS_MEMBER" if len(exact) > 1 else "MEMBER_NOT_FOUND",
                        "matches": exact,
                    }
                )
                continue
            add_member(exact[0])

        for name in member_names:
            requested = str(name or "").strip()
            if not requested:
                continue
            matches = self.member_search(profile=profile, query=requested, page_num=1, page_size=50, contain_disable=False)
            if matches.get("status") != "success":
                issues.append(
                    {
                        "kind": "member_name",
                        "value": requested,
                        "error_code": matches.get("error_code") or "MEMBER_SEARCH_FAILED",
                        "message": matches.get("message"),
                        "backend_code": matches.get("backend_code"),
                        "request_id": matches.get("request_id"),
                    }
                )
                continue
            items = matches.get("items", []) if matches.get("status") == "success" else []
            exact = [item for item in items if isinstance(item, dict) and str(item.get("name") or "").strip() == requested]
            if len(exact) != 1:
                issues.append(
                    {
                        "kind": "member_name",
                        "value": requested,
                        "error_code": "AMBIGUOUS_MEMBER" if len(exact) > 1 else "MEMBER_NOT_FOUND",
                        "matches": exact,
                    }
                )
                continue
            add_member(exact[0])

        return {"member_uids": [item["uid"] for item in resolved], "member_entries": resolved, "issues": issues}

    def _resolve_department_references(
        self,
        *,
        profile: str,
        dept_ids: list[int],
        dept_names: list[str],
    ) -> dict[str, Any]:
        issues: list[dict[str, Any]] = []
        resolved: list[dict[str, Any]] = []
        seen_ids: set[int] = set()
        if not dept_ids and not dept_names:
            return {"department_entries": resolved, "issues": issues}

        def add_department(item: dict[str, Any], *, fallback_name: str | None = None) -> None:
            dept_id = _coerce_positive_int(item.get("deptId") or item.get("id"))
            if dept_id is None or dept_id in seen_ids:
                return
            resolved.append(
                {
                    "deptId": dept_id,
                    "deptName": str(item.get("deptName") or item.get("departName") or item.get("name") or fallback_name or dept_id),
                    "deptIcon": item.get("deptIcon"),
                    "beingFrontendConfig": True,
                }
            )
            seen_ids.add(dept_id)

        for dept_id in dept_ids:
            normalized = _coerce_positive_int(dept_id)
            if normalized is None:
                continue
            add_department({"deptId": normalized}, fallback_name=str(normalized))

        if not dept_names:
            return {"department_entries": resolved, "issues": issues}

        listed = self.directory.directory_list_all_departments(
            profile=profile,
            parent_dept_id=None,
            max_depth=20,
            max_items=5000,
        )
        if listed.get("status") == "failed" and listed.get("error_code") == "CONTACT_DIRECTORY_PERMISSION_DENIED":
            for dept_name in dept_names:
                requested = str(dept_name or "").strip()
                if not requested:
                    continue
                issues.append(
                    {
                        "kind": "department_name",
                        "value": requested,
                        "error_code": "CONTACT_DIRECTORY_PERMISSION_DENIED",
                        "message": "department names require contact-directory lookup; pass dept_ids or grant directory access",
                        "backend_code": listed.get("backend_code"),
                        "request_id": listed.get("request_id"),
                    }
                )
            return {"department_entries": resolved, "issues": issues}

        items = _extract_directory_items(listed)
        by_name: dict[str, list[dict[str, Any]]] = {}
        for item in items:
            dept_name = str(item.get("deptName") or item.get("departName") or item.get("name") or "").strip()
            if dept_name:
                by_name.setdefault(dept_name, []).append(item)

        for dept_name in dept_names:
            requested = str(dept_name or "").strip()
            if not requested:
                continue
            exact = [item for item in (by_name.get(requested) or []) if isinstance(item, dict)]
            if len(exact) != 1:
                issues.append(
                    {
                        "kind": "department_name",
                        "value": requested,
                        "error_code": "AMBIGUOUS_DEPARTMENT" if len(exact) > 1 else "DEPARTMENT_NOT_FOUND",
                        "matches": exact,
                    }
                )
                continue
            add_department(exact[0], fallback_name=requested)

        return {"department_entries": resolved, "issues": issues}

    def _resolve_external_member_references(
        self,
        *,
        profile: str,
        member_ids: list[int],
        member_emails: list[str],
    ) -> dict[str, Any]:
        issues: list[dict[str, Any]] = []
        resolved: list[dict[str, Any]] = []
        seen_ids: set[int] = set()

        def _external_member_id(item: dict[str, Any]) -> int | None:
            return _coerce_positive_int(item.get("uid") or item.get("memberId") or item.get("id"))

        def add_member(item: dict[str, Any], *, fallback_name: str | None = None) -> None:
            member_id = _external_member_id(item)
            if member_id is None or member_id in seen_ids:
                return
            resolved.append(
                {
                    "uid": member_id,
                    "remark": item.get("remark") or item.get("nickName") or item.get("name") or fallback_name or str(member_id),
                    "nickName": item.get("nickName") or item.get("name") or fallback_name or str(member_id),
                    "email": item.get("email"),
                    "headImg": item.get("headImg") or item.get("avatar"),
                    "beingFrontendConfig": True,
                }
            )
            seen_ids.add(member_id)

        for member_id in member_ids:
            normalized = _coerce_positive_int(member_id)
            if normalized is not None and normalized not in seen_ids:
                add_member({"uid": normalized}, fallback_name=str(normalized))

        for member_email in member_emails:
            requested = str(member_email or "").strip()
            if not requested:
                continue
            listed = self.directory.directory_list_external_members(
                profile=profile,
                keyword=requested,
                page_num=1,
                page_size=100,
                simple=True,
            )
            if listed.get("status") == "failed":
                issues.append(
                    {
                        "kind": "external_member_email",
                        "value": requested,
                        "error_code": listed.get("error_code") or "EXTERNAL_MEMBER_SEARCH_FAILED",
                        "message": listed.get("message"),
                        "backend_code": listed.get("backend_code"),
                        "request_id": listed.get("request_id"),
                    }
                )
                continue
            items = _extract_directory_items(listed)
            exact = [
                item
                for item in items
                if isinstance(item, dict) and str(item.get("email") or "").strip().lower() == requested.lower()
            ]
            if len(exact) != 1:
                issues.append(
                    {
                        "kind": "external_member_email",
                        "value": requested,
                        "error_code": "AMBIGUOUS_EXTERNAL_MEMBER" if len(exact) > 1 else "EXTERNAL_MEMBER_NOT_FOUND",
                        "matches": exact,
                    }
                )
                continue
            add_member(exact[0], fallback_name=requested)

        return {"member_entries": resolved, "issues": issues}

    def _raise_visibility_resolution_issues(self, issues: list[dict[str, Any]]) -> None:
        if not issues:
            return
        first_issue = issues[0] if isinstance(issues[0], dict) else {}
        error_code = str(first_issue.get("error_code") or "VISIBILITY_SUBJECT_NOT_FOUND")
        raw_value = first_issue.get("value")
        requested_value = str(raw_value or "").strip()
        kind = str(first_issue.get("kind") or "subject")
        if error_code.startswith("AMBIGUOUS_"):
            public_code = "VISIBILITY_SUBJECT_AMBIGUOUS"
            message = f"{kind} '{requested_value}' matched multiple visibility subjects"
        elif error_code.endswith("_NOT_FOUND"):
            public_code = "VISIBILITY_SUBJECT_NOT_FOUND"
            message = f"{kind} '{requested_value}' was not found in the visibility directory"
        elif "PERMISSION_DENIED" in error_code or error_code.endswith("_FAILED"):
            public_code = "VISIBILITY_SUBJECT_LOOKUP_FAILED"
            message = f"{kind} '{requested_value}' could not be resolved because the visibility directory lookup failed"
        else:
            public_code = "VISIBILITY_SUBJECT_UNSUPPORTED"
            message = f"{kind} visibility selector is unsupported"
        raise VisibilityResolutionError(
            public_code,
            message,
            details={"issues": issues},
        )

    def _compile_visibility_to_member_auth(
        self,
        *,
        profile: str,
        visibility: VisibilityPatch | None,
    ) -> dict[str, Any]:
        if visibility is None:
            return default_member_auth()
        if visibility.mode == PublicVisibilityMode.everyone:
            auth = default_member_auth()
            auth["type"] = "ALL"
            auth["contactAuth"]["type"] = "WORKSPACE_ALL"
            auth["externalMemberAuth"]["type"] = "WORKSPACE_ALL"
            auth["contactAuth"]["authMembers"]["includeSubDeparts"] = None
            auth["externalMemberAuth"]["authMembers"]["includeSubDeparts"] = None
            return auth

        member_resolution = self._resolve_member_references(
            profile=profile,
            member_uids=visibility.selectors.member_uids,
            member_emails=visibility.selectors.member_emails,
            member_names=visibility.selectors.member_names,
        )
        role_resolution = self._resolve_role_references(
            profile=profile,
            role_ids=visibility.selectors.role_ids,
            role_names=visibility.selectors.role_names,
        )
        department_resolution = self._resolve_department_references(
            profile=profile,
            dept_ids=visibility.selectors.dept_ids,
            dept_names=visibility.selectors.dept_names,
        )
        external_member_resolution = self._resolve_external_member_references(
            profile=profile,
            member_ids=visibility.external_selectors.member_ids,
            member_emails=visibility.external_selectors.member_emails,
        )
        self._raise_visibility_resolution_issues(
            [
                *member_resolution["issues"],
                *role_resolution["issues"],
                *department_resolution["issues"],
                *external_member_resolution["issues"],
            ]
        )

        external_depart_entries: list[dict[str, Any]] = []
        for dept_id in visibility.external_selectors.dept_ids:
            normalized = _coerce_positive_int(dept_id)
            if normalized is None:
                continue
            external_depart_entries.append(
                {
                    "deptId": normalized,
                    "deptName": str(normalized),
                    "beingFrontendConfig": True,
                }
            )

        contact_auth_type = "WORKSPACE_ALL"
        if visibility.mode == PublicVisibilityMode.specific:
            contact_auth_type = "SPECIFIC"
        external_auth_type = {
            PublicExternalVisibilityMode.not_: "NOT",
            PublicExternalVisibilityMode.workspace: "WORKSPACE_ALL",
            PublicExternalVisibilityMode.specific: "SPECIFIC",
        }[visibility.external_mode or PublicExternalVisibilityMode.not_]
        return {
            "type": "WORKSPACE",
            "contactAuth": {
                "type": contact_auth_type,
                "authMembers": {
                    "member": deepcopy(member_resolution["member_entries"]),
                    "depart": deepcopy(department_resolution["department_entries"]),
                    "role": deepcopy(role_resolution["role_entries"]),
                    "dynamic": [],
                    "includeSubDeparts": visibility.selectors.include_sub_departs,
                },
            },
            "externalMemberAuth": {
                "type": external_auth_type,
                "authMembers": {
                    "member": deepcopy(external_member_resolution["member_entries"]),
                    "depart": deepcopy(external_depart_entries),
                    "role": [],
                    "dynamic": [],
                    "includeSubDeparts": None,
                },
            },
        }

    def _compile_visibility_to_chart_visible_auth(
        self,
        *,
        profile: str,
        visibility: VisibilityPatch | None,
    ) -> dict[str, Any]:
        if visibility is None:
            return qingbi_workspace_visible_auth()

        member_auth = self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
        workspace_type = str(member_auth.get("type") or "").strip().upper()
        contact_auth = member_auth.get("contactAuth") if isinstance(member_auth.get("contactAuth"), dict) else {}
        external_auth = member_auth.get("externalMemberAuth") if isinstance(member_auth.get("externalMemberAuth"), dict) else {}
        contact_members = contact_auth.get("authMembers") if isinstance(contact_auth.get("authMembers"), dict) else {}
        external_members = external_auth.get("authMembers") if isinstance(external_auth.get("authMembers"), dict) else {}
        return {
            "type": "all" if workspace_type == "ALL" else "ws",
            "contactAuth": {
                "type": "assign" if str(contact_auth.get("type") or "").strip().upper() == "SPECIFIC" else "all",
                "authMembers": {
                    "member": deepcopy(contact_members.get("member") or []),
                    "depart": deepcopy(contact_members.get("depart") or []),
                    "role": deepcopy(contact_members.get("role") or []),
                    "includeSubDeparts": contact_members.get("includeSubDeparts"),
                },
            },
            "externalMemberAuth": {
                "type": {
                    "NOT": "not",
                    "WORKSPACE_ALL": "all",
                    "SPECIFIC": "assign",
                }.get(str(external_auth.get("type") or "").strip().upper(), "not"),
                "authMembers": {
                    "member": deepcopy(external_members.get("member") or []),
                    "depart": deepcopy(external_members.get("depart") or []),
                    "role": [],
                },
            },
        }

    def _normalize_flow_nodes(
        self,
        *,
        profile: str,
        current_fields: list[dict[str, Any]],
        nodes: list[dict[str, Any]],
    ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
        field_name_to_field = {
            str(field.get("name") or ""): field
            for field in current_fields
            if str(field.get("name") or "")
        }
        field_name_to_que_id = {
            str(field.get("name") or ""): int(field.get("que_id"))
            for field in current_fields
            if str(field.get("name") or "") and isinstance(field.get("que_id"), int)
        }
        normalized_nodes: list[dict[str, Any]] = []
        issues: list[dict[str, Any]] = []
        for node in nodes:
            if not isinstance(node, dict):
                continue
            normalized_node = deepcopy(node)
            assignees = FlowAssigneePatch.model_validate(node.get("assignees") or {})
            permissions = FlowNodePermissionsPatch.model_validate(node.get("permissions") or {})
            role_resolution = self._resolve_role_references(
                profile=profile,
                role_ids=assignees.role_ids,
                role_names=assignees.role_names,
            )
            member_resolution = self._resolve_member_references(
                profile=profile,
                member_uids=assignees.member_uids,
                member_emails=assignees.member_emails,
                member_names=assignees.member_names,
            )
            issues.extend({**issue, "node_id": node.get("id")} for issue in [*role_resolution["issues"], *member_resolution["issues"]])
            editable_que_ids: list[int] = []
            missing_editable_fields: list[str] = []
            for field_name in permissions.editable_fields:
                if field_name not in field_name_to_que_id:
                    missing_editable_fields.append(field_name)
                else:
                    editable_que_ids.append(field_name_to_que_id[field_name])
            if missing_editable_fields:
                issues.append(
                    {
                        "node_id": node.get("id"),
                        "kind": "editable_fields",
                        "error_code": "UNKNOWN_FLOW_FIELD",
                        "missing_fields": missing_editable_fields,
                    }
                )
            condition_matrix, condition_issues = _build_flow_condition_matrix(
                current_fields_by_name=field_name_to_field,
                node=normalized_node,
            )
            issues.extend({**issue, "node_id": node.get("id")} for issue in condition_issues)
            config_payload = deepcopy(normalized_node.get("config") or {}) if isinstance(normalized_node.get("config"), dict) else {}
            if condition_matrix:
                # Backend judge nodes persist branch conditions from `autoJudges`.
                # Keep the legacy mirror key for internal verification logic.
                config_payload["autoJudges"] = condition_matrix
                config_payload["conditionFormatMatrix"] = condition_matrix
            normalized_node["assignees"] = {
                "member_uids": member_resolution["member_uids"],
                "role_entries": role_resolution["role_entries"],
                "include_sub_departs": assignees.include_sub_departs,
            }
            normalized_node["permissions"] = {
                "editable_fields": permissions.editable_fields,
                "editable_que_ids": editable_que_ids,
            }
            if config_payload:
                normalized_node["config"] = config_payload
            normalized_nodes.append(normalized_node)
        return normalized_nodes, issues

    def _unsupported_public_flow_nodes(self, *, nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
        unsupported: list[dict[str, Any]] = []
        for node in nodes:
            if not isinstance(node, dict):
                continue
            node_type = str(node.get("type") or "").strip().lower()
            if node_type in DISABLED_PUBLIC_FLOW_NODE_TYPES:
                unsupported.append(
                    {
                        "id": node.get("id"),
                        "name": node.get("name"),
                        "type": node_type,
                    }
                )
        return unsupported

    def _canonicalize_flow_nodes_for_public_output(self, nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
        public_nodes: list[dict[str, Any]] = []
        for node in nodes:
            if not isinstance(node, dict):
                continue
            payload = deepcopy(node)
            assignees = payload.get("assignees") if isinstance(payload.get("assignees"), dict) else {}
            permissions = payload.get("permissions") if isinstance(payload.get("permissions"), dict) else {}
            public_assignees: dict[str, Any] = {}
            role_ids = [
                role_id
                for role_id in (
                    _coerce_positive_int(entry.get("roleId"))
                    for entry in (assignees.get("role_entries") or [])
                    if isinstance(entry, dict)
                )
                if role_id is not None
            ]
            member_uids = [
                member_uid
                for member_uid in (_coerce_positive_int(value) for value in (assignees.get("member_uids") or []))
                if member_uid is not None
            ]
            if role_ids:
                public_assignees["role_ids"] = role_ids
            if member_uids:
                public_assignees["member_uids"] = member_uids
            if bool(assignees.get("include_sub_departs")):
                public_assignees["include_sub_departs"] = True
            public_permissions: dict[str, Any] = {}
            editable_fields = [str(name) for name in (permissions.get("editable_fields") or []) if str(name or "").strip()]
            if editable_fields:
                public_permissions["editable_fields"] = editable_fields
            config_payload = payload.get("config") if isinstance(payload.get("config"), dict) else {}
            if isinstance(config_payload, dict):
                config_payload = deepcopy(config_payload)
                config_payload.pop("autoJudges", None)
                config_payload.pop("conditionFormatMatrix", None)
            if public_assignees:
                payload["assignees"] = public_assignees
            else:
                payload.pop("assignees", None)
            if public_permissions:
                payload["permissions"] = public_permissions
            else:
                payload.pop("permissions", None)
            if config_payload:
                payload["config"] = config_payload
            else:
                payload.pop("config", None)
            public_nodes.append(payload)
        return public_nodes

    def package_attach_app(
        self,
        *,
        profile: str,
        tag_id: int,
        app_key: str,
        app_title: str = "",
    ) -> JSONObject:
        normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
        if _coerce_positive_int(tag_id) is None:
            return _failed("TAG_ID_REQUIRED", "tag_id must be positive", suggested_next_call=None)
        permission_outcomes: list[PermissionCheckOutcome] = []
        package_permission_outcome = self._guard_package_permission(
            profile=profile,
            tag_id=tag_id,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if package_permission_outcome.block is not None:
            return package_permission_outcome.block
        permission_outcomes.append(package_permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        resolved = self.app_resolve(profile=profile, app_key=app_key)
        if resolved.get("status") == "failed":
            return finalize(resolved)
        resolved_outcome = _permission_outcome_from_result(resolved)
        if resolved_outcome is not None:
            permission_outcomes.append(resolved_outcome)
        try:
            base_before = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
            tag_ids_before = _coerce_int_list(base_before.get("tagIds"))
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            tag_ids_before = []
            if _is_permission_restricted_api_error(api_error):
                permission_outcomes.append(
                    _verification_read_outcome(
                        resource="app",
                        target={"app_key": app_key},
                        transport_error=api_error,
                    )
                )
            else:
                return finalize(
                    _failed_from_api_error(
                        "PACKAGE_ATTACH_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details={"tag_id": tag_id, "app_key": app_key},
                        suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, "tag_id": tag_id, "app_key": app_key}},
                    )
                )
        already_attached = tag_id in tag_ids_before
        try:
            self._attach_app_to_package(
                profile=profile,
                app_key=str(resolved["app_key"]),
                app_title=app_title or str(resolved.get("app_name") or app_key),
                package_tag_id=tag_id,
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return finalize(
                _failed_from_api_error(
                    "PACKAGE_ATTACH_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details=_with_state_read_blocked_details(
                        {"tag_id": tag_id, "app_key": app_key},
                        resource="package",
                        error=api_error,
                    ),
                    suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, "tag_id": tag_id, "app_key": app_key}},
                )
            )
        verification_error: QingflowApiError | None = None
        try:
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
            tag_ids_after = _coerce_int_list(base.get("tagIds"))
            attached = tag_id in tag_ids_after
        except (QingflowApiError, RuntimeError) as error:
            verification_error = _coerce_api_error(error)
            tag_ids_after = []
            attached = False
            if _is_permission_restricted_api_error(verification_error):
                permission_outcomes.append(
                    _verification_read_outcome(
                        resource="app",
                        target={"app_key": app_key},
                        transport_error=verification_error,
                    )
                )
            else:
                return finalize(
                    _failed_from_api_error(
                        "PACKAGE_ATTACH_FAILED",
                        verification_error,
                        normalized_args=normalized_args,
                        details={"tag_id": tag_id, "app_key": app_key},
                        suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, "tag_id": tag_id, "app_key": app_key}},
                    )
                )
        response = {
            "status": "success" if attached else "partial_success",
            "error_code": None if attached else "PACKAGE_ATTACH_READBACK_PENDING",
            "recoverable": verification_error is not None or not attached,
            "message": "attached app to package" if attached else "app attachment could not be fully verified",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": verification_error.request_id if verification_error is not None else None,
            "suggested_next_call": None if attached else {"tool_name": "package_attach_app", "arguments": {"profile": profile, "tag_id": tag_id, "app_key": app_key}},
            "noop": already_attached,
            "verification": {
                "tag_ids_before": tag_ids_before,
                "tag_ids_after": tag_ids_after,
                "attachment_verified": attached if verification_error is None else None,
                "readback_unavailable": verification_error is not None,
            },
            "app_key": app_key,
            "tag_id": tag_id,
            "tag_ids_after": tag_ids_after,
            "attached": attached,
            "write_executed": not already_attached,
            "write_succeeded": not already_attached or attached,
            "safe_to_retry": bool(already_attached),
        }
        if verification_error is not None:
            response["details"]["verification_error"] = _transport_error_payload(verification_error)
        return finalize(response)

    def app_release_edit_lock_if_mine(
        self,
        *,
        profile: str,
        app_key: str,
        lock_owner_email: str = "",
        lock_owner_name: str = "",
    ) -> JSONObject:
        normalized_args = {
            "app_key": app_key,
            "lock_owner_email": lock_owner_email,
            "lock_owner_name": lock_owner_name,
        }
        session_profile = self.apps.sessions.get_profile(profile)
        if session_profile is None:
            return _failed(
                "AUTH_REQUIRED",
                "auth profile is required before releasing an app edit lock",
                normalized_args=normalized_args,
                recoverable=False,
                suggested_next_call={"tool_name": "auth_whoami", "arguments": {"profile": profile}},
            )
        identity = self._resolve_current_user_identity(profile=profile)
        current_email = str(identity.get("email") or "").strip().lower()
        current_name = str(identity.get("nick_name") or "").strip()
        requested_owner_email = str(lock_owner_email or "").strip().lower()
        requested_owner_name = str(lock_owner_name or "").strip()
        if not requested_owner_email and not requested_owner_name:
            return _failed(
                "EDIT_LOCK_OWNER_UNKNOWN",
                "lock owner could not be verified; refuse to release edit lock blindly",
                normalized_args=normalized_args,
                recoverable=False,
                details={
                    "current_user_email": identity.get("email"),
                    "current_user_name": identity.get("nick_name"),
                },
                suggested_next_call=None,
            )
        owner_matches = True
        if requested_owner_email:
            owner_matches = bool(current_email) and current_email == requested_owner_email
        elif requested_owner_name:
            owner_matches = bool(current_name) and current_name == requested_owner_name
        if not owner_matches:
            return _failed(
                "EDIT_LOCK_HELD_BY_OTHER_USER",
                "edit lock is owned by another user; refusing to release it",
                normalized_args=normalized_args,
                recoverable=False,
                details={
                    "lock_owner_email": requested_owner_email or None,
                    "lock_owner_name": requested_owner_name or None,
                    "current_user_email": identity.get("email"),
                    "current_user_name": identity.get("nick_name"),
                },
                suggested_next_call=None,
            )
        try:
            version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "EDIT_LOCK_RELEASE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        edit_version_no = _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or 1
        try:
            self.apps.app_edit_finished(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "EDIT_LOCK_RELEASE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={
                    "app_key": app_key,
                    "edit_version_no": edit_version_no,
                    "lock_owner_email": requested_owner_email or None,
                    "lock_owner_name": requested_owner_name or None,
                },
                suggested_next_call={"tool_name": "app_release_edit_lock_if_mine", "arguments": {"profile": profile, **normalized_args}},
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "released app edit lock owned by current user",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {
                "lock_owner_email": requested_owner_email or None,
                "lock_owner_name": requested_owner_name or None,
                "current_user_email": identity.get("email"),
                "current_user_name": identity.get("nick_name"),
                "edit_version_no": edit_version_no,
            },
            "request_id": None,
            "suggested_next_call": {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            "noop": False,
            "verification": {"released": True},
            "app_key": app_key,
            "released": True,
            "write_executed": True,
            "safe_to_retry": False,
        }

    def app_resolve(
        self,
        *,
        profile: str,
        app_key: str = "",
        app_name: str = "",
        package_tag_id: int | None = None,
    ) -> JSONObject:
        if app_key:
            try:
                base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True)
            except (QingflowApiError, RuntimeError) as exc:
                api_error = _coerce_api_error(exc)
                if _is_permission_restricted_api_error(api_error):
                    return {
                        "status": "success",
                        "error_code": None,
                        "recoverable": False,
                        "message": "resolved app key; metadata unverified",
                        "normalized_args": {"app_key": app_key},
                        "missing_fields": [],
                        "allowed_values": {},
                        "details": {
                            "match_scope": "app_key",
                            "lookup_permission_blocked": {
                                "scope": "app",
                                "target": {"app_key": app_key},
                                "required_permission": None,
                                "transport_error": _transport_error_payload(api_error),
                            },
                            "permission_check_skipped": True,
                        },
                        "request_id": api_error.request_id,
                        "suggested_next_call": {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                        "noop": False,
                        "warnings": [
                            _warning(
                                "PERMISSION_CHECK_SKIPPED",
                                "app metadata lookup was permission-restricted; continuing with explicit app_key",
                                scope="app",
                                app_key=app_key,
                            )
                        ],
                        "verification": {"metadata_unverified": True},
                        "app_key": app_key,
                        "app_name": app_key,
                        "tag_ids": [],
                        "publish_status": None,
                    }
                return _failed_from_api_error(
                    "APP_NOT_FOUND" if api_error.http_status == 404 else "APP_RESOLVE_FAILED",
                    api_error,
                    details={"app_key": app_key},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            result = base.get("result") if isinstance(base.get("result"), dict) else {}
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "resolved app",
                "normalized_args": {"app_key": app_key},
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "verification": {},
                "app_key": app_key,
                "app_name": result.get("formTitle") or result.get("title") or app_key,
                "tag_ids": _coerce_int_list(result.get("tagIds")),
                "publish_status": result.get("appPublishStatus"),
            }
        requested = str(app_name or "").strip()
        if not requested:
            return _failed("APP_NAME_REQUIRED", "app_name or app_key is required", suggested_next_call=None)
        if package_tag_id is not None and package_tag_id > 0:
            try:
                package_matches = self._resolve_app_matches_in_package(
                    profile=profile,
                    app_name=requested,
                    package_tag_id=package_tag_id,
                )
            except (QingflowApiError, RuntimeError) as exc:
                api_error = _coerce_api_error(exc)
                return _failed_from_api_error(
                    "APP_RESOLVE_FAILED",
                    api_error,
                    details={"app_name": requested, "package_tag_id": package_tag_id, "match_scope": "package"},
                    suggested_next_call=None,
                )
            if len(package_matches) == 1:
                match = package_matches[0]
                return {
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "resolved app",
                    "normalized_args": {"app_name": requested, "package_tag_id": package_tag_id},
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {"match_scope": "package"},
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": False,
                    "verification": {},
                    **match,
                }
            if len(package_matches) > 1:
                return _failed(
                    "AMBIGUOUS_APP",
                    f"multiple apps matched '{requested}' inside package {package_tag_id}",
                    details={"app_name": requested, "package_tag_id": package_tag_id, "matches": package_matches},
                    suggested_next_call=None,
                )
        try:
            visible_matches = self._resolve_app_matches_in_visible_apps(
                profile=profile,
                app_name=requested,
                package_tag_id=package_tag_id,
            )
        except (QingflowApiError, RuntimeError) as exc:
            api_error = _coerce_api_error(exc)
            return _failed_from_api_error(
                "APP_RESOLVE_FAILED",
                api_error,
                details={"app_name": requested, "package_tag_id": package_tag_id, "match_scope": "visible_apps"},
                suggested_next_call=None,
            )
        if len(visible_matches) == 1:
            match = visible_matches[0]
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "resolved app",
                "normalized_args": {"app_name": requested, "package_tag_id": package_tag_id},
                "missing_fields": [],
                "allowed_values": {},
                "details": {"match_scope": "visible_apps"},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "verification": {},
                **match,
            }
        if len(visible_matches) > 1:
            return _failed(
                "AMBIGUOUS_APP",
                f"multiple apps matched '{requested}'",
                details={"app_name": requested, "package_tag_id": package_tag_id, "matches": visible_matches},
                suggested_next_call=None,
            )
        search_error: QingflowApiError | None = None
        try:
            search = self.apps.app_search(profile=profile, keyword=requested, page_num=1, page_size=200)
        except (QingflowApiError, RuntimeError) as exc:
            api_error = _coerce_api_error(exc)
            if is_auth_like_error(api_error) or package_tag_id is None or package_tag_id <= 0 or backend_code_int(api_error) not in {40002, 40027}:
                return _failed_from_api_error(
                    "APP_NOT_FOUND" if api_error.http_status == 404 else "APP_RESOLVE_FAILED",
                    api_error,
                    details={"app_name": requested, "package_tag_id": package_tag_id},
                    suggested_next_call=None,
                )
            search = {}
            search_error = api_error
        search_permission_blocked = _search_permission_blocked_from_warnings(search) if isinstance(search, dict) else None
        apps = search.get("apps") if isinstance(search.get("apps"), list) else []
        matches = []
        for item in apps:
            if not isinstance(item, dict):
                continue
            title = str(item.get("title") or "").strip()
            if title != requested:
                continue
            candidate_key = str(item.get("app_key") or "").strip()
            if not candidate_key:
                continue
            tag_ids = _coerce_int_list(item.get("tag_ids"))
            tag_id = _coerce_positive_int(item.get("tag_id"))
            if tag_id is not None and tag_id not in tag_ids:
                tag_ids.append(tag_id)
            if package_tag_id is not None and package_tag_id > 0:
                try:
                    base = self.apps.app_get_base(profile=profile, app_key=candidate_key, include_raw=True)
                except (QingflowApiError, RuntimeError) as exc:
                    api_error = _coerce_api_error(exc)
                    if _is_optional_builder_lookup_error(api_error):
                        continue
                    return _failed_from_api_error(
                        "APP_RESOLVE_FAILED",
                        api_error,
                        details={"app_name": requested, "candidate_app_key": candidate_key, "package_tag_id": package_tag_id},
                        suggested_next_call=None,
                    )
                result = base.get("result") if isinstance(base.get("result"), dict) else {}
                resolved_tag_ids = _coerce_int_list(result.get("tagIds"))
                if resolved_tag_ids:
                    tag_ids = resolved_tag_ids
                if package_tag_id not in tag_ids:
                    continue
            matches.append(
                {
                    "app_key": candidate_key,
                    "app_name": title,
                    "tag_ids": tag_ids,
                }
            )
        if not matches and search_error is not None:
            if len(visible_matches) == 1:
                match = visible_matches[0]
                return {
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "resolved app",
                    "normalized_args": {"app_name": requested, "package_tag_id": package_tag_id},
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {
                        "match_scope": "visible_apps",
                        "search_permission_blocked": {
                            "backend_code": search_error.backend_code,
                            "http_status": search_error.http_status,
                            "request_id": search_error.request_id,
                        },
                    },
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": False,
                    "verification": {},
                    **match,
                }
            if len(visible_matches) > 1:
                return _failed(
                    "AMBIGUOUS_APP",
                    f"multiple apps matched '{requested}' inside package {package_tag_id}",
                    details={
                        "app_name": requested,
                        "package_tag_id": package_tag_id,
                        "matches": visible_matches,
                        "search_permission_blocked": {
                            "backend_code": search_error.backend_code,
                            "http_status": search_error.http_status,
                            "request_id": search_error.request_id,
                        },
                    },
                    suggested_next_call=None,
                )
        if not matches:
            return _failed(
                "APP_NOT_FOUND",
                f"app '{requested}' was not found",
                details={
                    "app_name": requested,
                    "package_tag_id": package_tag_id,
                    **(
                        {
                            "search_permission_blocked": {
                                "backend_code": search_error.backend_code,
                                "http_status": search_error.http_status,
                                "request_id": search_error.request_id,
                            },
                            "match_scope": "visible_apps_fallback",
                        }
                        if search_error is not None
                        else {}
                    ),
                    **(
                        {
                            "search_permission_blocked": search_permission_blocked,
                            "match_scope": "visible_apps_fallback",
                        }
                        if search_permission_blocked is not None
                        else {}
                    ),
                },
                suggested_next_call=None,
            )
        if len(matches) > 1:
            return _failed(
                "AMBIGUOUS_APP",
                f"multiple apps matched '{requested}'",
                details={"app_name": requested, "matches": matches},
                suggested_next_call=None,
            )
        match = matches[0]
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "resolved app",
            "normalized_args": {"app_name": requested, "package_tag_id": package_tag_id},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "verification": {},
            **match,
        }

    def button_style_catalog_get(self, *, profile: str) -> JSONObject:
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read button style catalog",
            "normalized_args": {},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [],
            "verification": {"button_style_catalog_verified": True},
            "verified": True,
            "profile": profile,
            **button_style_catalog_payload(),
        }

    def _expand_custom_button_partial_patches(
        self,
        *,
        profile: str,
        app_key: str,
        existing_by_id: dict[int, dict[str, Any]],
        existing_by_text: dict[str, list[dict[str, Any]]],
        patch_buttons: list[CustomButtonPartialPatch],
    ) -> tuple[list[CustomButtonUpsertPatch], list[dict[str, Any]], list[dict[str, Any]]]:
        expanded: list[CustomButtonUpsertPatch] = []
        issues: list[dict[str, Any]] = []
        results: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_buttons):
            selector_id = _coerce_positive_int(patch.button_id)
            selector_text = str(patch.button_text or "").strip()
            button_id: int | None = None
            if selector_id is not None:
                if selector_id not in existing_by_id:
                    issue = {
                        "error_code": "CUSTOM_BUTTON_NOT_FOUND",
                        "reason_path": f"patch_buttons[{index}].button_id",
                        "button_id": selector_id,
                        "message": "button_id does not exist in the current app draft",
                        "next_action": "call app_get and use custom_buttons[].button_id",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
                button_id = selector_id
            else:
                matches = existing_by_text.get(selector_text, [])
                if len(matches) != 1:
                    issue = {
                        "error_code": "AMBIGUOUS_CUSTOM_BUTTON" if matches else "CUSTOM_BUTTON_NOT_FOUND",
                        "reason_path": f"patch_buttons[{index}].button_text",
                        "button_text": selector_text,
                        "candidate_button_ids": [
                            item.get("button_id")
                            for item in matches
                            if _coerce_positive_int(item.get("button_id")) is not None
                        ],
                        "message": "patch_buttons[] must target a single existing button; use button_id when names are duplicated",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
                button_id = _coerce_positive_int(matches[0].get("button_id"))
                if button_id is None:
                    issue = {
                        "error_code": "CUSTOM_BUTTON_ID_MISSING",
                        "reason_path": f"patch_buttons[{index}].button_text",
                        "button_text": selector_text,
                        "message": "matched button has no readable button_id",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
            try:
                detail_response = self.buttons.custom_button_get(
                    profile=profile,
                    app_key=app_key,
                    button_id=button_id,
                    being_draft=True,
                    include_raw=False,
                )
                detail = _normalize_custom_button_detail(detail_response.get("result") if isinstance(detail_response.get("result"), dict) else {})
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                issue = {
                    "error_code": "CUSTOM_BUTTON_PATCH_DETAIL_READ_FAILED",
                    "reason_path": f"patch_buttons[{index}]",
                    "button_id": button_id,
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            patch_payload = _custom_button_upsert_payload_from_detail(
                detail,
                button_id=button_id,
                client_key=str(patch.client_key or "").strip() or None,
            )
            normalized_set, set_issues = _normalize_custom_button_partial_set(patch.set, reason_path=f"patch_buttons[{index}].set")
            normalized_unset, unset_issues = _normalize_custom_button_partial_unset(patch.unset, reason_path=f"patch_buttons[{index}].unset")
            if set_issues or unset_issues:
                patch_issues = [*set_issues, *unset_issues]
                issues.extend(patch_issues)
                results.append({"index": index, "status": "failed", "button_id": button_id, "issues": patch_issues})
                continue
            for key, value in normalized_set.items():
                if key in {"trigger_add_data_config", "external_qrobot_config", "trigger_wings_config"} and isinstance(value, dict):
                    if key == "trigger_add_data_config" and _custom_button_add_data_config_has_semantic_inputs(value):
                        existing_config = patch_payload.get(key) if isinstance(patch_payload.get(key), dict) else {}
                        merged_value: dict[str, Any] = {}
                        for preserve_key in ("related_app_key", "related_app_name"):
                            if preserve_key in existing_config:
                                merged_value[preserve_key] = deepcopy(existing_config[preserve_key])
                        _deep_merge_public_config(merged_value, value)
                    else:
                        merged_value = deepcopy(patch_payload.get(key) if isinstance(patch_payload.get(key), dict) else {})
                        _deep_merge_public_config(merged_value, value)
                    if key == "trigger_add_data_config":
                        merged_value = _normalize_custom_button_add_data_config_for_public(merged_value)
                    patch_payload[key] = merged_value
                else:
                    patch_payload[key] = value
            for key in normalized_unset:
                patch_payload.pop(key, None)
            try:
                expanded_patch = CustomButtonUpsertPatch.model_validate(patch_payload)
            except Exception as error:
                issue = {
                    "error_code": "CUSTOM_BUTTON_PATCH_HYDRATION_FAILED",
                    "reason_path": f"patch_buttons[{index}]",
                    "button_id": button_id,
                    "message": str(error),
                    "hydrated_payload": _compact_dict(patch_payload),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            expanded.append(expanded_patch)
            results.append(
                {
                    "index": index,
                    "status": "expanded",
                    "button_id": button_id,
                    "set_paths": sorted(normalized_set),
                    "unset_paths": sorted(normalized_unset),
                }
            )
        return expanded, issues, results

    def app_custom_buttons_apply(self, *, profile: str, request: CustomButtonsApplyRequest) -> JSONObject:
        normalized_args = request.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
        app_key = request.app_key
        apply_started_at = time.perf_counter()
        permission_ms = 0
        button_inventory_ms = 0
        app_name_read_ms = 0
        button_patch_hydration_ms = 0
        add_data_compile_ms = 0
        edit_context_ms = 0
        button_write_ms = 0
        button_readback_ms = 0
        view_config_ms = 0
        publish_ms = 0
        permission_outcomes: list[PermissionCheckOutcome] = []
        button_write_intent = bool(request.upsert_buttons or request.patch_buttons or request.remove_buttons)
        if button_write_intent:
            permission_started_at = time.perf_counter()
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=app_key,
                required_permission="edit_app",
                normalized_args=normalized_args,
            )
            permission_ms += _duration_ms(permission_started_at)
            if permission_outcome.block is not None:
                _merge_duration_breakdown(
                    permission_outcome.block,
                    permission_ms=permission_ms,
                    total_ms=_duration_ms(apply_started_at),
                )
                return permission_outcome.block
            permission_outcomes.append(permission_outcome)
        if request.view_configs:
            permission_started_at = time.perf_counter()
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=app_key,
                required_permission="view_manage",
                normalized_args=normalized_args,
            )
            permission_ms += _duration_ms(permission_started_at)
            if permission_outcome.block is not None:
                _merge_duration_breakdown(
                    permission_outcome.block,
                    permission_ms=permission_ms,
                    total_ms=_duration_ms(apply_started_at),
                )
                return permission_outcome.block
            permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            response = _apply_permission_outcomes(response, *permission_outcomes)
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                button_inventory_ms=button_inventory_ms,
                app_name_read_ms=app_name_read_ms,
                button_patch_hydration_ms=button_patch_hydration_ms,
                add_data_compile_ms=add_data_compile_ms,
                edit_context_ms=edit_context_ms,
                button_write_ms=button_write_ms,
                button_readback_ms=button_readback_ms,
                view_config_ms=view_config_ms,
                publish_ms=publish_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        same_call_client_keys = {
            str(patch.client_key or "").strip()
            for patch in request.upsert_buttons
            if str(patch.client_key or "").strip()
        }
        all_upserts_are_named_creates = bool(request.upsert_buttons) and all(
            str(patch.client_key or "").strip()
            and _coerce_positive_int(patch.button_id) is None
            for patch in request.upsert_buttons
        )
        view_config_refs = [
            binding.button_ref
            for config in request.view_configs
            for binding in config.buttons
        ]
        view_refs_need_inventory = any(
            _coerce_positive_int(ref) is None and str(ref or "").strip() not in same_call_client_keys
            for ref in view_config_refs
        )
        needs_button_inventory = (
            bool(request.patch_buttons)
            or bool(request.remove_buttons)
            or any(_coerce_positive_int(patch.button_id) is not None for patch in request.upsert_buttons)
            or (bool(request.upsert_buttons) and not all_upserts_are_named_creates)
            or view_refs_need_inventory
        )
        try:
            button_inventory_started_at = time.perf_counter()
            existing_buttons = (
                self._load_custom_buttons_for_builder(profile=profile, app_key=app_key)
                if needs_button_inventory
                else []
            )
        except (QingflowApiError, RuntimeError) as error:
            button_inventory_ms += _duration_ms(button_inventory_started_at)
            api_error = _coerce_api_error(error)
            return finalize(_failed_from_api_error(
                "CUSTOM_BUTTON_LIST_FAILED",
                api_error,
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"app_key": app_key}, resource="custom_buttons", error=api_error),
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        button_inventory_ms += _duration_ms(button_inventory_started_at)
        app_name_started_at = time.perf_counter()
        app_name = self._read_app_name_for_builder_output(profile=profile, app_key=app_key)
        app_name_read_ms += _duration_ms(app_name_started_at)

        existing_by_id = {
            button_id: item
            for item in existing_buttons
            if (button_id := _coerce_positive_int(item.get("button_id"))) is not None
        }
        existing_by_text: dict[str, list[dict[str, Any]]] = {}
        for item in existing_buttons:
            text = str(item.get("button_text") or "").strip()
            if text:
                existing_by_text.setdefault(text, []).append(item)

        upsert_buttons = list(request.upsert_buttons)
        if request.patch_buttons:
            button_patch_started_at = time.perf_counter()
            expanded_buttons, patch_issues, patch_results = self._expand_custom_button_partial_patches(
                profile=profile,
                app_key=app_key,
                existing_by_id=existing_by_id,
                existing_by_text=existing_by_text,
                patch_buttons=request.patch_buttons,
            )
            button_patch_hydration_ms += _duration_ms(button_patch_started_at)
            if patch_issues:
                return finalize(
                    _failed(
                        "CUSTOM_BUTTON_PATCH_HYDRATION_FAILED",
                        "one or more custom button partial patches could not be hydrated; no write was executed",
                        normalized_args=normalized_args,
                        details={"patch_results": patch_results, "blocking_issues": patch_issues},
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                )
            upsert_buttons.extend(expanded_buttons)
            normalized_args["upsert_buttons"] = [
                patch.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
                for patch in upsert_buttons
            ]
            normalized_args["patch_results"] = patch_results

        add_data_compile_started_at = time.perf_counter()
        compiled_add_data_configs, add_data_issues = self._compile_custom_button_semantic_add_data_configs(
            profile=profile,
            app_key=app_key,
            patches=upsert_buttons,
        )
        add_data_compile_ms += _duration_ms(add_data_compile_started_at)
        upsert_ops: list[dict[str, Any]] = []
        remove_ops: list[dict[str, Any]] = []
        blocking_issues: list[dict[str, Any]] = []
        blocking_issues.extend(add_data_issues)
        touched_existing_ids: set[int] = set()
        used_client_keys: set[str] = set()

        for index, patch in enumerate(upsert_buttons):
            patch_payload = patch.model_dump(mode="json", exclude_none=True)
            client_key = str(patch.client_key or "").strip()
            if client_key:
                if client_key in used_client_keys:
                    blocking_issues.append(
                        {
                            "error_code": "DUPLICATE_CLIENT_KEY",
                            "reason_path": f"upsert_buttons[{index}].client_key",
                            "client_key": client_key,
                            "message": "client_key must be unique within one button apply call",
                        }
                    )
                used_client_keys.add(client_key)
            button_id = _coerce_positive_int(patch.button_id)
            if button_id is not None:
                if button_id not in existing_by_id:
                    blocking_issues.append(
                        {
                            "error_code": "CUSTOM_BUTTON_NOT_FOUND",
                            "reason_path": f"upsert_buttons[{index}].button_id",
                            "button_id": button_id,
                            "message": "button_id does not exist in the current app draft",
                            "next_action": "call app_get and use custom_buttons[].button_id",
                        }
                    )
                    continue
                if button_id in touched_existing_ids:
                    blocking_issues.append(
                        {
                            "error_code": "DUPLICATE_CUSTOM_BUTTON_OPERATION",
                            "reason_path": f"upsert_buttons[{index}]",
                            "button_id": button_id,
                            "message": "the same button is targeted more than once",
                        }
                    )
                    continue
                touched_existing_ids.add(button_id)
                upsert_ops.append({"operation": "update", "button_id": button_id, "patch": patch, "index": index})
                continue
            text = str(patch.button_text or "").strip()
            matches = existing_by_text.get(text, [])
            if len(matches) > 1:
                blocking_issues.append(
                    {
                        "error_code": "AMBIGUOUS_CUSTOM_BUTTON",
                        "reason_path": f"upsert_buttons[{index}].button_text",
                        "button_text": text,
                        "candidate_button_ids": [
                            item.get("button_id")
                            for item in matches
                            if _coerce_positive_int(item.get("button_id")) is not None
                        ],
                        "message": "button_text matches multiple existing custom buttons; pass button_id",
                    }
                )
                continue
            if len(matches) == 1:
                matched_id = _coerce_positive_int(matches[0].get("button_id"))
                if matched_id is None:
                    blocking_issues.append(
                        {
                            "error_code": "CUSTOM_BUTTON_ID_MISSING",
                            "reason_path": f"upsert_buttons[{index}].button_text",
                            "button_text": text,
                            "message": "matched button has no readable button_id",
                        }
                    )
                    continue
                if matched_id in touched_existing_ids:
                    blocking_issues.append(
                        {
                            "error_code": "DUPLICATE_CUSTOM_BUTTON_OPERATION",
                            "reason_path": f"upsert_buttons[{index}]",
                            "button_id": matched_id,
                            "message": "the same button is targeted more than once",
                        }
                    )
                    continue
                touched_existing_ids.add(matched_id)
                upsert_ops.append({"operation": "update", "button_id": matched_id, "patch": patch, "index": index})
            else:
                upsert_ops.append({"operation": "create", "button_id": None, "patch": patch, "index": index})

        for index, selector in enumerate(request.remove_buttons):
            selector_id = _coerce_positive_int(selector.button_id)
            if selector_id is not None:
                if selector_id not in existing_by_id:
                    blocking_issues.append(
                        {
                            "error_code": "CUSTOM_BUTTON_NOT_FOUND",
                            "reason_path": f"remove_buttons[{index}].button_id",
                            "button_id": selector_id,
                            "message": "button_id does not exist in the current app draft",
                            "next_action": "call app_get and use custom_buttons[].button_id",
                        }
                    )
                    continue
                if selector_id in touched_existing_ids:
                    blocking_issues.append(
                        {
                            "error_code": "DUPLICATE_CUSTOM_BUTTON_OPERATION",
                            "reason_path": f"remove_buttons[{index}]",
                            "button_id": selector_id,
                            "message": "the same button is targeted more than once",
                        }
                    )
                    continue
                touched_existing_ids.add(selector_id)
                remove_ops.append({"operation": "remove", "button_id": selector_id, "selector": selector, "index": index})
                continue
            text = str(selector.button_text or "").strip()
            matches = existing_by_text.get(text, [])
            if not matches:
                blocking_issues.append(
                    {
                        "error_code": "CUSTOM_BUTTON_NOT_FOUND",
                        "reason_path": f"remove_buttons[{index}].button_text",
                        "button_text": text,
                        "message": "button_text does not match any existing custom button",
                    }
                )
                continue
            if len(matches) > 1:
                blocking_issues.append(
                    {
                        "error_code": "AMBIGUOUS_CUSTOM_BUTTON",
                        "reason_path": f"remove_buttons[{index}].button_text",
                        "button_text": text,
                        "candidate_button_ids": [
                            item.get("button_id")
                            for item in matches
                            if _coerce_positive_int(item.get("button_id")) is not None
                        ],
                        "message": "button_text matches multiple existing custom buttons; pass button_id",
                    }
                )
                continue
            matched_id = _coerce_positive_int(matches[0].get("button_id"))
            if matched_id is None:
                blocking_issues.append(
                    {
                        "error_code": "CUSTOM_BUTTON_ID_MISSING",
                        "reason_path": f"remove_buttons[{index}].button_text",
                        "button_text": text,
                        "message": "matched button has no readable button_id",
                    }
                )
                continue
            if matched_id in touched_existing_ids:
                blocking_issues.append(
                    {
                        "error_code": "DUPLICATE_CUSTOM_BUTTON_OPERATION",
                        "reason_path": f"remove_buttons[{index}]",
                        "button_id": matched_id,
                        "message": "the same button is targeted more than once",
                    }
                )
                continue
            touched_existing_ids.add(matched_id)
            remove_ops.append({"operation": "remove", "button_id": matched_id, "selector": selector, "index": index})

        if blocking_issues:
            return finalize(
                _failed(
                    "CUSTOM_BUTTON_APPLY_BLOCKED",
                    "custom button apply has blocking issues; no write was executed",
                    normalized_args=normalized_args,
                    details={"blocking_issues": blocking_issues},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )

        edit_version_no = None
        if button_write_intent:
            edit_context_started_at = time.perf_counter()
            edit_version_no, edit_context_error = self._ensure_app_edit_context(
                profile=profile,
                app_key=app_key,
                normalized_args=normalized_args,
                failure_code="CUSTOM_BUTTON_APPLY_FAILED",
            )
            edit_context_ms += _duration_ms(edit_context_started_at)
            if edit_context_error is not None:
                return finalize(edit_context_error)

        created: list[dict[str, Any]] = []
        updated: list[dict[str, Any]] = []
        removed: list[dict[str, Any]] = []
        failed: list[dict[str, Any]] = []
        readback_errors: list[JSONObject] = []
        client_key_map: dict[str, int] = {}
        write_executed = False

        button_write_started_at = time.perf_counter()
        for op in upsert_ops:
            patch = cast(CustomButtonUpsertPatch, op["patch"])
            patch_payload = _serialize_custom_button_payload(patch, add_data_config_override=compiled_add_data_configs.get(int(op["index"])))
            try:
                if op["operation"] == "create":
                    write_executed = True
                    result = self.buttons.custom_button_create(profile=profile, app_key=app_key, payload=patch_payload)
                    button_id = _extract_custom_button_id(result.get("result"))
                    if button_id is None:
                        try:
                            button_readback_started_at = time.perf_counter()
                            readback_buttons = self._load_custom_buttons_for_builder(profile=profile, app_key=app_key)
                            button_readback_ms += _duration_ms(button_readback_started_at)
                            matches = [
                                item
                                for item in readback_buttons
                                if str(item.get("button_text") or "").strip() == str(patch.button_text or "").strip()
                                and _coerce_positive_int(item.get("button_id")) is not None
                            ]
                            if len(matches) == 1:
                                button_id = _coerce_positive_int(matches[0].get("button_id"))
                        except (QingflowApiError, RuntimeError) as error:
                            button_readback_ms += _duration_ms(button_readback_started_at)
                            readback_errors.append(
                                {
                                    "resource": "custom_buttons",
                                    "phase": "create_id_lookup",
                                    "index": op["index"],
                                    "transport_error": _transport_error_payload(_coerce_api_error(error)),
                                }
                            )
                            button_id = None
                    entry = {
                        "index": op["index"],
                        "operation": "create",
                        "status": "success" if button_id is not None else "unverified",
                        "button_id": button_id,
                        "button_text": patch.button_text,
                    }
                    created.append(entry)
                    if button_id is not None and patch.client_key:
                        client_key_map[str(patch.client_key)] = button_id
                else:
                    button_id = int(op["button_id"])
                    write_executed = True
                    self.buttons.custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=patch_payload)
                    updated.append(
                        {
                            "index": op["index"],
                            "operation": "update",
                            "status": "success",
                            "button_id": button_id,
                            "button_text": patch.button_text,
                        }
                    )
                    if patch.client_key:
                        client_key_map[str(patch.client_key)] = button_id
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failed.append(
                    {
                        "index": op["index"],
                        "operation": op["operation"],
                        "button_id": op.get("button_id"),
                        "button_text": patch.button_text,
                        "status": "failed",
                        "error_code": "CUSTOM_BUTTON_WRITE_FAILED",
                        "message": api_error.message,
                        "transport_error": _transport_error_payload(api_error),
                    }
                )

        for op in remove_ops:
            selector = cast(CustomButtonRemovePatch, op["selector"])
            button_id = int(op["button_id"])
            try:
                write_executed = True
                self.buttons.custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
                button_readback_started_at = time.perf_counter()
                delete_readback = self._verify_custom_button_deleted_by_id(profile=profile, app_key=app_key, button_id=button_id)
                button_readback_ms += _duration_ms(button_readback_started_at)
                removed.append(
                    {
                        "index": op["index"],
                        "operation": "remove",
                        "status": delete_readback.get("status") or "readback_pending",
                        "button_id": button_id,
                        "button_text": selector.button_text or (existing_by_id.get(button_id) or {}).get("button_text"),
                        "delete_executed": True,
                        "readback_status": delete_readback.get("readback_status"),
                        "safe_to_retry_delete": False,
                        **(
                            {
                                "error_code": delete_readback.get("error_code"),
                                "message": delete_readback.get("message"),
                                "request_id": delete_readback.get("request_id"),
                                "backend_code": delete_readback.get("backend_code"),
                                "http_status": delete_readback.get("http_status"),
                                "transport_error": delete_readback.get("transport_error"),
                            }
                            if delete_readback.get("readback_status") != "deleted"
                            else {}
                        ),
                    }
                )
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failed.append(
                    {
                        "index": op["index"],
                        "operation": "remove",
                        "button_id": button_id,
                        "button_text": selector.button_text,
                        "status": "failed",
                        "error_code": "CUSTOM_BUTTON_WRITE_FAILED",
                        "message": api_error.message,
                        "transport_error": _transport_error_payload(api_error),
                    }
                )

        button_write_ms += _duration_ms(button_write_started_at)
        created_ids = [
            int(item["button_id"])
            for item in created
            if _coerce_positive_int(item.get("button_id")) is not None
        ]
        updated_ids = [
            int(item["button_id"])
            for item in updated
            if _coerce_positive_int(item.get("button_id")) is not None
        ]
        removed_ids = [
            int(item["button_id"])
            for item in removed
            if _coerce_positive_int(item.get("button_id")) is not None
        ]
        same_call_view_binding_verifies_buttons = bool(request.view_configs) and not needs_button_inventory
        final_readback_skipped = (
            bool(created or updated)
            and (not request.view_configs or same_call_view_binding_verifies_buttons)
            and not removed
            and not failed
            and not readback_errors
            and all(_coerce_positive_int(item.get("button_id")) is not None for item in [*created, *updated])
        )
        needs_button_list_readback = bool(created or updated or (request.view_configs and needs_button_inventory)) and not final_readback_skipped
        readback_buttons: list[dict[str, Any]] = []
        readback_failed = False
        if needs_button_list_readback:
            try:
                button_readback_started_at = time.perf_counter()
                readback_buttons = self._load_custom_buttons_for_builder(profile=profile, app_key=app_key)
                button_readback_ms += _duration_ms(button_readback_started_at)
            except (QingflowApiError, RuntimeError) as error:
                button_readback_ms += _duration_ms(button_readback_started_at)
                readback_errors.append(
                    {
                        "resource": "custom_buttons",
                        "phase": "final_list",
                        "transport_error": _transport_error_payload(_coerce_api_error(error)),
                    }
                )
                readback_failed = True
        readback_ids = {
            button_id
            for item in readback_buttons
            if (button_id := _coerce_positive_int(item.get("button_id"))) is not None
        }
        removed_verified = all(str(item.get("readback_status") or "") == "deleted" for item in removed)
        remove_readback_pending = any(str(item.get("readback_status") or "") != "deleted" for item in removed)
        created_verified = final_readback_skipped or (not readback_failed and all(button_id in readback_ids for button_id in created_ids))
        updated_verified = final_readback_skipped or (not readback_failed and all(button_id in readback_ids for button_id in updated_ids))
        verified = (
            (final_readback_skipped or not readback_failed)
            and created_verified
            and updated_verified
            and removed_verified
            and not failed
            and all(_coerce_positive_int(item.get("button_id")) is not None for item in created)
        )
        custom_buttons_verified = verified
        view_config_results: list[dict[str, Any]] = []
        view_config_failed: list[dict[str, Any]] = []
        view_config_warnings: list[dict[str, Any]] = []
        view_config_verified = True
        view_config_write_executed = False
        view_config_write_succeeded = False
        created_verified_by_view_binding = False
        if request.view_configs:
            view_config_started_at = time.perf_counter()
            view_config_result = self._apply_custom_button_view_configs(
                profile=profile,
                app_key=app_key,
                view_configs=request.view_configs,
                client_key_map=client_key_map,
                existing_buttons=existing_buttons,
                readback_buttons=readback_buttons,
                created_ids=created_ids,
                updated_ids=updated_ids,
                removed_ids=removed_ids,
            )
            view_config_ms += _duration_ms(view_config_started_at)
            view_config_results = list(view_config_result.get("view_configs") or [])
            view_config_failed = list(view_config_result.get("failed") or [])
            view_config_warnings = list(view_config_result.get("warnings") or [])
            view_config_verified = bool(view_config_result.get("verified", False))
            view_config_write_executed = bool(view_config_result.get("write_executed", False))
            view_config_write_succeeded = bool(view_config_result.get("write_succeeded", False))
            write_executed = write_executed or view_config_write_executed
            if view_config_failed:
                failed.extend(view_config_failed)
            if view_config_warnings:
                if not isinstance(view_config_warnings, list):
                    view_config_warnings = []
            if not created_verified and created_ids and view_config_verified:
                bound_custom_button_ids = {
                    button_id
                    for view_config in view_config_results
                    if isinstance(view_config, dict)
                    for button in (view_config.get("actual_buttons") or [])
                    if isinstance(button, dict)
                    and str(button.get("button_type") or "").strip().upper() == "CUSTOM"
                    and (button_id := _coerce_positive_int(button.get("button_id"))) is not None
                }
                expected_custom_button_ids = {
                    button_id
                    for view_config in view_config_results
                    if isinstance(view_config, dict) and bool(view_config.get("verified_by_write_ack"))
                    for button in (view_config.get("expected_buttons") or [])
                    if isinstance(button, dict)
                    and str(button.get("button_type") or "").strip().upper() == "CUSTOM"
                    and (button_id := _coerce_positive_int(button.get("button_id"))) is not None
                }
                created_verified_by_view_binding = all(
                    button_id in bound_custom_button_ids or button_id in expected_custom_button_ids
                    for button_id in created_ids
                )
                if created_verified_by_view_binding:
                    created_verified = True
                    custom_buttons_verified = (
                        (final_readback_skipped or not readback_failed)
                        and updated_verified
                        and removed_verified
                        and not failed
                        and all(_coerce_positive_int(item.get("button_id")) is not None for item in created)
                    )
        verified = custom_buttons_verified and view_config_verified
        write_succeeded = bool(created or updated or removed or view_config_write_succeeded)
        status = "success" if verified else ("partial_success" if write_succeeded else "failed")
        error_code = None if verified else (
            "CUSTOM_BUTTON_READBACK_PENDING" if readback_failed else "CUSTOM_BUTTON_APPLY_PARTIAL" if write_succeeded else "CUSTOM_BUTTON_APPLY_FAILED"
        )
        message = (
            "applied custom button patch"
            if verified
            else "custom button apply completed with partial verification"
            if write_succeeded
            else "custom button apply failed; no successful write was confirmed"
        )
        warnings = []
        warnings.extend(view_config_warnings)
        if not verified:
            warnings.append(
                _warning(
                    "CUSTOM_BUTTON_APPLY_PARTIAL" if write_succeeded else "CUSTOM_BUTTON_APPLY_FAILED",
                    "custom button write landed but verification is partial; inspect created/updated/removed/failed"
                    if write_succeeded
                    else "custom button writes all failed or produced no confirmed result; application was not published",
                )
            )
        if remove_readback_pending:
            warnings.append(
                _warning(
                    "CUSTOM_BUTTON_DELETE_READBACK_PENDING",
                    "custom button delete was sent, but deletion readback is not fully verified; do not blindly repeat delete",
                )
            )
        if readback_errors:
            warnings.append(
                _warning(
                    "CUSTOM_BUTTON_READBACK_UNAVAILABLE",
                    "custom button write was executed but readback is unavailable",
                )
            )
        response = {
            "status": status,
            "error_code": error_code,
            "recoverable": not verified,
            "message": message,
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {
                "trigger_action": [member.value for member in PublicButtonTriggerAction],
                "style_preset": [item["key"] for item in button_style_catalog_payload()["presets"]],
            },
            "details": {
                "edit_version_no": edit_version_no,
                "button_ids_by_client_key": client_key_map,
                "readback_failed": readback_failed,
                "initial_inventory_read_skipped": not needs_button_inventory,
                "final_readback_skipped": final_readback_skipped,
                "same_call_view_binding_verifies_buttons": same_call_view_binding_verifies_buttons,
                "created_verified_by_view_binding": created_verified_by_view_binding,
                **({"readback_errors": readback_errors} if readback_errors else {}),
                "compiled_match_rules": {
                    str(index): _summarize_compiled_match_rules(config.get("que_relation") or [])
                    for index, config in compiled_add_data_configs.items()
                },
            },
            "request_id": None,
            "suggested_next_call": None if verified else {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            "noop": False,
            "warnings": warnings,
            "verification": {
                "custom_buttons_verified": custom_buttons_verified,
                "readback_loaded": False if final_readback_skipped else not readback_failed,
                "final_readback_skipped": final_readback_skipped,
                "created_verified": created_verified,
                "updated_verified": updated_verified,
                "removed_verified": removed_verified,
                "remove_readback_pending": remove_readback_pending,
                "removed_readback_results": deepcopy(removed),
                "view_button_bindings_verified": view_config_verified,
            },
            "verified": verified,
            "app_key": app_key,
            "app_name": app_name,
            "mode": "apply",
            "created": created,
            "updated": updated,
            "removed": removed,
            "failed": failed,
            "view_configs": view_config_results,
            "button_ids_by_client_key": client_key_map,
            "write_executed": write_executed,
            "write_succeeded": write_succeeded,
            "safe_to_retry": not write_executed,
        }
        publish_started_at = time.perf_counter()
        published_response = self._append_publish_result(
            profile=profile,
            app_key=app_key,
            publish=bool(write_succeeded and button_write_intent),
            response=response,
            edit_version_no=edit_version_no,
        )
        publish_ms += _duration_ms(publish_started_at)
        return finalize(
            published_response
        )

    def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
        normalized_args = {"app_key": app_key}
        permission_outcomes: list[PermissionCheckOutcome] = []
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        listing = self.buttons.custom_button_list(profile=profile, app_key=app_key, being_draft=True, include_raw=False)
        items = [
            _normalize_custom_button_summary(item)
            for item in (listing.get("items") or [])
            if isinstance(item, dict)
        ]
        return finalize(
            {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "read custom button summary",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": [],
                "verification": {"custom_buttons_verified": True},
                "verified": True,
                "app_key": app_key,
                "count": len(items),
                "buttons": items,
            }
        )

    def app_custom_button_get(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
        normalized_args = {"app_key": app_key, "button_id": button_id}
        permission_outcomes: list[PermissionCheckOutcome] = []
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        detail = self.buttons.custom_button_get(
            profile=profile,
            app_key=app_key,
            button_id=button_id,
            being_draft=True,
            include_raw=False,
        )
        button = _normalize_custom_button_detail(detail.get("result") if isinstance(detail.get("result"), dict) else {})
        return finalize(
            {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "read custom button detail",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": [],
                "verification": {"custom_button_verified": True},
                "verified": True,
                "app_key": app_key,
                "button_id": button_id,
                "button": button,
            }
        )

    def app_custom_button_create(self, *, profile: str, app_key: str, payload: CustomButtonPatch) -> JSONObject:
        normalized_args = {"app_key": app_key, "payload": payload.model_dump(mode="json")}
        permission_outcomes: list[PermissionCheckOutcome] = []
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        edit_version_no, edit_context_error = self._ensure_app_edit_context(
            profile=profile,
            app_key=app_key,
            normalized_args=normalized_args,
            failure_code="CUSTOM_BUTTON_CREATE_FAILED",
        )
        if edit_context_error is not None:
            return finalize(edit_context_error)

        create_result = self.buttons.custom_button_create(
            profile=profile,
            app_key=app_key,
            payload=_serialize_custom_button_payload(payload),
        )
        raw_result = create_result.get("result")
        button_id = _extract_custom_button_id(raw_result)
        if button_id is None:
            return finalize(
                _failed(
                    "CUSTOM_BUTTON_CREATE_FAILED",
                    "custom button create succeeded but no button_id was returned",
                    normalized_args=normalized_args,
                    details={"app_key": app_key, "result": deepcopy(raw_result), "edit_version_no": edit_version_no},
                    suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        try:
            readback = self.buttons.custom_button_get(
                profile=profile,
                app_key=app_key,
                button_id=button_id,
                being_draft=True,
                include_raw=False,
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            response = {
                "status": "partial_success",
                "error_code": "CUSTOM_BUTTON_READBACK_PENDING",
                "recoverable": True,
                "message": "created custom button; detail readback unavailable",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {
                    "app_key": app_key,
                    "button_id": button_id,
                    "edit_version_no": edit_version_no,
                    "transport_error": _transport_error_payload(api_error),
                },
                "request_id": api_error.request_id,
                "suggested_next_call": {
                    "tool_name": "app_custom_button_get",
                    "arguments": {"profile": profile, "app_key": app_key, "button_id": button_id},
                },
                "noop": False,
                "warnings": [_warning("CUSTOM_BUTTON_READBACK_PENDING", "custom button was created, but detail readback is not available")],
                "verification": {"custom_button_verified": False},
                "verified": False,
                "app_key": app_key,
                "button_id": button_id,
                "write_executed": True,
                "write_succeeded": True,
                "safe_to_retry": False,
            }
            if _is_permission_restricted_api_error(api_error):
                response = _apply_permission_outcomes(
                    response,
                    _verification_read_outcome(
                        resource="custom_button",
                        target={"app_key": app_key, "button_id": button_id},
                        transport_error=api_error,
                    ),
                )
            return finalize(self._append_publish_result(profile=profile, app_key=app_key, publish=True, response=response))
        button = _normalize_custom_button_detail(readback.get("result") if isinstance(readback.get("result"), dict) else {})
        return finalize(
            self._append_publish_result(
                profile=profile,
                app_key=app_key,
                publish=True,
                response={
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "created custom button",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {},
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": False,
                    "warnings": [],
                    "verification": {"custom_button_verified": True},
                    "verified": True,
                    "app_key": app_key,
                    "button_id": button_id,
                    "edit_version_no": edit_version_no,
                    "button": button,
                },
            )
        )

    def app_custom_button_update(
        self,
        *,
        profile: str,
        app_key: str,
        button_id: int,
        payload: CustomButtonPatch,
    ) -> JSONObject:
        normalized_args = {"app_key": app_key, "button_id": button_id, "payload": payload.model_dump(mode="json")}
        permission_outcomes: list[PermissionCheckOutcome] = []
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        edit_version_no, edit_context_error = self._ensure_app_edit_context(
            profile=profile,
            app_key=app_key,
            normalized_args=normalized_args,
            failure_code="CUSTOM_BUTTON_UPDATE_FAILED",
        )
        if edit_context_error is not None:
            return finalize(edit_context_error)

        self.buttons.custom_button_update(
            profile=profile,
            app_key=app_key,
            button_id=button_id,
            payload=_serialize_custom_button_payload(payload),
        )
        try:
            readback = self.buttons.custom_button_get(
                profile=profile,
                app_key=app_key,
                button_id=button_id,
                being_draft=True,
                include_raw=False,
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            response = {
                "status": "partial_success",
                "error_code": "CUSTOM_BUTTON_READBACK_PENDING",
                "recoverable": True,
                "message": "updated custom button; detail readback unavailable",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {
                    "app_key": app_key,
                    "button_id": button_id,
                    "edit_version_no": edit_version_no,
                    "transport_error": _transport_error_payload(api_error),
                },
                "request_id": api_error.request_id,
                "suggested_next_call": {
                    "tool_name": "app_custom_button_get",
                    "arguments": {"profile": profile, "app_key": app_key, "button_id": button_id},
                },
                "noop": False,
                "warnings": [_warning("CUSTOM_BUTTON_READBACK_PENDING", "custom button was updated, but detail readback is not available")],
                "verification": {"custom_button_verified": False},
                "verified": False,
                "app_key": app_key,
                "button_id": button_id,
                "write_executed": True,
                "write_succeeded": True,
                "safe_to_retry": False,
            }
            if _is_permission_restricted_api_error(api_error):
                response = _apply_permission_outcomes(
                    response,
                    _verification_read_outcome(
                        resource="custom_button",
                        target={"app_key": app_key, "button_id": button_id},
                        transport_error=api_error,
                    ),
                )
            return finalize(self._append_publish_result(profile=profile, app_key=app_key, publish=True, response=response))
        button = _normalize_custom_button_detail(readback.get("result") if isinstance(readback.get("result"), dict) else {})
        return finalize(
            self._append_publish_result(
                profile=profile,
                app_key=app_key,
                publish=True,
                response={
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "updated custom button",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {},
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": False,
                    "warnings": [],
                    "verification": {"custom_button_verified": True},
                    "verified": True,
                    "app_key": app_key,
                    "button_id": button_id,
                    "edit_version_no": edit_version_no,
                    "button": button,
                },
            )
        )

    def app_custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
        normalized_args = {"app_key": app_key, "button_id": button_id}
        permission_outcomes: list[PermissionCheckOutcome] = []
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        edit_version_no, edit_context_error = self._ensure_app_edit_context(
            profile=profile,
            app_key=app_key,
            normalized_args=normalized_args,
            failure_code="CUSTOM_BUTTON_DELETE_FAILED",
        )
        if edit_context_error is not None:
            return finalize(edit_context_error)

        self.buttons.custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
        delete_readback = self._verify_custom_button_deleted_by_id(profile=profile, app_key=app_key, button_id=button_id)
        verified = delete_readback.get("readback_status") == "deleted"
        return finalize(
            self._append_publish_result(
                profile=profile,
                app_key=app_key,
                publish=True,
                response={
                    "status": "success" if verified else "partial_success",
                    "error_code": None if verified else delete_readback.get("error_code") or "CUSTOM_BUTTON_DELETE_READBACK_PENDING",
                    "recoverable": not verified,
                    "message": "deleted custom button" if verified else "custom button delete completed; readback pending",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {},
                    "request_id": delete_readback.get("request_id"),
                    "suggested_next_call": None if verified else {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    "noop": False,
                    "warnings": [] if verified else [_warning("CUSTOM_BUTTON_DELETE_READBACK_PENDING", "custom button delete was sent, but deletion readback is not fully verified")],
                    "verification": {"custom_button_deleted": verified, "delete_readback": delete_readback},
                    "verified": verified,
                    "app_key": app_key,
                    "button_id": button_id,
                    "edit_version_no": edit_version_no,
                    "deleted": verified,
                    "delete_executed": True,
                    "readback_status": delete_readback.get("readback_status"),
                    "safe_to_retry_delete": False,
                },
            )
        )

    def _expand_associated_resource_partial_patches(
        self,
        *,
        existing_by_id: dict[int, dict[str, Any]],
        patch_resources: list[AssociatedResourcePartialPatch],
    ) -> tuple[list[AssociatedResourceUpsertPatch], list[dict[str, Any]], list[dict[str, Any]]]:
        expanded: list[AssociatedResourceUpsertPatch] = []
        issues: list[dict[str, Any]] = []
        results: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_resources):
            item_id = _coerce_positive_int(patch.associated_item_id)
            if item_id is None or item_id not in existing_by_id:
                issue = _associated_resource_not_found_issue(f"patch_resources[{index}].associated_item_id", int(patch.associated_item_id), existing_by_id)
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            patch_payload = _associated_resource_upsert_payload_from_existing_item(
                existing_by_id[item_id],
                associated_item_id=item_id,
                client_key=str(patch.client_key or "").strip() or None,
            )
            normalized_set, set_issues = _normalize_associated_resource_partial_set(patch.set, reason_path=f"patch_resources[{index}].set")
            normalized_unset, unset_issues = _normalize_associated_resource_partial_unset(patch.unset, reason_path=f"patch_resources[{index}].unset")
            if set_issues or unset_issues:
                patch_issues = [*set_issues, *unset_issues]
                issues.extend(patch_issues)
                results.append({"index": index, "status": "failed", "associated_item_id": item_id, "issues": patch_issues})
                continue
            for key, value in normalized_set.items():
                patch_payload[key] = value
                if key == "match_mappings":
                    patch_payload["match_rules"] = []
                elif key == "match_rules":
                    patch_payload["match_mappings"] = []
            for key in normalized_unset:
                if key == "match_rules":
                    patch_payload["match_rules"] = []
                    patch_payload["match_mappings"] = []
                if key == "match_mappings":
                    patch_payload["match_rules"] = []
                    patch_payload["match_mappings"] = []
            try:
                expanded_patch = AssociatedResourceUpsertPatch.model_validate(patch_payload)
            except Exception as error:
                issue = {
                    "error_code": "ASSOCIATED_RESOURCE_PATCH_HYDRATION_FAILED",
                    "reason_path": f"patch_resources[{index}]",
                    "associated_item_id": item_id,
                    "message": str(error),
                    "hydrated_payload": _compact_dict(patch_payload),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            validation_issue = _validate_associated_resource_patch(expanded_patch, reason_path=f"patch_resources[{index}]")
            if validation_issue is not None:
                issues.append(validation_issue)
                results.append({"index": index, "status": "failed", "associated_item_id": item_id, "issues": [validation_issue]})
                continue
            expanded.append(expanded_patch)
            results.append(
                {
                    "index": index,
                    "status": "expanded",
                    "associated_item_id": item_id,
                    "set_paths": sorted(normalized_set),
                    "unset_paths": sorted(normalized_unset),
                }
            )
        return expanded, issues, results

    def _compile_associated_resource_semantic_match_mappings(
        self,
        *,
        profile: str,
        app_key: str,
        patches: list[AssociatedResourceUpsertPatch],
    ) -> tuple[dict[int, list[dict[str, Any]]], list[dict[str, Any]]]:
        compiled_by_index: dict[int, list[dict[str, Any]]] = {}
        issues: list[dict[str, Any]] = []
        semantic_patches = [
            (index, patch)
            for index, patch in enumerate(patches)
            if patch.match_mappings or (patch.match_rules and patch.match_mappings)
        ]
        if not semantic_patches:
            return compiled_by_index, issues
        try:
            source_schema, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return {}, [
                {
                    "error_code": "ASSOCIATED_RESOURCE_SOURCE_SCHEMA_READ_FAILED",
                    "reason_path": "upsert_resources[].match_mappings",
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                    "next_action": "retry after app schema is readable",
                }
            ]
        source_fields = list(_parse_schema(source_schema).get("fields") or [])
        target_schema_cache: dict[str, list[dict[str, Any]]] = {}
        for index, patch in semantic_patches:
            reason_base = f"upsert_resources[{index}].match_mappings"
            if patch.match_mappings and patch.match_rules:
                issues.append(
                    {
                        "_patch_index": index,
                        "error_code": "MIXED_ASSOCIATED_RESOURCE_MAPPING_MODES",
                        "reason_path": f"upsert_resources[{index}]",
                        "message": "match_mappings cannot be used together with raw match_rules",
                        "next_action": "use semantic match_mappings only, or pass legacy match_rules only",
                    }
                )
                continue
            if not patch.match_mappings:
                continue
            target_app_key = str(patch.target_app_key or "").strip()
            if not target_app_key:
                issues.append(
                    {
                        "_patch_index": index,
                        "error_code": "ASSOCIATED_RESOURCE_TARGET_APP_REQUIRED",
                        "reason_path": f"upsert_resources[{index}].target_app_key",
                        "message": "match_mappings require target_app_key",
                    }
                )
                continue
            if target_app_key not in target_schema_cache:
                try:
                    target_schema, _target_schema_source = self._read_schema_with_fallback(profile=profile, app_key=target_app_key)
                    target_schema_cache[target_app_key] = list(_parse_schema(target_schema).get("fields") or [])
                except (QingflowApiError, RuntimeError) as error:
                    api_error = _coerce_api_error(error)
                    issues.append(
                        {
                            "_patch_index": index,
                            "error_code": "ASSOCIATED_RESOURCE_TARGET_SCHEMA_READ_FAILED",
                            "reason_path": f"upsert_resources[{index}].target_app_key",
                            "target_app_key": target_app_key,
                            "message": api_error.message,
                            "transport_error": _transport_error_payload(api_error),
                            "next_action": "verify target_app_key with app_get",
                        }
                    )
                    continue
            rules, mapping_issues = self._compile_field_match_mappings(
                profile=profile,
                source_app_key=app_key,
                source_fields=source_fields,
                target_fields=target_schema_cache[target_app_key],
                mappings=patch.match_mappings,
                reason_path=reason_base,
                type_error_code="ASSOCIATED_RESOURCE_MAPPING_TYPE_MISMATCH",
                context_label="associated resource match mapping",
            )
            if mapping_issues:
                for issue in mapping_issues:
                    issue["_patch_index"] = index
                issues.extend(mapping_issues)
                continue
            compiled_by_index[index] = rules
        return compiled_by_index, issues

    def _compile_field_match_mappings(
        self,
        *,
        profile: str,
        source_app_key: str,
        source_fields: list[dict[str, Any]],
        target_fields: list[dict[str, Any]],
        mappings: list[FieldMatchMappingPatch],
        reason_path: str,
        type_error_code: str,
        context_label: str,
    ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
        issues: list[dict[str, Any]] = []
        rules: list[dict[str, Any]] = []
        for mapping_index, mapping in enumerate(mappings):
            target_field, target_issue = _resolve_custom_button_schema_field(
                fields=target_fields,
                selector=mapping.target_field,
                reason_path=f"{reason_path}[{mapping_index}].target_field",
                role="match_target",
            )
            if target_issue or target_field is None:
                if target_issue:
                    issues.append(_retag_associated_resource_match_field_issue(target_issue))
                continue
            judge_type, operator_issue = _match_mapping_operator_to_judge_type(
                mapping.operator,
                reason_path=f"{reason_path}[{mapping_index}].operator",
            )
            if operator_issue:
                issues.append(operator_issue)
                continue
            if mapping.source_field is not None:
                source_field, source_issue = _resolve_custom_button_schema_field(
                    fields=source_fields,
                    selector=mapping.source_field,
                    reason_path=f"{reason_path}[{mapping_index}].source_field",
                    role="match_source",
                )
                if source_issue or source_field is None:
                    if source_issue:
                        issues.append(_retag_associated_resource_match_field_issue(source_issue))
                    continue
                type_issue = _custom_button_mapping_type_issue(
                    source_field=source_field,
                    target_field=target_field,
                    reason_path=f"{reason_path}[{mapping_index}]",
                    source_app_key=source_app_key,
                    error_code=type_error_code,
                    context_label=context_label,
                )
                if type_issue:
                    issues.append(type_issue)
                    continue
                rule = _custom_button_field_mapping_rule(source_field=source_field, target_field=target_field)
                rule["judge_type"] = judge_type
                rules.append(rule)
                continue
            if str(mapping.operator or "").strip().lower() in {"is_empty", "not_empty"}:
                rules.append(
                    {
                        **_custom_button_question_ref(target_field),
                        "match_type": MATCH_TYPE_ACCURACY,
                        "judge_type": judge_type,
                        "judge_values": [],
                        "judge_value_details": [],
                    }
                )
                continue
            rule, value_issue = self._custom_button_default_value_rule(
                profile=profile,
                target_field=target_field,
                value=mapping.value,
                reason_path=f"{reason_path}[{mapping_index}].value",
            )
            if value_issue:
                issues.append(_retag_associated_resource_static_value_issue(value_issue))
                continue
            rule["judge_type"] = judge_type
            rules.append(rule)
        return rules, issues

    def app_associated_resources_apply(self, *, profile: str, request: AssociatedResourcesApplyRequest) -> JSONObject:
        normalized_args = request.model_dump(mode="json", exclude_none=True)
        app_key = request.app_key
        apply_started_at = time.perf_counter()
        permission_ms = 0
        resource_inventory_ms = 0
        app_name_read_ms = 0
        resource_patch_hydration_ms = 0
        match_mapping_compile_ms = 0
        edit_context_ms = 0
        resource_write_ms = 0
        resource_readback_ms = 0
        view_config_ms = 0
        publish_ms = 0
        post_publish_verify_ms = 0
        permission_outcomes: list[PermissionCheckOutcome] = []
        resource_write_intent = bool(
            request.upsert_resources
            or request.patch_resources
            or request.remove_associated_item_ids
            or request.reorder_associated_item_ids
        )
        if resource_write_intent:
            permission_started_at = time.perf_counter()
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=app_key,
                required_permission="edit_app",
                normalized_args=normalized_args,
            )
            permission_ms += _duration_ms(permission_started_at)
            if permission_outcome.block is not None:
                _merge_duration_breakdown(
                    permission_outcome.block,
                    permission_ms=permission_ms,
                    total_ms=_duration_ms(apply_started_at),
                )
                return permission_outcome.block
            permission_outcomes.append(permission_outcome)
        if request.view_configs:
            permission_started_at = time.perf_counter()
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=app_key,
                required_permission="view_manage",
                normalized_args=normalized_args,
            )
            permission_ms += _duration_ms(permission_started_at)
            if permission_outcome.block is not None:
                _merge_duration_breakdown(
                    permission_outcome.block,
                    permission_ms=permission_ms,
                    total_ms=_duration_ms(apply_started_at),
                )
                return permission_outcome.block
            permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            response = _apply_permission_outcomes(response, *permission_outcomes)
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                resource_inventory_ms=resource_inventory_ms,
                app_name_read_ms=app_name_read_ms,
                resource_patch_hydration_ms=resource_patch_hydration_ms,
                match_mapping_compile_ms=match_mapping_compile_ms,
                edit_context_ms=edit_context_ms,
                resource_write_ms=resource_write_ms,
                resource_readback_ms=resource_readback_ms,
                view_config_ms=view_config_ms,
                publish_ms=publish_ms,
                post_publish_verify_ms=post_publish_verify_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        same_call_resource_client_keys = {
            str(patch.client_key or "").strip()
            for patch in request.upsert_resources
            if str(patch.client_key or "").strip()
        }
        all_resource_upserts_are_named_creates = bool(request.upsert_resources) and all(
            str(patch.client_key or "").strip()
            and _coerce_positive_int(patch.associated_item_id) is None
            for patch in request.upsert_resources
        )
        view_config_refs_need_inventory = any(
            str(ref or "").strip() not in same_call_resource_client_keys
            for config in request.view_configs
            for ref in config.associated_item_refs
            if str(ref or "").strip()
        )
        view_config_ids_need_inventory = any(
            bool(config.associated_item_ids)
            for config in request.view_configs
        )
        needs_resource_inventory = (
            bool(request.patch_resources)
            or bool(request.remove_associated_item_ids)
            or bool(request.reorder_associated_item_ids)
            or any(_coerce_positive_int(patch.associated_item_id) is not None for patch in request.upsert_resources)
            or (bool(request.upsert_resources) and not all_resource_upserts_are_named_creates)
            or view_config_refs_need_inventory
            or view_config_ids_need_inventory
        )
        try:
            resource_inventory_started_at = time.perf_counter()
            existing_resources = (
                self._load_associated_resources_for_builder(profile=profile, app_key=app_key, include_raw=True)
                if needs_resource_inventory
                else []
            )
        except (QingflowApiError, RuntimeError) as error:
            resource_inventory_ms += _duration_ms(resource_inventory_started_at)
            api_error = _coerce_api_error(error)
            return finalize(_failed_from_api_error(
                "ASSOCIATED_RESOURCES_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"app_key": app_key}, resource="associated_resources", error=api_error),
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        resource_inventory_ms += _duration_ms(resource_inventory_started_at)
        app_name_started_at = time.perf_counter()
        app_name = self._read_app_name_for_builder_output(profile=profile, app_key=app_key)
        app_name_read_ms += _duration_ms(app_name_started_at)

        existing_by_id = _associated_resource_index(existing_resources)
        blocking_issues: list[dict[str, Any]] = []
        upsert_ops: list[dict[str, Any]] = []
        touched_ids: set[int] = set()
        client_key_to_patch: dict[str, AssociatedResourceUpsertPatch] = {}
        client_key_to_id: dict[str, int] = {}
        used_client_keys: set[str] = set()
        resolved_associated_item_refs: dict[str, list[int]] = {}

        upsert_resources = list(request.upsert_resources)
        if request.patch_resources:
            resource_patch_started_at = time.perf_counter()
            expanded_resources, patch_issues, patch_results = self._expand_associated_resource_partial_patches(
                existing_by_id=existing_by_id,
                patch_resources=request.patch_resources,
            )
            resource_patch_hydration_ms += _duration_ms(resource_patch_started_at)
            if patch_issues:
                return finalize(
                    _failed(
                        "ASSOCIATED_RESOURCE_PATCH_HYDRATION_FAILED",
                        "one or more associated resource partial patches could not be hydrated; no write was executed",
                        normalized_args=normalized_args,
                        details={"patch_results": patch_results, "blocking_issues": patch_issues},
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                )
            upsert_resources.extend(expanded_resources)
            normalized_args["upsert_resources"] = [patch.model_dump(mode="json") for patch in upsert_resources]
            normalized_args["patch_results"] = patch_results

        match_mapping_compile_started_at = time.perf_counter()
        compiled_resource_match_rules, resource_match_issues = self._compile_associated_resource_semantic_match_mappings(
            profile=profile,
            app_key=app_key,
            patches=upsert_resources,
        )
        match_mapping_compile_ms += _duration_ms(match_mapping_compile_started_at)
        normalized_args["compiled_match_rules"] = {
            str(index): _summarize_compiled_match_rules(rules)
            for index, rules in compiled_resource_match_rules.items()
        }

        for index, patch in enumerate(upsert_resources):
            client_key = str(patch.client_key or "").strip()
            if client_key:
                if client_key in used_client_keys:
                    blocking_issues.append({"error_code": "DUPLICATE_CLIENT_KEY", "reason_path": f"upsert_resources[{index}].client_key", "client_key": client_key})
                used_client_keys.add(client_key)
                client_key_to_patch[client_key] = patch
            validation_issue = _validate_associated_resource_patch(patch, reason_path=f"upsert_resources[{index}]")
            if validation_issue is not None:
                blocking_issues.append(validation_issue)
                continue
            if index in {issue.get("_patch_index") for issue in resource_match_issues if isinstance(issue, dict)}:
                continue
            associated_item_id = _coerce_positive_int(patch.associated_item_id)
            if associated_item_id is not None:
                if associated_item_id not in existing_by_id:
                    blocking_issues.append(_associated_resource_not_found_issue(f"upsert_resources[{index}].associated_item_id", associated_item_id, existing_by_id))
                    continue
                if associated_item_id in touched_ids:
                    blocking_issues.append(_duplicate_associated_resource_issue(f"upsert_resources[{index}]", associated_item_id))
                    continue
                touched_ids.add(associated_item_id)
                if client_key:
                    client_key_to_id[client_key] = associated_item_id
                has_match_config = _associated_resource_patch_has_match_config(patch)
                identity_matches = _associated_resource_matches_patch(existing_by_id[associated_item_id], patch)
                operation = "update" if has_match_config or not identity_matches else "unchanged"
                upsert_ops.append({"operation": operation, "associated_item_id": associated_item_id, "patch": patch, "index": index})
                continue
            matches = [
                item
                for item in existing_resources
                if _associated_resource_matches_patch(item, patch)
                and _coerce_positive_int(item.get("associated_item_id")) is not None
            ]
            if len(matches) > 1:
                blocking_issues.append(
                    {
                        "error_code": "AMBIGUOUS_ASSOCIATED_RESOURCE",
                        "reason_path": f"upsert_resources[{index}]",
                        "candidate_associated_item_ids": [
                            item.get("associated_item_id")
                            for item in matches
                            if _coerce_positive_int(item.get("associated_item_id")) is not None
                        ],
                        "message": "resource identity matches multiple associated resources; pass associated_item_id",
                    }
                )
                continue
            if len(matches) == 1:
                matched_id = _coerce_positive_int(matches[0].get("associated_item_id"))
                if matched_id is None:
                    blocking_issues.append({"error_code": "ASSOCIATED_RESOURCE_ID_MISSING", "reason_path": f"upsert_resources[{index}]"})
                    continue
                if matched_id in touched_ids:
                    blocking_issues.append(_duplicate_associated_resource_issue(f"upsert_resources[{index}]", matched_id))
                    continue
                touched_ids.add(matched_id)
                if client_key:
                    client_key_to_id[client_key] = matched_id
                operation = "update" if _associated_resource_patch_has_match_config(patch) else "unchanged"
                upsert_ops.append({"operation": operation, "associated_item_id": matched_id, "patch": patch, "index": index})
            else:
                upsert_ops.append({"operation": "create", "associated_item_id": None, "patch": patch, "index": index})

        remove_ids: list[int] = []
        for raw_id in request.remove_associated_item_ids:
            item_id, issue = _resolve_associated_resource_selector(
                raw_id,
                existing_resources=existing_resources,
                existing_by_id=existing_by_id,
                reason_path="remove_associated_item_ids",
            )
            if item_id is None:
                blocking_issues.append(issue or {"error_code": "ASSOCIATED_RESOURCE_NOT_FOUND", "reason_path": "remove_associated_item_ids", "received": raw_id})
                continue
            remove_ids.append(item_id)
            if item_id in touched_ids:
                blocking_issues.append(_duplicate_associated_resource_issue("remove_associated_item_ids", item_id))
            else:
                touched_ids.add(item_id)

        reorder_ids: list[int] = []
        for raw_id in request.reorder_associated_item_ids:
            item_id, issue = _resolve_associated_resource_selector(
                raw_id,
                existing_resources=existing_resources,
                existing_by_id=existing_by_id,
                reason_path="reorder_associated_item_ids",
            )
            if item_id is None:
                blocking_issues.append(issue or {"error_code": "ASSOCIATED_RESOURCE_NOT_FOUND", "reason_path": "reorder_associated_item_ids", "received": raw_id})
                continue
            reorder_ids.append(item_id)

        for index, view_config in enumerate(request.view_configs):
            refs = [str(ref or "").strip() for ref in view_config.associated_item_refs if str(ref or "").strip()]
            missing_refs = [ref for ref in refs if ref not in client_key_to_patch]
            if missing_refs:
                blocking_issues.append(
                    {
                        "error_code": "ASSOCIATED_RESOURCE_REF_NOT_FOUND",
                        "reason_path": f"view_configs[{index}].associated_item_refs",
                        "missing_refs": missing_refs,
                        "message": "associated_item_refs must reference client_key values from upsert_resources in the same apply call",
                    }
                )
            resolved_ids: list[int] = []
            for raw_id in view_config.associated_item_ids:
                item_id, issue = _resolve_associated_resource_selector(
                    raw_id,
                    existing_resources=existing_resources,
                    existing_by_id=existing_by_id,
                    reason_path=f"view_configs[{index}].associated_item_ids",
                )
                if item_id is None:
                    blocking_issues.append(
                        issue
                        or {
                            "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
                            "reason_path": f"view_configs[{index}].associated_item_ids",
                            "received": raw_id,
                        }
                    )
                    continue
                resolved_ids.append(item_id)
            resolved_associated_item_refs[f"view_configs[{index}].associated_item_ids"] = resolved_ids
            raw_limit_type = str(view_config.limit_type or ("all" if view_config.visible else "")).strip().lower()
            if view_config.visible and raw_limit_type and raw_limit_type not in {"all", "select"}:
                blocking_issues.append(
                    {
                        "error_code": "INVALID_ASSOCIATED_RESOURCE_LIMIT_TYPE",
                        "reason_path": f"view_configs[{index}].limit_type",
                        "received": view_config.limit_type,
                        "expected_values": ["all", "select"],
                    }
                )

        blocking_issues.extend(
            [
                {key: value for key, value in issue.items() if key != "_patch_index"}
                for issue in resource_match_issues
            ]
        )
        if resolved_associated_item_refs:
            normalized_args["resolved_associated_item_refs"] = deepcopy(resolved_associated_item_refs)

        if blocking_issues:
            return finalize(
                _failed(
                    "ASSOCIATED_RESOURCES_APPLY_BLOCKED",
                    "associated resources apply has blocking issues; no write was executed",
                    normalized_args=normalized_args,
                    details={"blocking_issues": blocking_issues},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )

        has_write_intent = (
            any(str(op.get("operation") or "") != "unchanged" for op in upsert_ops)
            or bool(remove_ids)
            or bool(reorder_ids)
            or bool(request.view_configs)
        )
        if not has_write_intent:
            unchanged: list[dict[str, Any]] = []
            for op in upsert_ops:
                patch = cast(AssociatedResourceUpsertPatch, op["patch"])
                item_id = int(op["associated_item_id"])
                unchanged.append(_associated_resource_result_entry("unchanged", op["index"], patch, associated_item_id=item_id))
                if patch.client_key:
                    client_key_to_id[str(patch.client_key)] = item_id
            response = {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "associated resources already match requested state; no write was executed",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"graph_type": ["chart", "view"], "report_source": ["app", "dataset"], "view_config.limit_type": ["all", "select"]},
                "details": {"edit_version_no": None, "associated_item_ids_by_client_key": client_key_to_id, "readback_failed": False},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": [],
                "verification": {"associated_resources_verified": True, "associated_resource_view_configs_verified": True, "readback_loaded": True, "published": False},
                "verified": True,
                "app_key": app_key,
                "mode": "apply",
                "created": [],
                "updated": [],
                "unchanged": unchanged,
                "removed": [],
                "reordered_associated_item_ids": [],
                "view_configs": [],
                "failed": [],
                "associated_item_ids_by_client_key": client_key_to_id,
                "write_executed": False,
                "write_succeeded": False,
                "safe_to_retry": True,
                "publish_requested": False,
                "published": False,
                "associated_resources": _strip_internal_associated_resource_raw(existing_resources),
            }
            return finalize(response)

        edit_version_no = None
        if resource_write_intent:
            edit_context_started_at = time.perf_counter()
            edit_version_no, edit_context_error = self._ensure_app_edit_context(
                profile=profile,
                app_key=app_key,
                normalized_args=normalized_args,
                failure_code="ASSOCIATED_RESOURCES_APPLY_FAILED",
            )
            edit_context_ms += _duration_ms(edit_context_started_at)
            if edit_context_error is not None:
                return finalize(edit_context_error)

        created: list[dict[str, Any]] = []
        updated: list[dict[str, Any]] = []
        unchanged: list[dict[str, Any]] = []
        removed: list[dict[str, Any]] = []
        reordered: list[int] = []
        view_config_results: list[dict[str, Any]] = []
        failed: list[dict[str, Any]] = []
        readback_errors: list[JSONObject] = []
        verification_errors: list[JSONObject] = []
        write_executed = False

        resource_write_started_at = time.perf_counter()
        for op in upsert_ops:
            patch = cast(AssociatedResourceUpsertPatch, op["patch"])
            try:
                if op["operation"] == "unchanged":
                    item_id = int(op["associated_item_id"])
                    unchanged.append(_associated_resource_result_entry("unchanged", op["index"], patch, associated_item_id=item_id))
                    if patch.client_key:
                        client_key_to_id[str(patch.client_key)] = item_id
                elif op["operation"] == "create":
                    write_executed = True
                    create_result = self._associated_resource_create(
                        profile=profile,
                        app_key=app_key,
                        patch=patch,
                        match_rules_override=compiled_resource_match_rules.get(int(op["index"])),
                    )
                    created_id = _extract_associated_resource_id_from_result(create_result)
                    if created_id is None:
                        try:
                            resource_readback_started_at = time.perf_counter()
                            readback_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
                            resource_readback_ms += _duration_ms(resource_readback_started_at)
                            matches = [
                                item
                                for item in readback_resources
                                if _associated_resource_matches_patch(item, patch)
                                and _coerce_positive_int(item.get("associated_item_id")) is not None
                            ]
                            readback_id = _coerce_positive_int(matches[0].get("associated_item_id")) if len(matches) == 1 else None
                            if readback_id is not None:
                                created_id = readback_id
                        except (QingflowApiError, RuntimeError) as error:
                            resource_readback_ms += _duration_ms(resource_readback_started_at)
                            readback_errors.append(
                                {
                                    "resource": "associated_resources",
                                    "phase": "create_id_lookup",
                                    "index": op["index"],
                                    "transport_error": _transport_error_payload(_coerce_api_error(error)),
                                }
                            )
                    created.append(_associated_resource_result_entry("create", op["index"], patch, associated_item_id=created_id))
                    if created_id is not None and patch.client_key:
                        client_key_to_id[str(patch.client_key)] = created_id
                else:
                    item_id = int(op["associated_item_id"])
                    write_executed = True
                    self._associated_resource_update(
                        profile=profile,
                        app_key=app_key,
                        associated_item_id=item_id,
                        patch=patch,
                        existing_item=existing_by_id.get(item_id),
                        match_rules_override=compiled_resource_match_rules.get(int(op["index"])),
                    )
                    updated.append(_associated_resource_result_entry("update", op["index"], patch, associated_item_id=item_id))
                    if patch.client_key:
                        client_key_to_id[str(patch.client_key)] = item_id
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failed.append(
                    {
                        "index": op["index"],
                        "operation": op["operation"],
                        "associated_item_id": op.get("associated_item_id"),
                        "status": "failed",
                        "error_code": "ASSOCIATED_RESOURCE_WRITE_FAILED",
                        "message": api_error.message,
                        "transport_error": _transport_error_payload(api_error),
                    }
                )

        for item_id in remove_ids:
            try:
                write_executed = True
                self._associated_resource_delete(profile=profile, app_key=app_key, associated_item_id=item_id)
                removed.append(
                    {
                        "associated_item_id": item_id,
                        "operation": "remove",
                        "status": "readback_pending",
                        "delete_executed": True,
                        "readback_status": "unavailable",
                        "safe_to_retry_delete": False,
                    }
                )
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failed.append({"operation": "remove", "associated_item_id": item_id, "status": "failed", "error_code": "ASSOCIATED_RESOURCE_WRITE_FAILED", "message": api_error.message, "transport_error": _transport_error_payload(api_error)})

        if reorder_ids:
            try:
                write_executed = True
                self._associated_resource_reorder(profile=profile, app_key=app_key, associated_item_ids=reorder_ids)
                reordered = list(reorder_ids)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failed.append({"operation": "reorder", "associated_item_ids": reorder_ids, "status": "failed", "error_code": "ASSOCIATED_RESOURCE_REORDER_FAILED", "message": api_error.message, "transport_error": _transport_error_payload(api_error)})
        resource_write_ms += _duration_ms(resource_write_started_at)

        resources_after: list[dict[str, Any]] = []
        resources_after_loaded = False
        resources_after_readback_failed = False
        if request.view_configs:
            try:
                resource_readback_started_at = time.perf_counter()
                resources_after = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
                resource_readback_ms += _duration_ms(resource_readback_started_at)
                resources_after_loaded = True
            except (QingflowApiError, RuntimeError) as error:
                resource_readback_ms += _duration_ms(resource_readback_started_at)
                readback_errors.append({"resource": "associated_resources", "phase": "pre_view_config", "transport_error": _transport_error_payload(_coerce_api_error(error))})
                resources_after = []
                resources_after_readback_failed = True

        for index, view_config in enumerate(request.view_configs):
            resolved_view_config_ids = resolved_associated_item_refs.get(f"view_configs[{index}].associated_item_ids", [])
            view_name = str(view_config.view_key or "").strip()
            missing_ref_ids = [
                str(ref or "").strip()
                for ref in view_config.associated_item_refs
                if str(ref or "").strip() and str(ref or "").strip() not in client_key_to_id
            ]
            if missing_ref_ids:
                failed.append(
                    {
                        "operation": "view_config",
                        "index": index,
                        "view_key": view_config.view_key,
                        "view_name": view_name,
                        "status": "failed",
                        "error_code": "ASSOCIATED_RESOURCE_REF_UNRESOLVED",
                        "missing_refs": missing_ref_ids,
                        "message": "view_config references associated resources that were not successfully created or resolved; skipped view config write to avoid clearing existing associations",
                    }
                )
                continue
            selected_ids = [
                item_id
                for item_id in (
                    _coerce_positive_int(raw_id)
                    for raw_id in [
                        *resolved_view_config_ids,
                        *[client_key_to_id.get(str(ref or "").strip()) for ref in view_config.associated_item_refs],
                    ]
                )
                if item_id is not None
            ]
            view_payload, expected_config, issues = _build_view_associated_resources_payload(
                associated_resources={"visible": view_config.visible, "limit_type": view_config.limit_type or ("all" if view_config.visible else None), "associated_item_ids": selected_ids},
                available_resources=resources_after,
            )
            if issues:
                failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "failed", "issues": issues})
                continue
            try:
                view_config_started_at = time.perf_counter()
                write_executed = True
                self._update_view_associated_resources_config(profile=profile, view_key=view_config.view_key, associated_resources_payload=view_payload)
                try:
                    config = self.views.view_get_config(profile=profile, viewgraph_key=view_config.view_key).get("result") or {}
                    view_name = _extract_view_name(config if isinstance(config, dict) else {}) or view_name
                    actual_config = _extract_view_associated_resources_config(config if isinstance(config, dict) else {}, available_resources=resources_after)
                    verified_config = expected_config is not None and _associated_resources_config_matches(expected_config, actual_config)
                    if not verified_config and expected_config is not None:
                        self._update_view_associated_resources_config(profile=profile, view_key=view_config.view_key, associated_resources_payload=view_payload)
                        refreshed_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
                        refreshed_config = self.views.view_get_config(profile=profile, viewgraph_key=view_config.view_key).get("result") or {}
                        view_name = _extract_view_name(refreshed_config if isinstance(refreshed_config, dict) else {}) or view_name
                        actual_config = _extract_view_associated_resources_config(
                            refreshed_config if isinstance(refreshed_config, dict) else {},
                            available_resources=refreshed_resources,
                        )
                        verified_config = _associated_resources_config_matches(expected_config, actual_config)
                except (QingflowApiError, RuntimeError) as error:
                    verification_errors.append(
                        {
                            "resource": "view_associated_resources_config",
                            "phase": "view_config_readback",
                            "view_key": view_config.view_key,
                            "transport_error": _transport_error_payload(_coerce_api_error(error)),
                        }
                    )
                    actual_config = {}
                    verified_config = False
                view_config_results.append({"index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "success" if verified_config else "partial_success", "associated_resources_verified": verified_config, "expected": expected_config, "actual": actual_config})
                view_config_ms += _duration_ms(view_config_started_at)
            except (QingflowApiError, RuntimeError) as error:
                view_config_ms += _duration_ms(view_config_started_at)
                api_error = _coerce_api_error(error)
                failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "failed", "error_code": "VIEW_ASSOCIATED_RESOURCES_WRITE_FAILED", "message": api_error.message, "transport_error": _transport_error_payload(api_error)})

        created_ids = [int(item["associated_item_id"]) for item in created if _coerce_positive_int(item.get("associated_item_id")) is not None]
        updated_ids = [int(item["associated_item_id"]) for item in updated if _coerce_positive_int(item.get("associated_item_id")) is not None]
        unchanged_ids = [int(item["associated_item_id"]) for item in unchanged if _coerce_positive_int(item.get("associated_item_id")) is not None]
        final_readback_skipped = (
            bool(created or updated)
            and not request.view_configs
            and not removed
            and not reordered
            and not failed
            and not readback_errors
            and all(_coerce_positive_int(item.get("associated_item_id")) is not None for item in [*created, *updated])
        )
        final_resources: list[dict[str, Any]] = resources_after if resources_after_loaded else []
        readback_failed = resources_after_readback_failed
        if final_readback_skipped:
            final_resources = []
        elif not resources_after_loaded and not resources_after_readback_failed:
            try:
                resource_readback_started_at = time.perf_counter()
                final_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
                resource_readback_ms += _duration_ms(resource_readback_started_at)
            except (QingflowApiError, RuntimeError) as error:
                resource_readback_ms += _duration_ms(resource_readback_started_at)
                readback_errors.append({"resource": "associated_resources", "phase": "final_pool", "transport_error": _transport_error_payload(_coerce_api_error(error))})
                readback_failed = True
        final_by_id = _associated_resource_index(final_resources)
        if removed:
            removed = self._verify_associated_resources_deleted_by_pool(
                deleted_items=removed,
                resources=final_resources,
                readback_failed=readback_failed,
            )
        removed_ids = [int(item["associated_item_id"]) for item in removed if _coerce_positive_int(item.get("associated_item_id")) is not None]
        removed_verified = all(str(item.get("readback_status") or "") == "deleted" for item in removed)
        remove_readback_pending = any(str(item.get("readback_status") or "") != "deleted" for item in removed)
        pool_items_verified = final_readback_skipped or (
            not readback_failed
            and all(item_id in final_by_id for item_id in created_ids + updated_ids + unchanged_ids)
        )
        pool_verified = (
            (final_readback_skipped or not readback_failed)
            and pool_items_verified
            and removed_verified
            and not failed
            and all(_coerce_positive_int(item.get("associated_item_id")) is not None for item in created)
        )
        view_configs_verified = all(bool(item.get("associated_resources_verified")) for item in view_config_results) and not any(
            item.get("operation") == "view_config" for item in failed
        )
        verified = pool_verified and view_configs_verified
        write_succeeded = bool(created or updated or removed or reordered or view_config_results)
        operation_succeeded = write_succeeded or bool(unchanged)
        status = "success" if verified else ("partial_success" if operation_succeeded else "failed")
        error_code = None if verified else (
            "ASSOCIATED_RESOURCES_READBACK_PENDING"
            if readback_failed
            else "ASSOCIATED_RESOURCES_APPLY_PARTIAL"
            if operation_succeeded
            else "ASSOCIATED_RESOURCES_APPLY_FAILED"
        )
        message = (
            "applied associated resources patch"
            if verified
            else "associated resources apply completed with partial verification"
            if operation_succeeded
            else "associated resources apply failed; no successful write was confirmed"
        )
        warnings = []
        if not verified:
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCES_APPLY_PARTIAL" if operation_succeeded else "ASSOCIATED_RESOURCES_APPLY_FAILED",
                    "associated resource write landed but verification is partial; inspect created/updated/removed/failed/view_configs"
                    if operation_succeeded
                    else "associated resource writes all failed or produced no confirmed result; application was not published",
                )
            )
        if remove_readback_pending:
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCE_DELETE_READBACK_PENDING",
                    "associated resource delete was sent, but deletion readback is not fully verified; do not blindly repeat delete",
                )
            )
        if readback_errors:
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCES_READBACK_UNAVAILABLE",
                    "associated resource write was executed but readback is unavailable",
                )
            )
        if verification_errors:
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCE_VIEW_CONFIG_VERIFICATION_UNAVAILABLE",
                    "associated resource view config write was executed but verification readback is unavailable",
                )
            )
        response = {
            "status": status,
            "error_code": error_code,
            "recoverable": not verified,
            "message": message,
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"graph_type": ["chart", "view"], "report_source": ["app", "dataset"], "view_config.limit_type": ["all", "select"]},
            "details": {
                "edit_version_no": edit_version_no,
                "associated_item_ids_by_client_key": client_key_to_id,
                "readback_failed": readback_failed,
                "initial_inventory_read_skipped": not needs_resource_inventory,
                "final_readback_skipped": final_readback_skipped,
                **({"readback_errors": readback_errors} if readback_errors else {}),
                **({"verification_errors": verification_errors} if verification_errors else {}),
                "compiled_match_rules": {
                    str(index): _summarize_compiled_match_rules(rules)
                    for index, rules in compiled_resource_match_rules.items()
                },
                "resolved_associated_item_refs": resolved_associated_item_refs,
            },
            "request_id": None,
            "suggested_next_call": None if verified else {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            "noop": False,
            "warnings": warnings,
            "verification": {
                "associated_resources_verified": pool_verified,
                "associated_resource_view_configs_verified": view_configs_verified,
                "readback_loaded": False if final_readback_skipped else not readback_failed,
                "final_readback_skipped": final_readback_skipped,
                "removed_verified": removed_verified,
                "remove_readback_pending": remove_readback_pending,
                "removed_readback_results": deepcopy(removed),
            },
            "verified": verified,
            "app_key": app_key,
            "app_name": app_name,
            "mode": "apply",
            "created": created,
            "updated": updated,
            "unchanged": unchanged,
            "removed": removed,
            "reordered_associated_item_ids": reordered,
            "view_configs": view_config_results,
            "failed": failed,
            "associated_item_ids_by_client_key": client_key_to_id,
            "write_executed": write_executed,
            "write_succeeded": write_succeeded,
            "safe_to_retry": not write_executed,
            "associated_resources": final_resources,
        }
        publish_started_at = time.perf_counter()
        response = self._append_publish_result(
            profile=profile,
            app_key=app_key,
            publish=bool(write_succeeded and resource_write_intent),
            response=response,
            edit_version_no=edit_version_no,
        )
        publish_ms += _duration_ms(publish_started_at)
        if response.get("published") and view_config_results:
            post_publish_started_at = time.perf_counter()
            try:
                post_publish_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
            except (QingflowApiError, RuntimeError) as error:
                details = response.get("details") if isinstance(response.get("details"), dict) else {}
                response["details"] = details
                post_publish_readback_errors = details.get("readback_errors") if isinstance(details.get("readback_errors"), list) else []
                post_publish_readback_errors.append(
                    {
                        "resource": "associated_resources",
                        "phase": "post_publish_pool",
                        "transport_error": _transport_error_payload(_coerce_api_error(error)),
                    }
                )
                details["readback_errors"] = post_publish_readback_errors
                warnings = response.get("warnings")
                if not isinstance(warnings, list):
                    warnings = []
                    response["warnings"] = warnings
                if not any(isinstance(warning, dict) and warning.get("code") == "ASSOCIATED_RESOURCES_READBACK_UNAVAILABLE" for warning in warnings):
                    warnings.append(
                        _warning(
                            "ASSOCIATED_RESOURCES_READBACK_UNAVAILABLE",
                            "associated resource write was executed but readback is unavailable",
                        )
                    )
                post_publish_resources = final_resources
            if post_publish_resources:
                response["associated_resources"] = post_publish_resources
            post_publish_verified_any = False
            for result in view_config_results:
                if result.get("associated_resources_verified"):
                    continue
                expected_config = result.get("expected") if isinstance(result.get("expected"), dict) else None
                view_key = str(result.get("view_key") or "").strip()
                if expected_config is None or not view_key:
                    continue
                try:
                    config = self.views.view_get_config(profile=profile, viewgraph_key=view_key).get("result") or {}
                    actual_config = _extract_view_associated_resources_config(
                        config if isinstance(config, dict) else {},
                        available_resources=post_publish_resources,
                    )
                except (QingflowApiError, RuntimeError) as error:
                    details = response.get("details") if isinstance(response.get("details"), dict) else {}
                    response["details"] = details
                    post_publish_verification_errors = details.get("verification_errors") if isinstance(details.get("verification_errors"), list) else []
                    post_publish_verification_errors.append(
                        {
                            "resource": "view_associated_resources_config",
                            "phase": "post_publish_view_config_readback",
                            "view_key": view_key,
                            "transport_error": _transport_error_payload(_coerce_api_error(error)),
                        }
                    )
                    details["verification_errors"] = post_publish_verification_errors
                    warnings = response.get("warnings")
                    if not isinstance(warnings, list):
                        warnings = []
                        response["warnings"] = warnings
                    if not any(isinstance(warning, dict) and warning.get("code") == "ASSOCIATED_RESOURCE_VIEW_CONFIG_VERIFICATION_UNAVAILABLE" for warning in warnings):
                        warnings.append(
                            _warning(
                                "ASSOCIATED_RESOURCE_VIEW_CONFIG_VERIFICATION_UNAVAILABLE",
                                "associated resource view config write was executed but verification readback is unavailable",
                            )
                        )
                    continue
                if _associated_resources_config_matches(expected_config, actual_config):
                    result["status"] = "success"
                    result["associated_resources_verified"] = True
                    result["actual"] = actual_config
                    result["post_publish_verified"] = True
                    post_publish_verified_any = True
            verification = response.get("verification")
            if not isinstance(verification, dict):
                verification = {}
                response["verification"] = verification
            post_view_configs_verified = all(bool(item.get("associated_resources_verified")) for item in view_config_results) and not any(
                item.get("operation") == "view_config" for item in failed
            )
            verification["associated_resource_view_configs_verified"] = post_view_configs_verified
            if post_publish_verified_any:
                warnings = response.get("warnings")
                if not isinstance(warnings, list):
                    warnings = []
                    response["warnings"] = warnings
                warnings[:] = [
                    warning
                    for warning in warnings
                    if not (
                        isinstance(warning, dict)
                        and warning.get("code") in {"ASSOCIATED_RESOURCES_APPLY_PARTIAL", "ASSOCIATED_RESOURCES_APPLY_FAILED"}
                    )
                ]
                warnings.append(
                    _warning(
                        "VIEW_ASSOCIATED_RESOURCE_POST_PUBLISH_VERIFIED",
                        "view associated resources were verified after publish because immediate draft readback lagged",
                    )
                )
            post_verified = bool(verification.get("associated_resources_verified")) and post_view_configs_verified and response.get("published") and not failed
            response["verified"] = post_verified
            if post_verified:
                response["status"] = "success"
                response["error_code"] = None
                response["recoverable"] = False
                response["message"] = "applied associated resources patch"
                response["suggested_next_call"] = None
            post_publish_verify_ms += _duration_ms(post_publish_started_at)
        return finalize(response)

    def _resolve_app_matches_in_visible_apps(
        self,
        *,
        profile: str,
        app_name: str,
        package_tag_id: int | None,
    ) -> list[JSONObject]:
        try:
            listing = self.apps.app_list(profile=profile, ship_auth=False)
        except (QingflowApiError, RuntimeError) as exc:
            api_error = _coerce_api_error(exc)
            if not _is_optional_builder_lookup_error(api_error):
                raise
            return []
        items = listing.get("items") if isinstance(listing.get("items"), list) else []
        matches: list[JSONObject] = []
        seen_app_keys: set[str] = set()
        for item in items:
            if not isinstance(item, dict):
                continue
            title = str(item.get("title") or item.get("app_name") or "").strip()
            if title != app_name:
                continue
            candidate_key = str(item.get("app_key") or item.get("appKey") or "").strip()
            if not candidate_key or candidate_key in seen_app_keys:
                continue
            tag_ids = _coerce_int_list(item.get("tag_ids"))
            tag_id = _coerce_positive_int(item.get("tag_id"))
            if tag_id is not None and tag_id not in tag_ids:
                tag_ids.append(tag_id)
            if package_tag_id is not None and package_tag_id > 0 and package_tag_id not in tag_ids:
                continue
            seen_app_keys.add(candidate_key)
            matches.append({"app_key": candidate_key, "app_name": title, "tag_ids": tag_ids})
        return matches

    def _resolve_app_matches_in_package(
        self,
        *,
        profile: str,
        app_name: str,
        package_tag_id: int,
    ) -> list[JSONObject]:
        try:
            package_result = self.packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=True)
        except (QingflowApiError, RuntimeError) as exc:
            api_error = _coerce_api_error(exc)
            if not _is_optional_builder_lookup_error(api_error):
                raise
            return []
        raw_package = package_result.get("result") if isinstance(package_result.get("result"), dict) else {}
        tag_items = raw_package.get("tagItems") if isinstance(raw_package.get("tagItems"), list) else []
        matches: list[JSONObject] = []
        seen_app_keys: set[str] = set()
        for item in tag_items:
            if not isinstance(item, dict):
                continue
            title = str(item.get("title") or item.get("formTitle") or "").strip()
            if title != app_name:
                continue
            candidate_key = str(item.get("appKey") or item.get("app_key") or "").strip()
            if not candidate_key or candidate_key in seen_app_keys:
                continue
            seen_app_keys.add(candidate_key)
            matches.append({"app_key": candidate_key, "app_name": title, "tag_ids": [package_tag_id]})
        return matches

    def _read_package_permission_summary(self, *, profile: str, tag_id: int) -> JSONObject:
        base_result = self.packages.package_get_base(profile=profile, tag_id=tag_id, include_raw=True)
        base = base_result.get("result") if isinstance(base_result.get("result"), dict) else {}
        return {
            "tag_id": tag_id,
            "tag_name": base.get("tagName"),
            "can_add_app": _coerce_optional_bool(base.get("addAppStatus")),
            "can_edit_app": _coerce_optional_bool(base.get("editAppStatus")),
            "can_delete_app": _coerce_optional_bool(base.get("delAppStatus")),
            "can_edit_tag": _coerce_optional_bool(base.get("editTagStatus")),
        }

    def _read_app_permission_summary(self, *, profile: str, app_key: str) -> JSONObject:
        base_result = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True)
        base = base_result.get("result") if isinstance(base_result.get("result"), dict) else {}
        return {
            "app_key": app_key,
            "tag_ids": _coerce_int_list(base.get("tagIds")),
            "can_edit_app": _coerce_optional_bool(base.get("editItemStatus")),
            "can_manage_data": _coerce_optional_bool(base.get("dataManageStatus")),
            "can_manage_views": (
                _coerce_optional_bool(base.get("beingViewManageStatus"))
                if "beingViewManageStatus" in base
                else _coerce_optional_bool(base.get("dataManageStatus"))
            ),
            "can_delete_app": _coerce_optional_bool(base.get("deleteItemStatus")),
            "can_copy_app": _coerce_optional_bool(base.get("copyAppStatus")),
        }

    def _read_portal_permission_summary(self, *, dash_key: str, portal_result: dict[str, Any]) -> JSONObject:
        tag_ids = _coerce_int_list(portal_result.get("tagIds"))
        if not tag_ids:
            tags = portal_result.get("tags") if isinstance(portal_result.get("tags"), list) else []
            for item in tags:
                if not isinstance(item, dict):
                    continue
                tag_id = _coerce_positive_int(item.get("tagId"))
                if tag_id is not None and tag_id not in tag_ids:
                    tag_ids.append(tag_id)
        return {
            "dash_key": dash_key,
            "dash_name": portal_result.get("dashName"),
            "tag_ids": tag_ids,
            "can_edit_portal": _coerce_optional_bool(portal_result.get("editItemStatus")),
        }

    def _guard_app_permission(
        self,
        *,
        profile: str,
        app_key: str,
        required_permission: str,
        normalized_args: JSONObject,
        permission_summary: JSONObject | None = None,
    ) -> PermissionCheckOutcome:
        if permission_summary is None:
            try:
                permission_summary = self._read_app_permission_summary(profile=profile, app_key=app_key)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                if _is_permission_restricted_api_error(api_error):
                    return _permission_skip_outcome(
                        scope="app",
                        target={"app_key": app_key},
                        required_permission=required_permission,
                        transport_error=_transport_error_payload(api_error),
                    )
                return PermissionCheckOutcome(
                    block=_failed(
                        "APP_PERMISSION_UNVERIFIED",
                        "could not confirm current user's builder permissions for this app",
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "required_permission": required_permission,
                            "permission_read_error": {
                                "message": api_error.message,
                                "http_status": api_error.http_status,
                                "backend_code": api_error.backend_code,
                                "category": api_error.category,
                            },
                        },
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                        request_id=api_error.request_id,
                        backend_code=api_error.backend_code,
                        http_status=None if api_error.http_status == 404 else api_error.http_status,
                    )
                )
        permission_key = {
            "edit_app": "can_edit_app",
            "data_manage": "can_manage_data",
            "view_manage": "can_manage_views",
        }.get(required_permission)
        if permission_key is None:
            return PermissionCheckOutcome()
        permission_value = permission_summary.get(permission_key)
        if permission_value is None:
            return _permission_skip_outcome(
                scope="app",
                target={"app_key": app_key},
                required_permission=required_permission,
                permission_summary=permission_summary,
            )
        if permission_value is not False:
            return PermissionCheckOutcome()
        if required_permission == "edit_app":
            error_code = "EDIT_APP_UNAUTHORIZED"
            message = "current user does not have builder edit-app permission on this app"
        elif required_permission == "view_manage":
            error_code = "VIEW_MANAGE_UNAUTHORIZED"
            message = "current user does not have view-management permission on this app"
        else:
            error_code = "DATA_MANAGE_UNAUTHORIZED"
            message = "current user does not have data-management permission on this app"
        return PermissionCheckOutcome(
            block=_failed(
                error_code,
                message,
                normalized_args=normalized_args,
                details={
                    "app_key": app_key,
                    "required_permission": required_permission,
                    "permission_summary": permission_summary,
                },
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        )

    def _guard_package_permission(
        self,
        *,
        profile: str,
        tag_id: int,
        required_permission: str,
        normalized_args: JSONObject,
    ) -> PermissionCheckOutcome:
        try:
            permission_summary = self._read_package_permission_summary(profile=profile, tag_id=tag_id)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _is_permission_restricted_api_error(api_error):
                return _permission_skip_outcome(
                    scope="package",
                    target={"tag_id": tag_id},
                    required_permission=required_permission,
                    transport_error=_transport_error_payload(api_error),
                )
            return PermissionCheckOutcome(
                block=_failed(
                    "PACKAGE_PERMISSION_UNVERIFIED",
                    "could not confirm current user's builder permissions for this package",
                    normalized_args=normalized_args,
                    details={
                        "tag_id": tag_id,
                        "required_permission": required_permission,
                        "permission_read_error": {
                            "message": api_error.message,
                            "http_status": api_error.http_status,
                            "backend_code": api_error.backend_code,
                            "category": api_error.category,
                        },
                    },
                    suggested_next_call={"tool_name": "package_get_base", "arguments": {"profile": profile, "tag_id": tag_id}},
                    request_id=api_error.request_id,
                    backend_code=api_error.backend_code,
                    http_status=None if api_error.http_status == 404 else api_error.http_status,
                )
            )
        permission_specs = {
            "add_app": (
                "can_add_app",
                "PACKAGE_ADD_APP_UNAUTHORIZED",
                "current user does not have package add-app permission on this package",
            ),
            "edit_app": (
                "can_edit_app",
                "PACKAGE_EDIT_APP_UNAUTHORIZED",
                "current user does not have package edit-app permission on this package",
            ),
            "delete_app": (
                "can_delete_app",
                "PACKAGE_DELETE_APP_UNAUTHORIZED",
                "current user does not have package delete-app permission on this package",
            ),
            "edit_tag": (
                "can_edit_tag",
                "PACKAGE_EDIT_TAG_UNAUTHORIZED",
                "current user does not have package edit-tag permission on this package",
            ),
        }
        permission_key, error_code, message = permission_specs.get(required_permission, (None, None, None))
        if permission_key is None:
            return PermissionCheckOutcome()
        permission_value = permission_summary.get(permission_key)
        if permission_value is None:
            return _permission_skip_outcome(
                scope="package",
                target={"tag_id": tag_id},
                required_permission=required_permission,
                permission_summary=permission_summary,
            )
        if permission_value is not False:
            return PermissionCheckOutcome()
        return PermissionCheckOutcome(
            block=_failed(
                error_code or "PACKAGE_PERMISSION_UNVERIFIED",
                message or "current user does not have the required package permission",
                normalized_args=normalized_args,
                details={
                    "tag_id": tag_id,
                    "required_permission": required_permission,
                    "permission_summary": permission_summary,
                },
                suggested_next_call={"tool_name": "package_get_base", "arguments": {"profile": profile, "tag_id": tag_id}},
            )
        )

    def _guard_portal_permission(
        self,
        *,
        profile: str,
        dash_key: str,
        normalized_args: JSONObject,
        portal_result: dict[str, Any],
    ) -> PermissionCheckOutcome:
        permission_summary = self._read_portal_permission_summary(dash_key=dash_key, portal_result=portal_result)
        permission_value = permission_summary.get("can_edit_portal")
        if permission_value is None:
            return _permission_skip_outcome(
                scope="portal",
                target={"dash_key": dash_key},
                required_permission="edit_portal",
                permission_summary=permission_summary,
            )
        if permission_value is not False:
            return PermissionCheckOutcome()
        return PermissionCheckOutcome(
            block=_failed(
                "PORTAL_EDIT_UNAUTHORIZED",
                "current user does not have builder edit permission on this portal",
                normalized_args=normalized_args,
                details={"dash_key": dash_key, "permission_summary": permission_summary},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        )

    def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "APP_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, "app_key": app_key}},
            )
        base_result = base.get("result") if isinstance(base.get("result"), dict) else {}
        schema_unavailable = False
        try:
            schema_result, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
            parsed = _parse_schema(schema_result)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if api_error.http_status == 404 or _is_permission_restricted_api_error(api_error):
                schema_unavailable = True
                parsed = {"fields": [], "layout": {"sections": []}}
            else:
                return _failed_from_api_error(
                    "APP_READ_FAILED",
                    api_error,
                    normalized_args={"app_key": app_key},
                    details={"app_key": app_key},
                    suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, "app_key": app_key}},
                )
        views, views_unavailable, views_read_error = self._load_views_result(
            profile=profile,
            app_key=app_key,
            tolerate_404=True,
            tolerate_permission_restricted=True,
            include_error=True,
        )
        view_summaries = _summarize_views(views)
        readback_errors: list[JSONObject] = []
        charts_unavailable = False
        try:
            chart_items, _chart_source = self._load_chart_list_for_builder(profile=profile, app_key=app_key)
            chart_summaries = _summarize_charts(chart_items)
        except (QingflowApiError, RuntimeError) as error:
            charts_unavailable = True
            chart_summaries = []
            readback_errors.append(
                {
                    "resource": "charts",
                    "phase": "summary",
                    "transport_error": _transport_error_payload(_coerce_api_error(error)),
                }
            )
        associated_resources_unavailable = False
        try:
            associated_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            associated_resources_unavailable = True
            associated_resources = []
            readback_errors.append(
                {
                    "resource": "associated_resources",
                    "phase": "summary",
                    "transport_error": _transport_error_payload(_coerce_api_error(error)),
                }
            )
        custom_buttons_unavailable = False
        try:
            custom_buttons = self._load_custom_buttons_for_builder(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            custom_buttons_unavailable = True
            custom_buttons = []
            readback_errors.append(
                {
                    "resource": "custom_buttons",
                    "phase": "summary",
                    "transport_error": _transport_error_payload(_coerce_api_error(error)),
                }
            )
        workflow, workflow_unavailable, workflow_read_error = self._load_workflow_result(
            profile=profile,
            app_key=app_key,
            tolerate_404=True,
            tolerate_permission_restricted=True,
            include_error=True,
        )
        if views_read_error is not None:
            readback_errors.append(
                {
                    "resource": "views",
                    "phase": "summary",
                    "transport_error": _transport_error_payload(views_read_error),
                }
            )
        if workflow_read_error is not None:
            readback_errors.append(
                {
                    "resource": "workflow",
                    "phase": "summary",
                    "transport_error": _transport_error_payload(workflow_read_error),
                }
            )
        verification_hints = _build_verification_hints(
            tag_ids=_coerce_int_list(base_result.get("tagIds")),
            fields=parsed["fields"],
            layout=parsed["layout"],
            views=view_summaries,
        )
        if schema_unavailable:
            verification_hints.append("schema_read_unavailable")
        if views_unavailable:
            verification_hints.append("views_read_unavailable")
        if charts_unavailable:
            verification_hints.append("charts_read_unavailable")
        if associated_resources_unavailable:
            verification_hints.append("associated_resources_read_unavailable")
        if custom_buttons_unavailable:
            verification_hints.append("custom_buttons_read_unavailable")
        if workflow_unavailable:
            verification_hints.append("workflow_read_unavailable")
        counts = {
            "fields": len(parsed["fields"]),
            "layout_sections": len(parsed["layout"].get("sections", [])),
            "views": len(view_summaries),
            "charts": len(chart_summaries),
            "associated_resources": len(associated_resources),
            "custom_buttons": len(custom_buttons),
        }
        app_name = str(
            base_result.get("formTitle")
            or base_result.get("title")
            or base_result.get("appName")
            or base_result.get("name")
            or app_key
        ).strip() or app_key
        app_icon = str(base_result.get("appIcon") or "").strip() or None
        response = AppReadSummaryResponse(
            app_key=app_key,
            app_name=app_name,
            name=app_name,
            title=app_name,
            app_icon=app_icon,
            icon_config=workspace_icon_config(app_icon),
            visibility=_public_visibility_from_member_auth(base_result.get("auth")),
            tag_ids=_coerce_int_list(base_result.get("tagIds")),
            publish_status=base_result.get("appPublishStatus"),
            field_count=len(parsed["fields"]),
            layout_section_count=len(parsed["layout"].get("sections", [])),
            view_count=len(view_summaries),
            chart_count=len(chart_summaries),
            associated_resource_count=len(associated_resources),
            custom_button_count=len(custom_buttons),
            counts=counts,
            views=view_summaries,
            charts=chart_summaries,
            associated_resources=associated_resources,
            custom_buttons=custom_buttons,
            workflow_enabled=bool(workflow),
            verification_hints=verification_hints,
            form_settings={} if schema_unavailable else _form_settings_from_schema(schema_result, parsed["fields"]),
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app summary",
            "normalized_args": {"app_key": app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {"readback_errors": readback_errors} if readback_errors else {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": _warnings_from_verification_hints(verification_hints),
            "verification": {
                "app_exists": True,
                "schema_read_unavailable": schema_unavailable,
                "views_read_unavailable": views_unavailable,
                "charts_read_unavailable": charts_unavailable,
                "associated_resources_read_unavailable": associated_resources_unavailable,
                "custom_buttons_read_unavailable": custom_buttons_unavailable,
                "workflow_read_unavailable": workflow_unavailable,
            },
            "verified": not schema_unavailable
            and not views_unavailable
            and not charts_unavailable
            and not associated_resources_unavailable
            and not custom_buttons_unavailable
            and not workflow_unavailable,
            **response.model_dump(mode="json"),
        }

    def app_get(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_summary(profile=profile, app_key=app_key)
        if result.get("status") != "success":
            if not result.get("suggested_next_call"):
                result["suggested_next_call"] = {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}}
            return result
        try:
            permission_summary = self._read_app_permission_summary(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if not _is_permission_restricted_api_error(api_error):
                raise
            result["message"] = "read app config summary; editability unverified"
            result["editability"] = {
                "can_edit_app_base": None,
                "can_edit_form": None,
                "can_edit_flow": None,
                "can_edit_views": None,
                "can_edit_charts": None,
            }
            details = result.get("details") if isinstance(result.get("details"), dict) else {}
            permission_read_errors = details.get("permission_read_errors") if isinstance(details.get("permission_read_errors"), list) else []
            permission_read_errors.append(
                {
                    "resource": "app_permission",
                    "app_key": app_key,
                    "required_permission": None,
                    "transport_error": _transport_error_payload(api_error),
                }
            )
            details["permission_read_errors"] = permission_read_errors
            details["permission_check_skipped"] = True
            result["details"] = details
            warnings = result.get("warnings") if isinstance(result.get("warnings"), list) else []
            warnings.append(
                _warning(
                    "APP_EDITABILITY_UNAVAILABLE",
                    "could not confirm current user's builder permissions for this app; app summary remains available",
                    app_key=app_key,
                    **_transport_error_payload(api_error),
                )
            )
            result["warnings"] = warnings
            verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
            verification["editability_unavailable"] = True
            result["verification"] = verification
            result["verified"] = False
            return result
        result["message"] = "read app config summary"
        result["editability"] = {
            "can_edit_app_base": permission_summary.get("can_edit_app"),
            "can_edit_form": permission_summary.get("can_edit_app"),
            "can_edit_flow": permission_summary.get("can_edit_app"),
            "can_edit_views": permission_summary.get("can_manage_views"),
            "can_edit_charts": permission_summary.get("can_manage_data"),
        }
        return result

    def app_get_fields(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_fields(profile=profile, app_key=app_key)
        if result.get("status") == "success":
            result["message"] = "read app field config"
        return result

    def app_repair_code_blocks(
        self,
        *,
        profile: str,
        app_key: str,
        field: str | None = None,
        apply: bool = False,
    ) -> JSONObject:
        try:
            state = self._load_base_schema_state(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "FIELDS_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key, "field": field, "apply": apply},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
            )
        parsed_fields = cast(list[dict[str, Any]], state["parsed"].get("fields") or [])
        field_lookup = _build_public_field_lookup(parsed_fields)
        selected_fields: list[dict[str, Any]]
        requested_selector = str(field or "").strip()
        if requested_selector:
            try:
                selected_field = _resolve_public_field(requested_selector, field_lookup=field_lookup)
            except ValueError:
                return _failed(
                    "FIELD_NOT_FOUND",
                    "field selector did not match any existing field",
                    normalized_args={"app_key": app_key, "field": requested_selector, "apply": apply},
                    details={"app_key": app_key, "field": requested_selector},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            if str(selected_field.get("type") or "") != FieldType.code_block.value:
                return _failed(
                    "CODE_BLOCK_FIELD_REQUIRED",
                    "selected field is not a code_block field",
                    normalized_args={"app_key": app_key, "field": requested_selector, "apply": apply},
                    details={"app_key": app_key, "field": requested_selector, "field_type": selected_field.get("type")},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            selected_fields = [selected_field]
        else:
            selected_fields = [field_item for field_item in parsed_fields if str(field_item.get("type") or "") == FieldType.code_block.value]
        plans: list[dict[str, Any]] = []
        update_fields: list[FieldUpdatePatch] = []
        applied_fields: list[str] = []
        for code_block_field in selected_fields:
            field_name = _code_block_field_display_name(code_block_field)
            normalized_binding = _normalize_code_block_binding(code_block_field.get("code_block_binding"))
            normalized_config = _normalize_code_block_config(code_block_field.get("code_block_config") or code_block_field.get("config") or {}) or None
            raw_binding = code_block_field.get("code_block_binding") if isinstance(code_block_field.get("code_block_binding"), dict) else None
            preserved_output_targets = [
                str((output.get("target_field") or {}).get("name") or "").strip()
                for output in cast(list[dict[str, Any]], (normalized_binding or {}).get("outputs") or [])
                if isinstance(output.get("target_field"), dict) and str((output.get("target_field") or {}).get("name") or "").strip()
            ]
            detected_issues: list[str] = []
            warnings: list[dict[str, Any]] = []
            normalized_code_preview: str | None = None
            normalized_binding_code = None
            normalized_config_code = None
            if normalized_binding is not None:
                raw_code = str(normalized_binding.get("code") or "")
                normalized_binding_code = _normalize_code_block_output_assignment(raw_code)
                if normalized_binding_code != raw_code:
                    detected_issues.append("CONST_OR_LET_QF_OUTPUT_ASSIGNMENT")
                    normalized_code_preview = normalized_binding_code
            if normalized_config is not None:
                raw_code_content = str(normalized_config.get("code_content") or "")
                normalized_config_code = _normalize_code_block_output_assignment(raw_code_content)
                if normalized_code_preview is None and normalized_config_code != raw_code_content:
                    normalized_code_preview = normalized_config_code
            try:
                _validate_code_block_alias_config(
                    field_name=field_name,
                    raw_binding=raw_binding,
                    normalized_config=normalized_config,
                )
            except _CodeBlockValidationError as error:
                detected_issues.append(error.error_code)
                warnings.append(_warning(error.error_code, error.message))
            has_outputs = bool((normalized_binding or {}).get("outputs")) or bool((normalized_config or {}).get("result_alias_path"))
            effective_code = ""
            if normalized_binding is not None:
                effective_code = str(normalized_binding_code if normalized_binding_code is not None else normalized_binding.get("code") or "")
            elif normalized_config is not None:
                code_content = str(normalized_config_code if normalized_config_code is not None else normalized_config.get("code_content") or "")
                effective_code = _strip_code_block_generated_input_prelude(code_content)
            if has_outputs and not _code_block_has_effective_output_assignment(effective_code):
                detected_issues.append("CODE_BLOCK_OUTPUT_ASSIGNMENT_MISSING")
                warnings.append(
                    _warning(
                        "CODE_BLOCK_OUTPUT_ASSIGNMENT_MISSING",
                        "configured outputs require qf_output assignment before runtime writeback can succeed",
                    )
                )
            target_relation_default_verified = True
            binding_has_target_bindings = False
            for output in cast(list[dict[str, Any]], (normalized_binding or {}).get("outputs") or []):
                target_payload = output.get("target_field")
                if not isinstance(target_payload, dict):
                    continue
                if any(target_payload.values()):
                    binding_has_target_bindings = True
                try:
                    target_field = _resolve_field_selector_with_uniqueness(
                        fields=parsed_fields,
                        selector_payload=target_payload,
                        location="repair.target_field",
                    )
                except ValueError:
                    target_relation_default_verified = False
                    detected_issues.append("CODE_BLOCK_TARGET_DEFAULT_INVALID")
                    warnings.append(_warning("CODE_BLOCK_TARGET_DEFAULT_INVALID", "bound output target could not be resolved during repair scan"))
                    break
                if _coerce_any_int(target_field.get("default_type")) == DEFAULT_TYPE_RELATION:
                    continue
                target_relation_default_verified = False
                detected_issues.append("CODE_BLOCK_TARGET_DEFAULT_INVALID")
                warnings.append(
                    _warning(
                        "CODE_BLOCK_TARGET_DEFAULT_INVALID",
                        "bound output target is not stored as relation default and should be rebuilt through repair",
                        target_field_name=_code_block_field_display_name(target_field),
                    )
                )
            alias_issue_detected = "CODE_BLOCK_ALIAS_REQUIRED" in detected_issues
            assignment_missing_detected = "CODE_BLOCK_OUTPUT_ASSIGNMENT_MISSING" in detected_issues
            can_auto_repair = not alias_issue_detected and not assignment_missing_detected
            repair_mode = "none"
            patch_payload: dict[str, Any] | None = None
            safe_auto_fix = False
            if can_auto_repair and normalized_binding is not None and binding_has_target_bindings and (
                (normalized_binding_code is not None and normalized_binding_code != str(normalized_binding.get("code") or ""))
                or not target_relation_default_verified
            ):
                patch_payload = {
                    "selector": _field_selector_payload_for_field(code_block_field),
                    "set": {
                        "code_block_binding": _public_code_block_binding_payload(
                            {**normalized_binding, "code": normalized_binding_code if normalized_binding_code is not None else str(normalized_binding.get("code") or "")}
                        )
                    },
                }
                repair_mode = "binding"
                safe_auto_fix = True
            elif (
                can_auto_repair
                and normalized_config is not None
                and normalized_config_code is not None
                and normalized_config_code != str(normalized_config.get("code_content") or "")
            ):
                patch_payload = {
                    "selector": _field_selector_payload_for_field(code_block_field),
                    "set": {"code_block_config": {**normalized_config, "code_content": normalized_config_code}},
                }
                repair_mode = "config"
                safe_auto_fix = True
            if not detected_issues:
                detected_issues.append("NO_REPAIR_NEEDED")
            plan = {
                "field_name": field_name,
                "field_id": str(code_block_field.get("field_id") or "").strip() or None,
                "que_id": _coerce_positive_int(code_block_field.get("que_id")),
                "detected_issues": sorted(set(detected_issues)),
                "normalized_code_preview": normalized_code_preview,
                "would_update": bool(patch_payload and safe_auto_fix),
                "applied": False,
                "repair_mode": repair_mode,
                "preserved_output_targets": preserved_output_targets,
                "warnings": warnings,
            }
            if patch_payload and safe_auto_fix:
                update_fields.append(FieldUpdatePatch.model_validate(patch_payload))
            plans.append(plan)
        if not apply:
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "planned code block repair",
                "normalized_args": {"app_key": app_key, "field": requested_selector or None, "apply": False},
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": not bool(update_fields),
                "warnings": [],
                "verification": {
                    "app_exists": True,
                    "code_block_fields_scanned": len(plans),
                    "would_update": bool(update_fields),
                },
                "verified": True,
                "write_executed": False,
                "safe_to_retry": True,
                "app_key": app_key,
                "apply": False,
                "repair_plan": plans,
                "would_update_fields": [plan["field_name"] for plan in plans if plan["would_update"]],
            }
        if not update_fields:
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "no safe code block repairs were required",
                "normalized_args": {"app_key": app_key, "field": requested_selector or None, "apply": True},
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": True,
                "warnings": [],
                "verification": {
                    "app_exists": True,
                    "code_block_fields_scanned": len(plans),
                    "would_update": False,
                    "applied": False,
                },
                "verified": True,
                "write_executed": False,
                "safe_to_retry": True,
                "app_key": app_key,
                "apply": True,
                "repair_plan": plans,
                "applied_fields": [],
            }
        apply_result = self.app_schema_apply(
            profile=profile,
            app_key=app_key,
            package_tag_id=None,
            app_name="",
            publish=True,
            add_fields=[],
            update_fields=update_fields,
            remove_fields=[],
        )
        if apply_result.get("status") == "failed":
            return apply_result
        verification_error: JSONObject | None = None
        try:
            reread = self._load_base_schema_state(profile=profile, app_key=app_key)
            verified_fields = cast(list[dict[str, Any]], reread["parsed"].get("fields") or [])
            verified_lookup = _build_public_field_lookup(verified_fields)
            for plan in plans:
                selector = plan["field_id"] or plan["field_name"] or plan["que_id"]
                if selector is None:
                    continue
                try:
                    verified_field = _resolve_public_field(selector, field_lookup=verified_lookup)
                except ValueError:
                    continue
                verified_binding = _normalize_code_block_binding(verified_field.get("code_block_binding"))
                verified_config = _normalize_code_block_config(verified_field.get("code_block_config") or verified_field.get("config") or {}) or {}
                verified_code = str((verified_binding or {}).get("code") or "")
                verified_content = str(verified_config.get("code_content") or "")
                if "const qf_output =" not in verified_code and "let qf_output =" not in verified_code and "const qf_output =" not in verified_content and "let qf_output =" not in verified_content:
                    if plan["would_update"]:
                        plan["applied"] = True
                        applied_fields.append(plan["field_name"])
        except (QingflowApiError, RuntimeError) as error:
            verification_error = _transport_error_payload(_coerce_api_error(error))
        apply_result["message"] = "repaired code block fields"
        apply_result["apply"] = True
        apply_result["repair_plan"] = plans
        apply_result["applied_fields"] = applied_fields
        if verification_error is None:
            existing_details = apply_result.get("details") if isinstance(apply_result.get("details"), dict) else {}
            existing_verification_error = existing_details.get("verification_error") if isinstance(existing_details, dict) else None
            if isinstance(existing_verification_error, dict):
                verification_error = deepcopy(existing_verification_error)
        if verification_error is not None:
            details = apply_result.get("details") if isinstance(apply_result.get("details"), dict) else {}
            details = deepcopy(details)
            details["code_block_repair_verification_error"] = verification_error
            apply_result["details"] = details
            warnings = apply_result.get("warnings") if isinstance(apply_result.get("warnings"), list) else []
            apply_result["warnings"] = [
                *warnings,
                _warning(
                    "CODE_BLOCK_REPAIR_VERIFICATION_UNAVAILABLE",
                    "code block repair write was executed but post-write verification readback is unavailable",
                ),
            ]
        apply_result["verification"] = {
            **(apply_result.get("verification") if isinstance(apply_result.get("verification"), dict) else {}),
            "code_block_fields_scanned": len(plans),
            "would_update": bool(update_fields),
            "applied": bool(applied_fields),
            **({"code_block_repair_verification_unavailable": True} if verification_error is not None else {}),
        }
        return apply_result

    def app_get_layout(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_layout_summary(profile=profile, app_key=app_key)
        if result.get("status") == "success":
            result["message"] = "read app layout config"
        return result

    def app_get_views(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_views_summary(profile=profile, app_key=app_key)
        if result.get("status") == "success":
            result["message"] = "read app view config"
        return result

    def _load_view_summaries_for_builder(self, *, profile: str, app_key: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
        views, views_unavailable, views_read_error = self._load_views_result(
            profile=profile,
            app_key=app_key,
            tolerate_404=True,
            tolerate_permission_restricted=True,
            include_error=True,
        )
        summaries, config_read_errors = _summarize_views_with_config(self.views, profile=profile, views=views)
        if views_unavailable and views_read_error is not None:
            config_read_errors = [
                {
                    "resource": "views",
                    "phase": "view_list",
                    "transport_error": _transport_error_payload(views_read_error),
                },
                *config_read_errors,
            ]
        return summaries, config_read_errors

    def app_get_flow(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_flow_summary(profile=profile, app_key=app_key)
        if result.get("status") == "success":
            result["message"] = "read app flow config"
        return result

    def app_get_charts(self, *, profile: str, app_key: str) -> JSONObject:
        result = self.app_read_charts_summary(profile=profile, app_key=app_key)
        if result.get("status") == "success":
            result["message"] = "read app chart config"
        return result

    def _chart_filter_field_names_by_id(
        self,
        *,
        profile: str,
        app_key: str,
    ) -> tuple[dict[str, str], dict[str, Any] | None]:
        try:
            state = self._load_base_schema_state(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return {}, _transport_error_payload(api_error)
        fields = cast(list[dict[str, Any]], state["parsed"]["fields"])
        return _chart_field_names_by_id_from_public_fields(app_key=app_key, fields=fields), None

    def app_get_buttons(self, *, profile: str, app_key: str) -> JSONObject:
        self.apps._require_app_key(app_key)
        warnings: list[dict[str, Any]] = []
        verification: dict[str, Any] = {"buttons_loaded": True, "view_configs_loaded": True}
        try:
            raw = self.buttons.custom_button_list(profile=profile, app_key=app_key, being_draft=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "BUTTONS_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        buttons = raw.get("items") if isinstance(raw.get("items"), list) else []
        view_configs: list[dict[str, Any]] = []
        view_config_read_errors: list[dict[str, Any]] = []
        try:
            view_summaries, view_config_read_errors = self._load_view_summaries_for_builder(profile=profile, app_key=app_key)
            view_configs = _custom_button_view_configs_from_view_summaries(view_summaries)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            verification["view_configs_loaded"] = False
            warnings.append(
                _warning(
                    "BUTTON_VIEW_CONFIGS_UNAVAILABLE",
                    "custom buttons were read, but per-view button bindings could not be read back",
                    backend_code=api_error.backend_code,
                    request_id=api_error.request_id,
                )
            )
        if view_config_read_errors:
            verification["view_configs_loaded"] = False
            warnings.append(_warning("BUTTON_VIEW_CONFIGS_PARTIAL", "some view button binding configs could not be read back"))
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app buttons config",
            "verified": True,
            "app_key": app_key,
            "buttons": buttons,
            "view_configs": view_configs,
            "count": len(buttons),
            "view_config_count": len(view_configs),
            "warnings": warnings,
            "verification": verification,
            "details": {"view_config_read_errors": view_config_read_errors} if view_config_read_errors else {},
        }

    def app_get_associated_resources(self, *, profile: str, app_key: str) -> JSONObject:
        self.apps._require_app_key(app_key)
        warnings: list[dict[str, Any]] = []
        verification: dict[str, Any] = {"associated_resources_loaded": True, "view_configs_loaded": True}
        try:
            resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "ASSOCIATED_RESOURCES_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        match_mapping_errors = self._enrich_associated_resource_match_mappings(
            profile=profile,
            app_key=app_key,
            resources=resources,
        )
        if match_mapping_errors:
            verification["match_mappings_loaded"] = False
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCE_MATCH_MAPPINGS_PARTIAL",
                    "associated resources were read, but some raw match rules could not be converted to semantic match_mappings",
                )
            )
        else:
            verification["match_mappings_loaded"] = True
        view_configs: list[dict[str, Any]] = []
        view_config_read_errors: list[dict[str, Any]] = []
        try:
            view_summaries, view_config_read_errors = self._load_view_summaries_for_builder(profile=profile, app_key=app_key)
            view_configs = _associated_resource_view_configs_from_view_summaries(view_summaries)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            verification["view_configs_loaded"] = False
            warnings.append(
                _warning(
                    "ASSOCIATED_RESOURCE_VIEW_CONFIGS_UNAVAILABLE",
                    "associated resources were read, but per-view associated-resource bindings could not be read back",
                    backend_code=api_error.backend_code,
                    request_id=api_error.request_id,
                )
            )
        if view_config_read_errors:
            verification["view_configs_loaded"] = False
            warnings.append(_warning("ASSOCIATED_RESOURCE_VIEW_CONFIGS_PARTIAL", "some view associated-resource configs could not be read back"))
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app associated resources config",
            "verified": True,
            "app_key": app_key,
            "associated_resources": resources,
            "view_configs": view_configs,
            "count": len(resources),
            "view_config_count": len(view_configs),
            "warnings": warnings,
            "verification": verification,
            "details": {
                **({"view_config_read_errors": view_config_read_errors} if view_config_read_errors else {}),
                **({"match_mapping_errors": match_mapping_errors} if match_mapping_errors else {}),
            },
        }

    def flow_patch_nodes(
        self,
        *,
        profile: str,
        app_key: str,
        patch_nodes: list[dict[str, Any]],
        publish: bool = True,
        idempotency_key: str | None = None,
        schema_version: str | None = None,
    ) -> JSONObject:
        patch_started_at = time.perf_counter()
        flow_patch_read_ms = 0
        flow_patch_compile_ms = 0
        flow_patch_apply_ms = 0

        def attach_patch_duration(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                flow_patch_read_ms=flow_patch_read_ms,
                flow_patch_compile_ms=flow_patch_compile_ms,
                flow_patch_apply_ms=flow_patch_apply_ms,
                total_ms=_duration_ms(patch_started_at),
            )
            return response

        try:
            flow_patch_read_started_at = time.perf_counter()
            current = self.flow_get(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            flow_patch_read_ms += _duration_ms(flow_patch_read_started_at)
            api_error = _coerce_api_error(error)
            return attach_patch_duration(_failed_from_api_error(
                "FLOW_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        flow_patch_read_ms += _duration_ms(flow_patch_read_started_at)
        if str(current.get("status") or "").strip().lower() != "success":
            return attach_patch_duration(_failed(
                "FLOW_READ_FAILED",
                current.get("message") or "failed to read current flow",
                normalized_args={"app_key": app_key},
                suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        flow_patch_compile_started_at = time.perf_counter()
        spec = deepcopy(current.get("spec") or {})
        if not isinstance(spec, dict) or not spec:
            flow_patch_compile_ms += _duration_ms(flow_patch_compile_started_at)
            return attach_patch_duration(_failed(
                "FLOW_PATCH_FAILED",
                "current flow has no spec; use full app_flow_apply with spec first",
                normalized_args={"app_key": app_key, "patch_nodes": patch_nodes},
                suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        nodes = spec.get("nodes") if isinstance(spec.get("nodes"), list) else []
        node_by_id: dict[str, dict[str, Any]] = {}
        for node in nodes:
            if isinstance(node, dict) and str(node.get("id") or ""):
                node_by_id[str(node["id"])] = node
        not_found: list[str] = []
        invalid_items: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_nodes):
            if not isinstance(patch, dict):
                invalid_items.append({"index": index, "message": "patch_nodes[] item must be an object"})
                continue
            node_id = str(patch.get("id") or "").strip()
            if not node_id:
                invalid_items.append({"index": index, "message": "patch_nodes[] item requires id"})
                continue
            if node_id not in node_by_id:
                not_found.append(node_id)
                continue
            target = node_by_id[node_id]
            set_payload = patch.get("set") if isinstance(patch.get("set"), dict) else {}
            for key, value in set_payload.items():
                target[key] = deepcopy(value)
            unset_payload = patch.get("unset") if isinstance(patch.get("unset"), list) else []
            for key in unset_payload:
                target.pop(str(key), None)
        if invalid_items:
            flow_patch_compile_ms += _duration_ms(flow_patch_compile_started_at)
            return attach_patch_duration(_failed(
                "FLOW_PATCH_INVALID",
                "patch_nodes[] contains invalid items",
                normalized_args={"app_key": app_key, "invalid_items": invalid_items},
                suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        if not_found:
            flow_patch_compile_ms += _duration_ms(flow_patch_compile_started_at)
            return attach_patch_duration(_failed(
                "FLOW_NODE_NOT_FOUND",
                f"patch_nodes[] referenced node id(s) not found in current flow: {not_found}",
                normalized_args={"app_key": app_key, "not_found_ids": not_found},
                suggested_next_call={"tool_name": "app_flow_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        edge_container = spec.get("edges") if isinstance(spec.get("edges"), dict) else {}
        edges = edge_container.get("edges") if isinstance(edge_container, dict) else None
        transitions = spec.get("transitions") if isinstance(spec.get("transitions"), list) else None
        if not edges and not transitions:
            flow_patch_compile_ms += _duration_ms(flow_patch_compile_started_at)
            return attach_patch_duration(_failed(
                "FLOW_PATCH_EMPTY_GRAPH_UNSUPPORTED",
                "current flow spec has no edges; patch_nodes cannot safely save this backend shape",
                normalized_args={"app_key": app_key, "patch_nodes": patch_nodes},
                suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        flow_patch_compile_ms += _duration_ms(flow_patch_compile_started_at)
        flow_patch_apply_started_at = time.perf_counter()
        result = self.flow_apply(
            profile=profile,
            app_key=app_key,
            spec=spec,
            publish=publish,
            idempotency_key=idempotency_key,
            schema_version=schema_version,
        )
        flow_patch_apply_ms += _duration_ms(flow_patch_apply_started_at)
        return attach_patch_duration(result)

    def _components_to_sections_payload(self, components: list[dict[str, Any]]) -> list[dict[str, Any]]:
        result: list[dict[str, Any]] = []
        for component in components:
            if not isinstance(component, dict):
                continue
            item = {key: deepcopy(value) for key, value in component.items() if key != "order"}
            result.append(item)
        return result

    def portal_patch_sections(
        self,
        *,
        profile: str,
        dash_key: str,
        patch_sections: list[dict[str, Any]],
        publish: bool = True,
    ) -> JSONObject:
        try:
            current = self.portal_get(profile=profile, dash_key=dash_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PORTAL_READ_FAILED",
                api_error,
                normalized_args={"dash_key": dash_key},
                details={"dash_key": dash_key},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        if current.get("status") != "success":
            return _failed(
                "PORTAL_READ_FAILED",
                current.get("message") or "failed to read portal",
                normalized_args={"dash_key": dash_key},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        components = current.get("components") or []
        if not isinstance(components, list) or not components:
            return _failed(
                "PORTAL_PATCH_FAILED",
                "portal has no sections to patch; use portal_apply with sections[] first",
                normalized_args={"dash_key": dash_key},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        merged = [deepcopy(component) for component in components if isinstance(component, dict)]
        not_found: list[dict[str, Any]] = []
        invalid_items: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_sections):
            if not isinstance(patch, dict):
                invalid_items.append({"index": index, "message": "patch_sections[] item must be an object"})
                continue
            target_index: int | None = None
            if patch.get("order") is not None:
                try:
                    candidate_index = int(patch["order"])
                except (TypeError, ValueError):
                    invalid_items.append({"index": index, "message": "order must be an integer"})
                    continue
                if 0 <= candidate_index < len(merged):
                    target_index = candidate_index
            elif patch.get("chart_ref") and isinstance(patch["chart_ref"], dict):
                selector = patch["chart_ref"]
                for item_index, component in enumerate(merged):
                    if component.get("source_type") == "chart" and isinstance(component.get("chart_ref"), dict) and _portal_chart_ref_matches(component["chart_ref"], selector):
                        target_index = item_index
                        break
            elif patch.get("view_ref") and isinstance(patch["view_ref"], dict):
                selector = patch["view_ref"]
                for item_index, component in enumerate(merged):
                    if component.get("source_type") == "view" and isinstance(component.get("view_ref"), dict) and _portal_view_ref_matches(component["view_ref"], selector):
                        target_index = item_index
                        break
            if target_index is None:
                not_found.append({"selector": {key: value for key, value in patch.items() if key not in {"set", "unset"}}})
                continue
            set_payload = patch.get("set") if isinstance(patch.get("set"), dict) else {}
            for key, value in set_payload.items():
                merged[target_index][key] = deepcopy(value)
            unset_payload = patch.get("unset") if isinstance(patch.get("unset"), list) else []
            for key in unset_payload:
                merged[target_index].pop(str(key), None)
        if invalid_items:
            return _failed(
                "PORTAL_PATCH_INVALID",
                "patch_sections[] contains invalid items",
                normalized_args={"dash_key": dash_key, "invalid_items": invalid_items},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        if not_found:
            return _failed(
                "PORTAL_SECTION_NOT_FOUND",
                f"patch_sections[] could not find {len(not_found)} section(s) in portal",
                normalized_args={"dash_key": dash_key, "not_found": not_found},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
            )
        sections_payload = self._components_to_sections_payload(merged)
        return self.portal_apply(
            profile=profile,
            request=PortalApplyRequest.model_validate(
                {
                    "dash_key": dash_key,
                    "publish": publish,
                    "sections": sections_payload,
                }
            ),
        )

    def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            state = self._load_base_schema_state(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "FIELDS_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        parsed = state["parsed"]
        warnings: list[dict[str, Any]] = []
        field_lookup = _build_public_field_lookup(cast(list[dict[str, Any]], parsed["fields"]))
        chart_fields: list[dict[str, Any]] = []
        try:
            qingbi_fields = self.charts.qingbi_report_list_fields(profile=profile, app_key=app_key).get("items") or []
            chart_fields = _compact_public_chart_fields_read(
                app_key=app_key,
                qingbi_fields=[item for item in qingbi_fields if isinstance(item, dict)],
                field_lookup=field_lookup,
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if is_auth_like_error(api_error) or (
                backend_code_int(api_error) not in {40002, 40027, 404}
                and api_error.http_status != 404
            ):
                return _failed_from_api_error(
                    "APP_GET_FIELDS_FAILED",
                    api_error,
                    normalized_args={"app_key": app_key},
                    details={"app_key": app_key, "resource": "qingbi_fields"},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            warnings.append(
                _warning(
                    "QINGBI_FIELDS_READ_FAILED",
                    "form fields were read, but QingBI chart fields could not be read; chart configuration should use chart_fields when available",
                    backend_code=api_error.backend_code,
                    request_id=api_error.request_id,
                )
            )
        response = AppFieldsReadResponse(
            app_key=app_key,
            fields=[_compact_public_field_read(field=field, layout=parsed["layout"]) for field in parsed["fields"]],
            field_count=len(parsed["fields"]),
            chart_fields=chart_fields,
            chart_field_count=len(chart_fields),
            form_settings=_form_settings_from_schema(state["schema"], parsed["fields"]),
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app fields",
            "normalized_args": {"app_key": app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {"app_exists": True},
            "verified": True,
            **response.model_dump(mode="json"),
        }

    def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            state = self._load_base_schema_state(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "LAYOUT_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        parsed = state["parsed"]
        layout = parsed["layout"]
        response = AppLayoutReadResponse(
            app_key=app_key,
            sections=_decorate_layout_sections_as_paragraphs(layout.get("sections", [])),
            unplaced_fields=_find_unplaced_fields(parsed["fields"], layout),
            layout_mode_detected=_detect_layout_mode(layout),
        )
        layout_summary_verified = not (_schema_has_layout_content(state["schema"]) and not response.sections and response.layout_mode_detected != "flat")
        warnings = _layout_read_warnings(response.unplaced_fields)
        if not layout_summary_verified:
            warnings.append(_warning("LAYOUT_SUMMARY_UNVERIFIED", "layout summary is incomplete relative to raw schema readback"))
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app layout summary",
            "normalized_args": {"app_key": app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {
                "app_exists": True,
                "layout_summary_verified": layout_summary_verified,
            },
            "verified": layout_summary_verified,
            **response.model_dump(mode="json"),
        }

    def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            views, _ = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=False)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "VIEWS_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        summarized_views, config_read_errors = _summarize_views_with_config(self.views, profile=profile, views=views)
        response = AppViewsReadResponse(
            app_key=app_key,
            views=summarized_views,
        )
        warnings = []
        if response.views:
            warnings.append(
                _warning(
                    "VIEW_FILTERS_UNVERIFIED",
                    "view summary does not verify saved filter behavior; use apply verification or explicit reads before relying on filters",
                )
            )
        if config_read_errors:
            warnings.append(
                _warning(
                    "VIEW_CONFIG_READ_PARTIAL",
                    "some view configs could not be read back; field order and display order may be incomplete for those views",
                )
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app views summary",
            "normalized_args": {"app_key": app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {"view_config_read_errors": config_read_errors} if config_read_errors else {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {
                "app_exists": True,
                "view_filters_verified": False,
                "view_query_conditions_verified": False,
                "view_display_readback_complete": not config_read_errors,
            },
            "verified": True,
            **response.model_dump(mode="json"),
        }

    def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            workflow, workflow_unavailable = self._load_workflow_result(profile=profile, app_key=app_key, tolerate_404=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "FLOW_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        response = AppFlowReadResponse(
            app_key=app_key,
            enabled=bool(workflow),
            nodes=_summarize_workflow_nodes(workflow),
            transitions=[],
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app flow summary",
            "normalized_args": {"app_key": app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [_warning("WORKFLOW_READ_UNAVAILABLE", "workflow summary readback is unavailable")] if workflow_unavailable else [],
            "verification": {"app_exists": True, "workflow_read_unavailable": workflow_unavailable},
            "verified": not workflow_unavailable,
            **response.model_dump(mode="json"),
        }

    def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
        try:
            app_result = self.app_resolve(profile=profile, app_key=app_key)
            if app_result.get("status") != "success":
                return app_result
            resolved_app_key = str(app_result.get("app_key") or app_key)
            items, list_source = self._load_chart_list_for_builder(profile=profile, app_key=resolved_app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "CHARTS_READ_FAILED",
                api_error,
                normalized_args={"app_key": app_key},
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        charts = _summarize_charts(items)
        chart_visibility_read_errors: list[dict[str, Any]] = []
        chart_config_read_errors: list[dict[str, Any]] = []
        field_name_by_id, field_name_read_error = self._chart_filter_field_names_by_id(profile=profile, app_key=resolved_app_key)
        for chart in charts:
            chart_id = str(chart.get("chart_id") or "").strip()
            if not chart_id:
                continue
            try:
                base_info = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                chart_visibility_read_errors.append(
                    {
                        "chart_id": chart_id,
                        "request_id": api_error.request_id,
                        "http_status": api_error.http_status,
                        "backend_code": api_error.backend_code,
                    }
                )
                continue
            chart["visibility_summary"] = _visibility_summary(
                _public_visibility_from_chart_visible_auth(
                    base_info.get("visibleAuth") if isinstance(base_info, dict) else None
                )
            )
            try:
                config_response = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id)
                config = config_response.get("result") or {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                chart_config_read_errors.append(
                    {
                        "chart_id": chart_id,
                        "request_id": api_error.request_id,
                        "http_status": api_error.http_status,
                        "backend_code": api_error.backend_code,
                    }
                )
                continue
            if isinstance(config, dict):
                chart["group_by"] = _public_chart_group_by_from_qingbi_config(config)
                chart["metrics"] = _public_chart_metrics_from_qingbi_config(config)
                chart["filters"] = _public_chart_filter_groups_from_qingbi_config(config, field_name_by_id=field_name_by_id)
        response = AppChartsReadResponse(
            app_key=resolved_app_key,
            charts=charts,
            chart_count=len(charts),
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app charts summary",
            "normalized_args": {"app_key": resolved_app_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {
                **({"chart_visibility_read_errors": chart_visibility_read_errors} if chart_visibility_read_errors else {}),
                **({"chart_config_read_errors": chart_config_read_errors} if chart_config_read_errors else {}),
                **({"chart_filter_field_name_read_error": field_name_read_error} if field_name_read_error else {}),
            },
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": (
                ([] if list_source == "sorted" else [_warning("CHART_ORDER_UNVERIFIED", "chart summary order uses fallback listing and may not reflect saved chart sort order")])
                + (
                    [_warning("CHART_VISIBILITY_READ_PARTIAL", "some chart base infos could not be read back; visibility_summary is incomplete for those charts")]
                    if chart_visibility_read_errors
                    else []
                )
                + (
                    [_warning("CHART_CONFIG_READ_PARTIAL", "some chart configs could not be read back; metrics/group_by/filters are incomplete for those charts")]
                    if chart_config_read_errors
                    else []
                )
                + (
                    [_warning("CHART_FILTER_FIELD_NAMES_UNRESOLVED", "chart configs were read, but form fields could not be loaded to resolve filter field names")]
                    if field_name_read_error
                    else []
                )
            ),
            "verification": {
                "app_exists": True,
                "chart_order_verified": list_source == "sorted",
                "chart_list_source": list_source,
                "chart_visibility_readback_complete": not chart_visibility_read_errors,
                "chart_config_readback_complete": not chart_config_read_errors,
                "chart_filter_field_names_resolved": not field_name_read_error,
            },
            "verified": not chart_config_read_errors and not field_name_read_error,
            **response.model_dump(mode="json"),
        }

    def portal_list(self, *, profile: str) -> JSONObject:
        try:
            raw_items = self.portals.portal_list(profile=profile).get("items") or []
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PORTAL_LIST_FAILED",
                api_error,
                normalized_args={},
                details={},
                suggested_next_call=None,
            )
        warnings: list[dict[str, Any]] = []
        items: list[dict[str, Any]] = []
        permission_verified = True
        if isinstance(raw_items, list) and len(raw_items) > BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT:
            normalized = _normalize_portal_list_items(raw_items)
            response = PortalListResponse(items=normalized, total=len(normalized))
            warnings.append(
                _warning(
                    "PORTAL_PERMISSION_VERIFICATION_SKIPPED",
                    "builder portal_list returned the discovery list without per-portal edit permission checks because the workspace has too many portals",
                    total_available=len(raw_items),
                    detail_verify_limit=BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT,
                )
            )
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "list builder portal discovery items",
                "normalized_args": {},
                "missing_fields": [],
                "allowed_values": {},
                "details": {
                    "total_available": len(raw_items),
                    "detail_verify_limit": BUILDER_PORTAL_LIST_DETAIL_VERIFY_LIMIT,
                    "permission_filter_applied": False,
                },
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "warnings": warnings,
                "verification": {
                    "portal_list_loaded": True,
                    "portal_permissions_verified": False,
                    "permission_filter_applied": False,
                },
                "verified": False,
                **response.model_dump(mode="json"),
            }
        for raw_item in raw_items if isinstance(raw_items, list) else []:
            if not isinstance(raw_item, dict):
                continue
            dash_key = str(raw_item.get("dashKey") or "").strip()
            if not dash_key:
                continue
            try:
                portal_result = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                if not _is_optional_builder_lookup_error(api_error):
                    return _failed_from_api_error(
                        "PORTAL_LIST_FAILED",
                        api_error,
                        normalized_args={},
                        details={"dash_key": dash_key, "resource": "portal_detail"},
                        suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
                    )
                permission_verified = False
                warnings.append(
                    _warning(
                        "PORTAL_PERMISSION_READ_UNAVAILABLE",
                        f"builder portal_list skipped `{dash_key}` because portal detail readback was unavailable during permission verification",
                        dash_key=dash_key,
                        **_transport_error_payload(api_error),
                    )
                )
                continue
            permission_outcome = self._guard_portal_permission(
                profile=profile,
                dash_key=dash_key,
                normalized_args={"dash_key": dash_key},
                portal_result=portal_result if isinstance(portal_result, dict) else {},
            )
            if permission_outcome.block is not None:
                error_code = str(permission_outcome.block.get("error_code") or "")
                if error_code != "PORTAL_EDIT_UNAUTHORIZED":
                    permission_verified = False
                    warnings.append(
                        _warning(
                            "PORTAL_PERMISSION_UNVERIFIED",
                            f"builder portal_list skipped `{dash_key}` because builder edit permission could not be verified",
                            dash_key=dash_key,
                        )
                    )
                continue
            normalized = _normalize_portal_list_items([raw_item])
            if normalized:
                items.extend(normalized)
        response = PortalListResponse(items=items, total=len(items))
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "list builder-configurable portals",
            "normalized_args": {},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {
                "portal_list_loaded": True,
                "portal_permissions_verified": permission_verified,
            },
            "verified": permission_verified,
            **response.model_dump(mode="json"),
        }

    def _load_chart_list_for_builder(self, *, profile: str, app_key: str) -> tuple[list[dict[str, Any]], str]:
        try:
            sorted_items = self.charts.qingbi_report_list_sorted(profile=profile, app_key=app_key, page_num=1, page_size=500).get("items") or []
            if isinstance(sorted_items, list):
                return sorted_items, "sorted"
        except (QingflowApiError, RuntimeError) as exc:
            api_error = _coerce_api_error(exc)
            if not _is_optional_builder_lookup_error(api_error):
                raise
        fallback_items = self.charts.qingbi_report_list(profile=profile, app_key=app_key).get("items") or []
        return list(fallback_items) if isinstance(fallback_items, list) else [], "fallback"

    def _load_associated_resources_for_builder(self, *, profile: str, app_key: str, include_raw: bool = False) -> list[dict[str, Any]]:
        def runner(_: Any, context: BackendRequestContext) -> list[dict[str, Any]]:
            payload = self.apps.backend.request(
                "GET",
                context,
                f"/app/{app_key}/asosChart",
                params={"role": 1, "beingDraft": True},
            )
            return _normalize_associated_resources_payload(payload, include_raw=include_raw)

        return self.apps._run(profile, runner, tool_name="关联资源读取")

    def _match_field_index_for_app(self, *, profile: str, app_key: str) -> dict[int, dict[str, Any]]:
        state = self._load_base_schema_state(profile=profile, app_key=app_key)
        fields = cast(list[dict[str, Any]], state["parsed"]["fields"])
        indexed: dict[int, dict[str, Any]] = {
            -17: {"name": "数据ID", "que_id": -17, "type": "system"},
            0: {"name": "编号", "que_id": 0, "type": "system"},
        }
        for field in fields:
            que_id = _coerce_any_int(field.get("que_id"))
            if que_id is not None:
                indexed[que_id] = field
        return indexed

    def _enrich_associated_resource_match_mappings(
        self,
        *,
        profile: str,
        app_key: str,
        resources: list[dict[str, Any]],
    ) -> list[dict[str, Any]]:
        if not any(isinstance(resource, dict) and resource.get("match_rules") for resource in resources):
            return []
        errors: list[dict[str, Any]] = []
        source_fields: dict[int, dict[str, Any]] = {}
        try:
            source_fields = self._match_field_index_for_app(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            errors.append({"app_key": app_key, "resource": "source_fields", "transport_error": _transport_error_payload(_coerce_api_error(error))})
        target_fields_by_app: dict[str, dict[int, dict[str, Any]]] = {}
        for resource in resources:
            if not isinstance(resource, dict):
                continue
            raw_rules = resource.get("match_rules")
            if not raw_rules:
                continue
            target_app_key = str(resource.get("target_app_key") or app_key).strip()
            if target_app_key not in target_fields_by_app:
                try:
                    target_fields_by_app[target_app_key] = self._match_field_index_for_app(profile=profile, app_key=target_app_key)
                except (QingflowApiError, RuntimeError) as error:
                    errors.append(
                        {
                            "associated_item_id": resource.get("associated_item_id"),
                            "target_app_key": target_app_key,
                            "resource": "target_fields",
                            "transport_error": _transport_error_payload(_coerce_api_error(error)),
                        }
                    )
                    target_fields_by_app[target_app_key] = {}
            mappings = _public_associated_resource_match_mappings_from_rules(
                raw_rules if isinstance(raw_rules, list) else [],
                source_fields=source_fields,
                target_fields=target_fields_by_app.get(target_app_key, {}),
            )
            if mappings:
                resource["match_mappings"] = mappings
        return errors

    def _associated_resource_create(
        self,
        *,
        profile: str,
        app_key: str,
        patch: AssociatedResourceUpsertPatch,
        match_rules_override: list[dict[str, Any]] | None = None,
    ) -> JSONObject:
        payload = _serialize_associated_resource_create_payload(patch, match_rules_override=match_rules_override)

        def runner(_: Any, context: BackendRequestContext) -> JSONObject:
            result = self.apps.backend.request("POST", context, f"/app/{app_key}/asosChart", json_body=payload)
            return result if isinstance(result, dict) else {"result": result}

        return self.apps._run(profile, runner, tool_name="关联资源创建")

    def _associated_resource_update(
        self,
        *,
        profile: str,
        app_key: str,
        associated_item_id: int,
        patch: AssociatedResourceUpsertPatch,
        existing_item: dict[str, Any] | None = None,
        match_rules_override: list[dict[str, Any]] | None = None,
    ) -> JSONObject:
        payload = _serialize_associated_resource_update_payload(
            patch,
            associated_item_id=associated_item_id,
            existing_item=existing_item,
            match_rules_override=match_rules_override,
        )

        def runner(_: Any, context: BackendRequestContext) -> JSONObject:
            result = self.apps.backend.request("POST", context, f"/app/{app_key}/asosChart/{associated_item_id}", json_body=payload)
            return result if isinstance(result, dict) else {"result": result}

        return self.apps._run(profile, runner, tool_name="关联资源更新")

    def _associated_resource_delete(self, *, profile: str, app_key: str, associated_item_id: int) -> JSONObject:
        def runner(_: Any, context: BackendRequestContext) -> JSONObject:
            result = self.apps.backend.request("DELETE", context, f"/app/{app_key}/asosChart/{associated_item_id}")
            return result if isinstance(result, dict) else {"result": result}

        return self.apps._run(profile, runner, tool_name="关联资源删除")

    def _associated_resource_reorder(self, *, profile: str, app_key: str, associated_item_ids: list[int]) -> JSONObject:
        def runner(_: Any, context: BackendRequestContext) -> JSONObject:
            result = self.apps.backend.request("POST", context, f"/app/{app_key}/asosChart/ordinal", json_body=list(associated_item_ids))
            return result if isinstance(result, dict) else {"result": result}

        return self.apps._run(profile, runner, tool_name="关联资源排序")

    def _update_view_associated_resources_config(
        self,
        *,
        profile: str,
        view_key: str,
        associated_resources_payload: dict[str, Any] | None,
    ) -> JSONObject:
        config_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
        config = config_response.get("result") if isinstance(config_response.get("result"), dict) else {}
        payload = _build_view_associated_resources_only_update_payload(config, associated_resources_payload=associated_resources_payload)
        return self.views.view_update(profile=profile, viewgraph_key=view_key, payload=payload)

    def _load_custom_buttons_for_builder(self, *, profile: str, app_key: str) -> list[dict[str, Any]]:
        listing = self.buttons.custom_button_list(profile=profile, app_key=app_key, being_draft=True, include_raw=False)
        return [
            _normalize_custom_button_summary(item)
            for item in (listing.get("items") or [])
            if isinstance(item, dict)
        ]

    def _compile_custom_button_semantic_add_data_configs(
        self,
        *,
        profile: str,
        app_key: str,
        patches: list[CustomButtonUpsertPatch],
    ) -> tuple[dict[int, dict[str, Any]], list[dict[str, Any]]]:
        compiled_by_index: dict[int, dict[str, Any]] = {}
        issues: list[dict[str, Any]] = []
        semantic_patches = [
            (index, patch, patch.trigger_add_data_config)
            for index, patch in enumerate(patches)
            if patch.trigger_add_data_config is not None
            and _custom_button_add_data_config_has_semantic_inputs(patch.trigger_add_data_config.model_dump(mode="json", exclude_none=True))
        ]
        if not semantic_patches:
            return compiled_by_index, issues
        try:
            source_schema, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return {}, [
                {
                    "error_code": "CUSTOM_BUTTON_SOURCE_SCHEMA_READ_FAILED",
                    "reason_path": "upsert_buttons[].trigger_add_data_config.field_mappings",
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                    "next_action": "retry after app schema is readable",
                }
            ]
        source_fields = _parse_schema(source_schema).get("fields") or []
        target_schema_cache: dict[str, list[dict[str, Any]]] = {}
        target_name_cache: dict[str, str | None] = {}
        for index, patch, config in semantic_patches:
            config_payload = config.model_dump(mode="json", exclude_none=True)
            reason_base = f"upsert_buttons[{index}].trigger_add_data_config"
            if "que_relation" in config.model_fields_set:
                issues.append(
                    {
                        "error_code": "MIXED_CUSTOM_BUTTON_MAPPING_MODES",
                        "reason_path": reason_base,
                        "message": "field_mappings/default_values cannot be used together with legacy que_relation",
                        "next_action": "use field_mappings/default_values only, or pass legacy que_relation only",
                    }
                )
                continue
            target_app_key = str(config.related_app_key or "").strip()
            if not target_app_key:
                issues.append(
                    {
                        "error_code": "CUSTOM_BUTTON_TARGET_APP_REQUIRED",
                        "reason_path": f"{reason_base}.target_app_key",
                        "missing_fields": ["target_app_key"],
                        "message": "addData field_mappings/default_values require target_app_key",
                    }
                )
                continue
            if target_app_key not in target_schema_cache:
                try:
                    target_schema, _target_schema_source = self._read_schema_with_fallback(profile=profile, app_key=target_app_key)
                    target_schema_cache[target_app_key] = list(_parse_schema(target_schema).get("fields") or [])
                    target_base = self.apps.app_get_base(profile=profile, app_key=target_app_key, include_raw=True).get("result") or {}
                    target_name_cache[target_app_key] = str(target_base.get("formTitle") or target_base.get("appName") or "").strip() or None
                except (QingflowApiError, RuntimeError) as error:
                    api_error = _coerce_api_error(error)
                    issues.append(
                        {
                            "error_code": "CUSTOM_BUTTON_TARGET_SCHEMA_READ_FAILED",
                            "reason_path": f"{reason_base}.target_app_key",
                            "target_app_key": target_app_key,
                            "message": api_error.message,
                            "transport_error": _transport_error_payload(api_error),
                            "next_action": "verify target_app_key with app_get",
                        }
                    )
                    continue
            compiled_config, config_issues = self._compile_custom_button_add_data_config(
                profile=profile,
                source_app_key=app_key,
                source_fields=list(source_fields),
                target_fields=target_schema_cache[target_app_key],
                target_app_key=target_app_key,
                target_app_name=config.related_app_name or target_name_cache.get(target_app_key),
                config=config_payload,
                reason_path=reason_base,
            )
            if config_issues:
                issues.extend(config_issues)
                continue
            compiled_by_index[index] = compiled_config
        return compiled_by_index, issues

    def _compile_custom_button_add_data_config(
        self,
        *,
        profile: str,
        source_app_key: str,
        source_fields: list[dict[str, Any]],
        target_fields: list[dict[str, Any]],
        target_app_key: str,
        target_app_name: str | None,
        config: dict[str, Any],
        reason_path: str,
    ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
        issues: list[dict[str, Any]] = []
        rules: list[dict[str, Any]] = []
        for mapping_index, mapping in enumerate(config.get("field_mappings") or []):
            if not isinstance(mapping, dict):
                continue
            source_field, source_issue = _resolve_custom_button_schema_field(
                fields=source_fields,
                selector=mapping.get("source_field"),
                reason_path=f"{reason_path}.field_mappings[{mapping_index}].source_field",
                role="source",
            )
            target_field, target_issue = _resolve_custom_button_schema_field(
                fields=target_fields,
                selector=mapping.get("target_field"),
                reason_path=f"{reason_path}.field_mappings[{mapping_index}].target_field",
                role="target",
            )
            if source_issue:
                issues.append(source_issue)
            if target_issue:
                issues.append(target_issue)
            if source_issue or target_issue or source_field is None or target_field is None:
                continue
            type_issue = _custom_button_mapping_type_issue(
                source_field=source_field,
                target_field=target_field,
                reason_path=f"{reason_path}.field_mappings[{mapping_index}]",
                source_app_key=source_app_key,
                error_code="CUSTOM_BUTTON_MAPPING_TYPE_MISMATCH",
                context_label="addData copy mapping",
            )
            if type_issue:
                issues.append(type_issue)
                continue
            rules.append(_custom_button_field_mapping_rule(source_field=source_field, target_field=target_field))

        default_values = config.get("default_values") if isinstance(config.get("default_values"), dict) else {}
        for field_selector, raw_value in default_values.items():
            target_field, target_issue = _resolve_custom_button_schema_field(
                fields=target_fields,
                selector=field_selector,
                reason_path=f"{reason_path}.default_values.{field_selector}",
                role="target",
            )
            if target_issue or target_field is None:
                if target_issue:
                    issues.append(target_issue)
                continue
            rule, value_issue = self._custom_button_default_value_rule(
                profile=profile,
                target_field=target_field,
                value=raw_value,
                reason_path=f"{reason_path}.default_values.{field_selector}",
            )
            if value_issue:
                issues.append(value_issue)
                continue
            rules.append(rule)

        return {
            "related_app_key": target_app_key,
            "related_app_name": target_app_name,
            "que_relation": rules,
        }, issues

    def _custom_button_default_value_rule(
        self,
        *,
        profile: str,
        target_field: dict[str, Any],
        value: Any,
        reason_path: str,
    ) -> tuple[dict[str, Any], dict[str, Any] | None]:
        field_type = str(target_field.get("type") or "")
        values = value if isinstance(value, list) else [value]
        details: list[dict[str, Any]] = []
        for raw_value in values:
            detail, issue = self._custom_button_default_value_detail(
                profile=profile,
                target_field=target_field,
                value=raw_value,
                reason_path=reason_path,
            )
            if issue:
                return {}, issue
            if detail is not None:
                details.append(detail)
        if field_type not in {
            FieldType.text.value,
            FieldType.long_text.value,
            FieldType.number.value,
            FieldType.amount.value,
            FieldType.date.value,
            FieldType.datetime.value,
            FieldType.single_select.value,
            FieldType.multi_select.value,
            FieldType.boolean.value,
            FieldType.member.value,
            FieldType.department.value,
            FieldType.phone.value,
            FieldType.email.value,
            FieldType.relation.value,
        }:
            return {}, _custom_button_default_value_issue(
                target_field=target_field,
                reason_path=reason_path,
                value=value,
                message="this field type cannot be encoded as a custom button static default value",
            )
        return _custom_button_default_value_rule(target_field=target_field, value_details=details), None

    def _custom_button_default_value_detail(
        self,
        *,
        profile: str,
        target_field: dict[str, Any],
        value: Any,
        reason_path: str,
    ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
        field_type = str(target_field.get("type") or "")
        if value is None:
            return None, None
        if field_type in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
            option = _resolve_custom_button_option_detail(target_field=target_field, value=value)
            if option is None:
                return None, _custom_button_default_value_issue(
                    target_field=target_field,
                    reason_path=reason_path,
                    value=value,
                    message="default value must match an existing option title or option id",
                    allowed_values={"options": list(target_field.get("options") or [])},
                )
            return option, None
        if field_type == FieldType.member.value:
            member = self._resolve_custom_button_member_default(profile=profile, value=value)
            if member is None:
                return None, _custom_button_default_value_issue(
                    target_field=target_field,
                    reason_path=reason_path,
                    value=value,
                    message="member default value must resolve to exactly one member or pass {'id': uid, 'value': name}",
                    next_action="retry with an explicit member uid object",
                )
            return member, None
        if field_type == FieldType.department.value:
            department = self._resolve_custom_button_department_default(profile=profile, value=value)
            if department is None:
                return None, _custom_button_default_value_issue(
                    target_field=target_field,
                    reason_path=reason_path,
                    value=value,
                    message="department default value must resolve to exactly one department or pass {'id': deptId, 'value': name}",
                    next_action="retry with an explicit department id object",
                )
            return department, None
        scalar = _stringify_custom_button_default_value(value)
        return {"value": scalar}, None

    def _resolve_custom_button_member_default(self, *, profile: str, value: Any) -> dict[str, Any] | None:
        explicit_id = _coerce_positive_int(value.get("id", value.get("uid", value.get("member_id"))) if isinstance(value, dict) else value)
        if explicit_id is not None:
            return {"id": explicit_id, "value": str(value.get("value", value.get("name", explicit_id)) if isinstance(value, dict) else explicit_id)}
        keyword = str(value.get("value", value.get("name", value.get("email", ""))) if isinstance(value, dict) else value or "").strip()
        if not keyword:
            return None
        result = self.member_search(profile=profile, query=keyword, page_num=1, page_size=20)
        if result.get("status") != "success":
            return None
        items = [
            item
            for item in result.get("items") or []
            if isinstance(item, dict)
            and (
                str(item.get("name") or "").strip() == keyword
                or str(item.get("email") or "").strip() == keyword
            )
        ]
        if len(items) != 1:
            return None
        return {"id": items[0].get("uid"), "value": items[0].get("name") or items[0].get("email") or str(items[0].get("uid"))}

    def _resolve_custom_button_department_default(self, *, profile: str, value: Any) -> dict[str, Any] | None:
        explicit_id = _coerce_positive_int(value.get("id", value.get("deptId", value.get("dept_id"))) if isinstance(value, dict) else value)
        if explicit_id is not None:
            return {"id": explicit_id, "value": str(value.get("value", value.get("name", value.get("deptName", explicit_id))) if isinstance(value, dict) else explicit_id)}
        keyword = str(value.get("value", value.get("name", value.get("deptName", ""))) if isinstance(value, dict) else value or "").strip()
        if not keyword:
            return None
        resolved = self._resolve_department_references(profile=profile, dept_ids=[], dept_names=[keyword])
        if resolved.get("issues"):
            return None
        entries = resolved.get("department_entries") or []
        if len(entries) != 1:
            return None
        return {"id": entries[0].get("deptId"), "value": entries[0].get("deptName") or str(entries[0].get("deptId"))}

    def _apply_custom_button_view_configs(
        self,
        *,
        profile: str,
        app_key: str,
        view_configs: list[CustomButtonViewConfigPatch],
        client_key_map: dict[str, int],
        existing_buttons: list[dict[str, Any]],
        readback_buttons: list[dict[str, Any]],
        created_ids: list[int],
        updated_ids: list[int],
        removed_ids: list[int],
    ) -> dict[str, Any]:
        results: list[dict[str, Any]] = []
        failed: list[dict[str, Any]] = []
        warnings: list[dict[str, Any]] = []
        if not view_configs:
            return {"verified": True, "write_executed": False, "write_succeeded": False, "view_configs": results, "failed": failed, "warnings": warnings}
        try:
            schema, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
            parsed_schema = _parse_schema(schema)
            current_fields_by_name = {
                str(field.get("name") or ""): field
                for field in parsed_schema.get("fields") or []
                if isinstance(field, dict) and str(field.get("name") or "")
            }
            existing_views, _views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=False)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            failed.append(
                {
                    "operation": "view_config",
                    "status": "failed",
                    "error_code": "CUSTOM_BUTTON_VIEW_CONFIG_READ_FAILED",
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
            )
            return {"verified": False, "write_executed": False, "write_succeeded": False, "view_configs": results, "failed": failed, "warnings": warnings}

        existing_views_by_key = {
            _extract_view_key(view): view
            for view in (existing_views if isinstance(existing_views, list) else [])
            if isinstance(view, dict) and _extract_view_key(view)
        }
        view_keys = set(existing_views_by_key)
        button_inventory: dict[int, dict[str, Any]] = {}
        for item in [*existing_buttons, *readback_buttons]:
            if not isinstance(item, dict):
                continue
            button_id = _coerce_positive_int(item.get("button_id"))
            if button_id is not None:
                button_inventory[button_id] = item
        valid_custom_button_ids = (set(button_inventory) | set(created_ids) | set(updated_ids)) - set(removed_ids)
        allow_unverified_numeric_button_ids = not bool(valid_custom_button_ids)
        write_executed = False
        write_succeeded = False
        all_verified = True

        for config_index, config in enumerate(view_configs):
            view_key = str(config.view_key or "").strip()
            if not view_key or view_key not in view_keys:
                issue = {
                    "index": config_index,
                    "operation": "view_config",
                    "status": "failed",
                    "error_code": "UNKNOWN_VIEW",
                    "view_key": view_key,
                    "message": "view_key does not exist on this app",
                    "next_action": "call app_get and use views[].view_key",
                }
                failed.append(issue)
                results.append(issue)
                all_verified = False
                continue
            try:
                current_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
                current_config = current_response.get("result") if isinstance(current_response.get("result"), dict) else {}
                view_name = _extract_view_name(current_config) or _extract_view_name(existing_views_by_key.get(view_key) or {}) or view_key
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                issue = {
                    "index": config_index,
                    "operation": "view_config",
                    "status": "failed",
                    "error_code": "VIEW_CONFIG_READ_FAILED",
                    "view_key": view_key,
                    "view_name": _extract_view_name(existing_views_by_key.get(view_key) or {}) or view_key,
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
                failed.append(issue)
                results.append(issue)
                all_verified = False
                continue
            existing_dtos = _extract_existing_view_button_dtos(current_config)
            explicit_buttons = "buttons" in getattr(config, "model_fields_set", set())
            new_dtos: list[dict[str, Any]] = []
            config_issues: list[dict[str, Any]] = []
            for button_index, binding in enumerate(config.buttons):
                button_id, ref_issue = _resolve_custom_button_view_button_ref(
                    button_ref=binding.button_ref,
                    client_key_map=client_key_map,
                    button_inventory=button_inventory,
                    valid_custom_button_ids=valid_custom_button_ids,
                    reason_path=f"view_configs[{config_index}].buttons[{button_index}].button_ref",
                    allow_unverified_numeric_id=allow_unverified_numeric_button_ids,
                )
                if ref_issue:
                    config_issues.append(ref_issue)
                    continue
                if button_id is None:
                    continue
                view_binding = _custom_button_view_binding_to_view_button_patch(binding=binding, button_id=button_id)
                dto, binding_issues = _serialize_view_button_binding(
                    binding=view_binding,
                    current_fields_by_name=current_fields_by_name,
                    valid_custom_button_ids=valid_custom_button_ids,
                    allow_unverified_custom_button_id=allow_unverified_numeric_button_ids,
                )
                if binding_issues:
                    config_issues.extend(binding_issues)
                    continue
                new_dtos.append(dto)
            if config_issues:
                issue = {
                    "index": config_index,
                    "operation": "view_config",
                    "status": "failed",
                    "error_code": "CUSTOM_BUTTON_VIEW_CONFIG_BLOCKED",
                    "view_key": view_key,
                    "issues": config_issues,
                    "message": "view button config references invalid buttons or fields; no view write was executed for this view",
                }
                failed.append(issue)
                results.append(issue)
                all_verified = False
                continue
            replace_existing = str(config.mode or "").strip().lower() == "replace" or (explicit_buttons and not config.buttons)
            merged_dtos = (
                [deepcopy(item) for item in new_dtos]
                if replace_existing
                else _merge_custom_button_view_button_dtos(existing_dtos=existing_dtos, new_dtos=new_dtos)
            )
            payload = _build_view_buttons_only_update_payload(current_config, button_config_dtos=merged_dtos)
            effective_merged_dtos = merged_dtos
            unsupported_list_issue: dict[str, Any] | None = None
            try:
                write_executed = True
                self.views.view_update(profile=profile, viewgraph_key=view_key, payload=payload)
                write_succeeded = True
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                has_requested_list_buttons = any(
                    _normalize_view_button_config_type(item.get("configType")) == "INSIDE"
                    for item in new_dtos
                    if isinstance(item, dict)
                )
                fallback_dtos = [
                    item
                    for item in merged_dtos
                    if _normalize_view_button_config_type(item.get("configType")) != "INSIDE"
                ]
                fallback_new_dtos = [
                    item
                    for item in new_dtos
                    if _normalize_view_button_config_type(item.get("configType")) != "INSIDE"
                ]
                if has_requested_list_buttons and fallback_new_dtos and fallback_dtos != merged_dtos:
                    fallback_payload = _build_view_buttons_only_update_payload(current_config, button_config_dtos=fallback_dtos)
                    try:
                        self.views.view_update(profile=profile, viewgraph_key=view_key, payload=fallback_payload)
                        write_succeeded = True
                        effective_merged_dtos = fallback_dtos
                        unsupported_list_issue = {
                            "index": config_index,
                            "operation": "view_config",
                            "status": "failed",
                            "error_code": "INSIDE_BUTTON_BACKEND_UNSUPPORTED",
                            "view_key": view_key,
                            "view_name": view_name,
                            "message": "backend rejected inside/list-button placement; header/detail placements were retried without inside buttons",
                            "backend_message": api_error.message,
                            "transport_error": _transport_error_payload(api_error),
                            "next_action": "use placement=header/detail for now, or verify the backend inside-button payload separately",
                        }
                        failed.append(unsupported_list_issue)
                    except (QingflowApiError, RuntimeError) as fallback_error:
                        fallback_api_error = _coerce_api_error(fallback_error)
                        issue = {
                            "index": config_index,
                            "operation": "view_config",
                            "status": "failed",
                        "error_code": "VIEW_BUTTON_CONFIG_WRITE_FAILED",
                        "view_key": view_key,
                        "view_name": view_name,
                        "message": fallback_api_error.message,
                            "transport_error": _transport_error_payload(fallback_api_error),
                            "initial_error": _transport_error_payload(api_error),
                        }
                        failed.append(issue)
                        results.append(issue)
                        all_verified = False
                        continue
                elif has_requested_list_buttons:
                    issue = {
                        "index": config_index,
                        "operation": "view_config",
                        "status": "failed",
                        "error_code": "INSIDE_BUTTON_BACKEND_UNSUPPORTED",
                        "view_key": view_key,
                        "view_name": view_name,
                        "message": "backend rejected inside/list-button placement",
                        "backend_message": api_error.message,
                        "transport_error": _transport_error_payload(api_error),
                        "next_action": "use placement=header/detail for now, or verify the backend inside-button payload separately",
                    }
                    failed.append(issue)
                    results.append(issue)
                    all_verified = False
                    continue
                else:
                    issue = {
                        "index": config_index,
                        "operation": "view_config",
                        "status": "failed",
                        "error_code": "VIEW_BUTTON_CONFIG_WRITE_FAILED",
                        "view_key": view_key,
                        "view_name": view_name,
                        "message": api_error.message,
                        "transport_error": _transport_error_payload(api_error),
                    }
                    failed.append(issue)
                    results.append(issue)
                    all_verified = False
                    continue
            try:
                expected_summary = _normalize_expected_view_buttons_for_compare(
                    effective_merged_dtos,
                    custom_button_details_by_id=button_inventory,
                )
                actual_summary: list[dict[str, Any]] = []
                comparison: dict[str, Any] = {"verified": False, "custom_button_readback_pending": False}
                readback_retry_count = 0
                while True:
                    verify_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
                    verify_config = verify_response.get("result") if isinstance(verify_response.get("result"), dict) else {}
                    actual_summary = _normalize_view_buttons_for_compare(verify_config)
                    comparison = _compare_view_button_summaries(
                        expected=expected_summary,
                        actual=actual_summary,
                        pending_custom_button_ids=set(created_ids),
                    )
                    if not comparison.get("custom_button_readback_pending") or readback_retry_count >= 1:
                        break
                    readback_retry_count += 1
                    time.sleep(CUSTOM_BUTTON_VIEW_CONFIG_READBACK_RETRY_SECONDS)
                pending_custom_button_readback = bool(comparison.get("custom_button_readback_pending"))
                accepted_with_delayed_custom_button_readback = (
                    pending_custom_button_readback
                    and write_succeeded
                    and unsupported_list_issue is None
                )
                verified = (
                    bool(comparison.get("verified")) or accepted_with_delayed_custom_button_readback
                ) and unsupported_list_issue is None
                if not verified:
                    all_verified = False
                results.append(
                    {
                        "index": config_index,
                        "operation": "view_config",
                        "status": "success" if verified else ("partial_success" if unsupported_list_issue is not None else "unverified"),
                        "view_key": view_key,
                        "view_name": view_name,
                        "mode": "replace" if replace_existing else "merge",
                        "buttons_configured": len(new_dtos),
                        "view_buttons_verified": verified,
                        "supported_buttons_verified": bool(comparison.get("verified")) if unsupported_list_issue is not None else None,
                        "unsupported_placements": ["list"] if unsupported_list_issue is not None else [],
                        "custom_button_readback_pending": pending_custom_button_readback,
                        "verified_by_write_ack": accepted_with_delayed_custom_button_readback,
                        "readback_retry_count": readback_retry_count,
                        "expected_buttons": expected_summary,
                        "actual_buttons": actual_summary,
                    }
                )
                if pending_custom_button_readback and not accepted_with_delayed_custom_button_readback:
                    warnings.append(_warning("VIEW_CUSTOM_BUTTON_READBACK_PENDING", "view config write landed but custom button readback is delayed"))
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                all_verified = False
                warnings.append(_warning("VIEW_BUTTON_CONFIG_READBACK_FAILED", api_error.message))
                results.append(
                    {
                        "index": config_index,
                        "operation": "view_config",
                        "status": "unverified",
                        "view_key": view_key,
                        "view_name": view_name,
                        "mode": "replace" if replace_existing else "merge",
                        "buttons_configured": len(new_dtos),
                        "view_buttons_verified": None,
                    }
                )
        return {
            "verified": all_verified,
            "write_executed": write_executed,
            "write_succeeded": write_succeeded,
            "view_configs": results,
            "failed": failed,
            "warnings": warnings,
        }

    def portal_get(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
        try:
            result = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PORTAL_GET_FAILED",
                api_error,
                normalized_args={"dash_key": dash_key, "being_draft": being_draft},
                details={"dash_key": dash_key, "being_draft": being_draft},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key, "being_draft": being_draft}},
            )
        dash_icon = str(result.get("dashIcon") or "").strip() or None
        response = PortalGetResponse(
            dash_key=dash_key,
            being_draft=being_draft,
            dash_name=str(result.get("dashName") or "").strip() or None,
            package_tag_ids=[
                tag_id
                for tag_id in (
                    _coerce_positive_int((item or {}).get("tagId"))
                    for item in (result.get("tags") or [])
                    if isinstance(item, dict)
                )
                if tag_id is not None
            ],
            dash_icon=dash_icon,
            icon_config=workspace_icon_config(dash_icon),
            hide_copyright=bool(result.get("hideCopyright")) if "hideCopyright" in result else None,
            visibility=_public_visibility_from_member_auth(result.get("auth")),
            auth=deepcopy(result.get("auth")) if isinstance(result.get("auth"), dict) else {},
            config=deepcopy(result.get("config")) if isinstance(result.get("config"), dict) else {},
            dash_global_config=deepcopy(result.get("dashGlobalConfig")) if isinstance(result.get("dashGlobalConfig"), dict) else {},
            component_count=len(result.get("components") or []) if isinstance(result.get("components"), list) else 0,
            components=_normalize_portal_components(result.get("components")),
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read portal detail",
            "normalized_args": {"dash_key": dash_key, "being_draft": being_draft},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [],
            "verification": {"portal_exists": True, "being_draft": being_draft},
            "verified": True,
            **response.model_dump(mode="json"),
        }

    def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
        try:
            result = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PORTAL_READ_FAILED",
                api_error,
                normalized_args={"dash_key": dash_key, "being_draft": being_draft},
                details={"dash_key": dash_key, "being_draft": being_draft},
                suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key, "being_draft": being_draft}},
            )
        dash_icon = str(result.get("dashIcon") or "").strip() or None
        response = PortalReadSummaryResponse(
            dash_key=dash_key,
            being_draft=being_draft,
            dash_name=str(result.get("dashName") or "").strip() or None,
            package_tag_ids=[
                tag_id
                for tag_id in (
                    _coerce_positive_int((item or {}).get("tagId"))
                    for item in (result.get("tags") or [])
                    if isinstance(item, dict)
                )
                if tag_id is not None
            ],
            dash_icon=dash_icon,
            icon_config=workspace_icon_config(dash_icon),
            hide_copyright=bool(result.get("hideCopyright")) if "hideCopyright" in result else None,
            config_keys=sorted(str(key) for key in (result.get("config") or {}).keys()) if isinstance(result.get("config"), dict) else [],
            dash_global_config_keys=sorted(str(key) for key in (result.get("dashGlobalConfig") or {}).keys()) if isinstance(result.get("dashGlobalConfig"), dict) else [],
            section_count=len(result.get("components") or []) if isinstance(result.get("components"), list) else 0,
            sections=_summarize_portal_sections(result.get("components")),
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read portal summary",
            "normalized_args": {"dash_key": dash_key, "being_draft": being_draft},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [],
            "verification": {"portal_exists": True, "being_draft": being_draft},
            "verified": True,
            **response.model_dump(mode="json"),
        }

    def view_get(self, *, profile: str, view_key: str) -> JSONObject:
        try:
            config = self.views.view_get_config(profile=profile, viewgraph_key=view_key).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "VIEW_GET_FAILED",
                api_error,
                normalized_args={"view_key": view_key},
                details={"view_key": view_key},
                suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": view_key}},
            )

        warnings: list[dict[str, Any]] = []
        readback_errors: list[JSONObject] = []
        verification = {
            "view_exists": True,
            "base_info_verified": True,
            "questions_verified": True,
            "associations_verified": True,
            "associated_resources_verified": True,
        }

        base_info: dict[str, Any] = {}
        try:
            base_info_response = self.views.view_get_base_info(profile=profile, viewgraph_key=view_key, passcode=None)
            base_info_payload = base_info_response.get("result") or {}
            if isinstance(base_info_payload, dict):
                base_info = deepcopy(base_info_payload)
            base_info_verification = (
                base_info_response.get("verification")
                if isinstance(base_info_response.get("verification"), dict)
                else {}
            )
            if base_info_verification.get("base_info_verified") is False:
                verification["base_info_verified"] = False
                for warning in base_info_response.get("warnings") or []:
                    if not isinstance(warning, dict):
                        continue
                    warnings.append(deepcopy(warning))
                    readback_errors.append(
                        {
                            "resource": "view_base_info",
                            "phase": "view_get",
                            "view_key": view_key,
                            "transport_error": {
                                "http_status": warning.get("http_status"),
                                "backend_code": warning.get("backend_code"),
                                "category": warning.get("category"),
                                "request_id": warning.get("request_id"),
                            },
                        }
                    )
        except (QingflowApiError, RuntimeError) as error:
            verification["base_info_verified"] = False
            api_error = _coerce_api_error(error)
            if not _is_optional_builder_lookup_error(api_error):
                return _failed_from_api_error(
                    "VIEW_GET_FAILED",
                    api_error,
                    normalized_args={"view_key": view_key},
                    details={"view_key": view_key, "resource": "view_base_info"},
                    suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": view_key}},
                )
            readback_errors.append(
                {
                    "resource": "view_base_info",
                    "phase": "view_get",
                    "view_key": view_key,
                    "transport_error": _transport_error_payload(api_error),
                }
            )
            warnings.append(_warning("VIEW_BASE_INFO_UNAVAILABLE", "view base info readback is unavailable", **_transport_error_payload(api_error)))

        questions: list[dict[str, Any]] = []
        try:
            questions_payload = self.views.view_list_questions(profile=profile, viewgraph_key=view_key).get("result") or []
            if isinstance(questions_payload, list):
                questions = [deepcopy(item) for item in questions_payload if isinstance(item, dict)]
        except (QingflowApiError, RuntimeError) as error:
            verification["questions_verified"] = False
            api_error = _coerce_api_error(error)
            if not _is_optional_builder_lookup_error(api_error):
                return _failed_from_api_error(
                    "VIEW_GET_FAILED",
                    api_error,
                    normalized_args={"view_key": view_key},
                    details={"view_key": view_key, "resource": "view_questions"},
                    suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": view_key}},
                )
            readback_errors.append(
                {
                    "resource": "view_questions",
                    "phase": "view_get",
                    "view_key": view_key,
                    "transport_error": _transport_error_payload(api_error),
                }
            )
            warnings.append(_warning("VIEW_QUESTIONS_UNAVAILABLE", "view question list readback is unavailable", **_transport_error_payload(api_error)))

        associations: list[dict[str, Any]] = []
        try:
            associations_payload = self.views.view_list_associations(profile=profile, viewgraph_key=view_key).get("result") or []
            if isinstance(associations_payload, list):
                associations = [deepcopy(item) for item in associations_payload if isinstance(item, dict)]
        except (QingflowApiError, RuntimeError) as error:
            verification["associations_verified"] = False
            api_error = _coerce_api_error(error)
            if not _is_optional_builder_lookup_error(api_error):
                return _failed_from_api_error(
                    "VIEW_GET_FAILED",
                    api_error,
                    normalized_args={"view_key": view_key},
                    details={"view_key": view_key, "resource": "view_associations"},
                    suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": view_key}},
                )
            readback_errors.append(
                {
                    "resource": "view_associations",
                    "phase": "view_get",
                    "view_key": view_key,
                    "transport_error": _transport_error_payload(api_error),
                }
            )
            warnings.append(_warning("VIEW_ASSOCIATIONS_UNAVAILABLE", "view association list readback is unavailable", **_transport_error_payload(api_error)))

        app_key = str(_first_present(config, "appKey", "formKey") or _first_present(base_info, "appKey", "formKey") or "").strip()
        associated_resources: list[dict[str, Any]] = []
        if app_key:
            try:
                associated_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
            except (QingflowApiError, RuntimeError) as error:
                verification["associated_resources_verified"] = False
                api_error = _coerce_api_error(error)
                if not _is_optional_builder_lookup_error(api_error):
                    return _failed_from_api_error(
                        "VIEW_GET_FAILED",
                        api_error,
                        normalized_args={"view_key": view_key},
                        details={"view_key": view_key, "app_key": app_key, "resource": "associated_resources"},
                        suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": view_key}},
                    )
                readback_errors.append(
                    {
                        "resource": "associated_resources",
                        "phase": "view_get",
                        "view_key": view_key,
                        "app_key": app_key,
                        "transport_error": _transport_error_payload(api_error),
                    }
                )
                warnings.append(_warning("VIEW_ASSOCIATED_RESOURCES_UNAVAILABLE", "view associated resource pool readback is unavailable", **_transport_error_payload(api_error)))
        associated_resources_config = _extract_view_associated_resources_config(
            config if isinstance(config, dict) else {},
            available_resources=associated_resources,
        )
        buttons_config = _extract_view_buttons_config(config if isinstance(config, dict) else {})

        response = ViewGetResponse(
            view_key=view_key,
            base_info=base_info,
            visibility=_public_visibility_from_member_auth(config.get("auth") or base_info.get("auth")),
            config=deepcopy(config) if isinstance(config, dict) else {},
            questions=questions,
            associations=associations,
            buttons_config=buttons_config,
            associated_resources_config=associated_resources_config,
        )
        question_entries = _extract_view_question_entries(config.get("viewgraphQuestions"))
        canonical_question_entries = _extract_view_question_entries(questions)
        question_entries_by_id = {
            field_id: entry
            for entry in [*question_entries, *canonical_question_entries]
            if (field_id := _coerce_nonnegative_int(entry.get("field_id"))) is not None
        }
        query_conditions = _extract_view_query_conditions_config(config, question_entries_by_id=question_entries_by_id)
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read view detail",
            "normalized_args": {"view_key": view_key},
            "missing_fields": [],
            "allowed_values": {},
            "details": {"readback_errors": readback_errors} if readback_errors else {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": verification,
            "verified": all(bool(value) for value in verification.values()),
            "query_conditions": query_conditions,
            "buttons_config": buttons_config,
            "associated_resources_config": associated_resources_config,
            **response.model_dump(mode="json"),
        }

    def chart_get(
        self,
        *,
        profile: str,
        chart_id: str,
    ) -> JSONObject:
        warnings: list[dict[str, Any]] = []
        verification = {
            "chart_exists": True,
            "chart_config_loaded": True,
        }
        try:
            base = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "CHART_GET_FAILED",
                api_error,
                normalized_args={"chart_id": chart_id},
                details={"chart_id": chart_id},
                suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
            )

        try:
            config_response = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id)
            config = config_response.get("result") or {}
            config_warnings = config_response.get("warnings") if isinstance(config_response.get("warnings"), list) else []
            warnings.extend(item for item in config_warnings if isinstance(item, dict))
            config_verification = (
                config_response.get("verification") if isinstance(config_response.get("verification"), dict) else {}
            )
            if config_verification:
                verification.update(config_verification)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if not _is_optional_builder_lookup_error(api_error) and backend_code_int(api_error) != 81007:
                return _failed_from_api_error(
                    "CHART_GET_FAILED",
                    api_error,
                    normalized_args={"chart_id": chart_id},
                    details={"chart_id": chart_id, "resource": "chart_config"},
                    suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
                )
            fallback_config: dict[str, Any] | None = None
            fallback_api_error: QingflowApiError | None = None
            try:
                data_fallback = self.charts.qingbi_report_get_data(profile=profile, chart_id=chart_id, payload={}).get("result") or {}
                config_from_data = data_fallback.get("config") if isinstance(data_fallback, dict) else None
                if isinstance(config_from_data, dict):
                    fallback_config = deepcopy(config_from_data)
            except (QingflowApiError, RuntimeError) as fallback_error:
                fallback_api_error = _coerce_api_error(fallback_error)
                fallback_config = None
            if isinstance(fallback_config, dict):
                config = fallback_config
                warnings.append(
                    _warning(
                        "CHART_CONFIG_FALLBACK_FROM_DATA",
                        "chart config endpoint is unavailable for this chart id; using config embedded in chart data instead",
                    )
                )
            else:
                details: JSONObject = {
                    "chart_id": chart_id,
                    "config_error": _transport_error_payload(api_error),
                }
                if fallback_api_error is not None:
                    details["data_fallback_error"] = _transport_error_payload(fallback_api_error)
                return _failed_from_api_error(
                    "CHART_GET_FAILED",
                    api_error,
                    normalized_args={"chart_id": chart_id},
                    details=details,
                    suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
                )

        field_name_by_id: dict[str, str] = {}
        data_source = config.get("dataSource") if isinstance(config.get("dataSource"), dict) else {}
        data_source_app_key = str(data_source.get("dataSourceId") or config.get("dataSourceId") or "").strip()
        if data_source_app_key:
            field_name_by_id, field_name_error = self._chart_filter_field_names_by_id(profile=profile, app_key=data_source_app_key)
            if field_name_error:
                warnings.append(
                    _warning(
                        "CHART_FILTER_FIELD_NAMES_UNRESOLVED",
                        "chart config was read, but form fields could not be loaded to resolve filter field names",
                        **field_name_error,
                    )
                )
        response = ChartGetResponse(
            chart_id=chart_id,
            base=deepcopy(base) if isinstance(base, dict) else {},
            visibility=_public_visibility_from_chart_visible_auth(base.get("visibleAuth")),
            filters=_public_chart_filter_groups_from_qingbi_config(config, field_name_by_id=field_name_by_id) if isinstance(config, dict) else [],
            group_by=_public_chart_group_by_from_qingbi_config(config) if isinstance(config, dict) else [],
            metrics=_public_chart_metrics_from_qingbi_config(config) if isinstance(config, dict) else [],
            config=deepcopy(config) if isinstance(config, dict) else {},
        )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read chart config detail",
            "normalized_args": {"chart_id": chart_id},
            "missing_fields": [],
            "allowed_values": {},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": verification,
            "verified": True,
            **response.model_dump(mode="json"),
        }

    def app_schema_plan(self, *, profile: str, request: SchemaPlanRequest) -> JSONObject:
        normalized_args = request.model_dump(mode="json")
        target = self._preview_target_app(
            profile=profile,
            app_key=request.app_key,
            app_name=request.app_name,
            package_tag_id=request.package_tag_id,
        )
        if target.get("status") == "failed":
            target.setdefault("normalized_args", normalized_args)
            return target
        current_fields: list[dict[str, Any]] = []
        if not bool(target.get("would_create")):
            fields_result = self.app_read_fields(profile=profile, app_key=str(target["app_key"]))
            if fields_result.get("status") == "failed":
                fields_result.setdefault("normalized_args", normalized_args)
                return fields_result
            current_fields = fields_result.get("fields", [])
        current_by_name = {str(field.get("name") or ""): field for field in current_fields}
        blocking_issues: list[dict[str, Any]] = []
        preview_added: list[str] = []
        preview_updated: list[str] = []
        preview_removed: list[str] = []
        for patch in request.add_fields:
            if patch.name in current_by_name:
                blocking_issues.append({"error_code": "DUPLICATE_FIELD", "field_name": patch.name})
            else:
                preview_added.append(patch.name)
        for patch in request.update_fields:
            selector_name = patch.selector.name or ""
            if selector_name and selector_name not in current_by_name:
                blocking_issues.append(
                    {
                        "error_code": "FIELD_NOT_FOUND",
                        "selector": patch.selector.model_dump(mode="json"),
                    }
                )
            preview_updated.append(patch.set.name or selector_name or patch.selector.field_id or str(patch.selector.que_id or ""))
        for patch in request.remove_fields:
            if patch.name and patch.name not in current_by_name:
                blocking_issues.append(
                    {
                        "error_code": "FIELD_NOT_FOUND",
                        "selector": patch.model_dump(mode="json"),
                    }
                )
            preview_removed.append(patch.name or patch.field_id or str(patch.que_id or ""))
        return {
            "status": "failed" if blocking_issues else "success",
            "error_code": "SCHEMA_PLAN_BLOCKED" if blocking_issues else None,
            "recoverable": bool(blocking_issues),
            "message": "schema plan has blocking issues" if blocking_issues else "planned schema patch",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"field_types": [item.value for item in PublicFieldType]},
            "details": {},
            "request_id": None,
            "blocking_issues": blocking_issues,
            "field_diff_preview": {
                "added": preview_added,
                "updated": preview_updated,
                "removed": preview_removed,
            },
            "suggested_next_call": None
            if blocking_issues
            else {
                "tool_name": "app_schema_apply",
                "arguments": normalized_args,
            },
            "noop": False,
            "verification": {"target_app_resolved": not bool(target.get("would_create"))},
        }

    def app_layout_plan(self, *, profile: str, request: LayoutPlanRequest) -> JSONObject:
        read_fields = self.app_read_fields(profile=profile, app_key=request.app_key)
        if read_fields.get("status") == "failed":
            return read_fields
        current_fields = [field for field in read_fields.get("fields", []) if isinstance(field, dict)]
        current_names = [str(field.get("name") or "") for field in current_fields if field.get("name")]
        current_layout = self.app_read_layout_summary(profile=profile, app_key=request.app_key)
        if current_layout.get("status") == "failed":
            return current_layout
        requested_sections = [section.model_dump(mode="json", exclude_none=True) for section in request.sections]
        if request.preset is not None:
            requested_sections = _build_layout_preset_sections(preset=request.preset, field_names=current_names)
        else:
            requested_sections, missing_selectors = _resolve_layout_sections_to_names(requested_sections, current_fields)
            if missing_selectors:
                return _failed(
                    "UNKNOWN_LAYOUT_FIELD",
                    "layout references unknown field selectors",
                    normalized_args={
                        "app_key": request.app_key,
                        "mode": request.mode.value,
                        "sections": requested_sections,
                    },
                    details={"unknown_selectors": missing_selectors},
                    missing_fields=[str(item) for item in missing_selectors],
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": request.app_key}},
                )
        merged = _merge_layout(
            current_layout={
                "root_rows": [],
                "sections": current_layout.get("sections", []),
            },
            requested_sections=requested_sections,
            all_field_names=current_names,
        )
        missing_fields = []
        if request.mode == LayoutApplyMode.replace:
            seen = {name for section in requested_sections for row in section.get("rows", []) for name in row}
            missing_fields = sorted(name for name in current_names if name not in seen)
        normalized_args = {
            "app_key": request.app_key,
            "mode": request.mode.value,
            "sections": requested_sections,
        }
        if missing_fields:
            return _failed(
                "INCOMPLETE_LAYOUT",
                "layout must reference every current field exactly once in replace mode",
                normalized_args=normalized_args,
                details={
                    "layout_preview": {"sections": requested_sections},
                    "auto_fill_preview": merged["layout"],
                },
                missing_fields=missing_fields,
                suggested_next_call={
                    "tool_name": "app_layout_apply",
                    "arguments": {
                        "profile": profile,
                        "app_key": request.app_key,
                        "mode": "merge",
                        "sections": requested_sections,
                    },
                },
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "planned layout patch",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"presets": [preset.value for preset in LayoutPreset]},
            "details": {},
            "request_id": None,
            "layout_preview": merged["layout"] if request.mode == LayoutApplyMode.merge else {"root_rows": [], "sections": requested_sections},
            "auto_fill_preview": merged["layout"],
            "suggested_next_call": {"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
            "noop": False,
            "verification": {"field_count": len(current_names)},
        }

    def app_flow_plan(self, *, profile: str, request: FlowPlanRequest) -> JSONObject:
        nodes = [node.model_dump(mode="json") for node in request.nodes]
        transitions = [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions]
        if request.preset is not None:
            preset_nodes, preset_transitions = _build_flow_preset(request.preset)
            nodes, transitions = _merge_flow_graph(
                base_nodes=preset_nodes,
                base_transitions=preset_transitions,
                override_nodes=nodes,
                override_transitions=transitions,
            )
        fields_result = self.app_read_fields(profile=profile, app_key=request.app_key)
        if fields_result.get("status") == "failed":
            return fields_result
        current_fields = fields_result.get("fields", [])
        normalized_nodes, resolution_issues = self._normalize_flow_nodes(profile=profile, current_fields=current_fields, nodes=nodes)
        public_nodes = self._canonicalize_flow_nodes_for_public_output(normalized_nodes)
        unsupported_nodes = self._unsupported_public_flow_nodes(nodes=public_nodes)
        if unsupported_nodes:
            return _failed(
                "FLOW_NODE_TYPE_UNSUPPORTED",
                "public workflow writes use app_flow_get_schema/app_flow_get and app_flow_apply with WorkflowSpec spec; legacy nodes/transitions input cannot express this graph.",
                normalized_args={
                    "app_key": request.app_key,
                    "mode": str(request.mode or "replace"),
                    "preset": request.preset.value if request.preset else None,
                    "nodes": public_nodes,
                    "transitions": transitions,
                },
                details={
                    "unsupported_nodes": unsupported_nodes,
                    "supported_node_types": sorted(STABLE_PUBLIC_FLOW_NODE_TYPES),
                    "disabled_node_types": sorted(DISABLED_PUBLIC_FLOW_NODE_TYPES),
                },
                suggested_next_call={"tool_name": "builder_tool_contract", "arguments": {"tool_name": "app_flow_apply"}},
            )
        if resolution_issues:
            first_issue = resolution_issues[0]
            suggested_call = None
            if first_issue.get("kind", "").startswith("role"):
                suggested_call = {"tool_name": "role_search", "arguments": {"profile": profile, "keyword": first_issue.get("value") or ""}}
            elif first_issue.get("kind", "").startswith("member"):
                suggested_call = {"tool_name": "member_search", "arguments": {"profile": profile, "query": first_issue.get("value") or ""}}
            elif first_issue.get("kind") in {"editable_fields", "condition_fields"}:
                suggested_call = {"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": request.app_key}}
            return _failed(
                first_issue.get("error_code") or "FLOW_ASSIGNEE_UNRESOLVED",
                "workflow contains unresolved assignees or field permissions",
                normalized_args={
                    "app_key": request.app_key,
                    "mode": str(request.mode or "replace"),
                    "preset": request.preset.value if request.preset else None,
                    "nodes": public_nodes,
                    "transitions": transitions,
                },
                details={"issues": resolution_issues},
                suggested_next_call=suggested_call,
            )
        status_field_present = _infer_status_field_id(current_fields) is not None
        node_types = {str(node.get("type") or "") for node in normalized_nodes}
        assignee_required_nodes = [
            node.get("id")
            for node in normalized_nodes
            if str(node.get("type") or "") in {"approve", "fill", "copy"}
            and not (
                (node.get("assignees") or {}).get("role_entries")
                or (node.get("assignees") or {}).get("member_uids")
            )
        ]
        if assignee_required_nodes:
            return _failed(
                "FLOW_ASSIGNEE_REQUIRED",
                "workflow approval/fill/copy nodes must declare at least one role or member assignee",
                normalized_args={
                    "app_key": request.app_key,
                    "mode": str(request.mode or "replace"),
                    "preset": request.preset.value if request.preset else None,
                    "nodes": public_nodes,
                    "transitions": transitions,
                },
                details={"node_ids": assignee_required_nodes, "policy": "prefer role assignees; explicit members are also supported"},
                suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, "keyword": ""}},
            )
        workflow = _build_public_workflow_spec(nodes=normalized_nodes, transitions=transitions)
        if workflow.get("status") == "failed":
            workflow["normalized_args"] = {
                "app_key": request.app_key,
                "mode": str(request.mode or "replace"),
                "nodes": public_nodes,
                "transitions": transitions,
            }
            workflow["suggested_next_call"] = {
                "tool_name": "app_flow_plan",
                "arguments": {
                    "profile": profile,
                    "app_key": request.app_key,
                    "mode": "replace",
                    "nodes": public_nodes,
                    "transitions": transitions,
                },
            }
            return workflow
        if ("approve" in node_types or request.preset in {FlowPreset.basic_approval, FlowPreset.basic_fill_then_approve}) and not status_field_present:
            return _failed(
                "FLOW_DEPENDENCY_MISSING",
                "workflow requires an explicit status field",
                normalized_args={
                    "app_key": request.app_key,
                    "mode": str(request.mode or "replace"),
                    "nodes": public_nodes,
                    "transitions": transitions,
                },
                details={
                    "missing_dependencies": ["status field"],
                    "fix_hint": "Add an explicit business status select field before applying approval workflows. Do not create platform system fields such as 当前流程状态.",
                    "recommended_field_names": ["状态", "处理状态", "审批状态", "工单状态", "流程阶段"],
                    "forbidden_system_field_names": ["当前流程状态", "当前处理人", "当前处理节点", "流程标题"],
                },
                missing_fields=["status"],
                suggested_next_call={
                    "tool_name": "app_schema_apply",
                    "arguments": {
                        "profile": profile,
                        "app_key": request.app_key,
                        "add_fields": [{"name": "状态", "type": "select", "options": ["草稿", "进行中", "已完成"], "required": True}],
                        "update_fields": [],
                        "remove_fields": [],
                    },
                },
            )
        normalized_args = {
            "app_key": request.app_key,
            "mode": str(request.mode or "replace"),
            "nodes": public_nodes,
            "transitions": transitions,
        }
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "planned workflow patch",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"presets": [preset.value for preset in FlowPreset]},
            "details": {},
            "request_id": None,
            "flow_diff_preview": {"mode": "replace", "node_count": len([node for node in nodes if node.get("type") != "end"])},
            "dependency_issues": [],
            "suggested_next_call": {"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
            "noop": False,
            "verification": {"status_field_present": status_field_present},
        }

    def app_views_plan(self, *, profile: str, request: ViewsPlanRequest) -> JSONObject:
        fields_result = self.app_read_fields(profile=profile, app_key=request.app_key)
        if fields_result.get("status") == "failed":
            return fields_result
        current_fields = fields_result.get("fields", [])
        field_names = {str(field.get("name") or "") for field in current_fields}
        current_fields_by_name = {
            str(field.get("name") or ""): field
            for field in current_fields
            if isinstance(field, dict) and str(field.get("name") or "")
        }
        upsert_views = [public_view_upsert_payload(view) for view in request.upsert_views]
        patch_views = [public_view_partial_payload(patch) for patch in request.patch_views]
        if request.preset is not None:
            upsert_views = _build_views_preset(request.preset, list(field_names))
        blocking_issues: list[dict[str, Any]] = []
        for patch in upsert_views:
            raw_columns = [str(name or "").strip() for name in (patch.get("columns") or []) if str(name or "").strip()]
            columns = _filter_known_system_view_columns(raw_columns)
            if patch.get("type") in {"table", "card"} and raw_columns and not columns:
                blocking_issues.append(
                    {
                        "error_code": "VALIDATION_ERROR",
                        "view_name": patch.get("name"),
                        "message": "view columns must include at least one real app field; system columns cannot be applied directly",
                        "ignored_system_columns": [name for name in raw_columns if name in _KNOWN_SYSTEM_VIEW_COLUMNS],
                    }
                )
            missing_columns = [name for name in columns if name not in field_names]
            if missing_columns:
                blocking_issues.append({"error_code": "UNKNOWN_VIEW_FIELD", "view_name": patch.get("name"), "missing_fields": missing_columns})
            group_by = patch.get("group_by")
            if group_by and group_by not in field_names:
                blocking_issues.append({"error_code": "UNKNOWN_VIEW_FIELD", "view_name": patch.get("name"), "missing_fields": [group_by]})
            start_field = str(patch.get("start_field") or "").strip()
            end_field = str(patch.get("end_field") or "").strip()
            title_field = str(patch.get("title_field") or "").strip()
            if patch.get("type") == "gantt":
                missing_required = []
                if not start_field:
                    missing_required.append("start_field")
                if not end_field:
                    missing_required.append("end_field")
                if missing_required:
                    blocking_issues.append({"error_code": "INVALID_GANTT_CONFIG", "view_name": patch.get("name"), "missing_fields": missing_required})
                missing_gantt_fields = [name for name in (start_field, end_field, title_field) if name and name not in field_names]
                if missing_gantt_fields:
                    blocking_issues.append({"error_code": "UNKNOWN_VIEW_FIELD", "view_name": patch.get("name"), "missing_fields": missing_gantt_fields})
            translated_filters, filter_issues = _build_view_filter_groups(current_fields_by_name=current_fields_by_name, filters=patch.get("filters") or [])
            if filter_issues:
                blocking_issues.extend(
                    {
                        **issue,
                        "view_name": patch.get("name"),
                    }
                    for issue in filter_issues
                )
            if translated_filters:
                patch["filters"] = [dict(rule) for rule in (patch.get("filters") or [])]
            _, _, query_condition_issues = _build_view_query_conditions_payload(
                current_fields_by_name=current_fields_by_name,
                query_conditions=patch.get("query_conditions"),
            )
            if query_condition_issues:
                blocking_issues.extend(
                    {
                        **issue,
                        "view_name": patch.get("name"),
                    }
                    for issue in query_condition_issues
                )
        normalized_args = {
            "app_key": request.app_key,
            "upsert_views": upsert_views,
            "patch_views": patch_views,
            "remove_views": list(request.remove_views),
        }
        return {
            "status": "failed" if blocking_issues else "success",
            "error_code": "VIEWS_PLAN_BLOCKED" if blocking_issues else None,
            "recoverable": bool(blocking_issues),
            "message": "view plan has blocking issues" if blocking_issues else "planned view patch",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {
                "view_types": [member.value for member in PublicViewType],
                "presets": [preset.value for preset in ViewsPreset],
                "view.filter.operator": [member.value for member in ViewFilterOperator],
            },
            "details": {},
            "request_id": None,
            "views_diff_preview": {
                "upsert": [view.get("name") for view in upsert_views],
                "patch": [view.get("view_key") or view.get("name") for view in patch_views],
                "remove": list(request.remove_views),
            },
            "blocking_issues": blocking_issues,
            "suggested_next_call": None if blocking_issues else {"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
            "noop": False,
            "verification": {"field_count": len(field_names)},
        }

    def _expand_view_partial_patches(
        self,
        *,
        profile: str,
        app_key: str,
        schema: dict[str, Any],
        existing_by_key: dict[str, dict[str, Any]],
        existing_by_name: dict[str, list[dict[str, Any]]],
        patch_views: list[ViewPartialPatch],
    ) -> tuple[list[ViewUpsertPatch], list[dict[str, Any]], list[dict[str, Any]]]:
        parsed_schema = _parse_schema(schema)
        field_names_by_id = {
            field_id: str(field.get("name") or "").strip()
            for field in parsed_schema.get("fields") or []
            if isinstance(field, dict)
            and (field_id := _coerce_nonnegative_int(field.get("que_id") or field.get("field_id"))) is not None
            and str(field.get("name") or "").strip()
        }
        expanded: list[ViewUpsertPatch] = []
        issues: list[dict[str, Any]] = []
        results: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_views):
            view_key = str(patch.view_key or "").strip()
            name = str(patch.name or "").strip()
            matched_view: dict[str, Any] | None = None
            if view_key:
                matched_view = existing_by_key.get(view_key)
                if matched_view is None:
                    issue = {
                        "error_code": "UNKNOWN_VIEW",
                        "reason_path": f"patch_views[{index}].view_key",
                        "view_key": view_key,
                        "message": "view_key does not exist on this app",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
            else:
                matches = existing_by_name.get(name, [])
                if len(matches) != 1:
                    issue = {
                        "error_code": "AMBIGUOUS_VIEW" if matches else "UNKNOWN_VIEW",
                        "reason_path": f"patch_views[{index}].name",
                        "view_name": name,
                        "matches": [
                            {"name": _extract_view_name(view), "view_key": _extract_view_key(view), "type": _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))}
                            for view in matches
                        ],
                        "message": "patch_views[] must target a single existing view; use view_key when names are duplicated",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
                matched_view = matches[0]
                view_key = _extract_view_key(matched_view)
            try:
                config_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
                config = config_response.get("result") if isinstance(config_response.get("result"), dict) else {}
                if not isinstance(config, dict):
                    config = {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                issue = {
                    "error_code": "VIEW_PATCH_CONFIG_READ_FAILED",
                    "reason_path": f"patch_views[{index}]",
                    "view_key": view_key,
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            question_list: list[dict[str, Any]] = []
            try:
                question_response = self.views.view_list_questions(profile=profile, viewgraph_key=view_key)
                raw_questions = question_response.get("result")
                if isinstance(raw_questions, list):
                    question_list = [deepcopy(item) for item in raw_questions if isinstance(item, dict)]
            except (QingflowApiError, RuntimeError):
                question_list = []
            base_summary = {
                "name": _extract_view_name(config) or _extract_view_name(matched_view or {}) or name or view_key,
                "view_key": view_key,
                "type": _normalize_view_type_name(config.get("viewgraphType") or (matched_view or {}).get("viewgraphType") or (matched_view or {}).get("type")),
            }
            summary = _merge_view_summary_with_config(base_summary, config=config, question_list=question_list)
            current_payload = _view_upsert_payload_from_existing_view(
                config=config,
                summary=summary,
                view_key=view_key,
                field_names_by_id=field_names_by_id,
            )
            normalized_set, set_issues = _normalize_view_partial_set(patch.set, reason_path=f"patch_views[{index}].set")
            normalized_unset, unset_issues = _normalize_view_partial_unset(patch.unset, reason_path=f"patch_views[{index}].unset")
            if set_issues or unset_issues:
                patch_issues = [*set_issues, *unset_issues]
                issues.extend(patch_issues)
                results.append({"index": index, "status": "failed", "view_key": view_key, "issues": patch_issues})
                continue
            touched_keys = set(normalized_set) | set(normalized_unset)
            patch_payload = deepcopy(current_payload)
            for key, value in normalized_set.items():
                if key in {"query_conditions", "associated_resources", "visibility"} and isinstance(value, dict):
                    merged_value = deepcopy(patch_payload.get(key) if isinstance(patch_payload.get(key), dict) else {})
                    _deep_merge_public_config(merged_value, value)
                    patch_payload[key] = merged_value
                else:
                    patch_payload[key] = value
            for key in normalized_unset:
                if key == "filters":
                    patch_payload["filters"] = []
                elif key == "buttons":
                    patch_payload["buttons"] = []
                elif key == "query_conditions":
                    patch_payload["query_conditions"] = {"enabled": False, "exact": False, "hide_before_query": False, "rows": []}
                elif key == "associated_resources":
                    patch_payload["associated_resources"] = {"visible": False}
                elif key == "visibility":
                    patch_payload.pop("visibility", None)
                else:
                    issue = {
                        "error_code": "VIEW_PATCH_UNSET_NOT_SUPPORTED",
                        "reason_path": f"patch_views[{index}].unset",
                        "field": key,
                        "message": f"cannot unset {key}; use set with an explicit replacement value",
                    }
                    issues.append(issue)
            patch_payload["_partial_update"] = True
            patch_payload["_preserve_filters"] = "filters" not in touched_keys
            patch_payload["_preserve_buttons"] = "buttons" not in touched_keys
            patch_payload["_preserve_query_conditions"] = "query_conditions" not in touched_keys
            patch_payload["_preserve_associated_resources"] = "associated_resources" not in touched_keys
            try:
                expanded_patch = ViewUpsertPatch.model_validate(patch_payload)
            except Exception as error:
                issue = {
                    "error_code": "VIEW_PATCH_HYDRATION_FAILED",
                    "reason_path": f"patch_views[{index}]",
                    "view_key": view_key,
                    "message": str(error),
                    "hydrated_payload": _compact_dict({k: v for k, v in patch_payload.items() if not str(k).startswith("_")}),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            expanded.append(expanded_patch)
            results.append(
                {
                    "index": index,
                    "status": "expanded",
                    "view_key": view_key,
                    "set_paths": sorted(normalized_set),
                    "unset_paths": sorted(normalized_unset),
                    "preserved_paths": sorted(
                        key
                        for key, preserve in {
                            "filters": expanded_patch.preserve_filters,
                            "buttons": expanded_patch.preserve_buttons,
                            "query_conditions": expanded_patch.preserve_query_conditions,
                            "associated_resources": expanded_patch.preserve_associated_resources,
                        }.items()
                        if preserve
                    ),
                }
            )
        return expanded, issues, results

    def app_read(self, *, profile: str, app_key: str, include_raw: bool = False) -> JSONObject:
        state = self._load_app_state(profile=profile, app_key=app_key)
        base_result = state["base"]
        schema_result = state["schema"]
        parsed = state["parsed"]
        response: JSONObject = {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "read app state",
            "suggested_next_call": None,
            "app": {
                "app_key": app_key,
                "title": base_result.get("formTitle"),
                "app_icon": str(base_result.get("appIcon") or "").strip() or None,
                "tag_ids": _coerce_int_list(base_result.get("tagIds")),
                "publish_status": base_result.get("appPublishStatus"),
            },
            "schema": {
                "fields": parsed["fields"],
                "field_count": len(parsed["fields"]),
            },
            "form_settings": _form_settings_from_schema(schema_result, parsed["fields"]),
            "layout": parsed["layout"],
            "flow_summary": {
                "enabled": bool(state["workflow"]),
                "nodes": _summarize_workflow_nodes(state["workflow"]),
            },
            "views_summary": {
                "views": _summarize_views(state["views"]),
            },
        }
        if include_raw:
            response["raw"] = {
                "base": base_result,
                "schema": schema_result,
                "views": state["views"],
                "workflow": state["workflow"],
            }
        return response

    def app_schema_apply(
        self,
        *,
        profile: str,
        app_key: str = "",
        package_tag_id: int | None = None,
        app_name: str = "",
        icon: str | None = None,
        color: str | None = None,
        visibility: VisibilityPatch | None = None,
        publish: bool = True,
        add_fields: list[FieldPatch],
        update_fields: list[FieldUpdatePatch],
        remove_fields: list[FieldRemovePatch],
        inline_form_layout_sections: list[LayoutSectionPatch] | None = None,
    ) -> JSONObject:
        apply_started_at = time.perf_counter()
        schema_write_ms: int | None = None
        inline_layout_ms: int | None = None
        publish_ms: int | None = None
        readback_ms: int | None = None
        requested_inline_layout_sections = [
            section.model_dump(mode="json", exclude_none=True)
            for section in (inline_form_layout_sections or [])
        ]
        normalized_args = {
            "app_key": app_key,
            "package_tag_id": package_tag_id,
            "app_name": app_name,
            "icon": icon,
            "color": color,
            "visibility": visibility.model_dump(mode="json") if visibility is not None else None,
            "publish": publish,
            "add_fields": [patch.model_dump(mode="json") for patch in add_fields],
            "update_fields": [patch.model_dump(mode="json") for patch in update_fields],
            "remove_fields": [patch.model_dump(mode="json") for patch in remove_fields],
        }
        if requested_inline_layout_sections:
            normalized_args["inline_form_layout_sections"] = requested_inline_layout_sections
        try:
            desired_auth = (
                self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
                if visibility is not None
                else None
            )
        except VisibilityResolutionError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details=error.details,
                suggested_next_call=None,
            )
        permission_outcomes: list[PermissionCheckOutcome] = []
        requested_inline_layout = bool(requested_inline_layout_sections)
        requested_field_changes = bool(add_fields or update_fields or remove_fields or requested_inline_layout)

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        resolved: JSONObject
        if app_key:
            resolved = self.app_resolve(profile=profile, app_key=app_key)
        else:
            requested_app_name_for_create = str(app_name or "").strip()
            permission_tag_id = _coerce_positive_int(package_tag_id)
            if not requested_app_name_for_create:
                return _failed(
                    "APP_NAME_REQUIRED",
                    "app_name is required when creating an app without app_key",
                    normalized_args=normalized_args,
                    suggested_next_call=None,
                )
            if permission_tag_id is None:
                return _failed(
                    "PACKAGE_ID_REQUIRED",
                    "package_id is required when creating an app without app_key",
                    normalized_args=normalized_args,
                    suggested_next_call=None,
                )
            add_permission_outcome = self._guard_package_permission(
                profile=profile,
                tag_id=permission_tag_id,
                required_permission="add_app",
                normalized_args=normalized_args,
            )
            if add_permission_outcome.block is not None:
                return add_permission_outcome.block
            permission_outcomes.append(add_permission_outcome)
            resolved = self._create_target_app_shell(
                profile=profile,
                app_name=requested_app_name_for_create,
                package_tag_id=permission_tag_id,
                icon=icon,
                color=color,
                auth=desired_auth,
            )
            if resolved.get("status") == "failed":
                if not isinstance(resolved.get("normalized_args"), dict) or not resolved.get("normalized_args"):
                    resolved["normalized_args"] = normalized_args
                return finalize(resolved)
            if resolved.get("status") == "partial_success" and not str(resolved.get("app_key") or "").strip():
                if not isinstance(resolved.get("normalized_args"), dict) or not resolved.get("normalized_args"):
                    resolved["normalized_args"] = normalized_args
                return finalize(resolved)

        if resolved.get("status") == "failed":
            if not isinstance(resolved.get("normalized_args"), dict) or not resolved.get("normalized_args"):
                resolved["normalized_args"] = normalized_args
            return finalize(resolved)
        resolved_outcome = _permission_outcome_from_result(resolved)
        if resolved_outcome is not None:
            permission_outcomes.append(resolved_outcome)
        target = ResolvedApp(
            app_key=str(resolved["app_key"]),
            app_name=str(resolved["app_name"]),
            tag_ids=_coerce_int_list(resolved.get("tag_ids")),
        )
        requested_app_name = str(app_name or "").strip() or None
        requested_rename = requested_app_name if app_key else None
        requested_icon = str(icon or "").strip() or None
        requested_color = str(color or "").strip() or None
        requested_app_base_update = bool(requested_rename or requested_icon or requested_color or desired_auth is not None)
        effective_app_name = target.app_name
        if not bool(resolved.get("created")):
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=target.app_key,
                required_permission="edit_app",
                normalized_args=normalized_args,
            )
            if permission_outcome.block is not None:
                return permission_outcome.block
            permission_outcomes.append(permission_outcome)
            if requested_app_base_update:
                visual_result = self._ensure_app_base_visuals(
                    profile=profile,
                    app_key=target.app_key,
                    fallback_title=target.app_name,
                    app_name=requested_rename,
                    icon=requested_icon,
                    color=requested_color,
                    auth_override=desired_auth,
                    normalized_args=normalized_args,
                )
                if visual_result.get("status") == "failed":
                    return finalize(visual_result)
                effective_app_name = str(visual_result.get("app_name_after") or target.app_name).strip() or target.app_name
            else:
                visual_result = {
                    "status": "success",
                    "updated": False,
                    "app_icon": None,
                    "app_name_before": target.app_name,
                    "app_name_after": target.app_name,
                    "request_id": None,
                }
        else:
            visual_result = {
                "status": "success",
                "updated": False,
                "app_icon": str(resolved.get("app_icon") or "").strip() or None,
                "app_name_before": target.app_name,
                "app_name_after": target.app_name,
                "request_id": None,
            }
        if bool(resolved.get("created")) and not requested_field_changes:
            shell_readback_pending = (
                resolved.get("status") == "partial_success"
                or bool((resolved.get("verification") if isinstance(resolved.get("verification"), dict) else {}).get("readback_unavailable"))
                or str(resolved.get("next_action") or "") == "readback_before_retry"
            )
            shell_verification = {
                "fields_verified": not shell_readback_pending,
                "package_attached": None if package_tag_id is None else package_tag_id in target.tag_ids,
                "relation_field_limit_verified": True,
                "app_visuals_verified": not shell_readback_pending,
                "app_base_verified": not shell_readback_pending,
                "publish_skipped": True,
            }
            if isinstance(resolved.get("verification"), dict):
                shell_verification.update(deepcopy(resolved.get("verification") or {}))
            created_shell_response = {
                "status": "partial_success" if shell_readback_pending else "success",
                "error_code": resolved.get("error_code") if shell_readback_pending else None,
                "recoverable": bool(shell_readback_pending),
                "message": str(resolved.get("message") or "created app shell") if shell_readback_pending else "created app shell",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"field_types": [item.value for item in PublicFieldType]},
                "details": {"publish_skipped": True},
                "request_id": resolved.get("request_id"),
                "suggested_next_call": None,
                "noop": False,
                "warnings": deepcopy(resolved.get("warnings") or []),
                "verification": shell_verification,
                "app_key": target.app_key,
                "app_icon": str(resolved.get("app_icon") or visual_result.get("app_icon") or "").strip() or None,
                "app_name": str(visual_result.get("app_name_after") or target.app_name),
                "app_name_before": str(visual_result.get("app_name_before") or target.app_name),
                "app_name_after": str(visual_result.get("app_name_after") or target.app_name),
                "app_base_updated": bool(visual_result.get("updated")),
                "created": True,
                "field_diff": {"added": [], "updated": [], "removed": []},
                "verified": True,
                "write_executed": True,
                "write_succeeded": not shell_readback_pending or bool(resolved.get("write_succeeded", True)),
                "safe_to_retry": False,
                "tag_ids_after": list(target.tag_ids),
                "package_attached": None if package_tag_id is None else package_tag_id in target.tag_ids,
                "publish_requested": False,
                "published": False,
            }
            if shell_readback_pending:
                created_shell_response["write_may_have_succeeded"] = True
                created_shell_response["next_action"] = "readback_before_retry"
                created_shell_response["suggested_next_call"] = resolved.get("suggested_next_call") or {
                    "tool_name": "app_get",
                    "arguments": {"profile": profile, "app_key": target.app_key},
                }
            return finalize(created_shell_response)
        schema_readback_delayed = False
        schema_readback_delayed_error: JSONObject | None = None
        try:
            schema_result, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=target.app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if not bool(resolved.get("created")):
                return finalize(_failed_from_api_error(
                    "SCHEMA_READBACK_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    allowed_values={"field_types": [item.value for item in PublicFieldType]},
                    details=_with_state_read_blocked_details({"app_key": target.app_key}, resource="schema", error=api_error),
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": target.app_key}},
                ))
            schema_result = _empty_schema_result(effective_app_name)
            _schema_source = "synthetic_new_app"
            schema_readback_delayed = True
            schema_readback_delayed_error = {
                "message": api_error.message,
                **_transport_error_payload(api_error),
            }
        parsed = _parse_schema(schema_result)
        current_fields = parsed["fields"]
        original_fields = deepcopy(current_fields)
        layout = parsed["layout"]
        existing_index = {field["field_id"]: index for index, field in enumerate(current_fields)}
        selector_map = _build_selector_map(current_fields)
        added: list[str] = []
        updated: list[str] = []
        removed: list[str] = []

        for patch in add_fields:
            field_dict = _field_patch_to_internal(patch)
            existing_field = next((field for field in current_fields if str(field.get("name") or "") == patch.name), None)
            if existing_field is not None:
                if _field_matches_patch(existing_field, patch):
                    _merge_existing_field_with_patch(existing_field, patch)
                    continue
                return _failed(
                    "DUPLICATE_FIELD",
                    f"field '{patch.name}' already exists",
                    normalized_args=normalized_args,
                    details={"field_name": patch.name},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            current_fields.append(field_dict)
            existing_index[field_dict["field_id"]] = len(current_fields) - 1
            selector_map = _build_selector_map(current_fields)
            layout = _append_field_to_layout(layout, field_dict["name"])
            added.append(field_dict["name"])

        for patch in update_fields:
            matched = _resolve_selector(selector_map, patch.selector)
            if matched is None:
                return _failed(
                    "FIELD_NOT_FOUND",
                    "field selector did not match any existing field",
                    normalized_args=normalized_args,
                    details={"selector": patch.selector.model_dump(mode="json")},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            field = current_fields[matched]
            previous_name = field["name"]
            try:
                _apply_field_mutation(field, patch.set)
            except ValueError as error:
                return _failed(
                    "VALIDATION_ERROR",
                    str(error),
                    normalized_args=normalized_args,
                    details={
                        "selector": patch.selector.model_dump(mode="json"),
                        "app_key": target.app_key,
                    },
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            current_fields[matched] = field
            layout = _rename_field_in_layout(layout, previous_name, field["name"])
            updated.append(field["name"])
            selector_map = _build_selector_map(current_fields)

        for patch in remove_fields:
            matched = _resolve_remove_selector(current_fields, patch)
            if matched is None:
                return _failed(
                    "FIELD_NOT_FOUND",
                    "remove selector did not match any existing field",
                    normalized_args=normalized_args,
                    details={"selector": patch.model_dump(mode="json")},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            field = current_fields.pop(matched)
            layout = _remove_field_from_layout(layout, field["name"])
            removed.append(field["name"])
            selector_map = _build_selector_map(current_fields)

        relation_permission_outcome: PermissionCheckOutcome | None = None
        relation_degraded_expectations: list[dict[str, Any]] = []
        relation_target_metadata_verified = True
        try:
            relation_hydration = _hydrate_relation_field_configs(self, profile=profile, fields=current_fields)
            current_fields = relation_hydration.fields
            relation_permission_outcome = relation_hydration.permission_outcome
            relation_degraded_expectations = relation_hydration.degraded_expectations
            relation_target_metadata_verified = not bool(relation_degraded_expectations)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "RELATION_FIELD_TARGET_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": target.app_key},
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
            )
        except ValueError as error:
            return _failed(
                "RELATION_FIELD_CONFIG_INVALID",
                str(error),
                normalized_args=normalized_args,
                details={"app_key": target.app_key},
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
            )

        normalized_code_block_fields: list[str] = []
        try:
            current_fields, normalized_code_block_fields = _normalize_and_validate_code_block_fields_for_write(fields=current_fields)
            current_fields, compiled_question_relations = _compile_code_block_binding_fields(
                fields=current_fields,
                current_schema=schema_result,
            )
            _ensure_code_block_targets_compiled_as_relation_defaults(fields=current_fields)
        except _CodeBlockValidationError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details={"app_key": target.app_key, **error.details},
                suggested_next_call=_code_block_repair_suggested_next_call(
                    profile=profile,
                    app_key=target.app_key,
                    field_name=str(error.details.get("field_name") or "").strip() or None,
                ),
            )
        except ValueError as error:
            return _failed(
                "CODE_BLOCK_BINDING_INVALID",
                str(error),
                normalized_args=normalized_args,
                details={"app_key": target.app_key},
                suggested_next_call=_code_block_repair_suggested_next_call(profile=profile, app_key=target.app_key),
            )

        q_linker_schema_context = deepcopy(schema_result)
        q_linker_schema_context["questionRelations"] = deepcopy(compiled_question_relations)
        try:
            current_fields, compiled_question_relations = _compile_q_linker_binding_fields(
                fields=current_fields,
                current_schema=q_linker_schema_context,
            )
        except ValueError as error:
            return _failed(
                "Q_LINKER_BINDING_INVALID",
                str(error),
                normalized_args=normalized_args,
                details={"app_key": target.app_key},
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
            )

        try:
            data_display_selection = _collect_data_display_marker_selection(current_fields)
        except _DataDisplayConfigError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details={"app_key": target.app_key, **error.details},
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
            )

        inline_form_layout_target: dict[str, Any] | None = None
        inline_form_layout_auto_added_fields: list[str] = []
        inline_form_layout_changed = False
        if requested_inline_layout:
            inline_layout_started_at = time.perf_counter()
            resolved_inline_sections, missing_selectors = _resolve_layout_sections_to_names(
                requested_inline_layout_sections,
                current_fields,
            )
            normalized_args["inline_form_layout_sections"] = resolved_inline_sections
            if missing_selectors:
                return _failed(
                    "UNKNOWN_LAYOUT_FIELD",
                    "inline form layout references unknown field selectors",
                    normalized_args=normalized_args,
                    details={"unknown_selectors": missing_selectors},
                    missing_fields=[str(item) for item in missing_selectors],
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            fields_by_name = {field["name"]: field for field in current_fields}
            seen_layout_fields: list[str] = []
            for section in resolved_inline_sections:
                for row in section.get("rows", []):
                    for field_name in row:
                        if field_name not in fields_by_name:
                            return _failed(
                                "UNKNOWN_LAYOUT_FIELD",
                                f"inline form layout references unknown field '{field_name}'",
                                normalized_args=normalized_args,
                                details={"field_name": field_name},
                                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                            )
                        if field_name in seen_layout_fields:
                            return _failed(
                                "DUPLICATE_LAYOUT_FIELD",
                                f"inline form layout references field '{field_name}' more than once",
                                normalized_args=normalized_args,
                                details={"field_name": field_name},
                                suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
                            )
                        seen_layout_fields.append(field_name)
            merged_inline_layout = _merge_layout(
                current_layout=layout,
                requested_sections=resolved_inline_sections,
                all_field_names=[field["name"] for field in current_fields],
            )
            inline_form_layout_auto_added_fields = list(merged_inline_layout.get("auto_added_fields") or [])
            inline_form_layout_target = cast(dict[str, Any], merged_inline_layout["layout"])
            inline_form_layout_changed = not _layouts_equal(layout, inline_form_layout_target)
            if inline_form_layout_changed:
                layout = inline_form_layout_target
            inline_layout_ms = _duration_ms(inline_layout_started_at)

        schema_write_requested = bool(
            added
            or updated
            or removed
            or normalized_code_block_fields
            or data_display_selection.has_any
            or bool(resolved.get("created"))
            or inline_form_layout_changed
        )
        if schema_write_requested:
            try:
                _ensure_required_data_title_config(
                    current_schema=schema_result,
                    original_fields=original_fields,
                    fields=current_fields,
                    selection=data_display_selection,
                )
            except _DataDisplayConfigError as error:
                return _failed(
                    error.error_code,
                    error.message,
                    normalized_args=normalized_args,
                    details={"app_key": target.app_key, **error.details},
                    suggested_next_call={
                        "tool_name": "app_get_fields",
                        "arguments": {"profile": profile, "app_key": target.app_key},
                    },
                )

        relation_field_count = _count_relation_fields(current_fields)
        relation_limit_verified = True
        relation_warnings: list[dict[str, Any]] = []
        code_block_normalization_warnings = (
            [
                _warning(
                    "CODE_BLOCK_OUTPUT_ASSIGNMENT_NORMALIZED",
                    "normalized code block qf_output assignment before schema write",
                    field_names=normalized_code_block_fields,
                )
            ]
            if normalized_code_block_fields
            else []
        )

        if not schema_write_requested:
            readback_started_at = time.perf_counter()
            try:
                base_info = self.apps.app_get_base(profile=profile, app_key=target.app_key, include_raw=True).get("result") or {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                if bool(visual_result.get("updated")):
                    return finalize(_post_write_readback_pending_result(
                        error_code="APP_BASE_READBACK_PENDING",
                        message="updated app base metadata; app base readback is unavailable",
                        normalized_args=normalized_args,
                        details={
                            "app_key": target.app_key,
                            "verification_error": _transport_error_payload(api_error),
                            "app_name_after": effective_app_name,
                            "app_base_updated": True,
                            "field_diff": {"added": [], "updated": [], "removed": []},
                        },
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": target.app_key}},
                        request_id=api_error.request_id,
                        backend_code=api_error.backend_code,
                        http_status=None if api_error.http_status == 404 else api_error.http_status,
                    ))
                return finalize(_failed_from_api_error(
                    "APP_BASE_READBACK_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details=_with_state_read_blocked_details({"app_key": target.app_key}, resource="app_base", error=api_error),
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": target.app_key}},
                ))
            tag_ids_after = _coerce_int_list(base_info.get("tagIds"))
            package_attached = None if package_tag_id is None else package_tag_id in tag_ids_after
            actual_app_name = str(base_info.get("formTitle") or effective_app_name).strip() or effective_app_name
            actual_app_icon = str(base_info.get("appIcon") or visual_result.get("app_icon") or "").strip() or None
            expected_app_icon = str(visual_result.get("app_icon") or "").strip() or None
            app_icon_verified = True if expected_app_icon is None else actual_app_icon == expected_app_icon
            app_base_verified = actual_app_name == effective_app_name and app_icon_verified
            readback_ms = _duration_ms(readback_started_at)
            verified = app_base_verified and relation_target_metadata_verified
            response = {
                "status": "success" if verified else "partial_success",
                "error_code": None if verified else "APP_BASE_READBACK_PENDING",
                "recoverable": not verified,
                "message": "updated app base metadata; schema already matches requested state" if bool(visual_result.get("updated")) else "schema already matches requested state",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"field_types": [item.value for item in PublicFieldType]},
                "details": {},
                "request_id": None,
                "suggested_next_call": None if package_attached is not False else {"tool_name": "package_attach_app", "arguments": {"profile": profile, "tag_id": package_tag_id, "app_key": target.app_key}},
                "noop": not bool(visual_result.get("updated")),
                "warnings": relation_warnings + code_block_normalization_warnings,
                "verification": {
                    "fields_verified": True,
                    "relation_field_limit_verified": relation_limit_verified,
                    "app_visuals_verified": app_base_verified,
                    "app_base_verified": app_base_verified,
                    "relation_target_metadata_verified": relation_target_metadata_verified,
                },
                "app_key": target.app_key,
                "app_icon": str(visual_result.get("app_icon") or "").strip() or None,
                "app_name": effective_app_name,
                "app_name_before": str(visual_result.get("app_name_before") or target.app_name),
                "app_name_after": effective_app_name,
                "app_base_updated": bool(visual_result.get("updated")),
                "created": False,
                "field_diff": {"added": [], "updated": [], "removed": []},
                "verified": verified,
                "write_executed": bool(visual_result.get("updated")),
                "write_succeeded": bool(visual_result.get("updated")),
                "safe_to_retry": not bool(visual_result.get("updated")),
                "tag_ids_after": tag_ids_after,
                "package_attached": package_attached,
            }
            response["details"]["relation_field_count"] = relation_field_count
            if requested_inline_layout:
                response["inline_form_layout"] = {
                    "requested": True,
                    "section_count": len(requested_inline_layout_sections),
                    "app_key": target.app_key,
                    "applied": True,
                    "applied_in_schema": True,
                    "layout_status": "success",
                    "message": "layout already matched requested state",
                    "auto_added_fields": inline_form_layout_auto_added_fields,
                }
                response["verification"]["inline_form_layout_verified"] = True
            if normalized_code_block_fields:
                response["normalized_code_block_output_assignment"] = True
                response["normalized_code_block_fields"] = normalized_code_block_fields
            response = _apply_permission_outcomes(response, relation_permission_outcome)
            publish_started_at = time.perf_counter()
            response = self._append_publish_result(profile=profile, app_key=target.app_key, publish=publish, response=response)
            publish_ms = _duration_ms(publish_started_at)
            _merge_duration_breakdown(
                response,
                schema_write_ms=0,
                inline_layout_ms=inline_layout_ms,
                publish_ms=publish_ms,
                readback_ms=readback_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return finalize(response)

        payload = (
            _build_form_payload_from_fields(
                title=effective_app_name,
                current_schema=schema_result,
                fields=current_fields,
                layout=layout,
                question_relations=compiled_question_relations,
            )
            if bool(resolved.get("created"))
            else _build_form_payload_for_edit_fields(
                title=effective_app_name,
                current_schema=schema_result,
                fields=current_fields,
                layout=layout,
                question_relations=compiled_question_relations,
            )
        )
        _apply_data_display_selection_to_payload(payload, fields=current_fields, selection=data_display_selection)
        payload["editVersionNo"] = self._resolve_form_edit_version(
            profile=profile,
            app_key=target.app_key,
            current_schema=schema_result,
        )
        try:
            schema_write_started_at = time.perf_counter()
            self.apps.app_update_form_schema(profile=profile, app_key=target.app_key, payload=payload)
            schema_write_ms = _duration_ms(schema_write_started_at)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _is_uncertain_write_transport_error(api_error):
                uncertain = _post_write_may_have_succeeded_result(
                    error_code="SCHEMA_WRITE_RESULT_UNCERTAIN",
                    message="schema write request did not return a final result; read the app schema before retrying",
                    normalized_args=normalized_args,
                    details={
                        "app_key": target.app_key,
                        "app_name": effective_app_name,
                        "created": bool(resolved.get("created")),
                        "field_diff": {"added": added, "updated": updated, "removed": removed},
                        "transport_error": _transport_error_payload(api_error),
                    },
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                    request_id=api_error.request_id,
                    backend_code=api_error.backend_code,
                    http_status=api_error.http_status,
                )
                uncertain.update(
                    {
                        "app_key": target.app_key,
                        "app_icon": str(visual_result.get("app_icon") or resolved.get("app_icon") or "").strip() or None,
                        "app_name": effective_app_name,
                        "app_name_before": str(visual_result.get("app_name_before") or target.app_name),
                        "app_name_after": effective_app_name,
                        "app_base_updated": bool(visual_result.get("updated")),
                        "created": bool(resolved.get("created")),
                        "field_diff": {"added": added, "updated": updated, "removed": removed},
                        "field_diff_details": _schema_field_diff_details(
                            added=added,
                            updated=updated,
                            removed=removed,
                            before_fields=original_fields,
                            after_fields=current_fields,
                        ),
                        "tag_ids_after": list(target.tag_ids),
                        "package_attached": None if package_tag_id is None else package_tag_id in target.tag_ids,
                        "publish_requested": False,
                        "published": False,
                    }
                )
                return finalize(uncertain)
            return _failed_from_api_error(
                "SCHEMA_APPLY_FAILED",
                api_error,
                normalized_args=normalized_args,
                allowed_values={"field_types": [item.value for item in PublicFieldType]},
                details={
                    "app_key": target.app_key,
                    "field_diff": {"added": added, "updated": updated, "removed": removed},
                    "relation_field_count": relation_field_count,
                },
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
            )
        if _code_block_relations_need_source_rebind(compiled_question_relations) or _q_linker_relations_need_source_rebind(compiled_question_relations):
            try:
                rebound_schema = self.apps.app_get_form_schema(
                    profile=profile,
                    app_key=target.app_key,
                    form_type=1,
                    being_draft=True,
                    being_apply=None,
                    audit_node_id=None,
                    include_raw=True,
                ).get("result") or {}
                rebound_parsed = _parse_schema(rebound_schema)
                rebound_fields = cast(list[dict[str, Any]], rebound_parsed.get("fields") or [])
                rebound_layout = cast(dict[str, Any], rebound_parsed.get("layout") or {"root_rows": [], "sections": []})
                _overlay_code_block_binding_fields(target_fields=rebound_fields, source_fields=current_fields)
                _overlay_q_linker_binding_fields(target_fields=rebound_fields, source_fields=current_fields)
                rebound_fields, _normalized_rebound_code_blocks = _normalize_and_validate_code_block_fields_for_write(fields=rebound_fields)
                rebound_fields, compiled_question_relations = _compile_code_block_binding_fields(
                    fields=rebound_fields,
                    current_schema=rebound_schema,
                )
                _ensure_code_block_targets_compiled_as_relation_defaults(fields=rebound_fields)
                rebound_q_linker_schema = deepcopy(rebound_schema)
                rebound_q_linker_schema["questionRelations"] = deepcopy(compiled_question_relations)
                rebound_fields, compiled_question_relations = _compile_q_linker_binding_fields(
                    fields=rebound_fields,
                    current_schema=rebound_q_linker_schema,
                )
            except _CodeBlockValidationError as error:
                return _failed(
                    error.error_code,
                    error.message,
                    normalized_args=normalized_args,
                    details={"app_key": target.app_key, **error.details},
                    suggested_next_call=_code_block_repair_suggested_next_call(
                        profile=profile,
                        app_key=target.app_key,
                        field_name=str(error.details.get("field_name") or "").strip() or None,
                    ),
                )
            except ValueError as error:
                return _failed(
                    "Q_LINKER_BINDING_INVALID" if "q_linker" in str(error) else "CODE_BLOCK_BINDING_INVALID",
                    str(error),
                    normalized_args=normalized_args,
                    details={"app_key": target.app_key},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                return _failed_from_api_error(
                    "SCHEMA_APPLY_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    allowed_values={"field_types": [item.value for item in PublicFieldType]},
                    details={
                        "app_key": target.app_key,
                        "field_diff": {"added": added, "updated": updated, "removed": removed},
                    },
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            rebound_payload = (
                _build_form_payload_from_fields(
                    title=effective_app_name,
                    current_schema=rebound_schema,
                    fields=rebound_fields,
                    layout=rebound_layout,
                    question_relations=compiled_question_relations,
                )
                if bool(resolved.get("created"))
                else _build_form_payload_for_edit_fields(
                    title=effective_app_name,
                    current_schema=rebound_schema,
                    fields=rebound_fields,
                    layout=rebound_layout,
                    question_relations=compiled_question_relations,
                )
            )
            _apply_data_display_selection_to_payload(rebound_payload, fields=rebound_fields, selection=data_display_selection)
            rebound_payload["editVersionNo"] = self._resolve_form_edit_version(
                profile=profile,
                app_key=target.app_key,
                current_schema=rebound_schema,
            )
            try:
                schema_write_started_at = time.perf_counter()
                self.apps.app_update_form_schema(profile=profile, app_key=target.app_key, payload=rebound_payload)
                schema_write_ms = (schema_write_ms or 0) + _duration_ms(schema_write_started_at)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                return _failed_from_api_error(
                    "SCHEMA_APPLY_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    allowed_values={"field_types": [item.value for item in PublicFieldType]},
                    details={
                        "app_key": target.app_key,
                        "field_diff": {"added": added, "updated": updated, "removed": removed},
                    },
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
                )
            current_fields = rebound_fields
        response = {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "applied schema patch",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"field_types": [item.value for item in PublicFieldType]},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": relation_warnings + code_block_normalization_warnings,
            "verification": {
                "fields_verified": False,
                "package_attached": None,
                "app_visuals_verified": True,
                "app_base_verified": True,
                "relation_field_limit_verified": relation_limit_verified,
                "relation_target_metadata_verified": relation_target_metadata_verified,
            },
            "app_key": target.app_key,
            "app_icon": str(visual_result.get("app_icon") or resolved.get("app_icon") or "").strip() or None,
            "app_name": effective_app_name,
            "app_name_before": str(visual_result.get("app_name_before") or target.app_name),
            "app_name_after": effective_app_name,
            "app_base_updated": bool(visual_result.get("updated")),
            "created": bool(resolved.get("created")),
            "field_diff": {
                "added": added,
                "updated": updated,
                "removed": removed,
            },
            "field_diff_details": _schema_field_diff_details(
                added=added,
                updated=updated,
                removed=removed,
                before_fields=original_fields,
                after_fields=current_fields,
            ),
            "verified": False,
            "write_executed": True,
            "write_succeeded": True,
            "safe_to_retry": False,
            "tag_ids_after": [],
            "package_attached": None,
        }
        response["details"]["relation_field_count"] = relation_field_count
        if requested_inline_layout:
            response["inline_form_layout"] = {
                "requested": True,
                "section_count": len(requested_inline_layout_sections),
                "app_key": target.app_key,
                "applied": True,
                "applied_in_schema": True,
                "layout_status": "success",
                "message": (
                    "applied inline form layout inside schema write"
                    if inline_form_layout_changed
                    else "layout already matched requested state"
                ),
                "auto_added_fields": inline_form_layout_auto_added_fields,
            }
            response["verification"]["inline_form_layout_verified"] = False
        if normalized_code_block_fields:
            response["normalized_code_block_output_assignment"] = True
            response["normalized_code_block_fields"] = normalized_code_block_fields
        if schema_readback_delayed:
            response["verification"]["schema_readback_delayed"] = True
            if schema_readback_delayed_error is not None:
                response["details"]["schema_readback_delayed_error"] = schema_readback_delayed_error
        response = _apply_permission_outcomes(response, relation_permission_outcome)
        publish_started_at = time.perf_counter()
        response = self._append_publish_result(profile=profile, app_key=target.app_key, publish=publish, response=response)
        publish_ms = _duration_ms(publish_started_at)
        verification_ok = False
        tag_ids_after: list[int] = []
        package_attached: bool | None = None
        verification_error: QingflowApiError | None = None
        readback_started_at = time.perf_counter()
        try:
            verified = self.app_read(profile=profile, app_key=target.app_key, include_raw=False)
            verified_field_names = {field["name"] for field in verified["schema"]["fields"]}
            verified_fields = cast(list[dict[str, Any]], verified["schema"]["fields"])
            response["field_diff_details"] = _schema_field_diff_details(
                added=added,
                updated=updated,
                removed=removed,
                before_fields=original_fields,
                after_fields=verified_fields,
            )
            verification_ok = all(name in verified_field_names for name in added + updated) and all(name not in verified_field_names for name in removed)
            relation_readback_matrix = _schema_relation_readback_matrix(
                expected_fields=current_fields,
                verified_fields=verified_fields,
                changed_field_names=set(added + updated),
                degraded_expectations=relation_degraded_expectations,
            )
            if relation_readback_matrix:
                relation_matrix_verified = all(bool(item.get("readback_verified")) for item in relation_readback_matrix)
                response["details"]["relation_readback_matrix"] = relation_readback_matrix
                response["verification"]["relation_readback_matrix_verified"] = relation_matrix_verified
                verification_ok = verification_ok and relation_matrix_verified
                relation_repair_plan = _schema_relation_repair_plan(relation_readback_matrix)
                if relation_repair_plan:
                    response["details"]["relation_repair_plan"] = relation_repair_plan
            data_display_verification = _verify_data_display_readback(
                form_settings=verified.get("form_settings"),
                selection=data_display_selection,
            )
            response["verification"].update(data_display_verification)
            if data_display_verification.get("data_display_config_verified") is False:
                verification_ok = False
            if requested_inline_layout and inline_form_layout_target is not None:
                verified_layout = verified.get("layout") if isinstance(verified.get("layout"), dict) else {}
                inline_layout_verified = _layouts_equal(verified_layout, inline_form_layout_target) or _layouts_semantically_equal(
                    verified_layout,
                    inline_form_layout_target,
                )
                response["verification"]["inline_form_layout_verified"] = inline_layout_verified
                verification_ok = verification_ok and inline_layout_verified
            if relation_degraded_expectations:
                relation_verified_fields = cast(list[dict[str, Any]], verified["schema"]["fields"])
                try:
                    relation_readback = self.app_read_fields(profile=profile, app_key=target.app_key)
                    relation_verified_fields = cast(list[dict[str, Any]], relation_readback.get("fields") or relation_verified_fields)
                except (QingflowApiError, RuntimeError):
                    relation_verified_fields = cast(list[dict[str, Any]], verified["schema"]["fields"])
                relation_readback_verified = _verify_relation_readback_by_name(
                    verified_fields=relation_verified_fields,
                    degraded_expectations=relation_degraded_expectations,
                )
                response["verification"]["relation_target_readback_by_name_verified"] = relation_readback_verified
                verification_ok = verification_ok and relation_readback_verified
        except (QingflowApiError, RuntimeError) as error:
            verification_error = _coerce_api_error(error)
            verification_ok = False
        try:
            base_info = self.apps.app_get_base(profile=profile, app_key=target.app_key, include_raw=True).get("result") or {}
            tag_ids_after = _coerce_int_list(base_info.get("tagIds"))
            package_attached = None if package_tag_id is None else package_tag_id in tag_ids_after
            actual_app_name = str(base_info.get("formTitle") or effective_app_name).strip() or effective_app_name
            actual_app_icon = str(base_info.get("appIcon") or visual_result.get("app_icon") or "").strip() or None
            expected_app_icon = str(visual_result.get("app_icon") or "").strip() or None
            app_icon_verified = True if expected_app_icon is None else actual_app_icon == expected_app_icon
            app_base_verified = actual_app_name == effective_app_name and app_icon_verified
        except (QingflowApiError, RuntimeError) as error:
            base_error = _coerce_api_error(error)
            if verification_error is None:
                verification_error = base_error
            tag_ids_after = []
            package_attached = None if package_tag_id is None else False
            app_base_verified = False
        readback_ms = _duration_ms(readback_started_at)
        response["verification"]["fields_verified"] = verification_ok
        response["verification"]["package_attached"] = package_attached
        response["verification"]["app_visuals_verified"] = app_base_verified
        response["verification"]["app_base_verified"] = app_base_verified
        response["verification"]["relation_field_limit_verified"] = relation_limit_verified
        response["verified"] = verification_ok and app_base_verified
        response["tag_ids_after"] = tag_ids_after
        response["package_attached"] = package_attached
        if package_attached is False:
            response["suggested_next_call"] = {
                "tool_name": "package_attach_app",
                "arguments": {
                    "profile": profile,
                    "tag_id": package_tag_id,
                    "app_key": target.app_key,
                    "app_title": effective_app_name,
                },
            }
        publish_failed = bool(response.get("publish_requested")) and not bool(response.get("published"))
        if verification_ok and app_base_verified and package_attached is not False and not publish_failed:
            response["status"] = "success"
        else:
            response["status"] = "partial_success"
        if not app_base_verified and verification_error is None:
            response["recoverable"] = True
            response["error_code"] = response.get("error_code") or "APP_BASE_READBACK_PENDING"
            response["message"] = f"{response.get('message') or 'apply succeeded'}; app base readback pending"
            response["write_may_have_succeeded"] = True
            response["next_action"] = "readback_before_retry"
            response["verification"]["readback_before_retry"] = True
        if verification_error is not None:
            response["recoverable"] = True
            response["error_code"] = response.get("error_code") or (
                "READBACK_PENDING" if verification_error.http_status == 404 else "READBACK_FAILED"
            )
            response["message"] = f"{response.get('message') or 'apply succeeded'}; readback pending"
            response["write_may_have_succeeded"] = True
            response["next_action"] = "readback_before_retry"
            response["verification"]["readback_before_retry"] = True
            response["request_id"] = response.get("request_id") or verification_error.request_id
            details = response.get("details")
            if not isinstance(details, dict):
                details = {}
                response["details"] = details
            details["verification_error"] = {
                "message": verification_error.message,
                **_transport_error_payload(verification_error),
            }
        _merge_duration_breakdown(
            response,
            schema_write_ms=schema_write_ms,
            inline_layout_ms=inline_layout_ms,
            publish_ms=publish_ms,
            readback_ms=readback_ms,
            total_ms=_duration_ms(apply_started_at),
        )
        return finalize(response)

    def app_layout_apply(
        self,
        *,
        profile: str,
        app_key: str,
        mode: LayoutApplyMode = LayoutApplyMode.merge,
        sections: list[LayoutSectionPatch],
        publish: bool = True,
    ) -> JSONObject:
        requested_sections = [section.model_dump(mode="json", exclude_none=True) for section in sections]
        normalized_args = {
            "app_key": app_key,
            "mode": mode.value,
            "sections": requested_sections,
            "publish": publish,
        }
        apply_started_at = time.perf_counter()
        permission_ms = 0
        schema_read_ms = 0
        layout_compile_ms = 0
        edit_version_ms = 0
        layout_write_ms = 0
        layout_readback_ms = 0
        publish_ms = 0
        permission_outcomes: list[PermissionCheckOutcome] = []
        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        def attach_duration(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                schema_read_ms=schema_read_ms,
                layout_compile_ms=layout_compile_ms,
                edit_version_ms=edit_version_ms,
                layout_write_ms=layout_write_ms,
                layout_readback_ms=layout_readback_ms,
                publish_ms=publish_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        def finish(response: JSONObject) -> JSONObject:
            return finalize(attach_duration(response))

        def finish_with_publish(response: JSONObject) -> JSONObject:
            nonlocal publish_ms
            publish_started_at = time.perf_counter()
            published_response = self._append_publish_result(profile=profile, app_key=app_key, publish=publish, response=response)
            publish_ms += _duration_ms(publish_started_at)
            return finish(published_response)

        permission_started_at = time.perf_counter()
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        permission_ms += _duration_ms(permission_started_at)
        if permission_outcome.block is not None:
            return finish(permission_outcome.block)
        permission_outcomes.append(permission_outcome)

        try:
            schema_read_started_at = time.perf_counter()
            schema_result, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            schema_read_ms += _duration_ms(schema_read_started_at)
            api_error = _coerce_api_error(error)
            return finish(_failed_from_api_error(
                "LAYOUT_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"app_key": app_key}, resource="schema", error=api_error),
                suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        schema_read_ms += _duration_ms(schema_read_started_at)
        layout_compile_started_at = time.perf_counter()
        app_name = str(schema_result.get("formTitle") or schema_result.get("title") or schema_result.get("appName") or app_key).strip() or app_key
        parsed = _parse_schema(schema_result)
        current_fields = parsed["fields"]
        requested_sections, missing_selectors = _resolve_layout_sections_to_names(requested_sections, current_fields)
        normalized_args["sections"] = requested_sections
        if missing_selectors:
            layout_compile_ms += _duration_ms(layout_compile_started_at)
            return finish(_failed(
                "UNKNOWN_LAYOUT_FIELD",
                "layout references unknown field selectors",
                normalized_args=normalized_args,
                details={"unknown_selectors": missing_selectors},
                missing_fields=[str(item) for item in missing_selectors],
                suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        fields_by_name = {field["name"]: field for field in current_fields}
        seen: list[str] = []
        for section in requested_sections:
            for row in section.get("rows", []):
                for field_name in row:
                    if field_name not in fields_by_name:
                        layout_compile_ms += _duration_ms(layout_compile_started_at)
                        return finish(_failed(
                            "UNKNOWN_LAYOUT_FIELD",
                            f"layout references unknown field '{field_name}'",
                            normalized_args=normalized_args,
                            details={"field_name": field_name},
                            suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
                        ))
                    if field_name in seen:
                        layout_compile_ms += _duration_ms(layout_compile_started_at)
                        return finish(_failed(
                            "DUPLICATE_LAYOUT_FIELD",
                            f"layout references field '{field_name}' more than once",
                            normalized_args=normalized_args,
                            details={"field_name": field_name},
                            suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
                        ))
                    seen.append(field_name)
        expected = {field["name"] for field in current_fields}
        if mode == LayoutApplyMode.replace and set(seen) != expected:
            missing = sorted(expected.difference(seen))
            layout_compile_ms += _duration_ms(layout_compile_started_at)
            return finish(_failed(
                "INCOMPLETE_LAYOUT",
                "layout must reference every current field exactly once in replace mode",
                normalized_args=normalized_args,
                details={
                    "missing_fields": missing,
                    "auto_fill_preview": _merge_layout(
                        current_layout=parsed["layout"],
                        requested_sections=requested_sections,
                        all_field_names=[field["name"] for field in current_fields],
                    )["layout"],
                },
                missing_fields=missing,
                suggested_next_call={
                    "tool_name": "app_layout_apply",
                    "arguments": {"profile": profile, "app_key": app_key, "mode": "merge", "sections": requested_sections},
                },
            ))
        merged = _merge_layout(
            current_layout=parsed["layout"],
            requested_sections=requested_sections,
            all_field_names=[field["name"] for field in current_fields],
        )
        target_layout = (
            {"root_rows": [], "sections": requested_sections}
            if mode == LayoutApplyMode.replace
            else merged["layout"]
        )
        if _layouts_equal(parsed["layout"], target_layout):
            layout_compile_ms += _duration_ms(layout_compile_started_at)
            response = {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "layout already matches requested state",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"modes": ["merge", "replace"]},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": True,
                "warnings": [],
                "verification": {"layout_verified": True, "layout_summary_verified": True},
                "app_key": app_key,
                "app_name": app_name,
                "layout_diff": {
                    "mode": mode.value,
                    "replaced": mode == LayoutApplyMode.replace,
                    "merged": mode == LayoutApplyMode.merge,
                    "auto_added_fields": merged["auto_added_fields"] if mode == LayoutApplyMode.merge else [],
                    "fallback_applied": None,
                },
                "verified": True,
                "write_executed": False,
                "write_succeeded": False,
                "safe_to_retry": True,
            }
            return finish_with_publish(response)
        payload = _build_form_payload_from_existing_schema(
            current_schema=schema_result,
            layout=target_layout,
        )
        layout_compile_ms += _duration_ms(layout_compile_started_at)
        edit_version_started_at = time.perf_counter()
        payload["editVersionNo"] = self._resolve_form_edit_version(
            profile=profile,
            app_key=app_key,
            current_schema=schema_result,
        )
        edit_version_ms += _duration_ms(edit_version_started_at)
        applied_layout = target_layout
        fallback_applied = None
        try:
            layout_write_started_at = time.perf_counter()
            self.apps.app_update_form_schema(profile=profile, app_key=app_key, payload=payload)
        except (QingflowApiError, RuntimeError) as error:
            layout_write_ms += _duration_ms(layout_write_started_at)
            api_error = _coerce_api_error(error)
            if backend_code_int(api_error) == 400 and target_layout.get("sections"):
                flattened_layout = _flatten_layout_sections(target_layout)
                fallback_payload = _build_form_payload_from_existing_schema(
                    current_schema=schema_result,
                    layout=flattened_layout,
                )
                fallback_payload["editVersionNo"] = self._resolve_form_edit_version(
                    profile=profile,
                    app_key=app_key,
                    current_schema=schema_result,
                )
                try:
                    fallback_write_started_at = time.perf_counter()
                    self.apps.app_update_form_schema(profile=profile, app_key=app_key, payload=fallback_payload)
                    layout_write_ms += _duration_ms(fallback_write_started_at)
                    applied_layout = flattened_layout
                    fallback_applied = "flatten_sections"
                except (QingflowApiError, RuntimeError) as fallback_error:
                    layout_write_ms += _duration_ms(fallback_write_started_at)
                    api_fallback_error = _coerce_api_error(fallback_error)
                    return finish(_failed_from_api_error(
                        "LAYOUT_APPLY_FAILED",
                        api_fallback_error,
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "mode": mode.value,
                            "requested_sections": requested_sections,
                            "current_field_names": [field["name"] for field in current_fields],
                            "fallback_attempted": True,
                            "fallback_layout": flattened_layout,
                        },
                        suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
                    ))
            else:
                return finish(_failed_from_api_error(
                    "LAYOUT_APPLY_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "mode": mode.value,
                        "requested_sections": requested_sections,
                        "current_field_names": [field["name"] for field in current_fields],
                    },
                    suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
                ))
        else:
            layout_write_ms += _duration_ms(layout_write_started_at)
        try:
            layout_readback_started_at = time.perf_counter()
            verified_schema, _verified_schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            layout_readback_ms += _duration_ms(layout_readback_started_at)
            api_error = _coerce_api_error(error)
            response = {
                "status": "partial_success",
                "error_code": "LAYOUT_READBACK_PENDING",
                "recoverable": True,
                "message": "applied app layout; layout readback pending",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"modes": ["merge", "replace"]},
                "details": {"readback_error": _transport_error_payload(api_error)},
                "request_id": api_error.request_id,
                "backend_code": api_error.backend_code,
                "http_status": None if api_error.http_status == 404 else api_error.http_status,
                "suggested_next_call": {"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
                "noop": False,
                "warnings": [
                    _warning(
                        "READBACK_UNAVAILABLE_AFTER_WRITE",
                        "write was executed but layout readback is unavailable",
                        backend_code=api_error.backend_code,
                        http_status=None if api_error.http_status == 404 else api_error.http_status,
                        request_id=api_error.request_id,
                    )
                ],
                "verification": {"layout_verified": False, "layout_summary_verified": False, "layout_read_unavailable": True},
                "app_key": app_key,
                "app_name": app_name,
                "layout_diff": {
                    "mode": mode.value,
                    "replaced": mode == LayoutApplyMode.replace,
                    "merged": mode == LayoutApplyMode.merge,
                    "auto_added_fields": merged["auto_added_fields"] if mode == LayoutApplyMode.merge else [],
                    "fallback_applied": fallback_applied,
                },
                "verified": False,
                "write_executed": True,
                "write_succeeded": True,
                "safe_to_retry": False,
            }
            return finish_with_publish(response)
        layout_readback_ms += _duration_ms(layout_readback_started_at)
        verified_layout = _parse_schema(verified_schema)["layout"]
        layout_verified = _layouts_equal(verified_layout, applied_layout) or _layouts_semantically_equal(verified_layout, applied_layout)
        raw_layout_has_content = _schema_has_layout_content(verified_schema)
        layout_summary_verified = not (
            raw_layout_has_content
            and bool(applied_layout.get("sections"))
            and not bool(verified_layout.get("sections"))
        )
        warnings: list[dict[str, Any]] = []
        if not layout_summary_verified:
            warnings.append(_warning("LAYOUT_SUMMARY_UNVERIFIED", "layout summary is incomplete relative to raw schema readback"))
        if fallback_applied is not None and layout_verified and layout_summary_verified:
            warnings.append(_warning("LAYOUT_FALLBACK_APPLIED", "layout readback normalized sectioned layout into flat layout while preserving field placement"))
        result_verified = layout_verified
        response = {
            "status": "success" if result_verified else "partial_success",
            "error_code": (
                None
                if result_verified
                else "LAYOUT_READBACK_MISMATCH"
            ),
            "recoverable": not result_verified,
            "message": (
                "applied app layout with flattened section fallback"
                if layout_verified and layout_summary_verified and fallback_applied is not None
                else
                "applied app layout"
                if layout_verified
                else "applied app layout with flattened section fallback"
                if fallback_applied is not None
                else "applied app layout; readback did not fully match the requested layout"
            ),
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"modes": ["merge", "replace"]},
            "details": {},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": warnings,
            "verification": {"layout_verified": layout_verified, "layout_summary_verified": layout_summary_verified},
            "app_key": app_key,
            "app_name": app_name,
            "layout_diff": {
                "mode": mode.value,
                "replaced": mode == LayoutApplyMode.replace,
                "merged": mode == LayoutApplyMode.merge,
                "auto_added_fields": merged["auto_added_fields"] if mode == LayoutApplyMode.merge else [],
                "fallback_applied": fallback_applied,
            },
            "verified": result_verified,
            "write_executed": True,
            "write_succeeded": True,
            "safe_to_retry": False,
        }
        return finish_with_publish(response)

    def app_flow_apply(
        self,
        *,
        profile: str,
        app_key: str,
        nodes: list[dict[str, Any]],
        transitions: list[dict[str, Any]],
        mode: str = "replace",
        publish: bool = True,
    ) -> JSONObject:
        normalized_args = {
            "app_key": app_key,
            "mode": mode,
            "nodes": nodes,
            "transitions": transitions,
            "publish": publish,
        }
        apply_started_at = time.perf_counter()
        permission_ms = 0
        flow_state_read_ms = 0
        flow_compile_ms = 0
        flow_write_ms = 0
        flow_readback_ms = 0
        publish_ms = 0
        permission_outcomes: list[PermissionCheckOutcome] = []
        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        def attach_duration(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                flow_state_read_ms=flow_state_read_ms,
                flow_compile_ms=flow_compile_ms,
                flow_write_ms=flow_write_ms,
                flow_readback_ms=flow_readback_ms,
                publish_ms=publish_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        def finish(response: JSONObject) -> JSONObject:
            return finalize(attach_duration(response))

        def finish_with_publish(response: JSONObject) -> JSONObject:
            nonlocal publish_ms
            publish_started_at = time.perf_counter()
            published_response = self._append_publish_result(profile=profile, app_key=app_key, publish=publish, response=response)
            publish_ms += _duration_ms(publish_started_at)
            return finish(published_response)

        permission_started_at = time.perf_counter()
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="edit_app",
            normalized_args=normalized_args,
        )
        permission_ms += _duration_ms(permission_started_at)
        if permission_outcome.block is not None:
            return finish(permission_outcome.block)
        permission_outcomes.append(permission_outcome)

        if mode != "replace":
            return finish(_failed(
                "UNSUPPORTED_FLOW_MODE",
                "only mode='replace' is supported",
                normalized_args=normalized_args,
                allowed_values={"modes": ["replace"]},
                suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
            ))
        try:
            flow_state_read_started_at = time.perf_counter()
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
            schema, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            flow_state_read_ms += _duration_ms(flow_state_read_started_at)
            api_error = _coerce_api_error(error)
            return finish(_failed_from_api_error(
                "FLOW_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"app_key": app_key}, resource="workflow", error=api_error),
                suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        flow_state_read_ms += _duration_ms(flow_state_read_started_at)
        flow_compile_started_at = time.perf_counter()
        app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or app_key).strip() or app_key
        entity = _entity_spec_from_app(base_info=base, schema=schema, views=None)
        current_fields = _parse_schema(schema)["fields"]
        normalized_nodes, resolution_issues = self._normalize_flow_nodes(profile=profile, current_fields=current_fields, nodes=nodes)
        public_nodes = self._canonicalize_flow_nodes_for_public_output(normalized_nodes)
        unsupported_nodes = self._unsupported_public_flow_nodes(nodes=public_nodes)
        normalized_args["nodes"] = public_nodes
        if unsupported_nodes:
            flow_compile_ms += _duration_ms(flow_compile_started_at)
            return finish(_failed(
                "FLOW_NODE_TYPE_UNSUPPORTED",
                "public workflow writes use app_flow_get_schema/app_flow_get and app_flow_apply with WorkflowSpec spec; legacy nodes/transitions input cannot express this graph.",
                normalized_args=normalized_args,
                details={
                    "unsupported_nodes": unsupported_nodes,
                    "supported_node_types": sorted(STABLE_PUBLIC_FLOW_NODE_TYPES),
                    "disabled_node_types": sorted(DISABLED_PUBLIC_FLOW_NODE_TYPES),
                },
                suggested_next_call={"tool_name": "builder_tool_contract", "arguments": {"tool_name": "app_flow_apply"}},
            ))
        if resolution_issues:
            first_issue = resolution_issues[0]
            suggested_call = None
            if first_issue.get("kind", "").startswith("role"):
                suggested_call = {"tool_name": "role_search", "arguments": {"profile": profile, "keyword": first_issue.get("value") or ""}}
            elif first_issue.get("kind", "").startswith("member"):
                suggested_call = {"tool_name": "member_search", "arguments": {"profile": profile, "query": first_issue.get("value") or ""}}
            elif first_issue.get("kind") in {"editable_fields", "condition_fields"}:
                suggested_call = {"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}}
            flow_compile_ms += _duration_ms(flow_compile_started_at)
            return finish(_failed(
                first_issue.get("error_code") or "FLOW_ASSIGNEE_UNRESOLVED",
                "workflow contains unresolved assignees or field permissions",
                normalized_args=normalized_args,
                details={"issues": resolution_issues},
                suggested_next_call=suggested_call,
            ))
        assignee_required_nodes = [
            node.get("id")
            for node in normalized_nodes
            if str(node.get("type") or "") in {"approve", "fill", "copy"}
            and not (
                (node.get("assignees") or {}).get("role_entries")
                or (node.get("assignees") or {}).get("member_uids")
            )
        ]
        if assignee_required_nodes:
            flow_compile_ms += _duration_ms(flow_compile_started_at)
            return finish(_failed(
                "FLOW_ASSIGNEE_REQUIRED",
                "workflow approval/fill/copy nodes must declare at least one role or member assignee",
                normalized_args=normalized_args,
                details={"node_ids": assignee_required_nodes, "policy": "prefer role assignees; explicit members are also supported"},
                suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, "keyword": ""}},
            ))
        workflow_spec = _build_public_workflow_spec(nodes=normalized_nodes, transitions=transitions)
        if workflow_spec.get("status") == "failed":
            workflow_spec["normalized_args"] = normalized_args
            workflow_spec.setdefault("request_id", None)
            workflow_spec["suggested_next_call"] = {"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}}
            flow_compile_ms += _duration_ms(flow_compile_started_at)
            return finish(workflow_spec)
        desired_node_count = len([node for node in normalized_nodes if node.get("type") != "end"])
        build_id = f"facade-flow-{uuid4().hex[:12]}"
        previous_build_home = os.getenv("QINGFLOW_MCP_BUILD_HOME")
        temporary_build_home: str | None = None
        if previous_build_home is None:
            temporary_build_home = tempfile.mkdtemp(prefix="qingflow-mcp-build-", dir="/tmp")
            os.environ["QINGFLOW_MCP_BUILD_HOME"] = temporary_build_home
        try:
            assembly = BuildAssemblyStore.open(build_id=build_id, create=True)
            manifest = default_manifest()
            manifest["solution_name"] = base.get("formTitle") or app_key
            manifest["preferences"]["create_package"] = False
            manifest["preferences"]["create_portal"] = False
            manifest["preferences"]["create_navigation"] = False
            manifest["entities"] = [entity]
            assembly.set_manifest(manifest)
            artifacts = default_artifacts()
            artifacts["apps"][entity["entity_id"]] = {"app_key": app_key}
            assembly.set_artifacts(artifacts)
            flow_stage_spec = {
                "solution_name": manifest["solution_name"],
                "entities": [{"entity_id": entity["entity_id"], "workflow": workflow_spec["workflow"]}],
            }
            assembly.set_stage_spec("app_flow", flow_stage_spec)
            flow_compile_ms += _duration_ms(flow_compile_started_at)
            flow_write_started_at = time.perf_counter()
            stage = self.solutions.solution_build_flow(
                profile=profile,
                mode="apply",
                build_id=build_id,
                flow_spec=flow_stage_spec,
                publish=False,
                run_label=None,
                repair_patch={},
            )
            flow_write_ms += _duration_ms(flow_write_started_at)
        finally:
            if previous_build_home is None:
                os.environ.pop("QINGFLOW_MCP_BUILD_HOME", None)
        if stage.get("status") != "success":
            failed = _normalize_flow_stage_failure(stage, profile=profile, app_key=app_key, entity=entity)
            failed["normalized_args"] = normalized_args
            suggested_next_call = failed.get("suggested_next_call")
            if not isinstance(suggested_next_call, dict):
                suggested_next_call = {"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}}
            elif suggested_next_call.get("tool_name") == "app_flow_plan":
                arguments = suggested_next_call.get("arguments")
                if not isinstance(arguments, dict):
                    arguments = {}
                arguments.setdefault("profile", profile)
                arguments.setdefault("app_key", app_key)
                arguments.setdefault("mode", mode)
                arguments.setdefault("nodes", public_nodes)
                arguments.setdefault("transitions", transitions)
                suggested_next_call["tool_name"] = "app_flow_apply"
                suggested_next_call["arguments"] = arguments
            failed["suggested_next_call"] = suggested_next_call
            return finish(failed)
        flow_readback_started_at = time.perf_counter()
        verified_nodes, verified_nodes_unavailable = self._load_workflow_result(
            profile=profile,
            app_key=app_key,
            tolerate_404=True,
            legacy_fallback=True,
        )
        flow_readback_ms += _duration_ms(flow_readback_started_at)
        workflow_structure_verified = bool(verified_nodes) and _workflow_nodes_semantically_equal(
            current_workflow=verified_nodes,
            requested_nodes=normalized_nodes,
        )
        branch_structure_verified = bool(verified_nodes) and _workflow_branch_structure_verified(
            current_workflow=verified_nodes,
            requested_nodes=normalized_nodes,
        )
        workflow_verified = workflow_structure_verified and branch_structure_verified
        warnings: list[dict[str, Any]] = []
        if verified_nodes_unavailable:
            status = "partial_success"
            error_code = "FLOW_READBACK_PENDING"
            recoverable = True
            message = "applied workflow patch; flow readback pending"
            suggested_next_call = {"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}}
        elif workflow_verified:
            status = "success"
            error_code = None
            recoverable = False
            message = "applied workflow patch"
            suggested_next_call = None
        else:
            status = "partial_success"
            error_code = "FLOW_BRANCH_VERIFICATION_FAILED" if workflow_structure_verified and not branch_structure_verified else "FLOW_VERIFICATION_FAILED"
            recoverable = True
            message = (
                "applied workflow patch; branch or condition structure did not fully verify"
                if workflow_structure_verified and not branch_structure_verified
                else "applied workflow patch; flow readback did not confirm the requested workflow"
            )
            suggested_next_call = {"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}}
            if not branch_structure_verified:
                warnings.append(_warning("WORKFLOW_BRANCH_STRUCTURE_UNVERIFIED", "branch or condition structure was written, but MCP could not fully verify downstream lane structure"))
        response = {
            "status": status,
            "error_code": error_code,
            "recoverable": recoverable,
            "message": message,
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"modes": ["replace"]},
            "details": {},
            "request_id": None,
            "suggested_next_call": suggested_next_call,
            "noop": False,
            "warnings": warnings,
            "verification": {
                "workflow_verified": workflow_verified,
                "workflow_structure_verified": workflow_structure_verified,
                "branch_structure_verified": branch_structure_verified,
                "workflow_read_unavailable": verified_nodes_unavailable,
            },
            "app_key": app_key,
            "app_name": app_name,
            "flow_diff": {"mode": "replace", "node_count": desired_node_count},
            "verified": workflow_verified,
            "write_executed": True,
            "write_succeeded": True,
            "safe_to_retry": False,
        }
        return finish_with_publish(response)

    def _extract_view_action_button_patch_intents(
        self,
        *,
        existing_by_key: dict[str, dict[str, Any]],
        existing_by_name: dict[str, list[dict[str, Any]]],
        patch_views: list[ViewPartialPatch],
    ) -> tuple[list[ViewPartialPatch], list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
        action_keys = {"action_buttons", "actionButtons"}
        mode_keys = {"action_buttons_mode", "actionButtonsMode", "button_mode", "buttonMode"}
        sanitized: list[ViewPartialPatch] = []
        intents: list[dict[str, Any]] = []
        issues: list[dict[str, Any]] = []
        results: list[dict[str, Any]] = []
        sentinel = object()
        for index, patch in enumerate(patch_views):
            raw_set = dict(patch.set or {})
            action_buttons_raw: Any = sentinel
            for key in action_keys:
                if key in raw_set:
                    action_buttons_raw = raw_set.pop(key)
                    break
            mode_raw: Any = sentinel
            for key in mode_keys:
                if key in raw_set:
                    mode_raw = raw_set.pop(key)
                    break
            if action_buttons_raw is not sentinel:
                requires_view_write = bool(raw_set or patch.unset)
                if not isinstance(action_buttons_raw, list):
                    issue = {
                        "error_code": "INVALID_VIEW_ACTION_BUTTONS",
                        "reason_path": f"patch_views[{index}].set.action_buttons",
                        "message": "action_buttons must be a list",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                else:
                    mode = str(mode_raw if mode_raw is not sentinel else "merge").strip().lower() or "merge"
                    if mode not in {"merge", "replace"}:
                        issue = {
                            "error_code": "INVALID_VIEW_ACTION_BUTTONS_MODE",
                            "reason_path": f"patch_views[{index}].set.action_buttons_mode",
                            "message": "action_buttons_mode must be merge or replace",
                        }
                        issues.append(issue)
                        results.append({"index": index, "status": "failed", **issue})
                    else:
                        try:
                            action_buttons = [
                                ViewActionButtonPatch.model_validate(item)
                                for item in action_buttons_raw
                            ]
                        except Exception as error:
                            issue = {
                                "error_code": "INVALID_VIEW_ACTION_BUTTONS",
                                "reason_path": f"patch_views[{index}].set.action_buttons",
                                "message": str(error),
                            }
                            issues.append(issue)
                            results.append({"index": index, "status": "failed", **issue})
                        else:
                            view_key = str(patch.view_key or "").strip()
                            view_name = str(patch.name or "").strip()
                            matched_view: dict[str, Any] | None = None
                            if view_key:
                                matched_view = existing_by_key.get(view_key)
                                if matched_view is None:
                                    issue = {
                                        "error_code": "UNKNOWN_VIEW",
                                        "reason_path": f"patch_views[{index}].view_key",
                                        "view_key": view_key,
                                        "message": "view_key does not exist on this app",
                                    }
                                    issues.append(issue)
                                    results.append({"index": index, "status": "failed", **issue})
                            else:
                                matches = existing_by_name.get(view_name, [])
                                if len(matches) != 1:
                                    issue = {
                                        "error_code": "AMBIGUOUS_VIEW" if matches else "UNKNOWN_VIEW",
                                        "reason_path": f"patch_views[{index}].name",
                                        "view_name": view_name,
                                        "matches": [
                                            {"name": _extract_view_name(view), "view_key": _extract_view_key(view), "type": _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))}
                                            for view in matches
                                        ],
                                        "message": "patch_views[].set.action_buttons must target a single existing view; use view_key when names are duplicated",
                                    }
                                    issues.append(issue)
                                    results.append({"index": index, "status": "failed", **issue})
                                else:
                                    matched_view = matches[0]
                                    view_key = _extract_view_key(matched_view)
                            if matched_view is not None or view_key:
                                view_name = _extract_view_name(matched_view or {}) or view_name or view_key
                                intents.append(
                                    {
                                        "source": "patch_views",
                                        "index": index,
                                        "view_key": view_key,
                                        "view_name": view_name,
                                        "action_buttons": action_buttons,
                                        "mode": mode,
                                        "requires_view_write": requires_view_write,
                                    }
                                )
                                results.append(
                                    {
                                        "index": index,
                                        "status": "action_buttons_extracted",
                                        "view_key": view_key,
                                        "view_name": view_name,
                                        "action_buttons_count": len(action_buttons),
                                        "action_buttons_mode": mode,
                                    }
                                )
            elif mode_raw is not sentinel:
                issue = {
                    "error_code": "ACTION_BUTTONS_MODE_WITHOUT_ACTION_BUTTONS",
                    "reason_path": f"patch_views[{index}].set.action_buttons_mode",
                    "message": "action_buttons_mode is only valid together with action_buttons",
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
            if raw_set or patch.unset:
                sanitized.append(
                    ViewPartialPatch.model_validate(
                        {
                            "view_key": patch.view_key,
                            "name": patch.name,
                            "set": raw_set,
                            "unset": list(patch.unset or []),
                        }
                    )
                )
        return sanitized, intents, issues, results

    def _view_action_button_to_custom_button_payload(self, *, button: ViewActionButtonPatch, client_key: str | None) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "button_text": button.text,
            "trigger_action": button.action.value,
            "style_preset": button.style_preset or "primary_blue",
        }
        if button.button_id is not None:
            payload["button_id"] = button.button_id
        if client_key:
            payload["client_key"] = client_key
        if button.background_color:
            payload["background_color"] = button.background_color
        if button.text_color:
            payload["text_color"] = button.text_color
        if button.button_icon:
            payload["button_icon"] = button.button_icon
        if button.action == PublicButtonTriggerAction.link:
            payload["trigger_link_url"] = button.url
        elif button.action == PublicButtonTriggerAction.add_data:
            add_data_config: dict[str, Any] = {
                "related_app_key": button.target_app_key,
                "related_app_name": button.target_app_name,
                "field_mappings": deepcopy(button.field_mappings),
                "default_values": deepcopy(button.default_values),
            }
            payload["trigger_add_data_config"] = _compact_dict(add_data_config)
        elif button.action == PublicButtonTriggerAction.qrobot:
            payload["external_qrobot_config"] = deepcopy(button.external_qrobot_config)
        elif button.action == PublicButtonTriggerAction.wings:
            payload["trigger_wings_config"] = deepcopy(button.trigger_wings_config)
        return payload

    def _view_action_button_binding_payload(self, *, button: ViewActionButtonPatch, button_ref: Any) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "button_ref": button_ref,
            "placement": button.placement.value,
            "primary": button.primary,
            "button_limit": [
                [rule.model_dump(mode="json") for rule in group]
                for group in button.visible_when
            ],
            "button_formula_type": button.button_formula_type,
            "print_tpls": deepcopy(button.print_tpls),
        }
        if button.button_formula:
            payload["button_formula"] = button.button_formula
        return payload

    def _compile_view_action_buttons_request(
        self,
        *,
        app_key: str,
        intents: list[dict[str, Any]],
    ) -> tuple[CustomButtonsApplyRequest | None, list[dict[str, Any]]]:
        upsert_buttons: list[CustomButtonUpsertPatch] = []
        view_configs: list[CustomButtonViewConfigPatch] = []
        issues: list[dict[str, Any]] = []
        seen_button_payloads: dict[str, dict[str, Any]] = {}
        seen_button_refs: dict[str, Any] = {}
        used_client_keys: set[str] = set()
        for intent_index, intent in enumerate(intents):
            view_key = str(intent.get("view_key") or "").strip()
            mode = str(intent.get("mode") or "merge").strip().lower() or "merge"
            action_buttons = [
                item for item in (intent.get("action_buttons") or [])
                if isinstance(item, ViewActionButtonPatch)
            ]
            bindings: list[dict[str, Any]] = []
            for button_index, button in enumerate(action_buttons):
                identity = f"id:{button.button_id}" if button.button_id is not None else f"text:{str(button.text or '').strip()}"
                explicit_client_key = str(button.client_key or "").strip() or None
                generated_client_key = explicit_client_key or f"view_action_{_slugify(str(button.text or ''), default='button')}"
                client_key = generated_client_key
                payload = self._view_action_button_to_custom_button_payload(
                    button=button,
                    client_key=None if button.button_id is not None else client_key,
                )
                compare_payload = deepcopy(payload)
                compare_payload.pop("client_key", None)
                existing_payload = seen_button_payloads.get(identity)
                if existing_payload is not None:
                    if existing_payload != compare_payload:
                        issues.append(
                            {
                                "error_code": "DUPLICATE_ACTION_BUTTON_CONFLICT",
                                "reason_path": f"{intent.get('source') or 'views'}[{intent.get('index', intent_index)}].action_buttons[{button_index}]",
                                "button_text": button.text,
                                "message": "the same app cannot declare two action_buttons with the same button text or id but different button body config in one app_views_apply call",
                            }
                        )
                        continue
                    button_ref = seen_button_refs[identity]
                else:
                    seen_button_payloads[identity] = compare_payload
                    if button.button_id is not None:
                        button_ref = button.button_id
                    else:
                        unique_client_key = client_key
                        if unique_client_key in used_client_keys:
                            unique_client_key = f"{unique_client_key}_{len(used_client_keys) + 1}"
                            payload["client_key"] = unique_client_key
                        used_client_keys.add(unique_client_key)
                        button_ref = unique_client_key
                        payload["client_key"] = unique_client_key
                        try:
                            upsert_buttons.append(CustomButtonUpsertPatch.model_validate(payload))
                        except Exception as error:
                            issues.append(
                                {
                                    "error_code": "INVALID_VIEW_ACTION_BUTTON",
                                    "reason_path": f"{intent.get('source') or 'views'}[{intent.get('index', intent_index)}].action_buttons[{button_index}]",
                                    "message": str(error),
                                }
                            )
                            continue
                    seen_button_refs[identity] = button_ref
                bindings.append(self._view_action_button_binding_payload(button=button, button_ref=button_ref))
            if mode == "replace" or bindings:
                try:
                    view_configs.append(CustomButtonViewConfigPatch.model_validate({"view_key": view_key, "mode": mode, "buttons": bindings}))
                except Exception as error:
                    issues.append(
                        {
                            "error_code": "INVALID_VIEW_ACTION_BUTTON_BINDING",
                            "reason_path": f"{intent.get('source') or 'views'}[{intent.get('index', intent_index)}].action_buttons",
                            "view_key": view_key,
                            "message": str(error),
                        }
                    )
        if issues:
            return None, issues
        if not upsert_buttons and not view_configs:
            return None, []
        try:
            return CustomButtonsApplyRequest.model_validate(
                {
                    "app_key": app_key,
                    "upsert_buttons": [
                        item.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
                        for item in upsert_buttons
                    ],
                    "view_configs": [
                        item.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
                        for item in view_configs
                    ],
                }
            ), []
        except Exception as error:
            return None, [{"error_code": "INVALID_VIEW_ACTION_BUTTONS", "message": str(error)}]

    def _view_action_buttons_retry_payload(self, *, profile: str, app_key: str, intents: list[dict[str, Any]]) -> dict[str, Any]:
        return {
            "tool_name": "app_views_apply",
            "arguments": {
                "profile": profile,
                "app_key": app_key,
                "publish": True,
                "upsert_views": [],
                "patch_views": [
                    {
                        "view_key": intent.get("view_key"),
                        "set": {
                            "action_buttons": [
                                public_view_action_button_payload(button, compact=True)
                                for button in (intent.get("action_buttons") or [])
                                if isinstance(button, ViewActionButtonPatch)
                            ],
                            "action_buttons_mode": intent.get("mode") or "merge",
                        },
                    }
                    for intent in intents
                    if str(intent.get("view_key") or "").strip()
                ],
                "remove_views": [],
            },
        }

    def _failed_view_action_button_intents(self, *, intents: list[dict[str, Any]], result: dict[str, Any]) -> list[dict[str, Any]]:
        failed_view_keys: set[str] = set()
        failed_button_texts: set[str] = set()
        for item in result.get("failed") or []:
            if not isinstance(item, dict):
                continue
            view_key = str(item.get("view_key") or item.get("viewgraph_key") or "").strip()
            if view_key:
                failed_view_keys.add(view_key)
            button_text = str(item.get("button_text") or item.get("text") or "").strip()
            if button_text:
                failed_button_texts.add(button_text)
        for item in result.get("view_configs") or []:
            if not isinstance(item, dict):
                continue
            status = str(item.get("status") or "").strip()
            if status and status not in {"success", "noop", "skipped"}:
                view_key = str(item.get("view_key") or item.get("viewgraph_key") or "").strip()
                if view_key:
                    failed_view_keys.add(view_key)
        if failed_view_keys:
            return [
                intent
                for intent in intents
                if str(intent.get("view_key") or "").strip() in failed_view_keys
            ]
        if failed_button_texts:
            return [
                intent
                for intent in intents
                if any(
                    isinstance(button, ViewActionButtonPatch) and str(button.text or "").strip() in failed_button_texts
                    for button in (intent.get("action_buttons") or [])
                )
            ]
        if str(result.get("status") or "").strip() == "failed" or (result.get("write_succeeded") is False and bool(result.get("write_executed"))):
            return list(intents)
        return []

    def _apply_view_action_buttons(
        self,
        *,
        profile: str,
        app_key: str,
        intents: list[dict[str, Any]],
    ) -> dict[str, Any]:
        if not intents:
            return {
                "status": "success",
                "verified": True,
                "write_executed": False,
                "write_succeeded": False,
                "verification": {
                    "action_buttons_verified": True,
                    "view_button_bindings_verified": True,
                },
                "retry_payload": None,
            }
        request, issues = self._compile_view_action_buttons_request(app_key=app_key, intents=intents)
        if issues:
            return {
                "status": "failed",
                "error_code": "DUPLICATE_ACTION_BUTTON_CONFLICT" if any(item.get("error_code") == "DUPLICATE_ACTION_BUTTON_CONFLICT" for item in issues) else "VIEW_ACTION_BUTTONS_INVALID",
                "recoverable": True,
                "message": "view action_buttons could not be compiled; view writes may already have completed",
                "details": {"blocking_issues": issues},
                "verification": {
                    "action_buttons_verified": False,
                    "view_button_bindings_verified": False,
                },
                "verified": False,
                "write_executed": False,
                "write_succeeded": False,
                "retry_payload": self._view_action_buttons_retry_payload(profile=profile, app_key=app_key, intents=intents),
            }
        if request is None:
            return {
                "status": "success",
                "verified": True,
                "write_executed": False,
                "write_succeeded": False,
                "verification": {
                    "action_buttons_verified": True,
                    "view_button_bindings_verified": True,
                },
                "retry_payload": None,
            }
        result = self.app_custom_buttons_apply(profile=profile, request=request)
        retry_intents = [] if result.get("verified") else self._failed_view_action_button_intents(intents=intents, result=result)
        result["retry_payload"] = self._view_action_buttons_retry_payload(profile=profile, app_key=app_key, intents=retry_intents) if retry_intents else None
        return result

    def app_views_apply(
        self,
        *,
        profile: str,
        app_key: str,
        upsert_views: list[ViewUpsertPatch],
        patch_views: list[ViewPartialPatch] | None = None,
        remove_views: list[str],
        publish: bool = True,
    ) -> JSONObject:
        patch_views = patch_views or []
        normalized_args = {
            "app_key": app_key,
            "upsert_views": [public_view_upsert_payload(patch) for patch in upsert_views],
            "patch_views": [public_view_partial_payload(patch) for patch in patch_views],
            "remove_views": list(remove_views),
            "publish": publish,
        }
        apply_started_at = time.perf_counter()
        permission_ms = 0
        view_state_read_ms = 0
        custom_button_inventory_ms = 0
        associated_resource_inventory_ms = 0
        view_mutation_ms = 0
        action_buttons_ms = 0
        view_readback_ms = 0
        view_verification_ms = 0
        publish_ms = 0
        publish_already_satisfied_by_action_buttons = False
        permission_outcomes: list[PermissionCheckOutcome] = []

        def finalize(response: JSONObject) -> JSONObject:
            return _apply_permission_outcomes(response, *permission_outcomes)

        def attach_duration(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                permission_ms=permission_ms,
                view_state_read_ms=view_state_read_ms,
                custom_button_inventory_ms=custom_button_inventory_ms,
                associated_resource_inventory_ms=associated_resource_inventory_ms,
                view_mutation_ms=view_mutation_ms,
                action_buttons_ms=action_buttons_ms,
                view_readback_ms=view_readback_ms,
                view_verification_ms=view_verification_ms,
                publish_ms=publish_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        def finish(response: JSONObject) -> JSONObject:
            return finalize(attach_duration(response))

        def finish_with_publish(response: JSONObject) -> JSONObject:
            nonlocal publish_ms
            if publish and publish_already_satisfied_by_action_buttons and not bool(response.get("noop")):
                verification = response.get("verification")
                if not isinstance(verification, dict):
                    verification = {}
                    response["verification"] = verification
                response["publish_requested"] = True
                response["publish_skipped"] = True
                response["publish_skip_reason"] = "already_published_by_action_buttons"
                response["published"] = True
                verification["published"] = True
                return finish(response)
            publish_started_at = time.perf_counter()
            published_response = self._append_publish_result(profile=profile, app_key=app_key, publish=publish, response=response)
            publish_ms += _duration_ms(publish_started_at)
            return finish(published_response)

        has_action_button_intent = any(patch.action_buttons is not None for patch in upsert_views) or any(
            any(key in (patch.set or {}) for key in ("action_buttons", "actionButtons"))
            for patch in patch_views
        )
        if has_action_button_intent and not publish:
            return finish(_failed(
                "VIEW_ACTION_BUTTONS_REQUIRE_PUBLISH",
                "app_views_apply action_buttons require publish=true because the underlying custom button writer may publish after successful button writes",
                normalized_args=normalized_args,
                details={
                    "app_key": app_key,
                    "required_publish": True,
                    "reason": "action_buttons are compiled through app_custom_buttons_apply",
                },
                suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **{**normalized_args, "publish": True}}},
            ))
        if not upsert_views and not patch_views and not remove_views:
            response = {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "no view changes requested",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator], "view.associated_resources.limit_type": ["all", "select"]},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": True,
                "warnings": [],
                "verification": {
                    "views_verified": True,
                    "view_filters_verified": True,
                    "view_query_conditions_verified": True,
                    "view_associated_resources_verified": True,
                },
                "app_key": app_key,
                "views_diff": {"created": [], "updated": [], "removed": []},
                "verified": True,
                "write_executed": False,
                "write_succeeded": False,
                "safe_to_retry": True,
            }
            return finish_with_publish(response)
        app_permission_summary: JSONObject | None = None
        app_permission_error: QingflowApiError | None = None
        permission_started_at = time.perf_counter()
        try:
            app_permission_summary = self._read_app_permission_summary(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            app_permission_error = _coerce_api_error(error)
            app_permission_summary = None
        if app_permission_error is not None:
            if _is_permission_restricted_api_error(app_permission_error):
                permission_outcome = _permission_skip_outcome(
                    scope="app",
                    target={"app_key": app_key},
                    required_permission="view_manage",
                    transport_error=_transport_error_payload(app_permission_error),
                )
            else:
                permission_outcome = PermissionCheckOutcome(
                    block=_failed(
                        "APP_PERMISSION_UNVERIFIED",
                        "could not confirm current user's builder permissions for this app",
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "required_permission": "view_manage",
                            "permission_read_error": {
                                "message": app_permission_error.message,
                                "http_status": app_permission_error.http_status,
                                "backend_code": app_permission_error.backend_code,
                                "category": app_permission_error.category,
                            },
                        },
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                        request_id=app_permission_error.request_id,
                        backend_code=app_permission_error.backend_code,
                        http_status=None if app_permission_error.http_status == 404 else app_permission_error.http_status,
                    )
                )
        else:
            permission_outcome = self._guard_app_permission(
                profile=profile,
                app_key=app_key,
                required_permission="view_manage",
                normalized_args=normalized_args,
                permission_summary=app_permission_summary,
            )
        permission_ms += _duration_ms(permission_started_at)
        if permission_outcome.block is not None:
            return finish(permission_outcome.block)
        permission_outcomes.append(permission_outcome)

        try:
            view_state_read_started_at = time.perf_counter()
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
            schema, _schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
            existing_views, _views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=False)
        except (QingflowApiError, RuntimeError) as error:
            view_state_read_ms += _duration_ms(view_state_read_started_at)
            api_error = _coerce_api_error(error)
            return finish(_failed_from_api_error(
                "VIEWS_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"app_key": app_key}, resource="views", error=api_error),
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            ))
        view_state_read_ms += _duration_ms(view_state_read_started_at)
        app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or "").strip() or None
        existing_views = existing_views or []
        existing_by_key: dict[str, dict[str, Any]] = {}
        existing_by_name: dict[str, list[dict[str, Any]]] = {}
        for view in existing_views if isinstance(existing_views, list) else []:
            if not isinstance(view, dict):
                continue
            name = _extract_view_name(view)
            key = _extract_view_key(view)
            if name and key:
                existing_by_key[key] = view
                existing_by_name.setdefault(name, []).append(view)
        patch_action_button_intents: list[dict[str, Any]] = []
        action_button_patch_results: list[dict[str, Any]] = []
        if patch_views:
            sanitized_patch_views, patch_action_button_intents, action_button_patch_issues, action_button_patch_results = self._extract_view_action_button_patch_intents(
                existing_by_key=existing_by_key,
                existing_by_name=existing_by_name,
                patch_views=patch_views,
            )
            if action_button_patch_results:
                normalized_args["action_button_patch_results"] = action_button_patch_results
            if action_button_patch_issues:
                return finish(
                    _failed(
                        "VIEW_ACTION_BUTTON_PATCH_FAILED",
                        "one or more patch_views action_buttons entries could not be resolved; no write was executed",
                        normalized_args=normalized_args,
                        details={"patch_results": action_button_patch_results, "blocking_issues": action_button_patch_issues},
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                )
            patch_views = sanitized_patch_views
            normalized_args["patch_views"] = [public_view_partial_payload(patch) for patch in patch_views]
        creating_view_names = [
            patch.name
            for patch in upsert_views
            if not patch.view_key and not existing_by_name.get(patch.name)
        ]
        if creating_view_names:
            if app_permission_error is not None and _is_permission_restricted_api_error(app_permission_error):
                create_permission_outcome = _permission_skip_outcome(
                    scope="app",
                    target={"app_key": app_key},
                    required_permission="data_manage",
                    transport_error=_transport_error_payload(app_permission_error),
                )
            else:
                create_permission_started_at = time.perf_counter()
                create_permission_outcome = self._guard_app_permission(
                    profile=profile,
                    app_key=app_key,
                    required_permission="data_manage",
                    normalized_args=normalized_args,
                    permission_summary=app_permission_summary,
                )
                permission_ms += _duration_ms(create_permission_started_at)
            if create_permission_outcome.block is not None:
                details = create_permission_outcome.block.get("details")
                if isinstance(details, dict):
                    details["operation"] = "view_create"
                    details["view_names"] = creating_view_names
                    details["also_required_permission"] = "view_manage"
                return finish(create_permission_outcome.block)
            permission_outcomes.append(create_permission_outcome)
        parsed_schema = _parse_schema(schema)
        field_names = {field["name"] for field in parsed_schema["fields"]}
        if patch_views:
            expanded_views, patch_issues, patch_results = self._expand_view_partial_patches(
                profile=profile,
                app_key=app_key,
                schema=schema,
                existing_by_key=existing_by_key,
                existing_by_name=existing_by_name,
                patch_views=patch_views,
            )
            if patch_issues:
                return finalize(
                    _failed(
                        "VIEW_PATCH_HYDRATION_FAILED",
                        "one or more view partial patches could not be hydrated; no write was executed",
                        normalized_args=normalized_args,
                        details={"patch_results": patch_results, "blocking_issues": patch_issues},
                        suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, "view_key": patch_issues[0].get("view_key") or "VIEW_KEY"}},
                    )
                )
            upsert_views = [*upsert_views, *expanded_views]
            normalized_args["upsert_views"] = [public_view_upsert_payload(patch) for patch in upsert_views]
            normalized_args["patch_results"] = patch_results
        current_fields_by_name = {
            str(field.get("name") or ""): field
            for field in parsed_schema["fields"]
            if isinstance(field, dict) and str(field.get("name") or "")
        }
        requires_custom_button_validation = any(
            any(binding.button_type == PublicViewButtonType.custom for binding in (patch.buttons or []))
            for patch in upsert_views
        )
        valid_custom_button_ids: set[int] = set()
        custom_button_details_by_id: dict[int, dict[str, Any]] = {}
        if requires_custom_button_validation:
            try:
                custom_button_inventory_started_at = time.perf_counter()
                button_listing = self.buttons.custom_button_list(
                    profile=profile,
                    app_key=app_key,
                    being_draft=True,
                    include_raw=False,
                )
            except (QingflowApiError, RuntimeError) as error:
                custom_button_inventory_ms += _duration_ms(custom_button_inventory_started_at)
                api_error = _coerce_api_error(error)
                return finish(
                    _failed_from_api_error(
                        "CUSTOM_BUTTON_LIST_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details=_with_state_read_blocked_details({"app_key": app_key}, resource="custom_button", error=api_error),
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                )
            custom_button_inventory_ms += _duration_ms(custom_button_inventory_started_at)
            valid_custom_button_ids = {
                button_id
                for item in (button_listing.get("items") or [])
                if isinstance(item, dict) and (button_id := _coerce_positive_int(item.get("button_id"))) is not None
            }
            referenced_custom_button_ids = {
                binding.button_id
                for patch in upsert_views
                for binding in (patch.buttons or [])
                if binding.button_type == PublicViewButtonType.custom and binding.button_id in valid_custom_button_ids
            }
            for button_id in sorted(referenced_custom_button_ids):
                try:
                    custom_button_detail_started_at = time.perf_counter()
                    detail = self.buttons.custom_button_get(
                        profile=profile,
                        app_key=app_key,
                        button_id=button_id,
                        being_draft=True,
                        include_raw=False,
                    )
                    custom_button_inventory_ms += _duration_ms(custom_button_detail_started_at)
                except (QingflowApiError, RuntimeError) as error:
                    custom_button_inventory_ms += _duration_ms(custom_button_detail_started_at)
                    api_error = _coerce_api_error(error)
                    if _is_optional_builder_lookup_error(api_error):
                        continue
                    failed = _failed_from_api_error(
                        "CUSTOM_BUTTON_DETAIL_READ_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details=_with_state_read_blocked_details(
                            {"app_key": app_key, "button_id": button_id},
                            resource="custom_button",
                            error=api_error,
                        ),
                        suggested_next_call={
                            "tool_name": "app_custom_button_get",
                            "arguments": {"profile": profile, "app_key": app_key, "button_id": button_id},
                        },
                    )
                    failed.update({"write_executed": False, "write_succeeded": False, "safe_to_retry": True})
                    return finish(failed)
                detail_result = detail.get("result")
                if isinstance(detail_result, dict):
                    custom_button_details_by_id[button_id] = _normalize_custom_button_detail(detail_result)
        requires_associated_resource_validation = any(patch.associated_resources is not None for patch in upsert_views)
        associated_resources: list[dict[str, Any]] = []
        if requires_associated_resource_validation:
            try:
                associated_resource_inventory_started_at = time.perf_counter()
                associated_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
            except (QingflowApiError, RuntimeError) as error:
                associated_resource_inventory_ms += _duration_ms(associated_resource_inventory_started_at)
                api_error = _coerce_api_error(error)
                return finish(
                    _failed_from_api_error(
                        "ASSOCIATED_RESOURCES_READ_FAILED",
                        api_error,
                        normalized_args=normalized_args,
                        details=_with_state_read_blocked_details({"app_key": app_key}, resource="associated_resources", error=api_error),
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                )
            associated_resource_inventory_ms += _duration_ms(associated_resource_inventory_started_at)
        removed: list[str] = []
        removed_keys: set[str] = set()
        view_results: list[dict[str, Any]] = []
        failed_views: list[dict[str, Any]] = []
        action_button_intents: list[dict[str, Any]] = list(patch_action_button_intents)

        def record_action_button_intent(*, patch: ViewUpsertPatch, view_key: str | None, index: int) -> None:
            if patch.action_buttons is None:
                return
            action_button_intents.append(
                {
                    "source": "upsert_views",
                    "index": index,
                    "view_key": str(view_key or "").strip(),
                    "view_name": patch.name,
                    "action_buttons": list(patch.action_buttons),
                    "mode": patch.action_buttons_mode,
                }
            )

        view_mutation_started_at = time.perf_counter()
        for selector in remove_views:
            selector_text = str(selector or "").strip()
            if not selector_text:
                continue
            key_match = existing_by_key.get(selector_text)
            matches = [key_match] if isinstance(key_match, dict) else existing_by_name.get(selector_text, [])
            if len(matches) > 1:
                return _failed(
                    "AMBIGUOUS_VIEW",
                    "multiple views matched remove request; use app_get and resolve duplicates before removing by name",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": selector_text,
                        "matches": [
                            {"name": _extract_view_name(view), "view_key": _extract_view_key(view), "type": _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))}
                            for view in matches
                        ],
                    },
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            if not matches:
                failed_view = {
                    "name": selector_text,
                    "view_key": selector_text,
                    "type": None,
                    "status": "failed",
                    "error_code": "VIEW_NOT_FOUND",
                    "message": "remove_views item did not match an existing view name or view_key",
                }
                failed_views.append(failed_view)
                view_results.append(deepcopy(failed_view))
                continue
            if len(matches) == 1:
                key = _extract_view_key(matches[0])
                removed_name = _extract_view_name(matches[0]) or selector_text
                try:
                    self.views.view_delete(profile=profile, viewgraph_key=key)
                    delete_readback = self._verify_view_deleted_by_key(profile=profile, view_key=key)
                    removed.append(removed_name)
                    if key:
                        removed_keys.add(key)
                        existing_by_key.pop(key, None)
                    existing_by_name.pop(removed_name, None)
                    view_results.append(
                        {
                            "name": removed_name,
                            "view_key": key,
                            "type": None,
                            "status": delete_readback.get("status") or "readback_pending",
                            "operation": "delete",
                            "delete_executed": True,
                            "readback_status": delete_readback.get("readback_status"),
                            "safe_to_retry_delete": False,
                            **(
                                {
                                    "error_code": delete_readback.get("error_code"),
                                    "message": delete_readback.get("message"),
                                    "request_id": delete_readback.get("request_id"),
                                    "backend_code": delete_readback.get("backend_code"),
                                    "http_status": delete_readback.get("http_status"),
                                    "transport_error": delete_readback.get("transport_error"),
                                }
                                if delete_readback.get("readback_status") != "deleted"
                                else {}
                            ),
                        }
                    )
                except (QingflowApiError, RuntimeError) as error:
                    api_error = _coerce_api_error(error)
                    failed_view = {
                        "name": removed_name,
                        "view_key": key,
                        "type": None,
                        "status": "failed",
                        "operation": "delete",
                        "delete_executed": False,
                        "safe_to_retry_delete": True,
                        "error_code": "VIEW_DELETE_FAILED",
                        "message": _public_error_message("VIEW_APPLY_FAILED", api_error),
                        "request_id": api_error.request_id,
                        "backend_code": api_error.backend_code,
                        "http_status": None if api_error.http_status == 404 else api_error.http_status,
                    }
                    failed_views.append(failed_view)
                    view_results.append(deepcopy(failed_view))
        created: list[str] = []
        updated: list[str] = []
        existing_view_list = [
            view
            for view in (existing_views if isinstance(existing_views, list) else [])
            if isinstance(view, dict)
            and _extract_view_name(view) not in removed
            and _extract_view_key(view) not in removed_keys
        ]
        for ordinal, patch in enumerate(upsert_views, start=1):
            apply_columns = _resolve_view_visible_field_names(patch)
            ignored_system_columns = [
                name
                for name in [str(value or "").strip() for value in patch.columns]
                if name in _KNOWN_SYSTEM_VIEW_COLUMNS
            ]
            if patch.type in {PublicViewType.table, PublicViewType.card} and patch.columns and not apply_columns:
                return _failed(
                    "VALIDATION_ERROR",
                    "view columns must include at least one real app field; system columns cannot be applied directly",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        "ignored_system_columns": ignored_system_columns,
                    },
                )
            missing_columns = [name for name in apply_columns if name not in field_names]
            if missing_columns:
                return _failed(
                    "UNKNOWN_VIEW_FIELD",
                    "view columns reference unknown fields",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        "missing_fields": missing_columns,
                        "ignored_system_columns": ignored_system_columns,
                    },
                    missing_fields=missing_columns,
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            if patch.group_by and patch.group_by not in field_names:
                return _failed(
                    "UNKNOWN_VIEW_FIELD",
                    f"group_by references unknown field '{patch.group_by}'",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        "missing_fields": [patch.group_by],
                    },
                    missing_fields=[patch.group_by],
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            for gantt_field_name in (patch.start_field, patch.end_field, patch.title_field):
                if gantt_field_name and gantt_field_name not in field_names:
                    return _failed(
                        "UNKNOWN_VIEW_FIELD",
                        f"gantt configuration references unknown field '{gantt_field_name}'",
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "view_name": patch.name,
                            "missing_fields": [gantt_field_name],
                        },
                        missing_fields=[gantt_field_name],
                        suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                    )
            translated_filters: list[list[dict[str, Any]]] | None
            filter_issues: list[dict[str, Any]]
            if patch.preserve_filters:
                translated_filters = None
                filter_issues = []
            else:
                translated_filters, filter_issues = _build_view_filter_groups(current_fields_by_name=current_fields_by_name, filters=patch.filters)
            if filter_issues:
                first_issue = filter_issues[0]
                return _failed(
                    str(first_issue.get("error_code") or "UNKNOWN_VIEW_FIELD"),
                    "view filters reference invalid fields or values",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        **first_issue,
                    },
                    missing_fields=list(first_issue.get("missing_fields") or []),
                    allowed_values=first_issue.get("allowed_values") or {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            if patch.preserve_query_conditions:
                query_condition_payload = None
                expected_query_conditions = None
                query_condition_issues = []
            else:
                query_condition_payload, expected_query_conditions, query_condition_issues = _build_view_query_conditions_payload(
                    current_fields_by_name=current_fields_by_name,
                    query_conditions=patch.query_conditions,
                )
            if query_condition_issues:
                first_issue = query_condition_issues[0]
                return _failed(
                    str(first_issue.get("error_code") or "INVALID_QUERY_CONDITION_FIELD"),
                    "view query conditions reference invalid fields or values",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        **first_issue,
                    },
                    missing_fields=list(first_issue.get("missing_fields") or []),
                    allowed_values={
                        "view_types": [member.value for member in PublicViewType],
                        "view.query_condition.supported_field_types": sorted(
                            set(FIELD_TYPE_TO_QUESTION_TYPE) - QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES
                        ),
                    },
                    suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
                )
            if patch.preserve_associated_resources:
                associated_resources_payload = None
                expected_associated_resources = None
                associated_resource_issues = []
            else:
                associated_resources_payload, expected_associated_resources, associated_resource_issues = _build_view_associated_resources_payload(
                    associated_resources=patch.associated_resources,
                    available_resources=associated_resources,
                )
            if associated_resource_issues:
                first_issue = associated_resource_issues[0]
                return _failed(
                    str(first_issue.get("error_code") or "INVALID_ASSOCIATED_RESOURCE"),
                    "view associated resources reference invalid ids or values",
                    normalized_args=normalized_args,
                    details={
                        "app_key": app_key,
                        "view_name": patch.name,
                        **first_issue,
                    },
                    missing_fields=list(first_issue.get("missing_fields") or []),
                    allowed_values={
                        "view.associated_resources.limit_type": ["all", "select"],
                        "view.associated_resources.available_associated_item_ids": sorted(_associated_resource_index(associated_resources)),
                    },
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            explicit_button_dtos: list[dict[str, Any]] | None = None
            expected_button_summary: list[dict[str, Any]] | None = None
            if patch.preserve_buttons:
                explicit_button_dtos = None
                expected_button_summary = None
            elif patch.buttons is not None:
                explicit_button_dtos, button_issues = _build_view_button_dtos(
                    current_fields_by_name=current_fields_by_name,
                    bindings=patch.buttons,
                    valid_custom_button_ids=valid_custom_button_ids,
                )
                if button_issues:
                    first_issue = button_issues[0]
                    return _failed(
                        str(first_issue.get("error_code") or "INVALID_VIEW_BUTTON"),
                        "view buttons reference invalid fields, values, or custom buttons",
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "view_name": patch.name,
                            **first_issue,
                        },
                        missing_fields=list(first_issue.get("missing_fields") or []),
                        allowed_values=first_issue.get("allowed_values") or {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                expected_button_summary = _normalize_expected_view_buttons_for_compare(
                    explicit_button_dtos or [],
                    custom_button_details_by_id=custom_button_details_by_id,
                )
            matched_existing_view: dict[str, Any] | None = None
            existing_key: str | None = None
            if patch.view_key:
                matched_existing_view = existing_by_key.get(patch.view_key)
                if not matched_existing_view:
                    return _failed(
                        "UNKNOWN_VIEW",
                        f"view_key '{patch.view_key}' does not exist on this app",
                        normalized_args=normalized_args,
                        details={"app_key": app_key, "view_key": patch.view_key, "view_name": patch.name},
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                existing_key = patch.view_key
            else:
                name_matches = existing_by_name.get(patch.name, [])
                if len(name_matches) > 1:
                    return _failed(
                        "AMBIGUOUS_VIEW",
                        "multiple views share this name; supply view_key to update the exact target",
                        normalized_args=normalized_args,
                        details={
                            "app_key": app_key,
                            "view_name": patch.name,
                            "matches": [
                                {"name": _extract_view_name(view), "view_key": _extract_view_key(view), "type": _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))}
                                for view in name_matches
                            ],
                        },
                        suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                    )
                if len(name_matches) == 1:
                    matched_existing_view = name_matches[0]
                    existing_key = _extract_view_key(matched_existing_view)
            created_key: str | None = None
            system_view_list_type = _resolve_system_view_list_type(view_key=existing_key, view_name=patch.name) if existing_key else None
            operation_phase = "view_update" if existing_key else "view_create"
            query_condition_payload_for_apply = deepcopy(query_condition_payload)
            expected_query_conditions_for_verify = deepcopy(expected_query_conditions)
            associated_resources_payload_for_apply = deepcopy(associated_resources_payload)
            expected_associated_resources_for_verify = deepcopy(expected_associated_resources)
            if not existing_key and query_condition_payload_for_apply is None:
                query_condition_payload_for_apply = _empty_view_query_conditions_payload()
                expected_query_conditions_for_verify = _normalize_view_query_conditions_for_compare(query_condition_payload_for_apply)
            if not existing_key and associated_resources_payload_for_apply is None:
                associated_resources_payload_for_apply, _, _ = _build_view_associated_resources_payload(
                    associated_resources={"visible": True, "limit_type": "all", "associated_item_ids": []},
                    available_resources=associated_resources,
                )
            try:
                view_auth_override = (
                    self._compile_visibility_to_member_auth(profile=profile, visibility=patch.visibility)
                    if patch.visibility is not None
                    else None
                )
            except VisibilityResolutionError as error:
                return _failed(
                    error.error_code,
                    error.message,
                    normalized_args=normalized_args,
                    details={"app_key": app_key, "view_name": patch.name, **error.details},
                    suggested_next_call=None,
                )
            try:
                if existing_key:
                    payload = _build_view_update_payload(
                        views=self.views,
                        profile=profile,
                        source_viewgraph_key=existing_key,
                        schema=schema,
                        patch=patch,
                        view_filters=translated_filters,
                        current_fields_by_name=current_fields_by_name,
                        auth_override=view_auth_override,
                        explicit_button_dtos=explicit_button_dtos,
                        query_condition_payload=query_condition_payload_for_apply,
                        associated_resources_payload=associated_resources_payload_for_apply,
                    )
                    self.views.view_update(profile=profile, viewgraph_key=existing_key, payload=payload)
                    system_view_sync: dict[str, Any] | None = None
                    if system_view_list_type is not None and patch.type.value == "table":
                        operation_phase = "default_view_apply_config_sync"
                        system_view_sync = self._sync_system_view_and_restore_buttons(
                            profile=profile,
                            app_key=app_key,
                            viewgraph_key=existing_key,
                            payload=payload,
                            list_type=system_view_list_type,
                            schema=schema,
                            visible_field_names=apply_columns,
                        )
                        if not bool(system_view_sync.get("verified")):
                            failure_entry = {
                                "name": patch.name,
                                "view_key": existing_key,
                                "type": patch.type.value,
                                "status": "failed",
                                "error_code": "SYSTEM_VIEW_ORDER_SYNC_FAILED",
                                "message": "default view column order did not verify through app apply/baseInfo readback",
                                "request_id": None,
                                "backend_code": None,
                                "http_status": None,
                                "operation": "sync_default_view",
                                "details": {
                                    "app_key": app_key,
                                    "view_name": patch.name,
                                    "view_key": existing_key,
                                    "view_type": patch.type.value,
                                    "list_type": system_view_list_type,
                                    "expected_visible_order": system_view_sync.get("expected_visible_order"),
                                    "actual_visible_order": system_view_sync.get("actual_visible_order"),
                                    "apply_columns": apply_columns,
                                },
                            }
                            failed_views.append(failure_entry)
                            view_results.append(failure_entry)
                            continue
                    updated.append(patch.name)
                    view_results.append(
                        {
                                "name": patch.name,
                                "view_key": existing_key,
                                "type": patch.type.value,
                                "status": "updated",
                                "expected_filters": deepcopy(translated_filters),
                                "expected_buttons": deepcopy(expected_button_summary),
                                "expected_query_conditions": deepcopy(expected_query_conditions_for_verify),
                                "expected_associated_resources": deepcopy(expected_associated_resources_for_verify),
                                "system_view_sync": system_view_sync,
                                "apply_columns": deepcopy(apply_columns),
                            }
                        )
                    record_action_button_intent(patch=patch, view_key=existing_key, index=ordinal - 1)
                else:
                    template_key = _pick_view_template_key(existing_view_list, desired_type=patch.type.value)
                    should_copy_template = patch.type.value == "table" and template_key and not translated_filters
                    if should_copy_template:
                        copied = self.views.view_copy(profile=profile, viewgraph_key=template_key)
                        created_key = str(copied.get("result") or "")
                        payload = _build_view_update_payload(
                            views=self.views,
                            profile=profile,
                            source_viewgraph_key=created_key,
                            schema=schema,
                            patch=patch,
                            view_filters=translated_filters,
                            current_fields_by_name=current_fields_by_name,
                            auth_override=view_auth_override,
                            explicit_button_dtos=explicit_button_dtos,
                            query_condition_payload=query_condition_payload_for_apply,
                            associated_resources_payload=associated_resources_payload_for_apply,
                        )
                        self.views.view_update(profile=profile, viewgraph_key=created_key, payload=payload)
                    else:
                        payload = _build_view_create_payload(
                            app_key=app_key,
                            base_info=base,
                            schema=schema,
                            patch=patch,
                            ordinal=ordinal,
                            view_filters=translated_filters,
                            current_fields_by_name=current_fields_by_name,
                            auth_override=view_auth_override,
                            explicit_button_dtos=explicit_button_dtos,
                            query_condition_payload=query_condition_payload_for_apply,
                            associated_resources_payload=associated_resources_payload_for_apply,
                        )
                        create_result = self.views.view_create(profile=profile, payload=payload)
                        raw_created = create_result.get("result")
                        if isinstance(raw_created, dict):
                            raw_view_key = raw_created.get("viewgraphKey") or raw_created.get("viewKey")
                            created_key = str(raw_view_key).strip() if raw_view_key is not None and str(raw_view_key).strip() else None
                        elif isinstance(raw_created, str):
                            created_key = raw_created.strip() or None
                    created.append(patch.name)
                    view_results.append(
                            {
                                "name": patch.name,
                                "view_key": created_key,
                                "type": patch.type.value,
                                "status": "created",
                                "expected_filters": deepcopy(translated_filters),
                                "expected_buttons": deepcopy(expected_button_summary),
                                "expected_query_conditions": deepcopy(expected_query_conditions_for_verify),
                                "expected_associated_resources": deepcopy(expected_associated_resources_for_verify),
                            }
                        )
                    record_action_button_intent(patch=patch, view_key=created_key, index=ordinal - 1)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                should_retry_minimal = operation_phase != "default_view_apply_config_sync" and (
                    backend_code_int(api_error) == 48104
                    or (patch.type.value == "table" and bool(patch.filters) and api_error.http_status is not None and api_error.http_status >= 500)
                )
                if should_retry_minimal:
                    try:
                        if existing_key or created_key:
                            target_key = created_key or existing_key or ""
                            fallback_button_dtos = explicit_button_dtos
                            if fallback_button_dtos is None:
                                fallback_config_response = self.views.view_get_config(profile=profile, viewgraph_key=target_key)
                                fallback_config = (
                                    fallback_config_response.get("result")
                                    if isinstance(fallback_config_response.get("result"), dict)
                                    else {}
                                )
                                fallback_button_dtos = _extract_existing_view_button_dtos(fallback_config)
                            fallback_payload = _build_minimal_view_payload(
                                app_key=app_key,
                                schema=schema,
                                patch=patch,
                                ordinal=ordinal,
                                view_filters=translated_filters,
                                current_fields_by_name=current_fields_by_name,
                                auth_override=view_auth_override,
                                explicit_button_dtos=fallback_button_dtos,
                                query_condition_payload=query_condition_payload_for_apply,
                                associated_resources_payload=associated_resources_payload_for_apply,
                            )
                            self.views.view_update(profile=profile, viewgraph_key=target_key, payload=fallback_payload)
                            system_view_sync: dict[str, Any] | None = None
                            fallback_system_view_list_type = (
                                _resolve_system_view_list_type(view_key=target_key, view_name=patch.name)
                                if patch.type.value == "table" and target_key
                                else None
                            )
                            if fallback_system_view_list_type is not None:
                                operation_phase = "default_view_apply_config_sync"
                                system_view_sync = self._sync_system_view_and_restore_buttons(
                                    profile=profile,
                                    app_key=app_key,
                                    viewgraph_key=target_key,
                                    payload=fallback_payload,
                                    list_type=fallback_system_view_list_type,
                                    schema=schema,
                                    visible_field_names=apply_columns,
                                )
                                if not bool(system_view_sync.get("verified")):
                                    failure_entry = {
                                        "name": patch.name,
                                        "view_key": target_key,
                                        "type": patch.type.value,
                                        "status": "failed",
                                        "error_code": "SYSTEM_VIEW_ORDER_SYNC_FAILED",
                                        "message": "default view column order did not verify through app apply/baseInfo readback",
                                        "request_id": None,
                                        "backend_code": None,
                                        "http_status": None,
                                        "operation": "sync_default_view",
                                        "details": {
                                            "app_key": app_key,
                                            "view_name": patch.name,
                                            "view_key": target_key,
                                            "view_type": patch.type.value,
                                            "list_type": fallback_system_view_list_type,
                                            "expected_visible_order": system_view_sync.get("expected_visible_order"),
                                            "actual_visible_order": system_view_sync.get("actual_visible_order"),
                                            "apply_columns": apply_columns,
                                        },
                                    }
                                    failed_views.append(failure_entry)
                                    view_results.append(failure_entry)
                                    continue
                            if existing_key:
                                updated.append(patch.name)
                                view_results.append(
                                    {
                                        "name": patch.name,
                                        "view_key": existing_key,
                                        "type": patch.type.value,
                                        "status": "updated",
                                        "fallback_applied": True,
                                        "expected_filters": deepcopy(translated_filters),
                                        "expected_buttons": deepcopy(expected_button_summary),
                                        "expected_query_conditions": deepcopy(expected_query_conditions_for_verify),
                                        "expected_associated_resources": deepcopy(expected_associated_resources_for_verify),
                                        "system_view_sync": system_view_sync,
                                        "apply_columns": deepcopy(apply_columns),
                                    }
                                )
                                record_action_button_intent(patch=patch, view_key=existing_key, index=ordinal - 1)
                            else:
                                created.append(patch.name)
                                view_results.append(
                                    {
                                        "name": patch.name,
                                        "view_key": created_key,
                                        "type": patch.type.value,
                                        "status": "created",
                                        "fallback_applied": True,
                                        "expected_filters": deepcopy(translated_filters),
                                        "expected_buttons": deepcopy(expected_button_summary),
                                        "expected_query_conditions": deepcopy(expected_query_conditions_for_verify),
                                        "expected_associated_resources": deepcopy(expected_associated_resources_for_verify),
                                        "system_view_sync": system_view_sync,
                                        "apply_columns": deepcopy(apply_columns),
                                    }
                                )
                                record_action_button_intent(patch=patch, view_key=created_key, index=ordinal - 1)
                            continue
                        fallback_payload = _build_minimal_view_payload(
                            app_key=app_key,
                            schema=schema,
                            patch=patch,
                            ordinal=ordinal,
                            view_filters=translated_filters,
                            current_fields_by_name=current_fields_by_name,
                            auth_override=view_auth_override,
                            explicit_button_dtos=explicit_button_dtos,
                            query_condition_payload=query_condition_payload_for_apply,
                            associated_resources_payload=associated_resources_payload_for_apply,
                        )
                        self.views.view_create(profile=profile, payload=fallback_payload)
                        created.append(patch.name)
                        view_results.append(
                            {
                                "name": patch.name,
                                "view_key": created_key,
                                "type": patch.type.value,
                                "status": "created",
                                "fallback_applied": True,
                                "expected_filters": deepcopy(translated_filters),
                                "expected_query_conditions": deepcopy(expected_query_conditions_for_verify),
                                "expected_associated_resources": deepcopy(expected_associated_resources_for_verify),
                            }
                        )
                        record_action_button_intent(patch=patch, view_key=created_key, index=ordinal - 1)
                        continue
                    except (QingflowApiError, RuntimeError) as fallback_error:
                        api_error = _coerce_api_error(fallback_error)
                if created_key:
                    try:
                        self.views.view_delete(profile=profile, viewgraph_key=created_key)
                    except Exception:
                        pass
                failure_entry = {
                    "name": patch.name,
                    "view_key": patch.view_key or existing_key or created_key,
                    "type": patch.type.value,
                    "status": "failed",
                    "error_code": "VIEW_APPLY_FAILED",
                    "message": _public_error_message("VIEW_APPLY_FAILED", api_error),
                    "request_id": api_error.request_id,
                    "backend_code": api_error.backend_code,
                    "http_status": None if api_error.http_status == 404 else api_error.http_status,
                    "operation": "sync_default_view" if operation_phase == "default_view_apply_config_sync" else ("update" if existing_key or created_key else "create"),
                    "details": {
                        "app_key": app_key,
                        "view_name": patch.name,
                        "view_type": patch.type.value,
                        "columns": patch.columns,
                        "group_by": patch.group_by,
                        "filters": [item.model_dump(mode="json") for item in patch.filters],
                        "start_field": patch.start_field,
                        "end_field": patch.end_field,
                        "title_field": patch.title_field,
                        "operation": "sync_default_view" if operation_phase == "default_view_apply_config_sync" else ("update" if existing_key or created_key else "create"),
                        "list_type": system_view_list_type,
                        "transport_error": {
                            "http_status": api_error.http_status,
                            "backend_code": api_error.backend_code,
                            "category": api_error.category,
                        },
                        **_view_apply_failure_diagnostics(
                            patch=patch,
                            current_fields_by_name=current_fields_by_name,
                            backend_code=api_error.backend_code,
                            operation_phase=operation_phase,
                        ),
                    },
                }
                failed_views.append(failure_entry)
                view_results.append(failure_entry)
                continue
        view_mutation_ms += _duration_ms(view_mutation_started_at)
        successful_action_view_keys = {
            str(item.get("view_key") or "").strip()
            for item in view_results
            if str(item.get("status") or "") in {"created", "updated"} and str(item.get("view_key") or "").strip()
        }
        successful_action_view_names = {
            str(item.get("name") or "").strip()
            for item in view_results
            if str(item.get("status") or "") in {"created", "updated"} and str(item.get("name") or "").strip()
        }
        skipped_action_button_intents = [
            intent
            for intent in action_button_intents
            if bool(intent.get("requires_view_write"))
            and str(intent.get("view_key") or "").strip() not in successful_action_view_keys
            and str(intent.get("view_name") or "").strip() not in successful_action_view_names
        ]
        skipped_action_button_intent_ids = {id(intent) for intent in skipped_action_button_intents}
        runnable_action_button_intents = [
            intent for intent in action_button_intents if id(intent) not in skipped_action_button_intent_ids
        ]
        action_buttons_started_at = time.perf_counter()
        action_buttons_result = self._apply_view_action_buttons(
            profile=profile,
            app_key=app_key,
            intents=runnable_action_button_intents,
        )
        action_buttons_ms += _duration_ms(action_buttons_started_at)
        if skipped_action_button_intents:
            action_buttons_result.setdefault("skipped_due_to_view_write_failure", [])
            if isinstance(action_buttons_result["skipped_due_to_view_write_failure"], list):
                action_buttons_result["skipped_due_to_view_write_failure"].extend(
                    {
                        "source": intent.get("source"),
                        "index": intent.get("index"),
                        "view_key": intent.get("view_key"),
                        "view_name": intent.get("view_name"),
                        "action_buttons_count": len(intent.get("action_buttons") or []),
                    }
                    for intent in skipped_action_button_intents
                )
        action_button_write_executed = bool(action_buttons_result.get("write_executed"))
        action_button_write_succeeded = bool(action_buttons_result.get("write_succeeded"))
        publish_already_satisfied_by_action_buttons = bool(
            publish
            and action_buttons_result.get("publish_requested") is True
            and action_buttons_result.get("published") is True
        )
        action_buttons_verification = action_buttons_result.get("verification") if isinstance(action_buttons_result.get("verification"), dict) else {}
        action_buttons_verified = (
            bool(action_buttons_result.get("verified", True))
            and bool(action_buttons_verification.get("custom_buttons_verified", action_buttons_verification.get("action_buttons_verified", True)))
            and not skipped_action_button_intents
        )
        view_action_button_bindings_verified = bool(action_buttons_verification.get("view_button_bindings_verified", True)) and not skipped_action_button_intents
        action_buttons_failed = bool(action_button_intents) and not (action_buttons_verified and view_action_button_bindings_verified)
        action_buttons_retry_payload = action_buttons_result.get("retry_payload") if isinstance(action_buttons_result.get("retry_payload"), dict) else None
        needs_view_list_readback = bool(created or updated)
        verified_view_result: list[dict[str, Any]] | None = []
        verified_views_unavailable = False
        if needs_view_list_readback:
            try:
                view_readback_started_at = time.perf_counter()
                verified_view_result, verified_views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=True)
            except (QingflowApiError, RuntimeError) as error:
                view_readback_ms += _duration_ms(view_readback_started_at)
                api_error = _coerce_api_error(error)
                return finish(_failed_from_api_error(
                    "VIEWS_READ_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details=_with_state_read_blocked_details({"app_key": app_key}, resource="views", error=api_error),
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                ))
            view_readback_ms += _duration_ms(view_readback_started_at)
        verified_names = {
            _extract_view_name(item)
            for item in (verified_view_result or [])
            if isinstance(item, dict) and _extract_view_name(item)
        }
        verified_by_key = {
            _extract_view_key(item): item
            for item in (verified_view_result or [])
            if isinstance(item, dict) and _extract_view_key(item)
        }
        verified_view_keys_by_name: dict[str, list[str]] = {}
        for item in (verified_view_result or []):
            if not isinstance(item, dict):
                continue
            name = _extract_view_name(item)
            key = _extract_view_key(item)
            if name and key:
                verified_view_keys_by_name.setdefault(name, []).append(key)
        verification_by_view: list[dict[str, Any]] = []
        filter_readback_pending = False
        filter_mismatches: list[dict[str, Any]] = []
        query_condition_readback_pending = False
        query_condition_mismatches: list[dict[str, Any]] = []
        associated_resource_readback_pending = False
        associated_resource_mismatches: list[dict[str, Any]] = []
        button_readback_pending = False
        button_mismatches: list[dict[str, Any]] = []
        custom_button_readback_pending = False
        custom_button_readback_pending_entries: list[dict[str, Any]] = []
        view_config_readback_cache: dict[str, tuple[dict[str, Any], QingflowApiError | None]] = {}

        def read_view_config_for_verification(view_key: str) -> tuple[dict[str, Any], QingflowApiError | None]:
            cached = view_config_readback_cache.get(view_key)
            if cached is not None:
                return cached
            try:
                config_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
                config_result = (config_response.get("result") or {}) if isinstance(config_response.get("result"), dict) else {}
                cached = (config_result, None)
            except (QingflowApiError, RuntimeError) as error:
                cached = ({}, _coerce_api_error(error))
            view_config_readback_cache[view_key] = cached
            return cached

        view_verification_started_at = time.perf_counter()
        for item in view_results:
            status = str(item.get("status") or "")
            name = str(item.get("name") or "")
            item_view_key = str(item.get("view_key") or "").strip()
            present_in_readback: bool | None
            if status in {"created", "updated"}:
                if verified_views_unavailable:
                    present_in_readback = None
                elif item_view_key:
                    present_in_readback = item_view_key in verified_by_key
                else:
                    present_in_readback = name in verified_names
                verification_entry: dict[str, Any] = {
                    "name": name,
                    "view_key": item_view_key or None,
                    "type": item.get("type"),
                    "status": status,
                    "present_in_readback": present_in_readback,
                }
                system_view_sync = item.get("system_view_sync")
                if isinstance(system_view_sync, dict):
                    verification_entry["system_view_sync"] = deepcopy(system_view_sync)
                expected_filters = item.get("expected_filters") or []
                expected_query_conditions = item.get("expected_query_conditions") if isinstance(item.get("expected_query_conditions"), dict) else None
                expected_associated_resources = item.get("expected_associated_resources") if isinstance(item.get("expected_associated_resources"), dict) else None
                expected_buttons = item.get("expected_buttons") if isinstance(item.get("expected_buttons"), list) else None
                if expected_filters:
                    if verified_views_unavailable or not present_in_readback:
                        verification_entry["filters_verified"] = None
                        verification_entry["filter_readback_pending"] = True
                        filter_readback_pending = True
                    else:
                        verification_key = item_view_key
                        if not verification_key:
                            matched_keys = verified_view_keys_by_name.get(name) or []
                            if len(matched_keys) == 1:
                                verification_key = matched_keys[0]
                            else:
                                verification_entry["filters_verified"] = None
                                verification_entry["filter_readback_pending"] = True
                                verification_entry["readback_ambiguous"] = True
                                verification_entry["matching_view_keys"] = matched_keys
                                filter_readback_pending = True
                                verification_by_view.append(verification_entry)
                                continue
                        try:
                            config_result, config_error = read_view_config_for_verification(verification_key)
                            if config_error is not None:
                                raise config_error
                            actual_filters = _normalize_view_filter_groups_for_compare(config_result.get("viewgraphLimit"))
                            expected_filter_summary = _normalize_view_filter_groups_for_compare(expected_filters)
                            expected_data_scope = "CUSTOM" if expected_filter_summary else "ALL"
                            actual_data_scope = str(config_result.get("dataScope") or "").strip().upper() or None
                            (
                                filters_semantically_equivalent,
                                semantic_actual_filters,
                                lossy_filter_readbacks,
                            ) = _view_filter_groups_semantic_readback(expected_filter_summary, actual_filters)
                            filters_verified = filters_semantically_equivalent and actual_data_scope == expected_data_scope
                            public_expected_filters = _public_view_filter_groups_from_match_rules(
                                expected_filter_summary,
                                current_fields_by_name=current_fields_by_name,
                            )
                            public_actual_filters = _public_view_filter_groups_from_match_rules(
                                semantic_actual_filters,
                                current_fields_by_name=current_fields_by_name,
                            )
                            verification_entry["filters_verified"] = filters_verified
                            verification_entry["view_key"] = verification_key
                            verification_entry["expected_filters"] = public_expected_filters
                            verification_entry["actual_filters"] = public_actual_filters
                            if lossy_filter_readbacks:
                                verification_entry["filter_value_readback_degraded"] = True
                                verification_entry["filter_value_readback_warnings"] = lossy_filter_readbacks
                            verification_entry["expected_data_scope"] = expected_data_scope
                            verification_entry["actual_data_scope"] = actual_data_scope
                            if not filters_verified:
                                filter_mismatches.append(
                                    {
                                        "name": name,
                                        "type": item.get("type"),
                                        "expected_filters": public_expected_filters,
                                        "actual_filters": public_actual_filters,
                                        "expected_data_scope": expected_data_scope,
                                        "actual_data_scope": actual_data_scope,
                                    }
                                )
                        except (QingflowApiError, RuntimeError) as error:
                            api_error = _coerce_api_error(error)
                            verification_entry["filters_verified"] = None
                            verification_entry["filter_readback_pending"] = True
                            verification_entry["request_id"] = api_error.request_id
                            verification_entry["transport_error"] = {
                                "http_status": api_error.http_status,
                                "backend_code": api_error.backend_code,
                                "category": api_error.category,
                            }
                            filter_readback_pending = True
                if expected_query_conditions is not None:
                    if verified_views_unavailable or not present_in_readback:
                        verification_entry["query_conditions_verified"] = None
                        verification_entry["query_condition_readback_pending"] = True
                        query_condition_readback_pending = True
                    else:
                        verification_key = item_view_key
                        if not verification_key:
                            matched_keys = verified_view_keys_by_name.get(name) or []
                            if len(matched_keys) == 1:
                                verification_key = matched_keys[0]
                            else:
                                verification_entry["query_conditions_verified"] = None
                                verification_entry["query_condition_readback_pending"] = True
                                verification_entry["readback_ambiguous"] = True
                                verification_entry["matching_view_keys"] = matched_keys
                                query_condition_readback_pending = True
                                verification_by_view.append(verification_entry)
                                continue
                        try:
                            config_result, config_error = read_view_config_for_verification(verification_key)
                            if config_error is not None:
                                raise config_error
                            actual_query_conditions = _normalize_view_query_conditions_for_compare(config_result)
                            query_conditions_verified = actual_query_conditions == expected_query_conditions
                            verification_entry["query_conditions_verified"] = query_conditions_verified
                            verification_entry["view_key"] = verification_key
                            verification_entry["expected_query_conditions"] = deepcopy(expected_query_conditions)
                            verification_entry["actual_query_conditions"] = actual_query_conditions
                            if not query_conditions_verified:
                                query_condition_mismatches.append(
                                    {
                                        "name": name,
                                        "type": item.get("type"),
                                        "expected_query_conditions": deepcopy(expected_query_conditions),
                                        "actual_query_conditions": actual_query_conditions,
                                    }
                                )
                        except (QingflowApiError, RuntimeError) as error:
                            api_error = _coerce_api_error(error)
                            verification_entry["query_conditions_verified"] = None
                            verification_entry["query_condition_readback_pending"] = True
                            verification_entry["request_id"] = api_error.request_id
                            verification_entry["transport_error"] = {
                                "http_status": api_error.http_status,
                                "backend_code": api_error.backend_code,
                                "category": api_error.category,
                            }
                            query_condition_readback_pending = True
                if expected_associated_resources is not None:
                    if verified_views_unavailable or not present_in_readback:
                        verification_entry["associated_resources_verified"] = None
                        verification_entry["associated_resource_readback_pending"] = True
                        associated_resource_readback_pending = True
                    else:
                        verification_key = item_view_key
                        if not verification_key:
                            matched_keys = verified_view_keys_by_name.get(name) or []
                            if len(matched_keys) == 1:
                                verification_key = matched_keys[0]
                            else:
                                verification_entry["associated_resources_verified"] = None
                                verification_entry["associated_resource_readback_pending"] = True
                                verification_entry["readback_ambiguous"] = True
                                verification_entry["matching_view_keys"] = matched_keys
                                associated_resource_readback_pending = True
                                verification_by_view.append(verification_entry)
                                continue
                        try:
                            config_result, config_error = read_view_config_for_verification(verification_key)
                            if config_error is not None:
                                raise config_error
                            actual_associated_resources = _extract_view_associated_resources_config(
                                config_result,
                                available_resources=associated_resources,
                            )
                            associated_resources_verified = _associated_resources_config_matches(
                                expected_associated_resources,
                                actual_associated_resources,
                            )
                            verification_entry["associated_resources_verified"] = associated_resources_verified
                            verification_entry["view_key"] = verification_key
                            verification_entry["expected_associated_resources"] = deepcopy(expected_associated_resources)
                            verification_entry["actual_associated_resources"] = actual_associated_resources
                            if not associated_resources_verified:
                                associated_resource_mismatches.append(
                                    {
                                        "name": name,
                                        "type": item.get("type"),
                                        "expected_associated_resources": deepcopy(expected_associated_resources),
                                        "actual_associated_resources": actual_associated_resources,
                                    }
                                )
                        except (QingflowApiError, RuntimeError) as error:
                            api_error = _coerce_api_error(error)
                            verification_entry["associated_resources_verified"] = None
                            verification_entry["associated_resource_readback_pending"] = True
                            verification_entry["request_id"] = api_error.request_id
                            verification_entry["transport_error"] = {
                                "http_status": api_error.http_status,
                                "backend_code": api_error.backend_code,
                                "category": api_error.category,
                            }
                            associated_resource_readback_pending = True
                if expected_buttons is not None:
                    if verified_views_unavailable or not present_in_readback:
                        verification_entry["buttons_verified"] = None
                        verification_entry["button_readback_pending"] = True
                        button_readback_pending = True
                    else:
                        verification_key = item_view_key
                        if not verification_key:
                            matched_keys = verified_view_keys_by_name.get(name) or []
                            if len(matched_keys) == 1:
                                verification_key = matched_keys[0]
                            else:
                                verification_entry["buttons_verified"] = None
                                verification_entry["button_readback_pending"] = True
                                verification_entry["readback_ambiguous"] = True
                                verification_entry["matching_view_keys"] = matched_keys
                                button_readback_pending = True
                                verification_by_view.append(verification_entry)
                                continue
                        try:
                            config_result, config_error = read_view_config_for_verification(verification_key)
                            if config_error is not None:
                                raise config_error
                            actual_buttons = _normalize_view_buttons_for_compare(config_result)
                            button_comparison = _compare_view_button_summaries(
                                expected=expected_buttons,
                                actual=actual_buttons,
                            )
                            buttons_verified = bool(button_comparison.get("verified"))
                            verification_entry["buttons_verified"] = buttons_verified
                            verification_entry["view_key"] = verification_key
                            verification_entry["expected_buttons"] = deepcopy(expected_buttons)
                            verification_entry["actual_buttons"] = actual_buttons
                            if button_comparison.get("custom_button_readback_pending"):
                                verification_entry["custom_button_readback_pending"] = True
                                verification_entry["pending_custom_buttons"] = deepcopy(button_comparison.get("pending_custom_buttons") or [])
                                custom_button_readback_pending = True
                                custom_button_readback_pending_entries.append(
                                    {
                                        "name": name,
                                        "type": item.get("type"),
                                        "view_key": verification_key,
                                        "pending_custom_buttons": deepcopy(button_comparison.get("pending_custom_buttons") or []),
                                    }
                                )
                            elif not buttons_verified:
                                button_mismatches.append(
                                    {
                                        "name": name,
                                        "type": item.get("type"),
                                        "expected_buttons": deepcopy(expected_buttons),
                                        "actual_buttons": actual_buttons,
                                    }
                                )
                        except (QingflowApiError, RuntimeError) as error:
                            api_error = _coerce_api_error(error)
                            verification_entry["buttons_verified"] = None
                            verification_entry["button_readback_pending"] = True
                            verification_entry["request_id"] = api_error.request_id
                            verification_entry["transport_error"] = {
                                "http_status": api_error.http_status,
                                "backend_code": api_error.backend_code,
                                "category": api_error.category,
                            }
                            button_readback_pending = True
                verification_by_view.append(verification_entry)
            elif status == "removed":
                verification_by_view.append(
                    {
                        "name": name,
                        "view_key": item.get("view_key"),
                        "type": item.get("type"),
                        "status": "removed",
                        "present_in_readback": False,
                        "removed_verified": True,
                        "delete_executed": bool(item.get("delete_executed")),
                        "readback_status": item.get("readback_status") or "deleted",
                        "safe_to_retry_delete": False,
                    }
                )
            elif status == "readback_pending" and item.get("operation") == "delete":
                readback_status = str(item.get("readback_status") or "unavailable")
                verification_by_view.append(
                    {
                        "name": name,
                        "view_key": item.get("view_key"),
                        "type": item.get("type"),
                        "status": "readback_pending",
                        "present_in_readback": True if readback_status == "still_exists" else None,
                        "removed_verified": False,
                        "delete_executed": bool(item.get("delete_executed")),
                        "readback_status": readback_status,
                        "safe_to_retry_delete": False,
                        "error_code": item.get("error_code"),
                    }
                )
            else:
                verification_by_view.append(
                    {
                        "name": item.get("name"),
                        "type": item.get("type"),
                        "status": "failed",
                        "present_in_readback": None,
                        "error_code": item.get("error_code"),
                    }
                )
        view_verification_ms += _duration_ms(view_verification_started_at)
        removed_delete_results = [
            item
            for item in view_results
            if item.get("operation") == "delete" and bool(item.get("delete_executed"))
        ]
        removed_verified = all(str(item.get("readback_status") or "") == "deleted" for item in removed_delete_results)
        delete_readback_pending = any(str(item.get("readback_status") or "") != "deleted" for item in removed_delete_results)
        verified = (
            (not verified_views_unavailable)
            and all(name in verified_names for name in created + updated)
            and removed_verified
        )
        view_filters_verified = verified and not filter_readback_pending and not filter_mismatches
        view_query_conditions_verified = verified and not query_condition_readback_pending and not query_condition_mismatches
        view_associated_resources_verified = verified and not associated_resource_readback_pending and not associated_resource_mismatches
        view_buttons_verified = verified and not button_readback_pending and not button_mismatches and not custom_button_readback_pending
        noop = not created and not updated and not removed and not action_button_write_executed
        if failed_views:
            successful_changes = bool(created or updated or removed or action_button_write_succeeded)
            first_failure = failed_views[0]
            response = {
                "status": "partial_success" if successful_changes else "failed",
                "error_code": "VIEW_APPLY_PARTIAL" if successful_changes else "VIEW_APPLY_FAILED",
                "recoverable": True,
                "message": "applied some view patches; at least one view failed" if successful_changes else "one or more view patches failed",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
                "details": {
                    "per_view_results": view_results,
                    "filter_mismatches": filter_mismatches,
                    "query_condition_mismatches": query_condition_mismatches,
                    "associated_resource_mismatches": associated_resource_mismatches,
                    "button_mismatches": button_mismatches,
                    **({"action_buttons_result": action_buttons_result} if action_button_intents else {}),
                    **(
                        {"custom_button_readback_pending": deepcopy(custom_button_readback_pending_entries)}
                        if custom_button_readback_pending_entries
                        else {}
                    ),
                },
                "request_id": first_failure.get("request_id"),
                "suggested_next_call": {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                "backend_code": first_failure.get("backend_code"),
                "http_status": first_failure.get("http_status"),
                "noop": noop,
                "warnings": (
                    (
                        [_warning("VIEW_FILTERS_UNVERIFIED", "view definitions may exist, but saved filter behavior is not fully verified")]
                        if (filter_readback_pending or filter_mismatches)
                        else []
                    )
                    + (
                        [_warning("VIEW_QUERY_CONDITIONS_UNVERIFIED", "view definitions may exist, but query condition behavior is not fully verified")]
                        if (query_condition_readback_pending or query_condition_mismatches)
                        else []
                    )
                    + (
                        [_warning("VIEW_ASSOCIATED_RESOURCES_UNVERIFIED", "view definitions may exist, but associated resource visibility is not fully verified")]
                        if (associated_resource_readback_pending or associated_resource_mismatches)
                        else []
                    )
                    + (
                        [_warning("VIEW_BUTTONS_UNVERIFIED", "view definitions may exist, but saved button behavior is not fully verified")]
                        if (button_readback_pending or button_mismatches)
                        else []
                    )
                    + (
                        [_warning("VIEW_ACTION_BUTTONS_UNVERIFIED", "view definitions may exist, but inline action button creation or binding is not fully verified")]
                        if action_buttons_failed
                        else []
                    )
                    + (
                        [_warning("VIEW_CUSTOM_BUTTON_READBACK_PENDING", "system buttons verified, but draft custom button bindings are not fully visible through view readback yet")]
                        if custom_button_readback_pending
                        else []
                    )
                ),
                "verification": {
                    "views_verified": verified,
                    "view_filters_verified": view_filters_verified,
                    "view_query_conditions_verified": view_query_conditions_verified,
                    "view_associated_resources_verified": view_associated_resources_verified,
                    "view_buttons_verified": view_buttons_verified,
                    "action_buttons_verified": action_buttons_verified,
                    "view_button_bindings_verified": view_action_button_bindings_verified,
                    "views_read_unavailable": verified_views_unavailable,
                    "by_view": verification_by_view,
                    "custom_button_readback_pending": custom_button_readback_pending,
                    "custom_button_readback_pending_entries": deepcopy(custom_button_readback_pending_entries),
                },
                "app_key": app_key,
                "app_name": app_name,
                "views_diff": {"created": created, "updated": updated, "removed": removed, "failed": failed_views},
                "verified": verified and view_filters_verified and view_query_conditions_verified and view_associated_resources_verified and view_buttons_verified and action_buttons_verified and view_action_button_bindings_verified,
                "write_executed": bool(created or updated or removed or action_button_write_executed),
                "write_succeeded": bool(created or updated or removed or action_button_write_succeeded),
                "safe_to_retry": not bool(created or updated or removed or action_button_write_executed),
            }
            return finish_with_publish(response)
        warnings: list[dict[str, Any]] = []
        if filter_readback_pending or filter_mismatches:
            warnings.append(_warning("VIEW_FILTERS_UNVERIFIED", "view definitions were applied, but saved filter behavior is not fully verified"))
        if query_condition_readback_pending or query_condition_mismatches:
            warnings.append(_warning("VIEW_QUERY_CONDITIONS_UNVERIFIED", "view definitions were applied, but query condition behavior is not fully verified"))
        if associated_resource_readback_pending or associated_resource_mismatches:
            warnings.append(_warning("VIEW_ASSOCIATED_RESOURCES_UNVERIFIED", "view definitions were applied, but associated resource visibility is not fully verified"))
        if button_readback_pending or button_mismatches:
            warnings.append(_warning("VIEW_BUTTONS_UNVERIFIED", "view definitions were applied, but saved button behavior is not fully verified"))
        if action_buttons_failed:
            warnings.append(_warning("VIEW_ACTION_BUTTONS_UNVERIFIED", "view definitions were applied, but inline action button creation or binding is not fully verified"))
        if custom_button_readback_pending:
            warnings.append(
                _warning(
                    "VIEW_CUSTOM_BUTTON_READBACK_PENDING",
                    "system buttons verified, but draft custom button bindings are not fully visible through view readback yet",
                )
            )
        if delete_readback_pending:
            warnings.append(
                _warning(
                    "VIEW_DELETE_READBACK_PENDING",
                    "view delete was sent, but deletion readback is not fully verified; do not blindly repeat delete",
                )
            )
        all_verified = (
            verified
            and view_filters_verified
            and view_query_conditions_verified
            and view_associated_resources_verified
            and view_buttons_verified
            and action_buttons_verified
            and view_action_button_bindings_verified
        )
        action_buttons_error_code = str(action_buttons_result.get("error_code") or "VIEW_ACTION_BUTTONS_APPLY_FAILED") if action_buttons_failed else None
        response = {
            "status": "success" if all_verified else "partial_success",
            "error_code": None if all_verified else (action_buttons_error_code if action_buttons_failed else "VIEW_BUTTON_READBACK_MISMATCH" if button_mismatches else "VIEW_ASSOCIATED_RESOURCE_READBACK_MISMATCH" if associated_resource_mismatches else "VIEW_QUERY_CONDITION_READBACK_MISMATCH" if query_condition_mismatches else "VIEW_FILTER_READBACK_MISMATCH" if filter_mismatches else "VIEWS_READBACK_PENDING"),
            "recoverable": not all_verified,
            "message": (
                "applied view patch"
                if all_verified
                else "applied view patch; inline action buttons did not fully verify"
                if action_buttons_failed
                else "applied view patch; buttons did not fully verify"
                if button_mismatches
                else "applied view patch; associated resources did not fully verify"
                if associated_resource_mismatches
                else "applied view patch; query conditions did not fully verify"
                if query_condition_mismatches
                else "applied view patch; filters did not fully verify"
                if filter_mismatches
                else "applied view patch; views readback pending"
            ),
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator], "view.associated_resources.limit_type": ["all", "select"]},
            "details": {
                **({"filter_mismatches": filter_mismatches} if filter_mismatches else {}),
                **({"query_condition_mismatches": query_condition_mismatches} if query_condition_mismatches else {}),
                **({"associated_resource_mismatches": associated_resource_mismatches} if associated_resource_mismatches else {}),
                **({"button_mismatches": button_mismatches} if button_mismatches else {}),
                **({"action_buttons_result": action_buttons_result} if action_button_intents else {}),
                **(
                    {"custom_button_readback_pending": deepcopy(custom_button_readback_pending_entries)}
                    if custom_button_readback_pending_entries
                    else {}
                ),
            },
            "request_id": None,
            "suggested_next_call": None if all_verified else (action_buttons_retry_payload if action_buttons_failed and action_buttons_retry_payload else {"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}}),
            "noop": noop,
            "warnings": warnings,
            "verification": {
                "views_verified": verified,
                "view_filters_verified": view_filters_verified,
                "view_query_conditions_verified": view_query_conditions_verified,
                "view_associated_resources_verified": view_associated_resources_verified,
                "view_buttons_verified": view_buttons_verified,
                "action_buttons_verified": action_buttons_verified,
                "view_button_bindings_verified": view_action_button_bindings_verified,
                "views_read_unavailable": verified_views_unavailable,
                "filter_readback_pending": filter_readback_pending,
                "query_condition_readback_pending": query_condition_readback_pending,
                "associated_resource_readback_pending": associated_resource_readback_pending,
                "button_readback_pending": button_readback_pending,
                "delete_readback_pending": delete_readback_pending,
                "custom_button_readback_pending": custom_button_readback_pending,
                "custom_button_readback_pending_entries": deepcopy(custom_button_readback_pending_entries),
                "by_view": verification_by_view,
            },
            "app_key": app_key,
            "app_name": app_name,
            "views_diff": {"created": created, "updated": updated, "removed": removed, "failed": []},
            "verified": all_verified,
            "write_executed": bool(created or updated or removed or action_button_write_executed),
            "write_succeeded": bool(created or updated or removed or action_button_write_succeeded),
            "safe_to_retry": not bool(created or updated or removed or action_button_write_executed),
        }
        return finish_with_publish(response)

    def app_publish_verify(
        self,
        *,
        profile: str,
        app_key: str,
        expected_package_tag_id: int | None = None,
    ) -> JSONObject:
        apply_started_at = time.perf_counter()
        initial_base_read_ms = 0
        initial_views_read_ms = 0
        edit_version_ms = 0
        publish_ms = 0
        base_readback_ms = 0
        views_readback_ms = 0
        normalized_args = {"app_key": app_key, "expected_package_tag_id": expected_package_tag_id}

        def finish(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                initial_base_read_ms=initial_base_read_ms,
                initial_views_read_ms=initial_views_read_ms,
                edit_version_ms=edit_version_ms,
                publish_ms=publish_ms,
                base_readback_ms=base_readback_ms,
                views_readback_ms=views_readback_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        initial_base_read_started_at = time.perf_counter()
        try:
            base_before = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            initial_base_read_ms += _duration_ms(initial_base_read_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "APP_READ_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        initial_base_read_ms += _duration_ms(initial_base_read_started_at)
        tag_ids_before = _coerce_int_list(base_before.get("tagIds"))
        app_name_before = str(base_before.get("formTitle") or base_before.get("title") or base_before.get("appName") or "").strip() or None
        already_published = bool(base_before.get("appPublishStatus") in {1, 2})
        package_already_attached = None if not expected_package_tag_id else expected_package_tag_id in tag_ids_before
        initial_views_read_started_at = time.perf_counter()
        try:
            views_before, views_before_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=True)
        except (QingflowApiError, RuntimeError) as error:
            initial_views_read_ms += _duration_ms(initial_views_read_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "VIEWS_READ_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key},
                    suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        initial_views_read_ms += _duration_ms(initial_views_read_started_at)
        views_before = views_before or []
        if already_published and package_already_attached is not False and isinstance(views_before, list) and not views_before_unavailable:
            return finish(
                {
                    "status": "success",
                    "error_code": None,
                    "recoverable": False,
                    "message": "app already published and verified",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {},
                    "details": {},
                    "request_id": None,
                    "suggested_next_call": None,
                    "noop": True,
                    "warnings": [],
                    "verification": {"published": True, "package_attached": package_already_attached, "views_ok": True},
                    "app_key": app_key,
                    "app_name": app_name_before,
                    "published": True,
                    "package_attached": package_already_attached,
                    "tag_ids_after": tag_ids_before,
                    "views_ok": True,
                    "verified": True,
                    "write_executed": False,
                    "write_succeeded": False,
                    "safe_to_retry": True,
                }
            )
        edit_version_started_at = time.perf_counter()
        try:
            version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            edit_version_ms += _duration_ms(edit_version_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "PUBLISH_PRECHECK_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key, "phase": "prepare_publish_edit_version"},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        edit_version_ms += _duration_ms(edit_version_started_at)
        edit_version_no = _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or 1
        publish_started_at = time.perf_counter()
        try:
            self.apps.app_publish(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
            self.apps.app_edit_finished(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
        except (QingflowApiError, RuntimeError) as error:
            publish_ms += _duration_ms(publish_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "PUBLISH_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key, "edit_version_no": edit_version_no},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                )
            )
        publish_ms += _duration_ms(publish_started_at)
        base_readback_started_at = time.perf_counter()
        try:
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            base_readback_ms += _duration_ms(base_readback_started_at)
            api_error = _coerce_api_error(error)
            result = _post_write_readback_pending_result(
                error_code="PUBLISH_READBACK_PENDING",
                message="published app; app base readback is unavailable",
                normalized_args=normalized_args,
                details={"app_key": app_key, "edit_version_no": edit_version_no, "readback_error": _transport_error_payload(api_error)},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
                request_id=api_error.request_id,
                backend_code=api_error.backend_code,
                http_status=None if api_error.http_status == 404 else api_error.http_status,
            )
            result.update({"app_key": app_key, "published": None, "verified": False})
            return finish(result)
        base_readback_ms += _duration_ms(base_readback_started_at)
        tag_ids_after = _coerce_int_list(base.get("tagIds"))
        app_name_after = str(base.get("formTitle") or base.get("title") or base.get("appName") or app_name_before or "").strip() or None
        package_attached = None if not expected_package_tag_id else expected_package_tag_id in tag_ids_after
        views_readback_started_at = time.perf_counter()
        try:
            views, views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=True)
        except (QingflowApiError, RuntimeError) as error:
            views_readback_ms += _duration_ms(views_readback_started_at)
            api_error = _coerce_api_error(error)
            result = _post_write_readback_pending_result(
                error_code="VIEWS_READBACK_PENDING",
                message="published app; views readback is unavailable",
                normalized_args=normalized_args,
                details={"app_key": app_key, "edit_version_no": edit_version_no, "readback_error": _transport_error_payload(api_error)},
                suggested_next_call={"tool_name": "app_get_views", "arguments": {"profile": profile, "app_key": app_key}},
                request_id=api_error.request_id,
                backend_code=api_error.backend_code,
                http_status=None if api_error.http_status == 404 else api_error.http_status,
            )
            result.update({"app_key": app_key, "app_name": app_name_after, "published": bool(base.get("appPublishStatus") in {1, 2}), "verified": False})
            return finish(result)
        views_readback_ms += _duration_ms(views_readback_started_at)
        views = views or []
        views_ok = isinstance(views, list) and not views_unavailable
        verified = bool(base.get("appPublishStatus") in {1, 2}) and (package_attached is not False) and views_ok
        warnings = _publish_verify_warnings(
            package_attached=package_attached,
            views_unavailable=views_unavailable,
            verified=verified,
        )
        return finish(
            {
                "status": "success" if verified else "partial_success",
                "error_code": None if not views_unavailable else "VIEWS_READBACK_PENDING",
                "recoverable": bool(views_unavailable),
                "message": "published and verified app" if not views_unavailable else "published app; views readback pending",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None
                if package_attached is not False
                else {
                    "tool_name": "package_attach_app",
                    "arguments": {"profile": profile, "tag_id": expected_package_tag_id, "app_key": app_key},
                },
                "noop": False,
                "warnings": warnings,
                "verification": {"published": bool(base.get("appPublishStatus") in {1, 2}), "package_attached": package_attached, "views_ok": views_ok, "views_read_unavailable": views_unavailable},
                "app_key": app_key,
                "app_name": app_name_after,
                "published": bool(base.get("appPublishStatus") in {1, 2}),
                "package_attached": package_attached,
                "tag_ids_after": tag_ids_after,
                "views_ok": views_ok,
                "verified": verified,
                "write_executed": True,
                "write_succeeded": True,
                "safe_to_retry": False,
            }
        )

    def _expand_chart_partial_patches(
        self,
        *,
        profile: str,
        app_key: str,
        existing_by_id: dict[str, dict[str, Any]],
        existing_by_name: dict[str, list[dict[str, Any]]],
        patch_charts: list[ChartPartialPatch],
    ) -> tuple[list[ChartUpsertPatch], list[dict[str, Any]], list[dict[str, Any]]]:
        expanded: list[ChartUpsertPatch] = []
        issues: list[dict[str, Any]] = []
        results: list[dict[str, Any]] = []
        for index, patch in enumerate(patch_charts):
            chart_id = str(patch.chart_id or "").strip()
            if chart_id:
                if chart_id not in existing_by_id:
                    issue = {
                        "error_code": "CHART_NOT_FOUND",
                        "reason_path": f"patch_charts[{index}].chart_id",
                        "chart_id": chart_id,
                        "message": "chart_id does not exist under this app",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
            else:
                name = str(patch.name or "").strip()
                matches = existing_by_name.get(name, [])
                if len(matches) != 1:
                    issue = {
                        "error_code": "AMBIGUOUS_CHART" if matches else "CHART_NOT_FOUND",
                        "reason_path": f"patch_charts[{index}].name",
                        "name": name,
                        "candidate_chart_ids": [
                            _extract_chart_identifier(item)
                            for item in matches
                            if _extract_chart_identifier(item)
                        ],
                        "message": "patch_charts[] must target a single existing chart; use chart_id when names are duplicated",
                    }
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
                chart_id = _extract_chart_identifier(matches[0])
                if not chart_id:
                    issue = {"error_code": "CHART_ID_MISSING", "reason_path": f"patch_charts[{index}].name", "name": name}
                    issues.append(issue)
                    results.append({"index": index, "status": "failed", **issue})
                    continue
            try:
                base = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
                config = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id).get("result") or {}
                if not isinstance(base, dict):
                    base = {}
                if not isinstance(config, dict):
                    config = {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                issue = {
                    "error_code": "CHART_PATCH_DETAIL_READ_FAILED",
                    "reason_path": f"patch_charts[{index}]",
                    "chart_id": chart_id,
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            patch_payload = _chart_upsert_payload_from_existing(chart_id=chart_id, base=base, config=config)
            normalized_set, set_issues = _normalize_chart_partial_set(patch.set, reason_path=f"patch_charts[{index}].set")
            normalized_unset, unset_issues = _normalize_chart_partial_unset(patch.unset, reason_path=f"patch_charts[{index}].unset")
            if set_issues or unset_issues:
                patch_issues = [*set_issues, *unset_issues]
                issues.extend(patch_issues)
                results.append({"index": index, "status": "failed", "chart_id": chart_id, "issues": patch_issues})
                continue
            explicit_set_paths = set(normalized_set)
            for key, value in normalized_set.items():
                if key in {"config", "visibility"} and isinstance(value, dict):
                    merged_value = deepcopy(patch_payload.get(key) if isinstance(patch_payload.get(key), dict) else {})
                    _deep_merge_public_config(merged_value, value)
                    patch_payload[key] = merged_value
                else:
                    patch_payload[key] = value
            for key in normalized_unset:
                if key == "filters":
                    patch_payload["filters"] = []
                    explicit_set_paths.add("filters")
                elif key in {"question_config", "user_config"}:
                    patch_payload[key] = []
                    explicit_set_paths.add(key)
                elif key == "visibility":
                    patch_payload.pop("visibility", None)
            try:
                expanded_patch = ChartUpsertPatch.model_validate(patch_payload)
            except Exception as error:
                issue = {
                    "error_code": "CHART_PATCH_HYDRATION_FAILED",
                    "reason_path": f"patch_charts[{index}]",
                    "chart_id": chart_id,
                    "message": str(error),
                    "hydrated_payload": _compact_dict(patch_payload),
                }
                issues.append(issue)
                results.append({"index": index, "status": "failed", **issue})
                continue
            # Preserve model_fields_set semantics so config generation knows which public fields were explicitly replaced.
            expanded_patch.__pydantic_fields_set__ = set(explicit_set_paths)
            expanded.append(expanded_patch)
            results.append(
                {
                    "index": index,
                    "status": "expanded",
                    "chart_id": chart_id,
                    "set_paths": sorted(normalized_set),
                    "unset_paths": sorted(normalized_unset),
                }
            )
        return expanded, issues, results

    def _verify_view_deleted_by_key(self, *, profile: str, view_key: str) -> JSONObject:
        try:
            self.views.view_get_config(profile=profile, viewgraph_key=view_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _delete_readback_is_not_found(api_error):
                return {
                    "view_key": view_key,
                    "operation": "delete",
                    "status": "removed",
                    "delete_executed": True,
                    "readback_status": "deleted",
                    "safe_to_retry_delete": False,
                }
            return {
                "view_key": view_key,
                "operation": "delete",
                "status": "readback_pending",
                "delete_executed": True,
                "readback_status": "unavailable",
                "safe_to_retry_delete": False,
                "error_code": "VIEW_DELETE_READBACK_UNAVAILABLE",
                "message": "delete request completed, but view existence could not be verified by view_key readback",
                "request_id": api_error.request_id,
                "backend_code": api_error.backend_code,
                "http_status": None if api_error.http_status == 404 else api_error.http_status,
                "transport_error": _transport_error_payload(api_error),
            }
        return {
            "view_key": view_key,
            "operation": "delete",
            "status": "readback_pending",
            "delete_executed": True,
            "readback_status": "still_exists",
            "safe_to_retry_delete": False,
            "error_code": "VIEW_DELETE_READBACK_STILL_EXISTS",
            "message": "delete request completed, but the view still exists during view_key readback",
        }

    def _verify_custom_button_deleted_by_id(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
        try:
            def runner(_: Any, context: BackendRequestContext) -> object:
                return self.buttons.backend.request(
                    "GET",
                    context,
                    f"/app/{app_key}/customButton/{button_id}",
                    params={"beingDraft": True},
                )

            self.buttons._run(profile, runner, tool_name="自定义按钮删除校验")
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _delete_readback_is_not_found(api_error):
                return {
                    "button_id": button_id,
                    "operation": "delete",
                    "status": "removed",
                    "delete_executed": True,
                    "readback_status": "deleted",
                    "safe_to_retry_delete": False,
                }
            return {
                "button_id": button_id,
                "operation": "delete",
                "status": "readback_pending",
                "delete_executed": True,
                "readback_status": "unavailable",
                "safe_to_retry_delete": False,
                "error_code": "CUSTOM_BUTTON_DELETE_READBACK_UNAVAILABLE",
                "message": "delete request completed, but custom button existence could not be verified by button_id readback",
                "request_id": api_error.request_id,
                "backend_code": api_error.backend_code,
                "http_status": None if api_error.http_status == 404 else api_error.http_status,
                "transport_error": _transport_error_payload(api_error),
            }
        return {
            "button_id": button_id,
            "operation": "delete",
            "status": "readback_pending",
            "delete_executed": True,
            "readback_status": "still_exists",
            "safe_to_retry_delete": False,
            "error_code": "CUSTOM_BUTTON_DELETE_READBACK_STILL_EXISTS",
            "message": "delete request completed, but the custom button still exists during button_id readback",
        }

    def _verify_associated_resources_deleted_by_pool(
        self,
        *,
        deleted_items: list[JSONObject],
        resources: list[dict[str, Any]],
        readback_failed: bool,
    ) -> list[JSONObject]:
        existing_by_id = _associated_resource_index(resources) if not readback_failed else {}
        verified_items: list[JSONObject] = []
        for item in deleted_items:
            associated_item_id = _coerce_positive_int(item.get("associated_item_id"))
            verified = deepcopy(item)
            verified["operation"] = "remove"
            verified["delete_executed"] = True
            verified["safe_to_retry_delete"] = False
            if associated_item_id is None or readback_failed:
                verified["status"] = "readback_pending"
                verified["readback_status"] = "unavailable"
                verified["error_code"] = "ASSOCIATED_RESOURCE_DELETE_READBACK_UNAVAILABLE"
                verified["message"] = "delete request completed, but associated resource pool readback is unavailable"
            elif associated_item_id in existing_by_id:
                verified["status"] = "readback_pending"
                verified["readback_status"] = "still_exists"
                verified["error_code"] = "ASSOCIATED_RESOURCE_DELETE_READBACK_STILL_EXISTS"
                verified["message"] = "delete request completed, but the associated resource still exists in pool readback"
            else:
                verified["status"] = "removed"
                verified["readback_status"] = "deleted"
                verified.pop("error_code", None)
                verified.pop("message", None)
            verified_items.append(verified)
        return verified_items

    def _verify_chart_deleted_by_id(self, *, profile: str, chart_id: str) -> JSONObject:
        base_result: dict[str, Any] | None = None
        try:
            raw = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
            base_result = raw if isinstance(raw, dict) else {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _chart_delete_readback_is_not_found(api_error):
                return {
                    "chart_id": chart_id,
                    "operation": "delete",
                    "status": "removed",
                    "delete_executed": True,
                    "readback_status": "deleted",
                    "safe_to_retry_delete": False,
                }
            return {
                "chart_id": chart_id,
                "operation": "delete",
                "status": "readback_pending",
                "delete_executed": True,
                "readback_status": "unavailable",
                "safe_to_retry_delete": False,
                "error_code": "CHART_DELETE_READBACK_UNAVAILABLE",
                "message": "delete request completed, but chart existence could not be verified by chart_id readback",
                "request_id": api_error.request_id,
                "backend_code": api_error.backend_code,
                "http_status": None if api_error.http_status == 404 else api_error.http_status,
                "transport_error": _transport_error_payload(api_error),
            }
        return {
            "chart_id": chart_id,
            "operation": "delete",
            "status": "readback_pending",
            "delete_executed": True,
            "readback_status": "still_exists",
            "safe_to_retry_delete": False,
            "error_code": "CHART_DELETE_READBACK_STILL_EXISTS",
            "message": "delete request completed, but the chart still exists during chart_id readback",
            "readback_name": base_result.get("chartName") or base_result.get("name") if isinstance(base_result, dict) else None,
        }

    def chart_apply(self, *, profile: str, request: ChartApplyRequest) -> JSONObject:
        apply_started_at = time.perf_counter()
        chart_inventory_ms: int | None = None
        chart_upsert_ms: int | None = None
        chart_delete_ms: int | None = None
        chart_reorder_ms: int | None = None
        chart_readback_ms: int | None = None
        normalized_args = request.model_dump(mode="json")
        permission_outcomes: list[PermissionCheckOutcome] = []
        app_result = self.app_resolve(profile=profile, app_key=request.app_key)
        if app_result.get("status") != "success":
            return app_result
        resolved_outcome = _permission_outcome_from_result(app_result)
        if resolved_outcome is not None:
            permission_outcomes.append(resolved_outcome)
        app_key = str(app_result.get("app_key") or request.app_key)
        app_name = str(app_result.get("app_name") or "").strip() or None
        permission_outcome = self._guard_app_permission(
            profile=profile,
            app_key=app_key,
            required_permission="data_manage",
            normalized_args=normalized_args,
        )
        if permission_outcome.block is not None:
            return permission_outcome.block
        permission_outcomes.append(permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                chart_inventory_ms=chart_inventory_ms,
                chart_upsert_ms=chart_upsert_ms,
                chart_delete_ms=chart_delete_ms,
                chart_reorder_ms=chart_reorder_ms,
                chart_readback_ms=chart_readback_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return _apply_permission_outcomes(response, *permission_outcomes)

        fields: list[dict[str, Any]] = []
        qingbi_fields: list[Any] = []
        existing_chart_items: list[Any] = []
        existing_chart_list_source: str | None = None
        upsert_charts = list(request.upsert_charts)
        direct_create_upserts = [
            patch for patch in upsert_charts if not str(patch.chart_id or "").strip()
        ]
        explicit_target_upserts = [
            patch for patch in upsert_charts if str(patch.chart_id or "").strip()
        ]
        pure_direct_create = bool(direct_create_upserts) and len(direct_create_upserts) == len(upsert_charts) and not (
            request.patch_charts or request.remove_chart_ids or request.reorder_chart_ids
        )
        chart_inventory_skipped = False
        chart_final_readback_skipped = False
        needs_schema_and_qingbi_fields = bool(upsert_charts or request.patch_charts)
        needs_existing_chart_inventory = bool(explicit_target_upserts or request.patch_charts or request.reorder_chart_ids)
        needs_chart_inventory = bool(needs_schema_and_qingbi_fields or needs_existing_chart_inventory)
        if needs_chart_inventory:
            chart_inventory_started_at = time.perf_counter()
            try:
                if needs_schema_and_qingbi_fields:
                    schema_state = self._load_base_schema_state(profile=profile, app_key=app_key)
                    parsed = schema_state.get("parsed") if isinstance(schema_state.get("parsed"), dict) else {}
                    fields = parsed.get("fields") if isinstance(parsed.get("fields"), list) else []
                    qingbi_fields = self.charts.qingbi_report_list_fields(profile=profile, app_key=app_key).get("items") or []
                if needs_existing_chart_inventory:
                    existing_chart_items, existing_chart_list_source = self._load_chart_list_for_builder(profile=profile, app_key=app_key)
                elif direct_create_upserts:
                    chart_inventory_skipped = True
                    existing_chart_list_source = "skipped_for_direct_create"
            except (QingflowApiError, RuntimeError) as error:
                chart_inventory_ms = _duration_ms(chart_inventory_started_at)
                api_error = _coerce_api_error(error)
                return finalize(_failed_from_api_error(
                    "CHART_APPLY_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details=_with_state_read_blocked_details({"app_key": app_key}, resource="chart", error=api_error),
                    suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
                ))
            chart_inventory_ms = _duration_ms(chart_inventory_started_at)

        field_lookup = _build_public_field_lookup(fields)
        qingbi_fields_by_id = {
            str(item.get("fieldId") or ""): deepcopy(item)
            for item in qingbi_fields
            if isinstance(item, dict) and item.get("fieldId")
        }
        chart_field_lookup = _build_qingbi_chart_field_lookup(
            app_key=app_key,
            qingbi_fields=[item for item in qingbi_fields if isinstance(item, dict)],
            field_lookup=field_lookup,
        )
        existing_by_id = {
            _extract_chart_identifier(item): deepcopy(item)
            for item in existing_chart_items
            if isinstance(item, dict) and _extract_chart_identifier(item)
        }
        existing_by_name: dict[str, list[dict[str, Any]]] = {}
        for item in existing_chart_items:
            if not isinstance(item, dict):
                continue
            item_name = str(item.get("chartName") or "").strip()
            if not item_name:
                continue
            existing_by_name.setdefault(item_name, []).append(deepcopy(item))

        if request.patch_charts:
            expanded_charts, patch_issues, patch_results = self._expand_chart_partial_patches(
                profile=profile,
                app_key=app_key,
                existing_by_id=existing_by_id,
                existing_by_name=existing_by_name,
                patch_charts=request.patch_charts,
            )
            if patch_issues:
                return finalize(
                    _failed(
                        "CHART_PATCH_HYDRATION_FAILED",
                        "one or more chart partial patches could not be hydrated; no write was executed",
                        normalized_args=normalized_args,
                        details={"patch_results": patch_results, "blocking_issues": patch_issues},
                        suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": patch_issues[0].get("chart_id") or "CHART_ID"}},
                    )
                )
            upsert_charts.extend(expanded_charts)
            normalized_args["upsert_charts"] = [patch.model_dump(mode="json") for patch in upsert_charts]
            normalized_args["patch_results"] = patch_results

        large_upsert_warnings = (
            [
                _warning(
                    "CHART_UPSERT_BATCH_SIZE_RECOMMENDED",
                    "large chart upsert batch accepted; monitor duration_breakdown_ms and readback before retrying",
                    upsert_count=len(upsert_charts),
                    recommended_upsert_count=CHART_APPLY_RECOMMENDED_UPSERT_BATCH_SIZE,
                )
            ]
            if len(upsert_charts) > CHART_APPLY_RECOMMENDED_UPSERT_BATCH_SIZE
            else []
        )

        chart_results: list[dict[str, Any]] = []
        created_ids: list[str] = []
        updated_ids: list[str] = []
        removed_ids: list[str] = []
        failed_items: list[dict[str, Any]] = []
        skipped_after_timeout_count = 0
        stopped_after_timeout = False
        delete_readback_issues: list[dict[str, Any]] = []
        write_attempted = False
        created_ids_from_create_response: list[str] = []
        create_id_readback_fallback_used = False

        chart_upsert_started_at = time.perf_counter()
        for patch_index, patch in enumerate(upsert_charts):
            chart_id = ""
            target_type = ""
            config_payload: dict[str, Any] | None = None
            patch_write_attempted = False
            try:
                dataset_source = _chart_patch_dataset_source_type(patch)
                if dataset_source:
                    raise ValueError(
                        "app_charts_apply only creates or edits app-source QingBI reports with dataSourceType=qingflow; "
                        f"dataset report source '{dataset_source}' is not supported for creation/update yet. "
                        "Create the dataset report in QingBI first, then attach it with app_associated_resources_apply using report_source='dataset'."
                    )
                config_update_requested = _chart_patch_updates_chart_config(patch)
                chart_visible_auth = (
                    self._compile_visibility_to_chart_visible_auth(profile=profile, visibility=patch.visibility)
                    if patch.visibility is not None
                    else None
                )
                existing = None
                if patch.chart_id:
                    existing = existing_by_id.get(str(patch.chart_id))
                    if existing is None:
                        raise ValueError(f"chart_id '{patch.chart_id}' was not found under app '{app_key}'")
                if existing is None and needs_existing_chart_inventory:
                    name_matches = list(existing_by_name.get(patch.name) or [])
                    if len(name_matches) > 1:
                        raise ValueError(
                            f"chart name '{patch.name}' is ambiguous under app '{app_key}'; supply chart_id to target an exact chart"
                        )
                    if name_matches:
                        existing = deepcopy(name_matches[0])
                chart_id = _extract_chart_identifier(existing or {}) or str(patch.chart_id or "")
                existing_name = str((existing or {}).get("chartName") or "").strip()
                existing_type = _normalize_backend_chart_type((existing or {}).get("chartType"))
                target_type = _map_public_chart_type_to_backend(patch.chart_type)
                existing_source_type = _chart_item_dataset_source_type(existing or {})
                if existing_source_type:
                    raise ValueError(
                        "app_charts_apply only creates or edits app-source QingBI reports with dataSourceType=qingflow; "
                        f"existing chart '{chart_id or patch.name}' uses dataset report source '{existing_source_type}' and is not supported for update yet. "
                        "Update it in QingBI directly, then attach the existing report with app_associated_resources_apply using report_source='dataset'."
                    )
                if existing is None or config_update_requested:
                    config_payload = _build_public_chart_config_payload(
                        patch=patch,
                        app_key=app_key,
                        field_lookup=field_lookup,
                        chart_field_lookup=chart_field_lookup,
                        qingbi_fields_by_id=qingbi_fields_by_id,
                    )
                if existing is None:
                    temp_chart_id = str(patch.chart_id or f"mcp_{uuid4().hex[:16]}")
                    chart_id = temp_chart_id
                    create_payload = {
                        "chartId": temp_chart_id,
                        "chartName": patch.name,
                        "chartType": target_type,
                        "dataSourceType": "qingflow",
                        "tagId": "0",
                        "parentId": "0",
                        "dataSourceId": app_key,
                        "visibleAuth": deepcopy(chart_visible_auth if isinstance(chart_visible_auth, dict) else qingbi_workspace_visible_auth()),
                        "editAuthList": [],
                        "editAuthType": "ws",
                        "editAuthIncludeSubDept": True,
                    }
                    write_attempted = True
                    patch_write_attempted = True
                    create_result = self.charts.qingbi_report_create(profile=profile, payload=create_payload).get("result") or {}
                    created_chart_id = _extract_chart_identifier(create_result or {})
                    created_id_from_response = bool(created_chart_id)
                    if not created_chart_id:
                        create_id_readback_fallback_used = True
                        try:
                            refreshed_items, _ = self._load_chart_list_for_builder(profile=profile, app_key=app_key)
                            refreshed_matches = _find_charts_by_name(
                                refreshed_items,
                                chart_name=patch.name,
                                chart_type=target_type,
                            )
                            if len(refreshed_matches) == 1:
                                created_chart_id = _extract_chart_identifier(refreshed_matches[0])
                            elif len(refreshed_matches) > 1:
                                raise ValueError(
                                    f"created chart '{patch.name}' could not be uniquely resolved from readback; supply chart_id on the next update"
                                )
                        except (QingflowApiError, RuntimeError):
                            created_chart_id = temp_chart_id
                    if not created_chart_id:
                        raise ValueError(
                            f"created chart '{patch.name}' did not return a real chart_id and could not be confirmed from readback"
                        )
                    chart_id = created_chart_id
                    created_ids.append(chart_id)
                    if created_id_from_response:
                        created_ids_from_create_response.append(chart_id)
                    created_chart = {
                        "chartId": chart_id,
                        "chartName": patch.name,
                        "chartType": target_type,
                    }
                    existing_by_id[chart_id] = deepcopy(created_chart)
                    existing_by_name.setdefault(patch.name, []).append(deepcopy(created_chart))
                elif existing_name != patch.name or existing_type != target_type or isinstance(chart_visible_auth, dict):
                    base_info = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
                    write_attempted = True
                    patch_write_attempted = True
                    self.charts.qingbi_report_update_base(
                        profile=profile,
                        chart_id=chart_id,
                        payload={
                            "chartId": chart_id,
                            "chartName": patch.name,
                            "chartType": target_type,
                            "dataSourceType": base_info.get("dataSourceType") or (existing or {}).get("dataSourceType") or "qingflow",
                            "tagId": "0",
                            "parentId": "0",
                            "dataSourceId": base_info.get("dataSourceId") or app_key,
                            "visibleAuth": deepcopy(
                                chart_visible_auth
                                if isinstance(chart_visible_auth, dict)
                                else base_info.get("visibleAuth") or qingbi_workspace_visible_auth()
                            ),
                            "editAuthList": deepcopy(base_info.get("editAuthList") or []),
                            "editAuthType": base_info.get("editAuthType") or "ws",
                            "editAuthIncludeSubDept": bool(base_info.get("editAuthIncludeSubDept", True)),
                        },
                    )
                    updated_ids.append(chart_id)
                    updated_chart = deepcopy(existing or {})
                    updated_chart["chartId"] = chart_id
                    updated_chart["chartName"] = patch.name
                    updated_chart["chartType"] = target_type
                    existing_by_id[chart_id] = deepcopy(updated_chart)
                    old_name = existing_name
                    if old_name and old_name in existing_by_name:
                        existing_by_name[old_name] = [
                            item for item in existing_by_name[old_name] if _extract_chart_identifier(item) != chart_id
                        ]
                        if not existing_by_name[old_name]:
                            existing_by_name.pop(old_name, None)
                    existing_by_name.setdefault(patch.name, []).append(deepcopy(updated_chart))

                config_updated = False
                if existing is None or config_update_requested:
                    write_attempted = True
                    patch_write_attempted = True
                    self.charts.qingbi_report_update_config(profile=profile, chart_id=chart_id, payload=config_payload or {})
                    config_updated = True
                if existing is not None and chart_id not in updated_ids and config_updated:
                    updated_ids.append(chart_id)
                if patch.question_config:
                    write_attempted = True
                    patch_write_attempted = True
                    self._request_backend(
                        profile=profile,
                        method="POST",
                        path=f"/chart/{chart_id}/question/config",
                        json_body=patch.question_config,
                    )
                if patch.user_config:
                    write_attempted = True
                    patch_write_attempted = True
                    self._request_backend(
                        profile=profile,
                        method="POST",
                        path=f"/chart/{chart_id}/user/config",
                        json_body=patch.user_config,
                    )
                if existing is not None and chart_id not in updated_ids and (patch.question_config or patch.user_config):
                    updated_ids.append(chart_id)
                chart_results.append(
                    {
                        "chart_id": chart_id,
                        "name": patch.name,
                        "chart_type": patch.chart_type.value,
                        "status": "created" if existing is None else "updated",
                    }
                )
            except (QingflowApiError, RuntimeError, ValueError, VisibilityResolutionError) as error:
                api_error = _coerce_api_error(error) if not isinstance(error, ValueError) else None
                diagnostics = (
                    error.diagnostics
                    if isinstance(error, ChartRuleViolation)
                    else _explain_chart_backend_validation_error(api_error=api_error, chart_type=target_type or patch.chart_type.value, payload=config_payload)
                    if api_error is not None
                    else None
                )
                failure = {
                    "chart_id": str(chart_id or patch.chart_id or ""),
                    "name": patch.name,
                    "chart_type": patch.chart_type.value,
                    "status": "failed",
                    "error_code": diagnostics.get("rule_code") if isinstance(diagnostics, dict) else "CHART_APPLY_FAILED",
                    "message": str(diagnostics.get("message") if isinstance(diagnostics, dict) and diagnostics.get("message") else error),
                    "diagnostics": diagnostics,
                    "request_id": api_error.request_id if api_error else None,
                    "backend_code": api_error.backend_code if api_error else None,
                    "http_status": None if api_error is None or api_error.http_status == 404 else api_error.http_status,
                }
                failed_items.append(failure)
                chart_results.append(failure)
                if api_error is not None and patch_write_attempted and _is_uncertain_write_transport_error(api_error):
                    stopped_after_timeout = True
                    remaining_upserts = upsert_charts[patch_index + 1 :]
                    skipped_after_timeout_count = len(remaining_upserts)
                    for remaining_patch in remaining_upserts:
                        skipped = {
                            "chart_id": str(remaining_patch.chart_id or ""),
                            "name": remaining_patch.name,
                            "chart_type": remaining_patch.chart_type.value,
                            "status": "skipped",
                            "error_code": "CHART_APPLY_SKIPPED_AFTER_TIMEOUT",
                            "message": "skipped because a previous chart write timed out with uncertain backend state; read back charts before retrying remaining items",
                            "depends_on_failed_chart": patch.name,
                            "readback_before_retry": True,
                        }
                        failed_items.append(skipped)
                        chart_results.append(skipped)
                    break
        chart_upsert_ms = _duration_ms(chart_upsert_started_at)

        chart_delete_started_at = time.perf_counter()
        for chart_id in request.remove_chart_ids:
            try:
                write_attempted = True
                self.charts.qingbi_report_delete(profile=profile, chart_id=chart_id)
                removed_ids.append(chart_id)
                delete_result = self._verify_chart_deleted_by_id(profile=profile, chart_id=chart_id)
                if delete_result.get("readback_status") != "deleted":
                    delete_readback_issues.append(delete_result)
                chart_results.append(delete_result)
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failure = {
                    "chart_id": chart_id,
                    "operation": "delete",
                    "status": "failed",
                    "delete_executed": False,
                    "safe_to_retry_delete": True,
                    "message": _public_error_message("CHART_APPLY_FAILED", api_error),
                    "request_id": api_error.request_id,
                    "backend_code": api_error.backend_code,
                    "http_status": None if api_error.http_status == 404 else api_error.http_status,
                }
                failed_items.append(failure)
                chart_results.append(failure)
        chart_delete_ms = _duration_ms(chart_delete_started_at)

        reordered = False
        chart_reorder_started_at = time.perf_counter()
        if request.reorder_chart_ids:
            try:
                current_order = [
                    _extract_chart_identifier(item)
                    for item in existing_chart_items
                    if isinstance(item, dict) and _extract_chart_identifier(item)
                ]
                desired_display_order = list(request.reorder_chart_ids) + [chart_id for chart_id in current_order if chart_id not in request.reorder_chart_ids]
                backend_reorder_ids = list(reversed(desired_display_order))
                write_attempted = True
                self.charts.qingbi_report_reorder(profile=profile, app_key=app_key, chart_ids=backend_reorder_ids)
                reordered = True
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                failure = {
                    "status": "failed",
                    "operation": "reorder",
                    "message": _public_error_message("CHART_APPLY_FAILED", api_error),
                    "request_id": api_error.request_id,
                    "backend_code": api_error.backend_code,
                    "http_status": None if api_error.http_status == 404 else api_error.http_status,
                }
                failed_items.append(failure)
                chart_results.append(failure)
        chart_reorder_ms = _duration_ms(chart_reorder_started_at)

        noop = not created_ids and not updated_ids and not removed_ids and not reordered and not failed_items
        confirmed_write = bool(created_ids or updated_ids or removed_ids or reordered)
        write_executed = bool(confirmed_write or write_attempted)
        write_succeeded = confirmed_write
        chart_final_readback_skipped = bool(
            pure_direct_create
            and created_ids
            and not updated_ids
            and not removed_ids
            and not reordered
            and not failed_items
            and not create_id_readback_fallback_used
            and len(created_ids_from_create_response) == len(created_ids) == len(direct_create_upserts)
        )
        needs_list_readback = bool(created_ids or updated_ids or reordered) and not chart_final_readback_skipped
        delete_readback_unavailable = any(item.get("readback_status") == "unavailable" for item in delete_readback_issues)
        deletes_verified = not delete_readback_issues
        readback_unavailable = False
        readback_error: QingflowApiError | None = None
        readback_list_source: str | None = existing_chart_list_source
        if chart_final_readback_skipped:
            verified = True
            chart_readback_ms = 0
            readback_list_source = "skipped_for_direct_create"
        elif needs_list_readback:
            chart_readback_started_at = time.perf_counter()
            try:
                readback_items, readback_list_source = self._load_chart_list_for_builder(profile=profile, app_key=app_key)
                readback_ids = {
                    _extract_chart_identifier(item)
                    for item in readback_items
                    if isinstance(item, dict) and _extract_chart_identifier(item)
                }
                verified = all(chart_id in readback_ids for chart_id in created_ids + updated_ids)
                if request.reorder_chart_ids:
                    ordered_readback = [
                        _extract_chart_identifier(item)
                        for item in readback_items
                        if isinstance(item, dict) and _extract_chart_identifier(item)
                    ]
                    requested_existing = [chart_id for chart_id in request.reorder_chart_ids if chart_id in ordered_readback]
                    verified = verified and readback_list_source == "sorted" and ordered_readback[: len(requested_existing)] == requested_existing
                readback_unavailable = False
            except (QingflowApiError, RuntimeError) as error:
                readback_error = _coerce_api_error(error)
                verified = False
                readback_unavailable = True
                readback_list_source = None
            chart_readback_ms = _duration_ms(chart_readback_started_at)
        else:
            verified = True
            chart_readback_ms = 0
        verified = verified and deletes_verified
        any_readback_unavailable = readback_unavailable or delete_readback_unavailable

        if failed_items:
            successful_changes = bool(created_ids or updated_ids or removed_ids or reordered)
            primary_failure = failed_items[0]
            primary_error_code = str(primary_failure.get("error_code") or "").strip()
            primary_message = str(primary_failure.get("message") or "").strip()
            retry_context = _chart_apply_retry_context(
                request=request,
                failed_items=failed_items,
                created_ids=created_ids,
                updated_ids=updated_ids,
                removed_ids=removed_ids,
                reordered=reordered,
                write_executed=write_executed,
            )
            return finalize({
                "status": "partial_success" if successful_changes else "failed",
                "error_code": "CHART_APPLY_PARTIAL" if successful_changes else primary_error_code or "CHART_APPLY_FAILED",
                "recoverable": True,
                "message": "applied some chart operations; at least one chart operation failed" if successful_changes else primary_message or "one or more chart operations failed",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {"chart.chart_type": [member.value for member in PublicChartType], "chart.filter.operator": [member.value for member in ViewFilterOperator]},
                "details": {
                    "per_chart_results": chart_results,
                    "stopped_after_timeout": stopped_after_timeout,
                    "skipped_after_timeout_count": skipped_after_timeout_count,
                    "inventory_readback_skipped": chart_inventory_skipped,
                    "final_readback_skipped": chart_final_readback_skipped,
                    **retry_context,
                    **({"readback_error": _transport_error_payload(readback_error)} if readback_error is not None else {}),
                },
                "request_id": failed_items[0].get("request_id") or (readback_error.request_id if readback_error is not None else None),
                "suggested_next_call": {"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
                "backend_code": failed_items[0].get("backend_code") or (readback_error.backend_code if readback_error is not None else None),
                "http_status": failed_items[0].get("http_status") or (None if readback_error is None or readback_error.http_status == 404 else readback_error.http_status),
                "noop": noop,
                "warnings": large_upsert_warnings + _chart_apply_warnings(
                    failed_items=failed_items,
                    readback_unavailable=readback_unavailable,
                    verified=False if failed_items else verified,
                    delete_readback_issues=delete_readback_issues,
                ),
                "verification": {
                    "charts_verified": False if failed_items else verified,
                    "readback_unavailable": any_readback_unavailable,
                    "readback_before_retry": bool(write_executed),
                    "chart_delete_readback_results": [deepcopy(item) for item in chart_results if item.get("operation") == "delete"],
                    "chart_order_verified": False if request.reorder_chart_ids else True,
                    "chart_list_source": readback_list_source or existing_chart_list_source,
                    "inventory_readback_skipped": chart_inventory_skipped,
                    "final_readback_skipped": chart_final_readback_skipped,
                },
                "app_key": app_key,
                "app_name": app_name,
                "chart_results": chart_results,
                "verified": False if failed_items else verified,
                "write_executed": write_executed,
                "write_succeeded": write_succeeded,
                **({"write_may_have_succeeded": True, "next_action": "readback_before_retry"} if write_executed else {}),
                "safe_to_retry": not write_executed,
            })
        result_verified = verified or noop
        pending_delete = bool(delete_readback_issues)
        pending_error_code = "CHART_DELETE_READBACK_PENDING" if pending_delete and not readback_unavailable else "CHART_READBACK_PENDING"
        pending_message = "applied chart operations; delete readback pending" if pending_delete else "applied chart operations; readback pending"
        pending_suggestion = (
            {"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": str(delete_readback_issues[0].get("chart_id") or "CHART_ID")}}
            if pending_delete
            else {"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}}
        )
        return finalize({
            "status": "success" if result_verified else "partial_success",
            "error_code": None if result_verified else pending_error_code,
            "recoverable": not result_verified,
            "message": "no chart changes requested" if noop else ("applied chart operations" if verified else pending_message),
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"chart.chart_type": [member.value for member in PublicChartType], "chart.filter.operator": [member.value for member in ViewFilterOperator]},
            "details": {
                "inventory_readback_skipped": chart_inventory_skipped,
                "final_readback_skipped": chart_final_readback_skipped,
                **({"readback_skip_reason": "direct_create_response_ids"} if chart_final_readback_skipped else {}),
                **({"readback_error": _transport_error_payload(readback_error)} if readback_error is not None else {}),
            },
            "request_id": readback_error.request_id if readback_error is not None else None,
            "suggested_next_call": None if result_verified else pending_suggestion,
            "backend_code": readback_error.backend_code if readback_error is not None else None,
            "http_status": None if readback_error is None or readback_error.http_status == 404 else readback_error.http_status,
            "noop": noop,
            "warnings": large_upsert_warnings + _chart_apply_warnings(
                failed_items=[],
                readback_unavailable=False if noop else readback_unavailable,
                verified=result_verified,
                delete_readback_issues=delete_readback_issues,
            ),
            "verification": {
                "charts_verified": result_verified,
                "readback_unavailable": False if noop else any_readback_unavailable,
                "chart_delete_readback_results": [deepcopy(item) for item in chart_results if item.get("operation") == "delete"],
                "chart_order_verified": (readback_list_source == "sorted" and result_verified) if request.reorder_chart_ids else True,
                "chart_list_source": existing_chart_list_source if noop else readback_list_source,
                "inventory_readback_skipped": chart_inventory_skipped,
                "final_readback_skipped": chart_final_readback_skipped,
            },
            "app_key": app_key,
            "app_name": app_name,
            "chart_results": chart_results,
            "verified": result_verified,
            "write_executed": write_executed,
            "write_succeeded": write_succeeded,
            "safe_to_retry": not write_executed,
        })

    def portal_apply(self, *, profile: str, request: PortalApplyRequest) -> JSONObject:
        apply_started_at = time.perf_counter()
        portal_initial_read_ms: int | None = None
        portal_create_ms: int | None = None
        portal_component_build_ms: int | None = None
        portal_update_ms: int | None = None
        portal_base_info_update_ms: int | None = None
        portal_draft_readback_ms: int | None = None
        portal_publish_ms: int | None = None
        portal_live_readback_ms: int | None = None
        normalized_args = request.model_dump(mode="json")
        permission_outcomes: list[PermissionCheckOutcome] = []
        dash_key = str(request.dash_key or "").strip()
        creating = not dash_key
        sections_requested = creating or bool(request.sections)
        verify_dash_name = creating or request.dash_name is not None
        verify_dash_icon = bool(request.icon or request.color)
        verify_auth = request.visibility is not None or request.auth is not None
        verify_hide_copyright = request.hide_copyright is not None and sections_requested
        verify_dash_global_config = request.dash_global_config is not None and sections_requested
        verify_tags = creating or request.package_tag_id is not None
        requested_visibility = request.visibility
        if requested_visibility is None and isinstance(request.auth, dict) and request.auth:
            requested_visibility = VisibilityPatch.model_validate(_public_visibility_from_member_auth(request.auth))
        try:
            desired_auth = (
                self._compile_visibility_to_member_auth(profile=profile, visibility=requested_visibility)
                if requested_visibility is not None
                else None
            )
        except VisibilityResolutionError as error:
            return _failed(
                error.error_code,
                error.message,
                normalized_args=normalized_args,
                details=error.details,
                suggested_next_call=None,
            )
        try:
            portal_initial_read_started_at = time.perf_counter()
            base_payload = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") if dash_key else {}
        except (QingflowApiError, RuntimeError) as error:
            portal_initial_read_ms = _duration_ms(portal_initial_read_started_at)
            api_error = _coerce_api_error(error)
            return _failed(
                "PORTAL_APPLY_FAILED",
                _public_error_message("PORTAL_APPLY_FAILED", api_error),
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"dash_key": dash_key or None}, resource="portal", error=api_error),
                suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
                request_id=api_error.request_id,
                backend_code=api_error.backend_code,
                http_status=None if api_error.http_status == 404 else api_error.http_status,
            )
        portal_initial_read_ms = _duration_ms(portal_initial_read_started_at) if dash_key else 0
        if not isinstance(base_payload, dict):
            base_payload = {}
        if not creating:
            portal_permission_outcome = self._guard_portal_permission(
                profile=profile,
                dash_key=dash_key,
                normalized_args=normalized_args,
                portal_result=base_payload,
            )
            if portal_permission_outcome.block is not None:
                return portal_permission_outcome.block
            permission_outcomes.append(portal_permission_outcome)

        def finalize(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                portal_initial_read_ms=portal_initial_read_ms,
                portal_create_ms=portal_create_ms,
                portal_component_build_ms=portal_component_build_ms,
                portal_update_ms=portal_update_ms,
                portal_base_info_update_ms=portal_base_info_update_ms,
                portal_draft_readback_ms=portal_draft_readback_ms,
                portal_publish_ms=portal_publish_ms,
                portal_live_readback_ms=portal_live_readback_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return _apply_permission_outcomes(response, *permission_outcomes)
        target_package_tag_id = request.package_tag_id
        if target_package_tag_id is None:
            target_package_tag_id = _coerce_positive_int(((base_payload.get("tags") or [{}])[0] if isinstance(base_payload.get("tags"), list) and base_payload.get("tags") else {}).get("tagId"))
        if creating and target_package_tag_id is None:
            return _failed(
                "PACKAGE_TAG_REQUIRED",
                "package_tag_id is required when creating a portal",
                normalized_args=normalized_args,
                suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
            )
        if creating:
            package_add_outcome = self._guard_package_permission(
                profile=profile,
                tag_id=target_package_tag_id,
                required_permission="add_app",
                normalized_args=normalized_args,
            )
            if package_add_outcome.block is not None:
                return package_add_outcome.block
            permission_outcomes.append(package_add_outcome)
        if not sections_requested:
            unsupported_base_only_keys: list[str] = []
            if request.hide_copyright is not None:
                unsupported_base_only_keys.append("hide_copyright")
            if request.dash_global_config is not None:
                unsupported_base_only_keys.append("dash_global_config")
            if request.config:
                unsupported_base_only_keys.append("config")
            if unsupported_base_only_keys:
                return _failed(
                    "PORTAL_SECTIONS_REQUIRED",
                    "editing a portal without sections only supports base-info updates",
                    normalized_args=normalized_args,
                    details={
                        "unsupported_without_sections": unsupported_base_only_keys,
                        "fix_hint": "Pass sections when changing layout or global portal config, or omit those keys for visibility/icon/package updates.",
                    },
                    suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
                )
        write_executed = False
        draft_readback_error: JSONObject | None = None
        live_readback_error: JSONObject | None = None
        update_payload: dict[str, Any] = {}
        inline_chart_results: list[dict[str, Any]] = []
        try:
            layout_diagnostics: dict[str, Any] = _empty_portal_layout_diagnostics()
            if creating:
                create_payload = _build_public_portal_base_payload(
                    dash_name=request.dash_name or "未命名门户",
                    package_tag_id=target_package_tag_id,
                    icon=request.icon,
                    color=request.color,
                    auth=desired_auth,
                    hide_copyright=request.hide_copyright,
                    dash_global_config=request.dash_global_config,
                    config=request.config,
                    base_payload=None,
                )
                portal_create_started_at = time.perf_counter()
                create_result = self.portals.portal_create(profile=profile, payload=create_payload)
                portal_create_ms = _duration_ms(portal_create_started_at)
                write_executed = True
                created = create_result.get("result") if isinstance(create_result.get("result"), dict) else {}
                dash_key = str(created.get("dashKey") or "")
                if not dash_key:
                    return _failed(
                        "PORTAL_APPLY_FAILED",
                        "portal was created but dash_key is missing from create result",
                        normalized_args=normalized_args,
                        details={"create_result": created},
                        suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
                    )
                try:
                    base_payload = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
                except (QingflowApiError, RuntimeError) as read_error:
                    api_read_error = _coerce_api_error(read_error)
                    draft_readback_error = {
                        "phase": "created_portal_draft_readback",
                        "transport_error": _transport_error_payload(api_read_error),
                    }
                    base_payload = {}
            update_payload = _build_public_portal_base_payload(
                dash_name=request.dash_name or str(base_payload.get("dashName") or "").strip() or "未命名门户",
                package_tag_id=target_package_tag_id,
                icon=request.icon,
                color=request.color,
                auth=desired_auth,
                hide_copyright=request.hide_copyright,
                dash_global_config=request.dash_global_config,
                config=request.config,
                base_payload=base_payload,
            )
            if sections_requested:
                portal_component_build_started_at = time.perf_counter()
                component_payload, layout_metadata, inline_chart_results = self._build_portal_components_from_sections(
                    profile=profile,
                    sections=request.sections,
                    layout_preset=request.layout_preset,
                    inline_chart_results=inline_chart_results,
                )
                portal_component_build_ms = _duration_ms(portal_component_build_started_at)
                layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload, layout_metadata=layout_metadata)
                update_payload["components"] = component_payload
                portal_update_started_at = time.perf_counter()
                self.portals.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
                portal_update_ms = _duration_ms(portal_update_started_at)
                write_executed = True
            else:
                portal_component_build_ms = 0
                portal_update_ms = 0
            base_info_update_requested = bool(
                (not creating and not sections_requested)
                or request.dash_name is not None
                or request.icon is not None
                or request.color is not None
                or request.visibility is not None
                or request.auth is not None
                or request.package_tag_id is not None
            )
            if base_info_update_requested:
                portal_base_info_update_started_at = time.perf_counter()
                self.portals.portal_update_base_info(
                    profile=profile,
                    dash_key=dash_key,
                    payload={
                        "dashName": update_payload.get("dashName"),
                        "dashIcon": update_payload.get("dashIcon"),
                        "auth": deepcopy(update_payload.get("auth")),
                        "tags": deepcopy(update_payload.get("tags") or []),
                    },
                )
                portal_base_info_update_ms = _duration_ms(portal_base_info_update_started_at)
                write_executed = True
            else:
                portal_base_info_update_ms = 0
            try:
                portal_draft_readback_started_at = time.perf_counter()
                draft_result = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
            except (QingflowApiError, RuntimeError) as read_error:
                portal_draft_readback_ms = _duration_ms(portal_draft_readback_started_at)
                api_read_error = _coerce_api_error(read_error)
                draft_readback_error = {
                    "phase": "portal_draft_readback",
                    "transport_error": _transport_error_payload(api_read_error),
                }
                draft_result = {}
            else:
                portal_draft_readback_ms = _duration_ms(portal_draft_readback_started_at)
        except (QingflowApiError, RuntimeError, ValueError) as error:
            api_error = _coerce_api_error(error) if not isinstance(error, ValueError) else None
            inline_chart_write_executed = any(bool(item.get("write_executed")) for item in inline_chart_results)
            if write_executed or inline_chart_write_executed:
                transport_error = _transport_error_payload(api_error) if api_error is not None else None
                warning = _warning(
                    "PORTAL_WRITE_INCOMPLETE_AFTER_PARTIAL_WRITE",
                    "one or more portal or inline-chart write steps executed before a later write step failed",
                )
                if transport_error is not None:
                    for key in ("backend_code", "http_status", "request_id"):
                        if transport_error.get(key) is not None:
                            warning[key] = transport_error.get(key)
                return finalize({
                    "status": "partial_success",
                    "error_code": "PORTAL_APPLY_PARTIAL",
                    "recoverable": True,
                    "message": "some portal write steps executed; a later portal write step failed",
                    "normalized_args": normalized_args,
                    "missing_fields": [],
                    "allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"]},
                    "details": {
                        "dash_key": dash_key or None,
                        "write_error": (
                            {"message": api_error.message, "transport_error": transport_error}
                            if api_error is not None
                            else {"message": str(error)}
                        ),
                        **({"inline_chart_results": deepcopy(inline_chart_results)} if inline_chart_results else {}),
                    },
                    "request_id": api_error.request_id if api_error else None,
                    "backend_code": api_error.backend_code if api_error else None,
                    "http_status": None if api_error is None or api_error.http_status == 404 else api_error.http_status,
                    "suggested_next_call": {"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
                    "noop": False,
                    "warnings": [warning],
                    "verification": {
                        "draft_verified": False,
                        "draft_metadata_verified": False,
                        "live_verified": None,
                        "live_metadata_verified": None,
                        "published": False,
                        "publish_failed": False,
                        "write_incomplete": True,
                        "inline_charts_verified": False if inline_chart_results else None,
                        "readback_unavailable": False,
                        "metadata_unverified": True,
                    },
                    "dash_key": dash_key,
                    "dash_name": update_payload.get("dashName") if isinstance(update_payload, dict) else None,
                    "package_id": target_package_tag_id,
                    "created": creating,
                    "published": False,
                    "verified": False,
                    "write_executed": True,
                    "safe_to_retry": False,
                    "parallelism": {
                        "inline_chart_workers": max((int(item.get("inline_chart_workers") or 0) for item in inline_chart_results), default=0),
                    },
                    "inline_chart_results": deepcopy(inline_chart_results),
                })
            return _failed(
                "PORTAL_APPLY_FAILED",
                _public_error_message("PORTAL_APPLY_FAILED", api_error) if api_error else str(error),
                normalized_args=normalized_args,
                details=_with_state_read_blocked_details({"dash_key": dash_key or None}, resource="portal", error=api_error) if api_error is not None else {"dash_key": dash_key or None},
                suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
                request_id=api_error.request_id if api_error else None,
                backend_code=api_error.backend_code if api_error else None,
                http_status=None if api_error is None or api_error.http_status == 404 else api_error.http_status,
            )

        live_result: dict[str, Any] | None = None
        published = False
        publish_failed = False
        publish_error: JSONObject | None = None
        if request.publish:
            try:
                portal_publish_started_at = time.perf_counter()
                self.portals.portal_publish(profile=profile, dash_key=dash_key)
                portal_publish_ms = _duration_ms(portal_publish_started_at)
                published = True
            except (QingflowApiError, RuntimeError) as error:
                portal_publish_ms = _duration_ms(portal_publish_started_at)
                api_error = _coerce_api_error(error)
                publish_failed = True
                publish_error = {
                    "message": api_error.message,
                    "transport_error": _transport_error_payload(api_error),
                }
            if published:
                try:
                    portal_live_readback_started_at = time.perf_counter()
                    live_result = self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=False).get("result") or {}
                except (QingflowApiError, RuntimeError) as read_error:
                    portal_live_readback_ms = _duration_ms(portal_live_readback_started_at)
                    api_read_error = _coerce_api_error(read_error)
                    live_readback_error = {
                        "phase": "portal_live_readback",
                        "transport_error": _transport_error_payload(api_read_error),
                    }
                else:
                    portal_live_readback_ms = _duration_ms(portal_live_readback_started_at)
        else:
            portal_publish_ms = 0
            portal_live_readback_ms = 0

        draft_components = draft_result.get("components") if isinstance(draft_result, dict) else None
        expected_count = len(request.sections) if sections_requested else None
        draft_verified = isinstance(draft_result, dict) and (
            expected_count is None or (isinstance(draft_components, list) and len(draft_components) == expected_count)
        )
        if draft_readback_error is not None:
            draft_verified = False
        draft_meta_verified, draft_meta_mismatches = _verify_portal_readback(
            actual=draft_result,
            expected_payload=update_payload,
            expected_visibility=requested_visibility.model_dump(mode="json") if requested_visibility is not None else None,
            expected_section_count=expected_count,
            requested_config_keys=set((request.config or {}).keys()),
            verify_dash_name=verify_dash_name,
            verify_dash_icon=verify_dash_icon,
            verify_auth=verify_auth,
            verify_hide_copyright=verify_hide_copyright,
            verify_dash_global_config=verify_dash_global_config,
            verify_tags=verify_tags,
        )
        live_verified: bool | None = None
        live_meta_verified: bool | None = None
        live_meta_mismatches: list[str] = []
        if request.publish:
            live_verified = (
                isinstance(live_result, dict)
                and (
                    expected_count is None
                    or (
                        isinstance(live_result.get("components"), list)
                        and len(live_result.get("components")) == expected_count
                    )
                )
            )
            if live_readback_error is not None:
                live_verified = False
            live_meta_verified, live_meta_mismatches = _verify_portal_readback(
                actual=live_result,
                expected_payload=update_payload,
                expected_visibility=requested_visibility.model_dump(mode="json") if requested_visibility is not None else None,
                expected_section_count=expected_count,
                requested_config_keys=set((request.config or {}).keys()),
                verify_dash_name=verify_dash_name,
                verify_dash_icon=verify_dash_icon,
                verify_auth=verify_auth,
                verify_hide_copyright=verify_hide_copyright,
                verify_dash_global_config=verify_dash_global_config,
                verify_tags=verify_tags,
            )
        verified = (
            draft_verified
            and draft_meta_verified
            and (published if request.publish else True)
            and (live_verified if request.publish else True)
            and (live_meta_verified if request.publish else True)
            and not publish_failed
        )
        status = "success" if verified else "partial_success"
        error_code = None if verified else "PORTAL_READBACK_PENDING"
        if publish_failed:
            error_code = "PORTAL_PUBLISH_FAILED"
        warnings = _portal_apply_warnings(
            publish_failed=publish_failed,
            draft_verified=draft_verified,
            draft_meta_verified=draft_meta_verified,
            live_verified=live_verified,
            live_meta_verified=live_meta_verified,
            publish_requested=request.publish,
        )
        details_payload = {
            "verification_mismatches": {
                "draft": draft_meta_mismatches,
                "live": live_meta_mismatches,
            },
            **({"inline_chart_results": deepcopy(inline_chart_results)} if inline_chart_results else {}),
            **({"publish_error": publish_error} if publish_error is not None else {}),
            **({"draft_readback_error": draft_readback_error} if draft_readback_error is not None else {}),
            **({"live_readback_error": live_readback_error} if live_readback_error is not None else {}),
        }
        readback_transport_error = _readback_transport_error_from_details(details_payload)
        if draft_readback_error is not None or live_readback_error is not None:
            warning = _warning("READBACK_UNAVAILABLE_AFTER_WRITE", "write was executed but portal readback is unavailable")
            if readback_transport_error is not None:
                for key in ("backend_code", "http_status", "request_id"):
                    if readback_transport_error.get(key) is not None:
                        warning[key] = readback_transport_error.get(key)
            warnings.append(warning)
        warnings.extend(_portal_layout_warning_items(layout_diagnostics))
        return finalize({
            "status": status,
            "error_code": error_code,
            "recoverable": not verified,
            "message": (
                "updated portal base info"
                if verified and not sections_requested
                else "applied portal"
                if verified
                else "updated portal base info; draft/live verification pending"
                if not sections_requested
                else "applied portal; draft/live verification pending"
            ),
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"]},
            "details": details_payload,
            "request_id": (readback_transport_error or {}).get("request_id"),
            "backend_code": (readback_transport_error or {}).get("backend_code"),
            "http_status": (readback_transport_error or {}).get("http_status"),
            "suggested_next_call": None if verified else {"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
            "noop": False,
            "warnings": warnings,
            "layout_diagnostics": layout_diagnostics,
            "inline_chart_results": deepcopy(inline_chart_results),
            "parallelism": {
                "inline_chart_workers": max((int(item.get("inline_chart_workers") or 0) for item in inline_chart_results), default=0),
            },
            "verification": {
                "draft_verified": draft_verified,
                "draft_metadata_verified": draft_meta_verified,
                "live_verified": live_verified,
                "live_metadata_verified": live_meta_verified,
                "published": published,
                "publish_failed": publish_failed,
                "inline_charts_verified": all(str(item.get("status") or "") in {"created", "updated"} and str(item.get("chart_id") or "").strip() for item in inline_chart_results),
                "readback_unavailable": draft_readback_error is not None or live_readback_error is not None,
                "metadata_unverified": draft_readback_error is not None or live_readback_error is not None,
            },
            "dash_key": dash_key,
            "dash_name": update_payload.get("dashName"),
            "package_id": target_package_tag_id,
            "created": creating,
            "published": published,
            "verified": verified,
            "write_executed": write_executed or published,
            "safe_to_retry": not (write_executed or published),
            "draft_result": draft_result,
            "live_result": live_result,
        })

    def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
        apply_started_at = time.perf_counter()
        portal_delete_ms = 0
        portal_delete_readback_ms = 0
        normalized_args = {"dash_key": dash_key}

        def finish(response: JSONObject) -> JSONObject:
            _merge_duration_breakdown(
                response,
                portal_delete_ms=portal_delete_ms,
                portal_delete_readback_ms=portal_delete_readback_ms,
                total_ms=_duration_ms(apply_started_at),
            )
            return response

        dash_key = str(dash_key or "").strip()
        if not dash_key:
            return finish(
                _failed(
                    "PORTAL_DASH_KEY_REQUIRED",
                    "dash_key is required when deleting a portal",
                    normalized_args=normalized_args,
                    missing_fields=["dash_key"],
                    suggested_next_call={"tool_name": "portal_delete", "arguments": {"profile": profile, "dash_key": "DASH_KEY"}},
                )
            )
        portal_delete_started_at = time.perf_counter()
        try:
            self.portals.portal_delete(profile=profile, dash_key=dash_key)
        except (QingflowApiError, RuntimeError) as error:
            portal_delete_ms += _duration_ms(portal_delete_started_at)
            api_error = _coerce_api_error(error)
            return finish(
                _failed_from_api_error(
                    "PORTAL_DELETE_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"dash_key": dash_key},
                    suggested_next_call={"tool_name": "portal_delete", "arguments": {"profile": profile, **normalized_args}},
                )
            )
        portal_delete_ms += _duration_ms(portal_delete_started_at)

        portal_delete_readback_started_at = time.perf_counter()
        delete_readback = self._verify_portal_deleted_by_key(profile=profile, dash_key=dash_key)
        portal_delete_readback_ms += _duration_ms(portal_delete_readback_started_at)
        verified = delete_readback.get("readback_status") == "deleted"
        return finish(
            {
                "status": "success" if verified else "partial_success",
                "error_code": None if verified else delete_readback.get("error_code") or "PORTAL_DELETE_READBACK_PENDING",
                "recoverable": not verified,
                "message": "deleted portal" if verified else "portal delete completed; readback pending",
                "normalized_args": normalized_args,
                "missing_fields": [],
                "allowed_values": {},
                "details": {} if verified else {"delete_readback": delete_readback},
                "request_id": delete_readback.get("request_id"),
                "backend_code": delete_readback.get("backend_code"),
                "http_status": delete_readback.get("http_status"),
                "suggested_next_call": None if verified else {"tool_name": "portal_get", "arguments": {"profile": profile, "dash_key": dash_key}},
                "noop": False,
                "warnings": [] if verified else [_warning("PORTAL_DELETE_READBACK_PENDING", "portal delete was sent, but deletion readback is not fully verified")],
                "verification": {"portal_deleted": verified, "delete_readback": delete_readback},
                "verified": verified,
                "dash_key": dash_key,
                "deleted": verified,
                "delete_executed": True,
                "readback_status": delete_readback.get("readback_status"),
                "safe_to_retry_delete": False,
                "write_executed": True,
                "safe_to_retry": False,
            }
        )

    def _verify_portal_deleted_by_key(self, *, profile: str, dash_key: str) -> JSONObject:
        try:
            self.portals.portal_get(profile=profile, dash_key=dash_key, being_draft=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if _delete_readback_is_not_found(api_error):
                return {
                    "dash_key": dash_key,
                    "operation": "delete",
                    "status": "removed",
                    "delete_executed": True,
                    "readback_status": "deleted",
                    "safe_to_retry_delete": False,
                }
            return {
                "dash_key": dash_key,
                "operation": "delete",
                "status": "readback_pending",
                "delete_executed": True,
                "readback_status": "unavailable",
                "safe_to_retry_delete": False,
                "error_code": "PORTAL_DELETE_READBACK_UNAVAILABLE",
                "message": "delete request completed, but portal existence could not be verified by dash_key readback",
                "request_id": api_error.request_id,
                "backend_code": api_error.backend_code,
                "http_status": None if api_error.http_status == 404 else api_error.http_status,
                "transport_error": _transport_error_payload(api_error),
            }
        return {
            "dash_key": dash_key,
            "operation": "delete",
            "status": "readback_pending",
            "delete_executed": True,
            "readback_status": "still_exists",
            "safe_to_retry_delete": False,
            "error_code": "PORTAL_DELETE_READBACK_STILL_EXISTS",
            "message": "delete request completed, but the portal still exists during dash_key readback",
        }

    def _publish_current_edit_version(self, *, profile: str, app_key: str, edit_version_no: int | None = None) -> JSONObject:
        normalized_args = {"app_key": app_key}
        if edit_version_no is None:
            try:
                version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                return _failed_from_api_error(
                    "PUBLISH_PRECHECK_FAILED",
                    api_error,
                    normalized_args=normalized_args,
                    details={"app_key": app_key, "phase": "prepare_publish_edit_version"},
                    suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, "app_key": app_key}},
                )
            edit_version_no = _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or 1
        try:
            self.apps.app_publish(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
            self.apps.app_edit_finished(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "PUBLISH_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key, "edit_version_no": edit_version_no},
                suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, "app_key": app_key}},
            )
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "published current app draft",
            "normalized_args": normalized_args,
            "missing_fields": [],
            "allowed_values": {},
            "details": {"app_key": app_key, "edit_version_no": edit_version_no},
            "request_id": None,
            "suggested_next_call": None,
            "noop": False,
            "warnings": [],
            "verification": {"published": True},
            "app_key": app_key,
            "published": True,
            "verified": True,
        }

    def _resolve_form_edit_version(self, *, profile: str, app_key: str, current_schema: dict[str, Any]) -> int:
        try:
            version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
        except (QingflowApiError, RuntimeError):
            version_result = {}
        return _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or int(current_schema.get("editVersionNo") or 1)

    def _ensure_app_edit_context(
        self,
        *,
        profile: str,
        app_key: str,
        normalized_args: dict[str, Any],
        failure_code: str,
    ) -> tuple[int | None, JSONObject | None]:
        try:
            version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return None, _failed_from_api_error(
                failure_code,
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key, "phase": "prepare_edit_context"},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        edit_version_no = _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or 1
        return edit_version_no, None

    def _append_publish_result(self, *, profile: str, app_key: str, publish: bool, response: JSONObject, edit_version_no: int | None = None) -> JSONObject:
        verification = response.get("verification")
        if not isinstance(verification, dict):
            verification = {}
            response["verification"] = verification
        warnings = response.get("warnings")
        if not isinstance(warnings, list):
            warnings = []
            response["warnings"] = warnings
        if bool(response.get("noop")):
            response["publish_requested"] = False
            response["publish_skipped"] = bool(publish)
            response["published"] = False
            verification["published"] = False
            return response
        response["publish_requested"] = publish
        if not publish:
            response["published"] = False
            verification["published"] = False
            return response
        publish_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=edit_version_no)
        if publish_result.get("status") == "failed" and publish_result.get("error_code") == "APP_EDIT_LOCKED":
            details = response.get("details")
            if not isinstance(details, dict):
                details = {}
                response["details"] = details
            suggested = publish_result.get("suggested_next_call")
            release_args = suggested.get("arguments") if isinstance(suggested, dict) else {}
            if isinstance(release_args, dict):
                release_result = self.app_release_edit_lock_if_mine(
                    profile=profile,
                    app_key=app_key,
                    lock_owner_email=str(release_args.get("lock_owner_email") or ""),
                    lock_owner_name=str(release_args.get("lock_owner_name") or ""),
                )
                details["edit_lock_release_result"] = release_result
                if release_result.get("status") == "success":
                    retry_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=None)
                    details["publish_retry_after_edit_lock_release"] = retry_result
                    publish_result = retry_result
                    response["retried_after_edit_lock_release"] = True
                    response["edit_lock_released"] = True
                elif release_result.get("error_code") == "EDIT_LOCK_OWNER_UNKNOWN" and edit_version_no is not None:
                    try:
                        self.apps.app_edit_finished(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
                        release_result = {
                            "status": "success",
                            "error_code": None,
                            "recoverable": False,
                            "message": "released current apply edit context before publish retry",
                            "details": {"app_key": app_key, "edit_version_no": edit_version_no, "release_strategy": "current_apply_edit_context"},
                            "verification": {"released": True},
                            "app_key": app_key,
                            "verified": True,
                        }
                        details["edit_lock_release_result"] = release_result
                        retry_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=None)
                        details["publish_retry_after_edit_lock_release"] = retry_result
                        publish_result = retry_result
                        response["retried_after_edit_lock_release"] = True
                        response["edit_lock_released"] = True
                    except (QingflowApiError, RuntimeError) as error:
                        api_error = _coerce_api_error(error)
                        details["current_apply_edit_context_release_error"] = _transport_error_payload(api_error)
        response["publish_result"] = publish_result
        response["published"] = bool(publish_result.get("published"))
        verification["published"] = bool(publish_result.get("published"))
        if publish_result.get("status") == "failed":
            warnings.append(_warning("PUBLISH_FAILED", "publish step failed after apply succeeded"))
            response["status"] = "partial_success"
            response["error_code"] = response.get("error_code") or publish_result.get("error_code")
            response["recoverable"] = True
            response["verified"] = False
            response["message"] = f"{response.get('message') or 'apply succeeded'}; publish failed"
            if not response.get("suggested_next_call"):
                response["suggested_next_call"] = publish_result.get("suggested_next_call")
        return response

    def _load_base_schema_state(self, *, profile: str, app_key: str) -> dict[str, Any]:
        base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True)
        schema_result, schema_source = self._read_schema_with_fallback(profile=profile, app_key=app_key)
        base_result = base.get("result") if isinstance(base.get("result"), dict) else {}
        return {
            "base": base_result,
            "schema": schema_result,
            "parsed": _parse_schema(schema_result),
            "schema_source": schema_source,
        }

    def _read_app_name_for_builder_output(self, *, profile: str, app_key: str) -> str | None:
        try:
            base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
        except (QingflowApiError, RuntimeError):
            return None
        app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or "").strip()
        return app_name or None

    def _sync_system_view_apply_config(
        self,
        *,
        profile: str,
        app_key: str,
        list_type: int,
        schema: dict[str, Any],
        visible_field_names: list[str],
    ) -> dict[str, Any]:
        base_info_response = self.apps.app_get_apply_base_info(profile=profile, app_key=app_key, list_type=list_type)
        base_info_result = base_info_response.get("result") if isinstance(base_info_response.get("result"), dict) else {}
        current_que_base_infos = base_info_result.get("queBaseInfos") if isinstance(base_info_result.get("queBaseInfos"), list) else []
        change_ques = _build_apply_config_change_ques(
            current_que_base_infos=current_que_base_infos,
            schema=schema,
            visible_field_names=visible_field_names,
        )
        self.apps.app_update_apply_config(
            profile=profile,
            app_key=app_key,
            payload={"type": list_type, "changeQues": change_ques},
        )
        readback_response = self.apps.app_get_apply_base_info(profile=profile, app_key=app_key, list_type=list_type)
        readback_result = readback_response.get("result") if isinstance(readback_response.get("result"), dict) else {}
        actual_visible_order = _extract_apply_visible_field_names(readback_result.get("queBaseInfos"))
        expected_visible_order = [name for name in visible_field_names if name]
        verified = actual_visible_order[: len(expected_visible_order)] == expected_visible_order
        return {
            "list_type": list_type,
            "expected_visible_order": expected_visible_order,
            "actual_visible_order": actual_visible_order,
            "verified": verified,
        }

    def _sync_system_view_and_restore_buttons(
        self,
        *,
        profile: str,
        app_key: str,
        viewgraph_key: str,
        payload: dict[str, Any],
        list_type: int,
        schema: dict[str, Any],
        visible_field_names: list[str],
    ) -> dict[str, Any]:
        sync_result = self._sync_system_view_apply_config(
            profile=profile,
            app_key=app_key,
            list_type=list_type,
            schema=schema,
            visible_field_names=visible_field_names,
        )
        if bool(sync_result.get("verified")) and "buttonConfigDTOList" in payload:
            self.views.view_update(profile=profile, viewgraph_key=viewgraph_key, payload=payload)
            sync_result = {**sync_result, "button_config_restored": True}
        return sync_result

    def _load_views_result(
        self,
        *,
        profile: str,
        app_key: str,
        tolerate_404: bool,
        tolerate_permission_restricted: bool = False,
        include_error: bool = False,
    ) -> tuple[Any, bool] | tuple[Any, bool, QingflowApiError | None]:
        try:
            views = self.views.view_list_flat(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if api_error.http_status == 404 or (
                tolerate_permission_restricted and _is_permission_restricted_api_error(api_error)
            ):
                try:
                    legacy_views = self.views.view_list(profile=profile, app_key=app_key)
                except (QingflowApiError, RuntimeError) as legacy_error:
                    legacy_api_error = _coerce_api_error(legacy_error)
                    if (
                        tolerate_404
                        and (
                            legacy_api_error.http_status == 404
                            or (
                                tolerate_permission_restricted
                                and _is_permission_restricted_api_error(legacy_api_error)
                            )
                        )
                    ):
                        return ([], True, legacy_api_error) if include_error else ([], True)
                    raise
                legacy_result = legacy_views.get("result")
                if _is_view_collection_shape(legacy_result):
                    result = _normalize_view_collection(legacy_result)
                    return (result, False, api_error) if include_error else (result, False)
                if tolerate_404:
                    return ([], True, api_error) if include_error else ([], True)
                raise error
            raise
        normalized_views = _normalize_view_collection(views.get("result"))
        if normalized_views:
            return (normalized_views, False, None) if include_error else (normalized_views, False)
        try:
            legacy_views = self.views.view_list(profile=profile, app_key=app_key)
        except (QingflowApiError, RuntimeError) as legacy_error:
            legacy_api_error = _coerce_api_error(legacy_error)
            if (
                tolerate_404
                and (
                    legacy_api_error.http_status == 404
                    or (
                        tolerate_permission_restricted
                        and _is_permission_restricted_api_error(legacy_api_error)
                    )
                )
            ):
                return (normalized_views, False, legacy_api_error) if include_error else (normalized_views, False)
            raise
        legacy_result = legacy_views.get("result")
        legacy_normalized = _normalize_view_collection(legacy_result)
        result = legacy_normalized or normalized_views
        return (result, False, None) if include_error else (result, False)

    def _load_workflow_result(
        self,
        *,
        profile: str,
        app_key: str,
        tolerate_404: bool,
        tolerate_permission_restricted: bool = False,
        legacy_fallback: bool = False,
        include_error: bool = False,
    ) -> tuple[Any, bool] | tuple[Any, bool, QingflowApiError | None]:
        def legacy_result() -> tuple[Any, bool, QingflowApiError | None]:
            try:
                legacy_workflow = self.workflows.workflow_list_nodes(profile=profile, app_key=app_key)
            except (QingflowApiError, RuntimeError) as legacy_error:
                legacy_api_error = _coerce_api_error(legacy_error)
                if tolerate_404 and (
                    legacy_api_error.http_status == 404
                    or (tolerate_permission_restricted and _is_permission_restricted_api_error(legacy_api_error))
                ):
                    return [], True, legacy_api_error
                raise
            legacy_payload = legacy_workflow.get("result") if isinstance(legacy_workflow, dict) and "result" in legacy_workflow else legacy_workflow
            if not legacy_fallback and not _summarize_workflow_nodes(legacy_payload):
                return [], True, None
            return legacy_payload, False, None

        try:
            workflow = self._with_backend_context(
                profile,
                lambda context: workflow_spec_api.fetch_spec(
                    self.apps.backend,
                    context,
                    app_key=app_key,
                ),
            )
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if tolerate_404 and (
                api_error.http_status == 404
                or (tolerate_permission_restricted and _is_permission_restricted_api_error(api_error))
            ):
                if not legacy_fallback:
                    return ([], True, api_error) if include_error else ([], True)
                legacy_workflow, legacy_unavailable, legacy_error = legacy_result()
                if not legacy_unavailable:
                    return (legacy_workflow, False, None) if include_error else (legacy_workflow, False)
                return ([], True, legacy_error or api_error) if include_error else ([], True)
            raise
        result = workflow if _looks_like_workflow_spec_payload(workflow) else []
        if not result:
            if not legacy_fallback:
                return ([], True, None) if include_error else ([], True)
            legacy_workflow, legacy_unavailable, legacy_error = legacy_result()
            if not legacy_unavailable:
                return (legacy_workflow, False, None) if include_error else (legacy_workflow, False)
            return ([], True, legacy_error) if include_error else ([], True)
        return (result, False, None) if include_error else (result, False)

    def _load_app_state(self, *, profile: str, app_key: str) -> dict[str, Any]:
        state = self._load_base_schema_state(profile=profile, app_key=app_key)
        views, views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=True)
        workflow, workflow_unavailable = self._load_workflow_result(profile=profile, app_key=app_key, tolerate_404=True)
        state["views"] = views
        state["workflow"] = workflow
        state["views_unavailable"] = views_unavailable
        state["workflow_unavailable"] = workflow_unavailable
        return state

    def _read_schema_with_fallback(self, *, profile: str, app_key: str) -> tuple[dict[str, Any], str]:
        attempts = (
            ("draft", True),
            ("current", None),
            ("published", False),
        )
        last_error: Exception | None = None
        for label, being_draft in attempts:
            try:
                schema = self.apps.app_get_form_schema(
                    profile=profile,
                    app_key=app_key,
                    form_type=1,
                    being_draft=being_draft,
                    being_apply=None,
                    audit_node_id=None,
                    include_raw=True,
                )
                result = schema.get("result")
                return (result if isinstance(result, dict) else {}), label
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                last_error = error
                if api_error.http_status == 404:
                    continue
                raise
        if last_error is not None:
            raise last_error
        return {}, "unknown"

    def _preview_target_app(
        self,
        *,
        profile: str,
        app_key: str,
        app_name: str,
        package_tag_id: int | None,
    ) -> JSONObject:
        if app_key:
            return self.app_resolve(profile=profile, app_key=app_key)
        if app_name:
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "target app would be created",
                "normalized_args": {},
                "missing_fields": [],
                "allowed_values": {},
                "details": {},
                "request_id": None,
                "suggested_next_call": None,
                "noop": False,
                "verification": {},
                "app_key": "",
                "app_name": app_name,
                "tag_ids": [package_tag_id] if package_tag_id else [],
                "would_create": True,
            }
        else:
            return _failed("APP_NAME_REQUIRED", "app_name or app_key is required", suggested_next_call=None)

    def _create_target_app_shell(
        self,
        *,
        profile: str,
        app_name: str,
        package_tag_id: int | None,
        icon: str | None,
        color: str | None,
        auth: dict[str, Any] | None = None,
    ) -> JSONObject:
        payload: JSONObject = {
            "appName": app_name or "未命名应用",
            "appIcon": encode_workspace_icon_with_defaults(
                icon=icon,
                color=color,
                title=app_name or "未命名应用",
                fallback_icon_name="template",
            ),
            "auth": deepcopy(auth if isinstance(auth, dict) else default_member_auth()),
            "tagIds": [package_tag_id] if package_tag_id and package_tag_id > 0 else [],
        }
        try:
            created = self.apps.app_create(profile=profile, payload=payload)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            request_route = self._current_request_route(profile)
            if _is_uncertain_write_transport_error(api_error):
                return _post_write_may_have_succeeded_result(
                    error_code="APP_CREATE_WRITE_RESULT_UNCERTAIN",
                    message="app create request did not return a final result; resolve the app in the package before retrying",
                    details={
                        "app_name": app_name,
                        "package_tag_id": package_tag_id,
                        "request_route": request_route,
                        "transport_error": _transport_error_payload(api_error),
                    },
                    suggested_next_call={
                        "tool_name": "app_resolve",
                        "arguments": {
                            "profile": profile,
                            "app_name": app_name,
                            "package_id": package_tag_id,
                        },
                    },
                    request_id=api_error.request_id,
                    backend_code=api_error.backend_code,
                    http_status=api_error.http_status,
                )
            return _failed_from_api_error(
                "CREATE_APP_ROUTE_NOT_FOUND" if api_error.http_status == 404 else "APP_CREATE_FAILED",
                api_error,
                details={
                    "app_name": app_name,
                    "package_tag_id": package_tag_id,
                    "request_route": request_route,
                },
                suggested_next_call=(
                    {"tool_name": "auth_use_credential", "arguments": {"profile": profile}}
                    if request_route.get("ws_id")
                    else None
                ),
            )
        result = created.get("result") if isinstance(created.get("result"), dict) else {}
        new_app_key = str(result.get("appKey") or (result.get("appKeys")[0] if isinstance(result.get("appKeys"), list) and result.get("appKeys") else ""))
        if not new_app_key:
            return _failed("APP_CREATE_FAILED", "failed to create app shell", details={"result": result}, suggested_next_call=None)
        try:
            base = self._read_app_base_raw_direct(profile=profile, app_key=new_app_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            if api_error.http_status != 404:
                pending = _post_write_readback_pending_result(
                    error_code="APP_CREATE_READBACK_PENDING",
                    message="created app; base readback is unavailable",
                    details={"app_key": new_app_key, "app_name": app_name, "package_tag_id": package_tag_id},
                    suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": new_app_key}},
                    request_id=api_error.request_id,
                    backend_code=api_error.backend_code,
                    http_status=api_error.http_status,
                )
                pending.update(
                    {
                        "app_key": new_app_key,
                        "app_name": app_name or "未命名应用",
                        "app_icon": payload.get("appIcon"),
                        "tag_ids": [package_tag_id] if package_tag_id and package_tag_id > 0 else [],
                        "created": True,
                    }
                )
                pending.setdefault("details", {})["readback_error"] = {
                    "message": api_error.message,
                    **_transport_error_payload(api_error),
                }
                return pending
            return {
                "status": "success",
                "error_code": None,
                "recoverable": False,
                "message": "created app; base readback pending",
                "suggested_next_call": None,
                "app_key": new_app_key,
                "app_name": app_name or "未命名应用",
                "app_icon": payload.get("appIcon"),
                "tag_ids": [package_tag_id] if package_tag_id and package_tag_id > 0 else [],
                "created": True,
            }
        return {
            "status": "success",
            "error_code": None,
            "recoverable": False,
            "message": "created app",
            "suggested_next_call": None,
            "app_key": new_app_key,
            "app_name": base.get("formTitle") or app_name or "未命名应用",
            "app_icon": str(base.get("appIcon") or payload.get("appIcon") or "").strip() or None,
            "tag_ids": _coerce_int_list(base.get("tagIds")),
            "created": True,
        }

    def _build_app_base_update_payload(
        self,
        *,
        raw_base: dict[str, Any],
        form_title: str,
        app_icon: str,
        auth_override: dict[str, Any] | None = None,
    ) -> JSONObject | None:
        auth = deepcopy(auth_override if isinstance(auth_override, dict) else raw_base.get("auth"))
        if not isinstance(auth, dict):
            return None
        payload: JSONObject = {
            "formTitle": form_title,
            "auth": auth,
            "appIcon": app_icon,
        }
        tag_ids = _coerce_int_list(raw_base.get("tagIds"))
        if tag_ids:
            payload["tagIds"] = tag_ids
        return payload

    def _ensure_app_base_visuals(
        self,
        *,
        profile: str,
        app_key: str,
        fallback_title: str,
        app_name: str | None,
        icon: str | None,
        color: str | None,
        auth_override: dict[str, Any] | None,
        normalized_args: dict[str, Any],
    ) -> JSONObject:
        try:
            base_result = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "APP_VISUAL_READ_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        raw_base = base_result.get("result") if isinstance(base_result.get("result"), dict) else {}
        effective_title = str(raw_base.get("formTitle") or fallback_title or "未命名应用").strip() or "未命名应用"
        desired_title = str(app_name or effective_title).strip() or effective_title
        existing_icon = str(raw_base.get("appIcon") or "").strip() or None
        existing_auth = deepcopy(raw_base.get("auth")) if isinstance(raw_base.get("auth"), dict) else None
        desired_icon = encode_workspace_icon_with_defaults(
            icon=icon,
            color=color,
            title=desired_title,
            fallback_icon_name="template",
            existing_icon=existing_icon,
        )
        auth_changed = isinstance(auth_override, dict) and deepcopy(auth_override) != existing_auth
        if desired_icon == existing_icon and desired_title == effective_title and not auth_changed:
            return {
                "status": "success",
                "updated": False,
                "app_icon": desired_icon or existing_icon,
                "app_name_before": effective_title,
                "app_name_after": desired_title,
                "request_id": None,
            }
        payload = self._build_app_base_update_payload(
            raw_base=raw_base,
            form_title=desired_title,
            app_icon=desired_icon,
            auth_override=auth_override,
        )
        if payload is None:
            return _failed(
                "APP_VISUAL_UPDATE_UNSUPPORTED",
                "app base info did not include editable auth payload required for icon update",
                normalized_args=normalized_args,
                details={"app_key": app_key},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        try:
            update_result = self.apps.app_update_base(profile=profile, app_key=app_key, payload=payload)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            return _failed_from_api_error(
                "APP_VISUAL_UPDATE_FAILED",
                api_error,
                normalized_args=normalized_args,
                details={"app_key": app_key, "app_icon": desired_icon},
                suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
            )
        return {
            "status": "success",
            "updated": True,
            "app_icon": desired_icon,
            "app_name_before": effective_title,
            "app_name_after": desired_title,
            "request_id": update_result.get("request_id") if isinstance(update_result, dict) else None,
        }

    def _current_request_route(self, profile: str) -> JSONObject:
        session_profile = self.apps.sessions.get_profile(profile)
        backend_session = self.apps.sessions.get_backend_session(profile)
        if session_profile is None or backend_session is None:
            return {}
        context = BackendRequestContext(
            base_url=backend_session.base_url,
            token=backend_session.token,
            ws_id=session_profile.selected_ws_id,
            qf_version=backend_session.qf_version,
            qf_version_source=backend_session.qf_version_source,
        )
        describe_route = getattr(self.apps.backend, "describe_route", None)
        route = describe_route(context) if callable(describe_route) else None
        payload = dict(route) if isinstance(route, dict) else {}
        payload.setdefault("ws_id", session_profile.selected_ws_id)
        return payload

    def _request_backend(
        self,
        *,
        profile: str,
        method: str,
        path: str,
        json_body: Any = None,
        params: dict[str, Any] | None = None,
    ) -> Any:
        _, _, context = self.apps._require_context(profile, require_workspace=True)
        return self.apps.backend.request(method, context, path, json_body=json_body, params=params)

    def _build_portal_components_from_sections(
        self,
        *,
        profile: str,
        sections: list[PortalSectionPatch],
        layout_preset: str | None = None,
        inline_chart_results: list[dict[str, Any]] | None = None,
    ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
        resolved_components: list[dict[str, Any]] = []
        layout_metadata: list[dict[str, Any]] = []
        inline_chart_results = inline_chart_results if inline_chart_results is not None else []
        inline_chart_resolutions: dict[int, dict[str, Any]] = {}
        inline_chart_result_by_section: dict[int, dict[str, Any]] = {}
        inline_chart_failures: list[str] = []
        inline_chart_jobs: list[dict[str, Any]] = []
        inline_chart_duplicates: dict[int, int] = {}
        inline_chart_signatures: dict[tuple[str, str, str], tuple[int, str]] = {}
        for section_index, section in enumerate(sections):
            if section.source_type != "chart" or section.chart is None:
                continue
            inline_chart_payload = section.chart.model_dump(mode="json", exclude={"app_key"})
            patch = ChartUpsertPatch.model_validate(inline_chart_payload)
            dedupe_key = (
                str(section.chart.app_key or "").strip(),
                str(section.chart.name or "").strip(),
                str(section.chart.chart_type.value if hasattr(section.chart.chart_type, "value") else section.chart.chart_type).strip(),
            )
            signature_payload = {"app_key": section.chart.app_key, **inline_chart_payload}
            signature = json.dumps(signature_payload, ensure_ascii=False, sort_keys=True, default=str)
            prior = inline_chart_signatures.get(dedupe_key)
            if prior is not None:
                primary_section_index, primary_signature = prior
                if primary_signature != signature:
                    inline_chart_failures.append(
                        "DUPLICATE_INLINE_CHART_CONFLICT: inline chart definitions with the same app_key, name, and chart_type must have identical config"
                    )
                    continue
                inline_chart_duplicates[section_index] = primary_section_index
                continue
            inline_chart_signatures[dedupe_key] = (section_index, signature)
            inline_chart_jobs.append(
                {
                    "section_index": section_index,
                    "section": section,
                    "patch": patch,
                    "app_key": section.chart.app_key,
                }
            )
        if inline_chart_failures:
            raise ValueError(inline_chart_failures[0])

        def _apply_inline_chart_job(job: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
            app_key = str(job["app_key"])
            chart_request = ChartApplyRequest(
                app_key=app_key,
                upsert_charts=[job["patch"]],
                patch_charts=[],
                remove_chart_ids=[],
                reorder_chart_ids=[],
            )
            return job, self.chart_apply(profile=profile, request=chart_request)

        inline_chart_workers = min(PORTAL_INLINE_CHART_MAX_WORKERS, len(inline_chart_jobs)) if inline_chart_jobs else 0
        inline_chart_job_results: list[tuple[dict[str, Any], dict[str, Any]]] = []
        if inline_chart_jobs:
            with ThreadPoolExecutor(max_workers=inline_chart_workers) as executor:
                future_map = {executor.submit(_apply_inline_chart_job, job): job for job in inline_chart_jobs}
                for future in as_completed(future_map):
                    job = future_map[future]
                    try:
                        inline_chart_job_results.append(future.result())
                    except (QingflowApiError, RuntimeError) as error:
                        api_error = _coerce_api_error(error)
                        inline_chart_job_results.append(
                            (
                                job,
                                {
                                    "status": "failed",
                                    "error_code": "PORTAL_INLINE_CHART_APPLY_FAILED",
                                    "message": _public_error_message("PORTAL_INLINE_CHART_APPLY_FAILED", api_error),
                                    "write_executed": False,
                                    "details": {"transport_error": _transport_error_payload(api_error)},
                                    "request_id": api_error.request_id,
                                    "backend_code": api_error.backend_code,
                                    "http_status": None if api_error.http_status == 404 else api_error.http_status,
                                },
                            )
                        )

        for job, chart_apply in sorted(inline_chart_job_results, key=lambda item: int(item[0].get("section_index") or 0)):
            app_key = str(job["app_key"])
            section_index = int(job["section_index"])
            section = cast(PortalSectionPatch, job["section"])
            patch = cast(ChartUpsertPatch, job["patch"])
            chart_items = chart_apply.get("chart_results") if isinstance(chart_apply.get("chart_results"), list) else []
            result_item: dict[str, Any] | None = None
            if chart_items and isinstance(chart_items[0], dict):
                result_item = chart_items[0]
            for item in chart_items:
                if isinstance(item, dict) and str(item.get("name") or "").strip() == patch.name:
                    result_item = item
                    break
            successful_chart = (
                result_item
                if isinstance(result_item, dict)
                and str(result_item.get("status") or "") in {"created", "updated"}
                and str(result_item.get("chart_id") or "").strip()
                else None
            )
            inline_chart_result_by_section[section_index] = {
                "title": section.title,
                "app_key": app_key,
                "chart_name": section.chart.name if section.chart is not None else patch.name,
                "chart_type": section.chart.chart_type.value if section.chart is not None else patch.chart_type.value,
                "status": str(successful_chart.get("status") or "") if isinstance(successful_chart, dict) else "failed",
                "chart_id": str(successful_chart.get("chart_id") or "") if isinstance(successful_chart, dict) else "",
                "chart_apply_status": chart_apply.get("status"),
                "write_executed": bool(chart_apply.get("write_executed")),
                "batch_size": 1,
                "batch_index": 0,
                "inline_chart_workers": inline_chart_workers,
                "inline_chart_job_count": len(inline_chart_jobs),
                **(
                    {"duration_breakdown_ms": deepcopy(chart_apply.get("duration_breakdown_ms"))}
                    if isinstance(chart_apply.get("duration_breakdown_ms"), dict)
                    else {}
                ),
                **({"error_code": chart_apply.get("error_code")} if chart_apply.get("error_code") else {}),
                **({"message": chart_apply.get("message")} if chart_apply.get("message") else {}),
            }
            if not isinstance(successful_chart, dict):
                inline_chart_failures.append(
                    f"PORTAL_INLINE_CHART_APPLY_FAILED: inline chart '{section.chart.name if section.chart is not None else patch.name}' could not be created or updated for portal section '{section.title}'"
                )
                continue
            inline_chart_resolutions[section_index] = {
                "chart_id": str(successful_chart.get("chart_id") or "").strip(),
                "chart_name": str(successful_chart.get("name") or patch.name).strip(),
                "app_key": app_key,
                "chart_type": section.chart.chart_type.value if section.chart is not None else patch.chart_type.value,
            }
        for section_index, primary_section_index in sorted(inline_chart_duplicates.items()):
            primary_resolution = inline_chart_resolutions.get(primary_section_index)
            primary_result = inline_chart_result_by_section.get(primary_section_index)
            section = sections[section_index]
            if not isinstance(primary_resolution, dict) or not isinstance(primary_result, dict):
                chart_name = section.chart.name if section.chart is not None else section.title
                inline_chart_failures.append(
                    f"PORTAL_INLINE_CHART_APPLY_FAILED: inline chart '{chart_name}' could not be reused for portal section '{section.title}' because the primary definition did not verify"
                )
                continue
            inline_chart_resolutions[section_index] = deepcopy(primary_resolution)
            reused_result = deepcopy(primary_result)
            reused_result["title"] = section.title
            reused_result["reused_from_section"] = primary_section_index
            reused_result["reused"] = True
            inline_chart_result_by_section[section_index] = reused_result
        if inline_chart_result_by_section:
            inline_chart_results.extend(
                deepcopy(inline_chart_result_by_section[section_index])
                for section_index in sorted(inline_chart_result_by_section)
            )
        if inline_chart_failures:
            raise ValueError(inline_chart_failures[0])
        pc_x = 0
        pc_y = 0
        pc_row_height = 0
        mobile_y = 0
        app_title_cache: dict[str, str] = {}
        for section_index, section in enumerate(sections):
            position_payload: dict[str, Any]
            if section.position is None:
                position_payload, pc_x, pc_y, pc_row_height, mobile_y = _portal_component_position_public(
                    section.source_type,
                    pc_x=pc_x,
                    pc_y=pc_y,
                    pc_row_height=pc_row_height,
                    mobile_y=mobile_y,
                    layout_preset=layout_preset,
                )
            else:
                position_payload = _portal_position_payload(section.position, inferred_mobile_y=mobile_y)
                mobile = position_payload.get("mobile") if isinstance(position_payload.get("mobile"), dict) else {}
                mobile_y = max(
                    mobile_y,
                    int(mobile.get("y") or 0) + int(mobile.get("rows") or 0),
                )
            dash_style = deepcopy(section.dash_style_config) if isinstance(section.dash_style_config, dict) else None
            component: dict[str, Any]
            if section.source_type == "chart":
                if section.chart is not None:
                    resolved_chart = inline_chart_resolutions.get(section_index)
                    if not isinstance(resolved_chart, dict):
                        raise ValueError(f"inline chart '{section.chart.name}' could not be created or updated for portal section '{section.title}'")
                else:
                    resolved_chart = _resolve_chart_reference(
                        charts=self.charts,
                        profile=profile,
                        ref=section.chart_ref,
                    )
                chart_type = str(
                    resolved_chart.get("chart_type")
                    or _public_chart_type_from_backend(section.config.get("chartType") or section.config.get("chart_type"))
                    or ""
                ).strip()
                chart_config = {
                    "biChartId": resolved_chart["chart_id"],
                    "chartComponentTitle": section.title,
                    "beingShowTitle": True,
                    **deepcopy(section.config),
                }
                component = {"type": 9, "position": position_payload, "chartConfig": _compact_dict(chart_config)}
                layout_metadata.append({"source_type": section.source_type, "chart_type": chart_type, "role": section.role})
            elif section.source_type == "view":
                resolved_view = _resolve_view_reference(
                    facade=self,
                    profile=profile,
                    ref=section.view_ref,
                )
                if resolved_view["app_key"] not in app_title_cache:
                    app_title_cache[resolved_view["app_key"]] = str(
                        self.app_resolve(profile=profile, app_key=resolved_view["app_key"]).get("app_name") or resolved_view["app_key"]
                    )
                view_config = {
                    "appKey": resolved_view["app_key"],
                    "viewgraphKey": resolved_view["view_key"],
                    "viewgraphName": section.title,
                    "formTitle": app_title_cache[resolved_view["app_key"]],
                    "componentTitle": section.title,
                    "viewgraphType": resolved_view["view_type"],
                    "beingShowTitle": True,
                    "dataManageStatus": True,
                    **deepcopy(section.config),
                }
                component = {"type": 10, "position": position_payload, "viewgraphConfig": _compact_dict(view_config)}
                layout_metadata.append({"source_type": section.source_type, "role": section.role})
            elif section.source_type == "grid":
                component = {
                    "type": 2,
                    "position": position_payload,
                    "gridConfig": _compact_dict({"gridTitle": section.title, "beingShowTitle": True, **deepcopy(section.config)}),
                }
                layout_metadata.append({"source_type": section.source_type, "role": section.role})
            elif section.source_type == "filter":
                component = {"type": 6, "position": position_payload, "filterConfig": deepcopy(section.config)}
                layout_metadata.append({"source_type": section.source_type, "role": section.role})
            elif section.source_type == "text":
                component = {"type": 5, "position": position_payload, "textConfig": {"text": section.text or "", **deepcopy(section.config)}}
                layout_metadata.append({"source_type": section.source_type, "role": section.role})
            else:
                component = {
                    "type": 4,
                    "position": position_payload,
                    "linkConfig": {"url": section.url or "", "beingLoginAuth": False, **deepcopy(section.config)},
                }
                layout_metadata.append({"source_type": section.source_type, "role": section.role})
            if dash_style is not None:
                component["dashStyleConfigBO"] = dash_style
            resolved_components.append(component)
        return resolved_components, layout_metadata, inline_chart_results

    def _resolve_current_user_identity(self, *, profile: str) -> JSONObject:
        session_profile = self.apps.sessions.get_profile(profile)
        backend_session = self.apps.sessions.get_backend_session(profile)
        current_email = str((session_profile.email if session_profile else None) or "").strip()
        current_name = str((session_profile.nick_name if session_profile else None) or "").strip()
        if current_email or current_name or session_profile is None or backend_session is None:
            return {"email": current_email or None, "nick_name": current_name or None}
        try:
            user_info = self.apps.backend.request(
                "GET",
                BackendRequestContext(
                    base_url=backend_session.base_url,
                    token=backend_session.token,
                    ws_id=session_profile.selected_ws_id,
                    qf_version=backend_session.qf_version,
                    qf_version_source=backend_session.qf_version_source,
                ),
                "/user",
            )
        except (QingflowApiError, RuntimeError):
            return {"email": current_email or None, "nick_name": current_name or None}
        if not isinstance(user_info, dict):
            return {"email": current_email or None, "nick_name": current_name or None}
        resolved_email = str(user_info.get("email") or "").strip() or None
        resolved_name = str(user_info.get("nickName") or user_info.get("displayName") or user_info.get("name") or "").strip() or None
        return {"email": resolved_email, "nick_name": resolved_name}

    def _attach_app_to_package(self, *, profile: str, app_key: str, app_title: str, package_tag_id: int) -> None:
        detail = self.packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=True)
        result = detail.get("result") if isinstance(detail.get("result"), dict) else {}
        tag_items = [deepcopy(item) for item in result.get("tagItems", []) if isinstance(item, dict)]
        if any(str(item.get("appKey") or "") == app_key for item in tag_items):
            return
        tag_items.append(
            {
                "itemType": 1,
                "appKey": app_key,
                "title": app_title,
                "iconUrl": None,
            }
        )
        self.packages.package_sort_items(profile=profile, tag_id=package_tag_id, tag_items=tag_items)
        verified = _verify_package_attachment(self.packages, profile=profile, tag_id=package_tag_id, app_key=app_key)
        verified_result = verified.get("result") if isinstance(verified.get("result"), dict) else {}
        if not _tag_items_include_app(verified_result.get("tagItems"), app_key):
            raise QingflowApiError(category="runtime", message="failed to attach app to package")


def _extract_custom_button_id(value: Any) -> int | None:
    if isinstance(value, dict):
        for key in ("buttonId", "customButtonId", "id"):
            button_id = _coerce_positive_int(value.get(key))
            if button_id is not None:
                return button_id
        nested_result = value.get("result")
        if nested_result is not value:
            return _extract_custom_button_id(nested_result)
        return None
    return _coerce_positive_int(value)


def _serialize_custom_button_payload(
    payload: CustomButtonPatch,
    *,
    add_data_config_override: dict[str, Any] | None = None,
) -> dict[str, Any]:
    data = payload.model_dump(mode="json", exclude_none=True)
    serialized: dict[str, Any] = {
        "buttonText": data["button_text"],
        "backgroundColor": data["background_color"],
        "textColor": data["text_color"],
        "buttonIcon": data["button_icon"],
        "triggerAction": data["trigger_action"],
    }
    if str(data.get("trigger_link_url") or "").strip():
        serialized["triggerLinkUrl"] = data["trigger_link_url"]
    trigger_add_data_config = add_data_config_override if add_data_config_override is not None else data.get("trigger_add_data_config")
    if isinstance(trigger_add_data_config, dict):
        serialized["triggerAddDataConfig"] = _serialize_custom_button_add_data_config(trigger_add_data_config)
    else:
        serialized["triggerAddDataConfig"] = _serialize_custom_button_add_data_config({})
    external_qrobot_config = data.get("external_qrobot_config")
    if isinstance(external_qrobot_config, dict):
        serialized["customButtonExternalQRobotRelationVO"] = _serialize_custom_button_external_qrobot_config(external_qrobot_config)
    trigger_wings_config = data.get("trigger_wings_config")
    if isinstance(trigger_wings_config, dict):
        serialized["triggerWingsConfig"] = _serialize_custom_button_wings_config(trigger_wings_config)
    return serialized


def _serialize_custom_button_add_data_config(value: dict[str, Any]) -> dict[str, Any]:
    relation_rules = value.get("que_relation") or []
    return {
        "relatedAppKey": value.get("related_app_key"),
        "relatedAppName": value.get("related_app_name"),
        "queRelation": [_serialize_custom_button_match_rule(rule) for rule in relation_rules if isinstance(rule, dict)],
    }


def _serialize_custom_button_external_qrobot_config(value: dict[str, Any]) -> dict[str, Any]:
    return {
        "externalQRobotConfigId": value.get("external_qrobot_config_id"),
        "triggeredText": value.get("triggered_text"),
    }


def _serialize_custom_button_wings_config(value: dict[str, Any]) -> dict[str, Any]:
    return {
        "wingsAgentId": value.get("wings_agent_id"),
        "wingsAgentName": value.get("wings_agent_name"),
        "bindQueIdList": list(value.get("bind_que_id_list") or []),
        "bindFileQueIdList": list(value.get("bind_file_que_id_list") or []),
        "defaultPrompt": value.get("default_prompt"),
        "beingAutoSend": value.get("being_auto_send"),
    }


def _serialize_custom_button_match_rule(value: dict[str, Any]) -> dict[str, Any]:
    serialized = {
        "queId": value.get("que_id"),
        "queTitle": value.get("que_title"),
        "queType": value.get("que_type"),
        "dateType": value.get("date_type"),
        "judgeType": value.get("judge_type"),
        "matchType": value.get("match_type"),
        "judgeValues": list(value.get("judge_values") or []),
        "judgeQueType": value.get("judge_que_type"),
        "judgeQueId": value.get("judge_que_id"),
        "pathValue": value.get("path_value"),
        "tableUpdateType": value.get("table_update_type"),
        "multiValue": value.get("multi_value"),
        "addRule": value.get("add_rule"),
        "fieldIdPrefix": value.get("field_id_prefix"),
    }
    judge_que_detail = value.get("judge_que_detail")
    if isinstance(judge_que_detail, dict):
        serialized["judgeQueDetail"] = {
            "queId": judge_que_detail.get("que_id"),
            "queTitle": judge_que_detail.get("que_title"),
            "queType": judge_que_detail.get("que_type"),
        }
    judge_value_details = value.get("judge_value_details")
    if isinstance(judge_value_details, list):
        serialized["judgeValueDetails"] = [
            {"id": item.get("id"), "value": item.get("value")}
            for item in judge_value_details
            if isinstance(item, dict)
        ]
    filter_condition = value.get("filter_condition")
    if isinstance(filter_condition, list):
        serialized["filterCondition"] = [
            [_serialize_custom_button_match_rule(item) for item in group if isinstance(item, dict)]
            for group in filter_condition
            if isinstance(group, list)
        ]
    return {key: deepcopy(item) for key, item in serialized.items() if item is not None}


def _custom_button_add_data_config_has_semantic_inputs(value: dict[str, Any]) -> bool:
    return bool(value.get("field_mappings") or value.get("fieldMappings") or value.get("default_values") or value.get("defaultValues"))


def _normalize_custom_button_add_data_config_for_public(value: dict[str, Any]) -> dict[str, Any]:
    normalized: dict[str, Any] = {}
    related_app_key = _first_present(value, "related_app_key", "relatedAppKey", "target_app_key", "targetAppKey")
    related_app_name = _first_present(value, "related_app_name", "relatedAppName")
    if related_app_key is not None:
        normalized["related_app_key"] = related_app_key
    if related_app_name is not None:
        normalized["related_app_name"] = related_app_name
    relation_rules = _first_present(value, "que_relation", "queRelation")
    if isinstance(relation_rules, list):
        normalized["que_relation"] = [
            _normalize_custom_button_match_rule_for_public(rule)
            for rule in relation_rules
            if isinstance(rule, dict)
        ]
    field_mappings = _first_present(value, "field_mappings", "fieldMappings", "mappings")
    if isinstance(field_mappings, list):
        normalized["field_mappings"] = deepcopy(field_mappings)
    default_values = _first_present(value, "default_values", "defaultValues", "defaults")
    if isinstance(default_values, dict):
        normalized["default_values"] = deepcopy(default_values)
    return _compact_dict(normalized)


def _normalize_custom_button_match_rule_for_public(value: dict[str, Any]) -> dict[str, Any]:
    normalized: dict[str, Any] = {}
    key_pairs = {
        "que_id": ("que_id", "queId"),
        "que_title": ("que_title", "queTitle"),
        "que_type": ("que_type", "queType"),
        "date_type": ("date_type", "dateType"),
        "judge_type": ("judge_type", "judgeType"),
        "match_type": ("match_type", "matchType"),
        "judge_que_type": ("judge_que_type", "judgeQueType"),
        "judge_que_id": ("judge_que_id", "judgeQueId"),
        "path_value": ("path_value", "pathValue"),
        "table_update_type": ("table_update_type", "tableUpdateType"),
        "multi_value": ("multi_value", "multiValue"),
        "add_rule": ("add_rule", "addRule"),
        "field_id_prefix": ("field_id_prefix", "fieldIdPrefix"),
    }
    for public_key, aliases in key_pairs.items():
        raw_value = _first_present(value, *aliases)
        if raw_value is not None:
            normalized[public_key] = raw_value
    judge_values = _first_present(value, "judge_values", "judgeValues")
    if isinstance(judge_values, list):
        normalized["judge_values"] = [str(item) for item in judge_values if item is not None]
    elif judge_values is not None:
        normalized["judge_values"] = [str(judge_values)]
    judge_que_detail = _first_present(value, "judge_que_detail", "judgeQueDetail")
    if isinstance(judge_que_detail, dict):
        normalized["judge_que_detail"] = _compact_dict(
            {
                "que_id": _first_present(judge_que_detail, "que_id", "queId"),
                "que_title": _first_present(judge_que_detail, "que_title", "queTitle"),
                "que_type": _first_present(judge_que_detail, "que_type", "queType"),
            }
        )
    judge_value_details = _first_present(value, "judge_value_details", "judgeValueDetails")
    if isinstance(judge_value_details, list):
        details: list[dict[str, Any]] = []
        for item in judge_value_details:
            if not isinstance(item, dict):
                continue
            detail = _compact_dict(
                {
                    "id": _first_present(item, "id", "opt_id", "optId", "member_id", "memberId"),
                    "value": _first_present(item, "value", "name", "label", "title"),
                }
            )
            if detail:
                details.append(detail)
        normalized["judge_value_details"] = details
    filter_condition = _first_present(value, "filter_condition", "filterCondition")
    if isinstance(filter_condition, list):
        normalized["filter_condition"] = [
            [
                _normalize_custom_button_match_rule_for_public(item)
                for item in group
                if isinstance(item, dict)
            ]
            for group in filter_condition
            if isinstance(group, list)
        ]
    return _compact_dict(normalized)


def _custom_button_selector_payload(selector: Any) -> dict[str, Any]:
    if isinstance(selector, dict):
        payload = dict(selector)
        if "title" in payload and "name" not in payload:
            payload["name"] = payload.pop("title")
        if "label" in payload and "name" not in payload:
            payload["name"] = payload.pop("label")
        if "fieldId" in payload and "field_id" not in payload:
            payload["field_id"] = payload.pop("fieldId")
        if "queId" in payload and "que_id" not in payload:
            payload["que_id"] = payload.pop("queId")
        return payload
    if isinstance(selector, int):
        return {"que_id": selector}
    raw = str(selector or "").strip()
    if not raw:
        return {}
    parsed_int = _coerce_any_int(raw)
    if parsed_int is not None:
        return {"que_id": parsed_int}
    return {"name": raw}


_SYSTEM_MATCH_FIELD_ALIASES: dict[str, int] = {
    "数据id": -17,
    "数据ID": -17,
    "数据_id": -17,
    "row_record_id": -17,
    "data_id": -17,
    "record_id": -17,
    "apply_id": -17,
    "_id": -17,
    "编号": 0,
    "数据编号": 0,
    "record_number": 0,
    "serial_number": 0,
    "apply_num": 0,
    "data_num": 0,
}


def _normalize_system_match_alias(value: Any) -> str:
    raw = str(value or "").strip()
    if raw.startswith("{{") and raw.endswith("}}"):
        raw = raw[2:-2].strip()
    return raw.replace(" ", "").lower()


def _system_match_field_from_selector(selector: Any) -> dict[str, Any] | None:
    if isinstance(selector, dict):
        for key in ("field_id", "fieldId", "que_id", "queId"):
            parsed = _coerce_any_int(selector.get(key))
            if parsed in {-17, 0}:
                return _system_match_field(parsed)
        for key in ("name", "title", "label", "value", "field", "source_field", "target_field"):
            raw = str(selector.get(key) or "").strip()
            if raw:
                field = _system_match_field_from_selector(raw)
                if field is not None:
                    return field
        return None
    parsed = _coerce_any_int(selector)
    if parsed in {-17, 0}:
        return _system_match_field(parsed)
    normalized = _normalize_system_match_alias(selector)
    if normalized in _SYSTEM_MATCH_FIELD_ALIASES:
        return _system_match_field(_SYSTEM_MATCH_FIELD_ALIASES[normalized])
    return None


def _system_match_field(que_id: int) -> dict[str, Any] | None:
    if que_id == -17:
        return {
            "field_id": -17,
            "que_id": -17,
            "que_type": 8,
            "name": "数据ID",
            "type": "system_record_id",
            "system_field": True,
            "system_kind": "row_record_id",
        }
    if que_id == 0:
        return {
            "field_id": 0,
            "que_id": 0,
            "que_type": 8,
            "name": "编号",
            "type": "system_record_number",
            "system_field": True,
            "system_kind": "record_number",
        }
    return None


def _resolve_custom_button_schema_field(
    *,
    fields: list[dict[str, Any]],
    selector: Any,
    reason_path: str,
    role: str,
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
    selector_payload = _custom_button_selector_payload(selector)
    allow_system = role in {"source", "match_source", "match_target"}
    if allow_system:
        system_field = _system_match_field_from_selector(selector_payload or selector)
        if system_field is not None:
            return system_field, None
    if not selector_payload:
        return None, {
            "error_code": "CUSTOM_BUTTON_MAPPING_FIELD_NOT_FOUND",
            "reason_path": reason_path,
            "message": f"{role} field selector is empty",
            "next_action": "pass a field title or {'field_id': ...}",
        }
    matched: list[dict[str, Any]] = []
    field_id = selector_payload.get("field_id")
    que_id = _coerce_any_int(selector_payload.get("que_id"))
    name = str(selector_payload.get("name") or "").strip()
    if field_id is not None:
        field_id_text = str(field_id).strip()
        field_id_int = _coerce_any_int(field_id_text)
        matched = [
            field
            for field in fields
            if str(field.get("field_id") or "").strip() == field_id_text
            or (field_id_int is not None and _coerce_any_int(field.get("que_id")) == field_id_int)
        ]
    elif que_id is not None:
        matched = [field for field in fields if _coerce_any_int(field.get("que_id")) == que_id]
    elif name:
        matched = [field for field in fields if str(field.get("name") or "").strip() == name]
    if len(matched) == 1:
        return matched[0], None
    return None, {
        "error_code": "CUSTOM_BUTTON_MAPPING_FIELD_NOT_FOUND" if not matched else "CUSTOM_BUTTON_MAPPING_FIELD_AMBIGUOUS",
        "reason_path": reason_path,
        "selector": selector,
        "message": f"{role} field selector did not resolve to exactly one field",
        "candidates": [
            {"field_id": item.get("field_id"), "que_id": item.get("que_id"), "title": item.get("name"), "type": item.get("type")}
            for item in matched[:10]
        ],
        "next_action": "call app_get_fields and retry with an exact field title or field_id",
    }


def _custom_button_mapping_type_issue(
    *,
    source_field: dict[str, Any],
    target_field: dict[str, Any],
    reason_path: str,
    source_app_key: str | None = None,
    error_code: str = "CUSTOM_BUTTON_MAPPING_TYPE_MISMATCH",
    context_label: str = "field mapping",
) -> dict[str, Any] | None:
    source_type = str(source_field.get("type") or "")
    target_type = str(target_field.get("type") or "")
    compatible, reason = _match_mapping_types_compatible(
        source_field=source_field,
        target_field=target_field,
        source_app_key=source_app_key,
    )
    if compatible:
        return None
    if reason == "reference_source_mismatch":
        target_app_key = _relation_target_app_key(target_field)
        return {
            "error_code": "MATCH_RULE_REFERENCE_SOURCE_MISMATCH",
            "reason_path": reason_path,
            "source_app_key": source_app_key,
            "target_relation_app_key": target_app_key,
            "source_field": _match_field_summary(source_field),
            "target_field": _match_field_summary(target_field),
            "message": "数据ID represents the current source record, but the target relation field points to another app",
            "next_action": "choose a relation field that targets the current source app, or map a compatible non-relation field",
        }
    return {
        "error_code": error_code,
        "reason_path": reason_path,
        "source_field": _match_field_summary(source_field),
        "target_field": _match_field_summary(target_field),
        "message": f"source and target fields have incompatible types for {context_label}",
        "compatibility_reason": reason,
        "next_action": "choose fields with the same type family; use 数据ID only for current-record id/relation matching and use static value/default_values for constants",
    }


def _retag_associated_resource_match_field_issue(issue: dict[str, Any]) -> dict[str, Any]:
    retagged = dict(issue)
    code = str(retagged.get("error_code") or "")
    if code == "CUSTOM_BUTTON_MAPPING_FIELD_NOT_FOUND":
        retagged["error_code"] = "ASSOCIATED_RESOURCE_MAPPING_FIELD_NOT_FOUND"
    elif code == "CUSTOM_BUTTON_MAPPING_FIELD_AMBIGUOUS":
        retagged["error_code"] = "ASSOCIATED_RESOURCE_MAPPING_FIELD_AMBIGUOUS"
    return retagged


def _retag_associated_resource_static_value_issue(issue: dict[str, Any]) -> dict[str, Any]:
    retagged = dict(issue)
    if str(retagged.get("error_code") or "") == "CUSTOM_BUTTON_DEFAULT_VALUE_UNSUPPORTED":
        retagged["error_code"] = "ASSOCIATED_RESOURCE_STATIC_VALUE_UNSUPPORTED"
        retagged["message"] = str(retagged.get("message") or "static associated resource match value is unsupported")
        retagged["next_action"] = str(
            retagged.get("next_action")
            or "retry match_mappings[].value with a literal value supported by the target field type"
        )
    return retagged


def _match_field_summary(field: dict[str, Any]) -> dict[str, Any]:
    return {
        "title": field.get("name"),
        "field_id": field.get("field_id"),
        "que_id": field.get("que_id"),
        "type": field.get("type"),
        "system_field": bool(field.get("system_field")),
    }


def _relation_target_app_key(field: dict[str, Any]) -> str | None:
    for key in ("target_app_key", "targetAppKey", "refer_app_key", "referAppKey"):
        value = str(field.get(key) or "").strip()
        if value:
            return value
    for container_key in ("relation", "reference", "reference_config", "referenceConfig", "config"):
        container = field.get(container_key)
        if not isinstance(container, dict):
            continue
        nested = _relation_target_app_key(container)
        if nested:
            return nested
    template = field.get("_reference_config_template")
    if isinstance(template, dict):
        nested = _relation_target_app_key(template)
        if nested:
            return nested
    return None


def _match_mapping_types_compatible(
    *,
    source_field: dict[str, Any],
    target_field: dict[str, Any],
    source_app_key: str | None = None,
) -> tuple[bool, str | None]:
    source_type = str(source_field.get("type") or "")
    target_type = str(target_field.get("type") or "")
    unsupported = {
        FieldType.attachment.value,
        FieldType.subtable.value,
        FieldType.code_block.value,
        FieldType.q_linker.value,
        FieldType.address.value,
    }
    if source_type in unsupported or target_type in unsupported:
        return False, "unsupported_field_type"

    if target_type == FieldType.relation.value:
        if source_type == "system_record_id":
            target_app_key = _relation_target_app_key(target_field)
            if source_app_key and target_app_key and str(target_app_key).strip() != str(source_app_key).strip():
                return False, "reference_source_mismatch"
            return True, None
        if source_type == FieldType.relation.value:
            source_target = _relation_target_app_key(source_field)
            target_target = _relation_target_app_key(target_field)
            if source_target and target_target and source_target != target_target:
                return False, "relation_target_app_mismatch"
            return True, None
        return False, "relation_requires_record_id_or_relation"
    if source_type == FieldType.relation.value:
        if target_type == "system_record_id":
            return True, None
        return False, "relation_only_matches_relation_or_record_id"

    if source_type == "system_record_id":
        if target_type in {
            "system_record_id",
            FieldType.text.value,
            FieldType.long_text.value,
            FieldType.number.value,
            FieldType.amount.value,
        }:
            return True, None
        return False, "record_id_requires_relation_or_id_compatible_target"
    if target_type == "system_record_id":
        if source_type in {
            "system_record_id",
            FieldType.text.value,
            FieldType.long_text.value,
            FieldType.number.value,
            FieldType.amount.value,
        }:
            return True, None
        return False, "record_id_requires_id_compatible_source"

    if source_type == "system_record_number":
        if target_type in {
            "system_record_number",
            FieldType.text.value,
            FieldType.long_text.value,
            FieldType.number.value,
            FieldType.amount.value,
        }:
            return True, None
        return False, "record_number_requires_text_or_number_target"
    if target_type == "system_record_number":
        if source_type in {
            "system_record_number",
            FieldType.text.value,
            FieldType.long_text.value,
            FieldType.number.value,
            FieldType.amount.value,
        }:
            return True, None
        return False, "record_number_requires_text_or_number_source"

    exact_only = {FieldType.member.value, FieldType.department.value}
    if source_type in exact_only or target_type in exact_only:
        return (source_type == target_type, None if source_type == target_type else "member_department_require_same_type")

    compatible_groups = [
        {FieldType.text.value, FieldType.long_text.value, FieldType.phone.value, FieldType.email.value},
        {FieldType.number.value, FieldType.amount.value},
        {FieldType.date.value, FieldType.datetime.value},
        {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value},
    ]
    if source_type == target_type or any(source_type in group and target_type in group for group in compatible_groups):
        return True, None
    return False, "different_type_family"


def _match_mapping_operator_to_judge_type(operator: Any, *, reason_path: str) -> tuple[int, dict[str, Any] | None]:
    normalized = normalize_public_condition_operator(
        operator or "eq",
        extra_aliases={"include_any": "include_any", "includes_any": "include_any", "includeany": "include_any"},
    )
    normalized = str(normalized or "eq").strip().lower()
    aliases = {
        "eq": JUDGE_EQUAL,
        "neq": JUDGE_UNEQUAL,
        "gte": JUDGE_GREATER_OR_EQUAL,
        "lte": JUDGE_LESS_OR_EQUAL,
        "contains": JUDGE_INCLUDE,
        "in": JUDGE_EQUAL_ANY,
        "include_any": JUDGE_INCLUDE_ANY,
        "is_empty": JUDGE_EQUAL,
        "not_empty": JUDGE_UNEQUAL,
    }
    if normalized in aliases:
        return aliases[normalized], None
    return JUDGE_EQUAL, {
        "error_code": "INVALID_MATCH_MAPPING_OPERATOR",
        "reason_path": reason_path,
        "operator": operator,
        "allowed_values": sorted(aliases),
        "message": "match_mappings[].operator is not supported",
        "next_action": "retry with one of the allowed operator values, such as eq, contains, in, gte, lte, is_empty, or not_empty",
    }


def _custom_button_question_ref(field: dict[str, Any]) -> dict[str, Any]:
    return {
        "que_id": _coerce_any_int(field.get("que_id")),
        "que_title": str(field.get("name") or ""),
        "que_type": _coerce_nonnegative_int(field.get("que_type")),
    }


def _custom_button_field_mapping_rule(*, source_field: dict[str, Any], target_field: dict[str, Any]) -> dict[str, Any]:
    source_que_id = _coerce_any_int(source_field.get("que_id"))
    return {
        **_custom_button_question_ref(target_field),
        "match_type": MATCH_TYPE_QUESTION,
        "judge_type": JUDGE_EQUAL,
        "judge_values": [str(source_que_id)] if source_que_id is not None else [],
        "judge_que_id": source_que_id,
        "judge_que_type": _coerce_nonnegative_int(source_field.get("que_type")),
        "judge_que_detail": _custom_button_question_ref(source_field),
    }


def _custom_button_default_value_rule(*, target_field: dict[str, Any], value_details: list[dict[str, Any]]) -> dict[str, Any]:
    return {
        **_custom_button_question_ref(target_field),
        "match_type": MATCH_TYPE_ACCURACY,
        "judge_type": JUDGE_EQUAL,
        "judge_values": [
            str(detail.get("id") if detail.get("id") is not None else detail.get("value", ""))
            for detail in value_details
        ],
        "judge_value_details": value_details,
    }


def _summarize_compiled_match_rules(rules: list[dict[str, Any]]) -> list[dict[str, Any]]:
    summary: list[dict[str, Any]] = []
    for rule in rules:
        if not isinstance(rule, dict):
            continue
        is_dynamic = rule.get("match_type") == MATCH_TYPE_QUESTION
        value_rule = {
            "judgeValues": rule.get("judge_values") or [],
            "judgeValueDetails": rule.get("judge_value_details") or [],
        }
        values = [] if is_dynamic else _public_view_filter_rule_values(value_rule, field={})
        operator = _public_view_filter_operator_from_judge_type(
            rule.get("judge_type"),
            values=None if is_dynamic else values,
        )
        item: dict[str, Any] = {
            "target_field": {
                "title": rule.get("que_title"),
                "field_id": rule.get("que_id"),
                "que_id": rule.get("que_id"),
                "que_type": rule.get("que_type"),
            },
            "match_type": rule.get("match_type"),
            "operator": operator,
            "source_field": (
                {
                    "title": (rule.get("judge_que_detail") or {}).get("que_title"),
                    "field_id": rule.get("judge_que_id"),
                    "que_id": rule.get("judge_que_id"),
                    "que_type": rule.get("judge_que_type"),
                }
                if is_dynamic
                else None
            ),
        }
        if not is_dynamic and values and operator == "in":
            item["values"] = values
        elif not is_dynamic and len(values) == 1:
            item["value"] = values[0]
        elif not is_dynamic and values:
            item["values"] = values
        summary.append(_compact_dict(item))
    return summary


def _resolve_custom_button_option_detail(*, target_field: dict[str, Any], value: Any) -> dict[str, Any] | None:
    option_details = [item for item in target_field.get("option_details") or [] if isinstance(item, dict)]
    value_id = _coerce_positive_int(value.get("id", value.get("opt_id", value.get("optId"))) if isinstance(value, dict) else value)
    value_text = str(value.get("value", value.get("name", "")) if isinstance(value, dict) else value or "").strip()
    for item in option_details:
        opt_id = _coerce_positive_int(item.get("id"))
        opt_value = str(item.get("value") or "").strip()
        if (value_id is not None and opt_id == value_id) or (value_text and opt_value == value_text):
            return {"id": opt_id, "value": opt_value}
    return None


def _custom_button_default_value_issue(
    *,
    target_field: dict[str, Any],
    reason_path: str,
    value: Any,
    message: str,
    allowed_values: dict[str, Any] | None = None,
    next_action: str | None = None,
) -> dict[str, Any]:
    return {
        "error_code": "CUSTOM_BUTTON_DEFAULT_VALUE_UNSUPPORTED",
        "reason_path": reason_path,
        "target_field": {
            "title": target_field.get("name"),
            "field_id": target_field.get("field_id"),
            "que_id": target_field.get("que_id"),
            "type": target_field.get("type"),
        },
        "received_value": deepcopy(value),
        "allowed_values": allowed_values or {},
        "message": message,
        "next_action": next_action or "retry with a literal value supported by the target field type",
    }


def _stringify_custom_button_default_value(value: Any) -> str:
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, dict):
        if "value" in value:
            return str(value.get("value") or "")
        if "name" in value:
            return str(value.get("name") or "")
    return str(value or "")


def _view_button_config_type_from_placement(placement: PublicButtonPlacement) -> PublicViewButtonConfigType:
    if placement == PublicButtonPlacement.header:
        return PublicViewButtonConfigType.top
    if placement == PublicButtonPlacement.list:
        return PublicViewButtonConfigType.list
    return PublicViewButtonConfigType.detail


def _custom_button_view_binding_to_view_button_patch(
    *,
    binding: CustomButtonViewButtonBindingPatch,
    button_id: int,
) -> ViewButtonBindingPatch:
    config_type = _view_button_config_type_from_placement(binding.placement)
    being_main = bool(binding.primary) if config_type == PublicViewButtonConfigType.detail else True
    return ViewButtonBindingPatch.model_validate(
        {
            "button_type": PublicViewButtonType.custom.value,
            "config_type": config_type.value,
            "button_id": button_id,
            "being_main": being_main,
            "button_limit": binding.button_limit,
            "button_formula": binding.button_formula,
            "button_formula_type": binding.button_formula_type,
            "print_tpls": binding.print_tpls,
        }
    )


def _resolve_custom_button_view_button_ref(
    *,
    button_ref: Any,
    client_key_map: dict[str, int],
    button_inventory: dict[int, dict[str, Any]],
    valid_custom_button_ids: set[int],
    reason_path: str,
    allow_unverified_numeric_id: bool = False,
) -> tuple[int | None, dict[str, Any] | None]:
    explicit_id = _coerce_positive_int(button_ref)
    if explicit_id is not None:
        if explicit_id in valid_custom_button_ids or allow_unverified_numeric_id:
            return explicit_id, None
        return None, {
            "error_code": "UNKNOWN_CUSTOM_BUTTON",
            "reason_path": reason_path,
            "button_ref": button_ref,
            "message": "button_ref id does not exist in the current app draft or was removed in this apply",
            "next_action": "call app_get and use custom_buttons[].button_id, or use a client_key from upsert_buttons",
        }
    ref_text = str(button_ref or "").strip()
    if ref_text in client_key_map:
        return client_key_map[ref_text], None
    text_matches = [
        button_id
        for button_id, item in button_inventory.items()
        if str(item.get("button_text") or "").strip() == ref_text
        and button_id in valid_custom_button_ids
    ]
    if len(text_matches) == 1:
        return text_matches[0], None
    return None, {
        "error_code": "UNKNOWN_CUSTOM_BUTTON_REF" if not text_matches else "AMBIGUOUS_CUSTOM_BUTTON_REF",
        "reason_path": reason_path,
        "button_ref": button_ref,
        "candidate_button_ids": text_matches[:10],
        "message": "button_ref must be a button_id, a same-call client_key, or an exact unique existing button_text",
        "next_action": "use upsert_buttons[].client_key for same-call placement or pass a numeric button_id",
    }


def _merge_custom_button_view_button_dtos(
    *,
    existing_dtos: list[dict[str, Any]],
    new_dtos: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    def key(item: dict[str, Any]) -> tuple[int | None, str]:
        return (_coerce_positive_int(item.get("buttonId")), str(item.get("configType") or "").strip().upper())

    replacement_keys = {key(item) for item in new_dtos}
    merged = [deepcopy(item) for item in existing_dtos if key(item) not in replacement_keys]
    merged.extend(deepcopy(item) for item in new_dtos)
    return merged


def _normalize_custom_button_summary(item: dict[str, Any]) -> dict[str, Any]:
    normalized = {
        "button_id": _coerce_positive_int(item.get("button_id") or item.get("buttonId") or item.get("id")),
        "button_text": str(item.get("button_text") or item.get("buttonText") or "").strip() or None,
        "button_icon": str(item.get("button_icon") or item.get("buttonIcon") or "").strip() or None,
        "background_color": str(item.get("background_color") or item.get("backgroundColor") or "").strip() or None,
        "text_color": str(item.get("text_color") or item.get("textColor") or "").strip() or None,
        "used_in_chart_count": _coerce_nonnegative_int(item.get("used_in_chart_count") or item.get("userInChartCount")),
        "being_effective_external_qrobot": bool(item.get("being_effective_external_qrobot") or item.get("beingEffectiveExternalQRobot")),
    }
    creator = item.get("creator_user_info") if isinstance(item.get("creator_user_info"), dict) else item.get("creatorUserInfo")
    if isinstance(creator, dict):
        normalized["creator_user_info"] = {
            "uid": creator.get("uid"),
            "name": creator.get("name"),
            "email": creator.get("email"),
        }
    return normalized


def _normalize_custom_button_detail(item: dict[str, Any]) -> dict[str, Any]:
    normalized = _normalize_custom_button_summary(item)
    normalized.update(
        {
            "trigger_action": str(item.get("trigger_action") or item.get("triggerAction") or "").strip() or None,
            "trigger_link_url": str(item.get("trigger_link_url") or item.get("triggerLinkUrl") or "").strip() or None,
        }
    )
    trigger_add_data_config = item.get("trigger_add_data_config")
    if not isinstance(trigger_add_data_config, dict):
        trigger_add_data_config = item.get("triggerAddDataConfig")
    if isinstance(trigger_add_data_config, dict):
        normalized["trigger_add_data_config"] = _normalize_custom_button_add_data_config_for_public(trigger_add_data_config)
    external_qrobot_config = item.get("external_qrobot_config")
    if not isinstance(external_qrobot_config, dict):
        external_qrobot_config = item.get("customButtonExternalQRobotRelationVO")
    if isinstance(external_qrobot_config, dict):
        normalized["external_qrobot_config"] = deepcopy(external_qrobot_config)
    trigger_wings_config = item.get("trigger_wings_config")
    if not isinstance(trigger_wings_config, dict):
        trigger_wings_config = item.get("triggerWingsConfig")
    if isinstance(trigger_wings_config, dict):
        normalized["trigger_wings_config"] = deepcopy(trigger_wings_config)
    return normalized


_CUSTOM_BUTTON_PARTIAL_PATCH_KEY_ALIASES = {
    "button_text": "button_text",
    "buttonText": "button_text",
    "name": "button_text",
    "text": "button_text",
    "background_color": "background_color",
    "backgroundColor": "background_color",
    "text_color": "text_color",
    "textColor": "text_color",
    "button_icon": "button_icon",
    "buttonIcon": "button_icon",
    "style_preset": "style_preset",
    "stylePreset": "style_preset",
    "trigger_action": "trigger_action",
    "triggerAction": "trigger_action",
    "trigger_link_url": "trigger_link_url",
    "triggerLinkUrl": "trigger_link_url",
    "trigger_add_data_config": "trigger_add_data_config",
    "triggerAddDataConfig": "trigger_add_data_config",
    "add_data_config": "trigger_add_data_config",
    "addDataConfig": "trigger_add_data_config",
    "external_qrobot_config": "external_qrobot_config",
    "externalQrobotConfig": "external_qrobot_config",
    "customButtonExternalQRobotRelationVO": "external_qrobot_config",
    "trigger_wings_config": "trigger_wings_config",
    "triggerWingsConfig": "trigger_wings_config",
}

_CUSTOM_BUTTON_PARTIAL_SET_KEYS = {
    "button_text",
    "background_color",
    "text_color",
    "button_icon",
    "style_preset",
    "trigger_action",
    "trigger_link_url",
    "trigger_add_data_config",
    "external_qrobot_config",
    "trigger_wings_config",
}

_CUSTOM_BUTTON_PARTIAL_UNSET_KEYS = {
    "trigger_link_url",
    "trigger_add_data_config",
    "external_qrobot_config",
    "trigger_wings_config",
}


def _canonical_custom_button_partial_patch_key(key: Any) -> str:
    raw = str(key or "").strip()
    return _CUSTOM_BUTTON_PARTIAL_PATCH_KEY_ALIASES.get(raw, raw)


def _normalize_custom_button_partial_set(raw_set: Any, *, reason_path: str) -> tuple[dict[str, Any], list[dict[str, Any]]]:
    if not isinstance(raw_set, dict):
        return {}, [{"error_code": "INVALID_CUSTOM_BUTTON_PATCH_SET", "reason_path": reason_path, "message": "patch_buttons[].set must be an object"}]
    normalized: dict[str, Any] = {}
    issues: list[dict[str, Any]] = []
    for raw_key, value in raw_set.items():
        key_path = [part for part in str(raw_key or "").strip().split(".") if part]
        if not key_path:
            continue
        root = _canonical_custom_button_partial_patch_key(key_path[0])
        if root not in _CUSTOM_BUTTON_PARTIAL_SET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_CUSTOM_BUTTON_PATCH_FIELD",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "field": raw_key,
                    "allowed_fields": sorted(_CUSTOM_BUTTON_PARTIAL_SET_KEYS),
                }
            )
            continue
        if len(key_path) == 1:
            normalized[root] = value
            continue
        target = normalized.setdefault(root, {})
        if not isinstance(target, dict):
            issues.append(
                {
                    "error_code": "INVALID_CUSTOM_BUTTON_PATCH_PATH",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "message": "cannot combine a scalar replacement and nested patch for the same field",
                }
            )
            continue
        cursor = target
        for part in key_path[1:-1]:
            next_value = cursor.setdefault(part, {})
            if not isinstance(next_value, dict):
                issues.append({"error_code": "INVALID_CUSTOM_BUTTON_PATCH_PATH", "reason_path": f"{reason_path}.{raw_key}"})
                break
            cursor = next_value
        else:
            cursor[key_path[-1]] = value
    return normalized, issues


def _normalize_custom_button_partial_unset(raw_unset: Any, *, reason_path: str) -> tuple[set[str], list[dict[str, Any]]]:
    if not isinstance(raw_unset, list):
        return set(), [{"error_code": "INVALID_CUSTOM_BUTTON_PATCH_UNSET", "reason_path": reason_path, "message": "patch_buttons[].unset must be a list"}]
    normalized: set[str] = set()
    issues: list[dict[str, Any]] = []
    for index, raw_key in enumerate(raw_unset):
        root = _canonical_custom_button_partial_patch_key(str(raw_key or "").strip().split(".")[0])
        if root not in _CUSTOM_BUTTON_PARTIAL_UNSET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_CUSTOM_BUTTON_PATCH_UNSET_FIELD",
                    "reason_path": f"{reason_path}[{index}]",
                    "field": raw_key,
                    "allowed_fields": sorted(_CUSTOM_BUTTON_PARTIAL_UNSET_KEYS),
                }
            )
            continue
        normalized.add(root)
    return normalized, issues


def _custom_button_upsert_payload_from_detail(
    detail: dict[str, Any],
    *,
    button_id: int,
    client_key: str | None = None,
) -> dict[str, Any]:
    payload = {
        "button_id": button_id,
        "client_key": client_key,
        "button_text": detail.get("button_text"),
        "background_color": detail.get("background_color"),
        "text_color": detail.get("text_color"),
        "button_icon": detail.get("button_icon"),
        "trigger_action": detail.get("trigger_action"),
        "trigger_link_url": detail.get("trigger_link_url"),
        "trigger_add_data_config": deepcopy(detail.get("trigger_add_data_config")) if isinstance(detail.get("trigger_add_data_config"), dict) else None,
        "external_qrobot_config": deepcopy(detail.get("external_qrobot_config")) if isinstance(detail.get("external_qrobot_config"), dict) else None,
        "trigger_wings_config": deepcopy(detail.get("trigger_wings_config")) if isinstance(detail.get("trigger_wings_config"), dict) else None,
    }
    return _compact_dict(payload)


def _failed(
    error_code: str,
    message: str,
    *,
    recoverable: bool = True,
    normalized_args: JSONObject | None = None,
    missing_fields: list[str] | None = None,
    allowed_values: JSONObject | None = None,
    details: JSONObject | None = None,
    suggested_next_call: JSONObject | None = None,
    request_id: str | None = None,
    backend_code: Any = None,
    http_status: int | None = None,
) -> JSONObject:
    return {
        "status": "failed",
        "error_code": error_code,
        "recoverable": recoverable,
        "message": message,
        "normalized_args": normalized_args or {},
        "missing_fields": missing_fields or [],
        "allowed_values": allowed_values or {},
        "details": details or {},
        "suggested_next_call": suggested_next_call,
        "request_id": request_id,
        "backend_code": backend_code,
        "http_status": http_status,
        "noop": False,
        "warnings": [],
        "verification": {},
        "verified": False,
    }


def _failed_from_api_error(
    error_code: str,
    error: QingflowApiError,
    *,
    normalized_args: JSONObject | None = None,
    missing_fields: list[str] | None = None,
    allowed_values: JSONObject | None = None,
    details: JSONObject | None = None,
    suggested_next_call: JSONObject | None = None,
    recoverable: bool = True,
) -> JSONObject:
    if is_auth_like_error(error):
        effective_error_code = "AUTH_REQUIRED"
    elif backend_code_int(error) == 40074:
        effective_error_code = "APP_EDIT_LOCKED"
    else:
        effective_error_code = error_code
    public_message = _public_error_message(effective_error_code, error)
    public_http_status = None if error.http_status == 404 else error.http_status
    merged_details = dict(details or {})
    if backend_code_int(error) == 40074:
        owner = _extract_edit_lock_owner(error.message)
        merged_details.setdefault("lock_owner_name", owner.get("lock_owner_name"))
        merged_details.setdefault("lock_owner_email", owner.get("lock_owner_email"))
        app_key = None
        if isinstance(normalized_args, dict):
            app_key = normalized_args.get("app_key")
        if not app_key and isinstance(details, dict):
            app_key = details.get("app_key")
        if isinstance(app_key, str) and app_key.strip():
            suggested_next_call = {
                "tool_name": "app_release_edit_lock_if_mine",
                "arguments": {
                    "app_key": app_key,
                    "lock_owner_name": owner.get("lock_owner_name") or "",
                    "lock_owner_email": owner.get("lock_owner_email") or "",
                },
            }
    if error.http_status is not None or error.backend_code is not None:
        merged_details.setdefault(
            "transport_error",
            {
                "http_status": error.http_status,
                "backend_code": error.backend_code,
                "category": error.category,
            },
        )
    result = _failed(
        effective_error_code,
        public_message,
        recoverable=recoverable,
        normalized_args=normalized_args,
        missing_fields=missing_fields,
        allowed_values=allowed_values,
        details=merged_details,
        suggested_next_call=suggested_next_call,
        request_id=error.request_id,
        backend_code=error.backend_code,
        http_status=public_http_status,
    )
    if _is_environment_quota_code(error.backend_code):
        _mark_environment_quota_block(
            result,
            write_executed=False,
            next_action="retry_after_quota_restored",
            message="backend quota/AI assistant limit blocked this operation; retry after quota is restored",
        )
    return result


def _post_write_readback_pending_result(
    *,
    error_code: str,
    message: str,
    normalized_args: JSONObject | None = None,
    details: JSONObject | None = None,
    suggested_next_call: JSONObject | None = None,
    request_id: str | None = None,
    backend_code: Any = None,
    http_status: int | None = None,
) -> JSONObject:
    effective_details = details or {}
    transport_error = _readback_transport_error_from_details(effective_details)
    effective_backend_code = backend_code if backend_code is not None else (transport_error or {}).get("backend_code")
    effective_http_status = http_status if http_status is not None else (transport_error or {}).get("http_status")
    effective_request_id = request_id if request_id is not None else (transport_error or {}).get("request_id")
    warning = _warning("READBACK_UNAVAILABLE_AFTER_WRITE", "write was executed but post-write readback is unavailable")
    for key, value in (
        ("backend_code", effective_backend_code),
        ("http_status", effective_http_status),
        ("request_id", effective_request_id),
    ):
        if value is not None:
            warning[key] = value
    result = {
        "status": "partial_success",
        "error_code": error_code,
        "recoverable": True,
        "message": message,
        "normalized_args": normalized_args or {},
        "missing_fields": [],
        "allowed_values": {},
        "details": effective_details,
        "suggested_next_call": suggested_next_call,
        "request_id": effective_request_id,
        "backend_code": effective_backend_code,
        "http_status": effective_http_status,
        "noop": False,
        "warnings": [warning],
        "verification": {
            "readback_unavailable": True,
            "metadata_unverified": True,
        },
        "verified": False,
        "write_executed": True,
        "write_succeeded": True,
        "write_may_have_succeeded": True,
        "safe_to_retry": False,
        "next_action": "readback_before_retry",
    }
    result["verification"]["readback_before_retry"] = True
    if _is_environment_quota_code(effective_backend_code):
        result["readback_blocked_by_environment"] = True
        result["verification"]["readback_blocked_by_environment"] = True
        _mark_environment_quota_block(
            result,
            write_executed=True,
            next_action="retry_after_quota_restored",
            message="post-write readback was blocked by backend quota/AI assistant limit; retry readback after quota is restored",
        )
    return result


def _post_write_may_have_succeeded_result(
    *,
    error_code: str,
    message: str,
    normalized_args: JSONObject | None = None,
    details: JSONObject | None = None,
    suggested_next_call: JSONObject | None = None,
    request_id: str | None = None,
    backend_code: Any = None,
    http_status: int | None = None,
) -> JSONObject:
    effective_details = details or {}
    transport_error = _readback_transport_error_from_details(effective_details)
    effective_backend_code = backend_code if backend_code is not None else (transport_error or {}).get("backend_code")
    effective_http_status = http_status if http_status is not None else (transport_error or {}).get("http_status")
    effective_request_id = request_id if request_id is not None else (transport_error or {}).get("request_id")
    warning = _warning("WRITE_RESULT_UNCERTAIN", "write request may have succeeded but no final response was received")
    for key, value in (
        ("backend_code", effective_backend_code),
        ("http_status", effective_http_status),
        ("request_id", effective_request_id),
    ):
        if value is not None:
            warning[key] = value
    result = {
        "status": "partial_success",
        "error_code": error_code,
        "recoverable": True,
        "message": message,
        "normalized_args": normalized_args or {},
        "missing_fields": [],
        "allowed_values": {},
        "details": effective_details,
        "suggested_next_call": suggested_next_call,
        "request_id": effective_request_id,
        "backend_code": effective_backend_code,
        "http_status": effective_http_status,
        "noop": False,
        "warnings": [warning],
        "verification": {
            "readback_unavailable": True,
            "metadata_unverified": True,
            "readback_before_retry": True,
        },
        "verified": False,
        "write_executed": True,
        "write_succeeded": False,
        "write_may_have_succeeded": True,
        "safe_to_retry": False,
        "next_action": "readback_before_retry",
    }
    if _is_environment_quota_code(effective_backend_code):
        result["readback_blocked_by_environment"] = True
        result["verification"]["readback_blocked_by_environment"] = True
        _mark_environment_quota_block(
            result,
            write_executed=True,
            next_action="retry_after_quota_restored",
            message="write result or readback was blocked by backend quota/AI assistant limit; retry after quota is restored",
        )
    return result


def _readback_transport_error_from_details(details: JSONObject) -> JSONObject | None:
    direct = details.get("transport_error")
    if isinstance(direct, dict):
        return direct
    for key in (
        "readback_error",
        "verification_error",
        "draft_readback_error",
        "live_readback_error",
        "state_read_blocked",
    ):
        value = details.get(key)
        if isinstance(value, dict) and isinstance(value.get("transport_error"), dict):
            return value.get("transport_error")
    verification_result = details.get("verification_result")
    if isinstance(verification_result, dict):
        verification_details = verification_result.get("details")
        if isinstance(verification_details, dict):
            nested = verification_details.get("transport_error")
            if isinstance(nested, dict):
                payload = dict(nested)
                for key in ("backend_code", "http_status", "request_id", "category"):
                    if payload.get(key) is None and verification_result.get(key) is not None:
                        payload[key] = verification_result.get(key)
                return payload
        payload = {
            key: verification_result.get(key)
            for key in ("backend_code", "http_status", "request_id", "category")
            if verification_result.get(key) is not None
        }
        if payload:
            return payload
    return None


def _transport_error_payload(error: QingflowApiError) -> JSONObject:
    return {
        "http_status": error.http_status,
        "backend_code": error.backend_code,
        "category": error.category,
        "request_id": error.request_id,
    }


def _is_environment_quota_code(code: Any) -> bool:
    return backend_code_value_int(code) == 59004


def _mark_environment_quota_block(
    payload: JSONObject,
    *,
    write_executed: bool,
    next_action: str,
    message: str,
) -> None:
    payload["environment_blocked"] = True
    payload["blocker_type"] = "quota_limit"
    payload["next_action"] = next_action
    payload["safe_to_retry"] = False
    payload["write_executed"] = bool(write_executed)
    details = payload.get("details")
    if not isinstance(details, dict):
        details = {}
        payload["details"] = details
    details.setdefault("environment_blocked", True)
    details.setdefault("blocker_type", "quota_limit")
    details.setdefault("next_action", next_action)
    details.setdefault("fix_hint", message)
    warnings = payload.get("warnings")
    if not isinstance(warnings, list):
        warnings = []
        payload["warnings"] = warnings
    if not any(isinstance(item, dict) and item.get("code") == "ENVIRONMENT_QUOTA_LIMIT" for item in warnings):
        warning = _warning("ENVIRONMENT_QUOTA_LIMIT", message)
        for key in ("backend_code", "http_status", "request_id"):
            value = payload.get(key)
            if value is not None:
                warning[key] = value
        warnings.append(warning)


def _is_uncertain_write_transport_error(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    category = str(error.category or "").strip().lower()
    message = str(error.message or "").strip().lower()
    if category == "timeout":
        return True
    if category != "network":
        return False
    return any(
        marker in message
        for marker in (
            "timeout",
            "timed out",
            "read timed out",
            "write timed out",
            "readtimeout",
            "writetimeout",
            "server disconnected",
            "connection reset",
            "remote protocol error",
            "response ended prematurely",
        )
    )


def _is_permission_restricted_api_error(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    return backend_code_int(error) in {40002, 40027}


def _is_optional_builder_lookup_error(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    return backend_code_int(error) in {40002, 40027, 404} or error.http_status == 404


def _search_permission_blocked_from_warnings(payload: JSONObject) -> JSONObject | None:
    warnings = payload.get("warnings")
    if not isinstance(warnings, list):
        return None
    for item in warnings:
        if not isinstance(item, dict) or item.get("code") != "APP_SEARCH_FALLBACK_VISIBLE_APPS":
            continue
        return {
            "backend_code": item.get("backend_code"),
            "http_status": item.get("http_status"),
            "request_id": item.get("request_id"),
        }
    return None


def _append_response_detail(details: JSONObject, *, key: str, value: Any) -> None:
    if value is None:
        return
    copied_value = deepcopy(value)
    existing = details.get(key)
    if existing is None:
        details[key] = copied_value
        return
    if isinstance(existing, list):
        existing.append(copied_value)
        return
    details[key] = [existing, copied_value]


def _permission_skip_outcome(
    *,
    scope: str,
    target: JSONObject,
    required_permission: str | None,
    transport_error: JSONObject | None = None,
    permission_summary: JSONObject | None = None,
) -> PermissionCheckOutcome:
    lookup_payload: JSONObject = {
        "scope": scope,
        "target": deepcopy(target),
        "required_permission": required_permission,
    }
    if transport_error:
        lookup_payload["transport_error"] = deepcopy(transport_error)
    if permission_summary:
        lookup_payload["permission_summary"] = deepcopy(permission_summary)
    warning_message = (
        "builder permission lookup was permission-restricted; continuing with downstream operations"
        if transport_error
        else "builder permission summary was incomplete; continuing with downstream operations"
    )
    return PermissionCheckOutcome(
        warnings=[
            _warning(
                "PERMISSION_CHECK_SKIPPED",
                warning_message,
                scope=scope,
                required_permission=required_permission,
            )
        ],
        details={
            "lookup_permission_blocked": lookup_payload,
            "permission_check_skipped": True,
        },
        verification={"metadata_unverified": True},
    )


def _verification_read_outcome(
    *,
    resource: str,
    target: JSONObject,
    transport_error: QingflowApiError,
) -> PermissionCheckOutcome:
    return PermissionCheckOutcome(
        warnings=[
            _warning(
                "VERIFICATION_READ_UNAVAILABLE",
                "post-write verification readback was permission-restricted",
                resource=resource,
            )
        ],
        details={
            "lookup_permission_blocked": {
                "scope": resource,
                "target": deepcopy(target),
                "phase": "verification",
                "transport_error": _transport_error_payload(transport_error),
            },
            "permission_check_skipped": True,
        },
        verification={"metadata_unverified": True},
    )


def _permission_outcome_from_result(result: JSONObject) -> PermissionCheckOutcome | None:
    details = result.get("details") if isinstance(result.get("details"), dict) else {}
    verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
    warnings = result.get("warnings") if isinstance(result.get("warnings"), list) else []
    filtered_details: JSONObject = {}
    if details.get("lookup_permission_blocked") is not None:
        filtered_details["lookup_permission_blocked"] = deepcopy(details["lookup_permission_blocked"])
    if details.get("permission_check_skipped") is not None:
        filtered_details["permission_check_skipped"] = bool(details.get("permission_check_skipped"))
    filtered_verification: JSONObject = {}
    if verification.get("metadata_unverified") is not None:
        filtered_verification["metadata_unverified"] = bool(verification.get("metadata_unverified"))
    filtered_warnings = [
        deepcopy(item)
        for item in warnings
        if isinstance(item, dict) and str(item.get("code") or "") in {"PERMISSION_CHECK_SKIPPED", "VERIFICATION_READ_UNAVAILABLE"}
    ]
    if not filtered_details and not filtered_verification and not filtered_warnings:
        return None
    return PermissionCheckOutcome(
        warnings=filtered_warnings,
        details=filtered_details,
        verification=filtered_verification,
    )


def _apply_permission_outcomes(response: JSONObject, *outcomes: PermissionCheckOutcome) -> JSONObject:
    if not isinstance(response, dict):
        return response
    details = response.get("details")
    if not isinstance(details, dict):
        details = {}
        response["details"] = details
    verification = response.get("verification")
    if not isinstance(verification, dict):
        verification = {}
        response["verification"] = verification
    warnings = response.get("warnings")
    if not isinstance(warnings, list):
        warnings = []
        response["warnings"] = warnings
    for outcome in outcomes:
        if not isinstance(outcome, PermissionCheckOutcome):
            continue
        for key, value in outcome.details.items():
            if key in {"lookup_permission_blocked", "state_read_blocked"}:
                _append_response_detail(details, key=key, value=value)
            elif key == "permission_check_skipped":
                details[key] = bool(details.get(key)) or bool(value)
            elif key not in details:
                details[key] = deepcopy(value)
        for key, value in outcome.verification.items():
            verification[key] = deepcopy(value)
        for warning in outcome.warnings:
            if warning not in warnings:
                warnings.append(deepcopy(warning))
    return response


def _with_state_read_blocked_details(
    details: JSONObject | None,
    *,
    resource: str,
    error: QingflowApiError,
) -> JSONObject:
    merged = deepcopy(details) if isinstance(details, dict) else {}
    if _is_permission_restricted_api_error(error):
        _append_response_detail(
            merged,
            key="state_read_blocked",
            value={
                "resource": resource,
                "transport_error": _transport_error_payload(error),
            },
        )
    return merged


def _from_stage_failure(stage: JSONObject, *, fallback_tool: str) -> JSONObject:
    return {
        "status": "failed",
        "error_code": stage.get("error_code") or "STAGE_BUILD_FAILED",
        "recoverable": bool(stage.get("recoverable", True)),
        "message": str(stage.get("message") or stage.get("detail") or "stage build failed"),
        "details": {"stage_result": stage},
        "suggested_next_call": stage.get("suggested_next_call") or {"tool_name": fallback_tool, "arguments": {}},
        "request_id": stage.get("request_id"),
        "backend_code": stage.get("backend_code"),
        "http_status": stage.get("http_status"),
        "noop": False,
        "warnings": [],
        "verification": {},
        "verified": False,
    }


def _coerce_api_error(error: Exception) -> QingflowApiError:
    if isinstance(error, QingflowApiError):
        return error
    if isinstance(error, RuntimeError):
        try:
            payload = json.loads(str(error))
        except json.JSONDecodeError:
            payload = None
        if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
            details = payload.get("details")
            return QingflowApiError(
                category=str(payload.get("category")),
                message=str(payload.get("message")),
                backend_code=payload.get("backend_code"),
                request_id=payload.get("request_id"),
                http_status=payload.get("http_status"),
                details=details if isinstance(details, dict) else None,
            )
    return QingflowApiError(category="runtime", message=str(error))


def _public_error_message(error_code: str, error: QingflowApiError) -> str:
    if backend_code_int(error) == 40074 or error_code == "APP_EDIT_LOCKED":
        owner = _extract_edit_lock_owner(error.message)
        owner_label = owner.get("lock_owner_email") or owner.get("lock_owner_name")
        if owner_label:
            return f"app is currently locked by active editor {owner_label}"
        return "app is currently locked by another active editor session"
    if error.http_status != 404:
        return error.message
    mapping = {
        "APP_READ_FAILED": "app base or schema is unavailable in the current route",
        "FIELDS_READ_FAILED": "app fields are unavailable in the current route",
        "LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
        "VIEWS_READ_FAILED": "views resource is unavailable for this app in the current route",
        "FLOW_READ_FAILED": "workflow resource is unavailable for this app in the current route",
        "SCHEMA_READBACK_FAILED": "schema was written but schema readback is unavailable in the current route",
        "CREATE_APP_ROUTE_NOT_FOUND": "create app route is unavailable in the current workspace route",
        "APP_CREATE_READBACK_FAILED": "app was created but base readback is unavailable in the current route",
        "PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
        "PUBLISH_FAILED": "publish route is unavailable in the current route",
        "VIEW_APPLY_FAILED": "view resource rejected the operation or is unavailable in the current route",
        "LAYOUT_APPLY_FAILED": "layout resource rejected the operation or is unavailable in the current route",
        "SCHEMA_APPLY_FAILED": "schema resource rejected the operation or is unavailable in the current route",
        "CHART_APPLY_FAILED": "chart resource rejected the operation or is unavailable in the current route",
        "PORTAL_APPLY_FAILED": "portal resource rejected the operation or is unavailable in the current route",
        "EDIT_LOCK_RELEASE_FAILED": "edit lock release route is unavailable in the current route",
    }
    return mapping.get(error_code, "requested builder resource is unavailable in the current route")


def _chart_delete_readback_is_not_found(error: QingflowApiError) -> bool:
    return _delete_readback_is_not_found(error)


def _delete_readback_is_not_found(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    backend_code = backend_code_int(error)
    if error.http_status == 404 or backend_code in {404, 40038, 81007}:
        return True
    message = str(error.message or "").lower()
    return any(
        marker in message
        for marker in (
            "object not exist",
            "not found",
            "not exist",
            "does not exist",
            "不存在",
            "未找到",
        )
    )


def _extract_edit_lock_owner(message: str) -> JSONObject:
    text = str(message or "").strip()
    if not text:
        return {"lock_owner_name": None, "lock_owner_email": None}
    patterns = [
        r"应用已被\s*(?P<name>[^（(]+?)\s*[（(](?P<email>[^）)]+)[）)]\s*编辑",
        r"edited by\s*(?P<name>[^<(]+?)\s*<(?P<email>[^>]+)>",
        r"active editor\s+(?P<email>[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,})",
        r"(?P<email>[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,})",
    ]
    for pattern in patterns:
        match = re.search(pattern, text)
        if match:
            return {
                "lock_owner_name": match.groupdict().get("name", "").strip() or None,
                "lock_owner_email": match.groupdict().get("email", "").strip() or None,
            }
    return {"lock_owner_name": None, "lock_owner_email": None}


def _coerce_positive_int(value: Any) -> int | None:
    if isinstance(value, bool) or value is None:
        return None
    if isinstance(value, int):
        return value if value > 0 else None
    if isinstance(value, float):
        return int(value) if value > 0 else None
    if isinstance(value, str) and value.strip():
        try:
            parsed = int(value)
        except ValueError:
            return None
        return parsed if parsed > 0 else None
    return None


def _coerce_nonnegative_int(value: Any) -> int | None:
    if isinstance(value, bool) or value is None:
        return None
    if isinstance(value, int):
        return value if value >= 0 else None
    if isinstance(value, float):
        parsed = int(value)
        return parsed if parsed >= 0 else None
    if isinstance(value, str) and value.strip():
        try:
            parsed = int(value)
        except ValueError:
            return None
        return parsed if parsed >= 0 else None
    return None


def _coerce_any_int(value: Any) -> int | None:
    if isinstance(value, bool) or value is None:
        return None
    if isinstance(value, int):
        return value
    if isinstance(value, float):
        return int(value)
    if isinstance(value, str) and value.strip():
        try:
            return int(value)
        except ValueError:
            return None
    return None


def _coerce_int_list(values: Any) -> list[int]:
    if not isinstance(values, list):
        return []
    result: list[int] = []
    for value in values:
        parsed = _coerce_positive_int(value)
        if parsed is not None:
            result.append(parsed)
    return result


def _coerce_optional_bool(value: Any) -> bool | None:
    if isinstance(value, bool):
        return value
    return None


def _normalize_view_collection(values: Any) -> list[dict[str, Any]]:
    if isinstance(values, list):
        normalized: list[dict[str, Any]] = []
        for item in values:
            if not isinstance(item, dict):
                continue
            nested_view_list = item.get("viewList")
            if isinstance(nested_view_list, list):
                normalized.extend(view for view in nested_view_list if isinstance(view, dict))
                continue
            normalized.append(item)
        return normalized
    if isinstance(values, dict):
        if _extract_view_key(values):
            return [values]
        for key in ("list", "viewList", "views", "result"):
            candidate = values.get(key)
            if isinstance(candidate, list):
                return _normalize_view_collection(candidate)
    return []


def _build_public_field_lookup(fields: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
    by_field_id: dict[str, dict[str, Any]] = {}
    by_name: dict[str, dict[str, Any]] = {}
    by_que_id: dict[int, dict[str, Any]] = {}
    for field in fields:
        if not isinstance(field, dict):
            continue
        field_id = str(field.get("field_id") or "").strip()
        field_name = str(field.get("name") or "").strip()
        que_id = _coerce_positive_int(field.get("que_id"))
        if field_id:
            by_field_id[field_id] = field
        if field_name:
            by_name[field_name] = field
        if que_id is not None:
            by_que_id[que_id] = field
    return {"by_field_id": by_field_id, "by_name": by_name, "by_que_id": by_que_id}


def _resolve_public_field(selector: Any, *, field_lookup: dict[str, dict[str, Any]]) -> dict[str, Any]:
    raw = str(selector or "").strip()
    if not raw:
        raise ValueError("field selector cannot be empty")
    by_field_id = field_lookup.get("by_field_id") or {}
    by_name = field_lookup.get("by_name") or {}
    by_que_id = field_lookup.get("by_que_id") or {}
    if raw in by_field_id:
        return by_field_id[raw]
    if raw in by_name:
        return by_name[raw]
    parsed_que_id = _coerce_positive_int(raw)
    if parsed_que_id is not None and parsed_que_id in by_que_id:
        return by_que_id[parsed_que_id]
    raise ValueError(f"field '{raw}' was not found in current app schema")


def _bi_field_id_for_field(*, app_key: str, field: dict[str, Any], qingbi_fields_by_id: dict[str, dict[str, Any]]) -> str:
    que_id = _coerce_positive_int(field.get("que_id"))
    if que_id is None:
        raise ValueError(f"field '{field.get('name')}' does not expose que_id")
    candidate = f"{app_key}:{que_id}"
    if candidate in qingbi_fields_by_id:
        return candidate
    return candidate


def _map_public_chart_type_to_backend(chart_type: PublicChartType) -> str:
    aliases = {
        PublicChartType.target: "indicator",
        PublicChartType.indicator: "indicator",
        PublicChartType.pie: "pie",
        PublicChartType.bar: "bar",
        PublicChartType.line: "line",
        PublicChartType.table: "detail",
        PublicChartType.detail: "detail",
    }
    return aliases.get(chart_type, chart_type.value)


_CHART_PARTIAL_PATCH_KEY_ALIASES = {
    "name": "name",
    "chart_name": "name",
    "chartName": "name",
    "type": "chart_type",
    "chart_type": "chart_type",
    "chartType": "chart_type",
    "dimension_fields": "dimension_field_ids",
    "dimension_field_ids": "dimension_field_ids",
    "dimensionFieldIds": "dimension_field_ids",
    "indicator_fields": "indicator_field_ids",
    "indicator_field_ids": "indicator_field_ids",
    "indicatorFieldIds": "indicator_field_ids",
    "metric_field_ids": "indicator_field_ids",
    "group_by": "group_by",
    "groupBy": "group_by",
    "dimensions": "group_by",
    "rows": "rows",
    "columns": "columns",
    "metric": "metric",
    "metrics": "metrics",
    "x_metric": "x_metric",
    "xMetric": "x_metric",
    "y_metric": "y_metric",
    "yMetric": "y_metric",
    "left_metric": "left_metric",
    "leftMetric": "left_metric",
    "right_metric": "right_metric",
    "rightMetric": "right_metric",
    "value_metric": "value_metric",
    "valueMetric": "value_metric",
    "target_metric": "target_metric",
    "targetMetric": "target_metric",
    "where": "filters",
    "filters": "filters",
    "filter_rules": "filters",
    "filterRules": "filters",
    "question_config": "question_config",
    "questionConfig": "question_config",
    "user_config": "user_config",
    "userConfig": "user_config",
    "config": "config",
    "visibility": "visibility",
}

_CHART_PARTIAL_SET_KEYS = {
    "name",
    "chart_type",
    "dimension_field_ids",
    "indicator_field_ids",
    "group_by",
    "rows",
    "columns",
    "metric",
    "metrics",
    "x_metric",
    "y_metric",
    "left_metric",
    "right_metric",
    "value_metric",
    "target_metric",
    "filters",
    "question_config",
    "user_config",
    "config",
    "visibility",
}

_CHART_PARTIAL_UNSET_KEYS = {"filters", "question_config", "user_config", "visibility"}


def _canonical_chart_partial_patch_key(key: Any) -> str:
    raw = str(key or "").strip()
    return _CHART_PARTIAL_PATCH_KEY_ALIASES.get(raw, raw)


def _normalize_chart_partial_set(raw_set: Any, *, reason_path: str) -> tuple[dict[str, Any], list[dict[str, Any]]]:
    if not isinstance(raw_set, dict):
        return {}, [{"error_code": "INVALID_CHART_PATCH_SET", "reason_path": reason_path, "message": "patch_charts[].set must be an object"}]
    normalized: dict[str, Any] = {}
    issues: list[dict[str, Any]] = []
    for raw_key, value in raw_set.items():
        key_path = [part for part in str(raw_key or "").strip().split(".") if part]
        if not key_path:
            continue
        root = _canonical_chart_partial_patch_key(key_path[0])
        if root not in _CHART_PARTIAL_SET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_CHART_PATCH_FIELD",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "field": raw_key,
                    "allowed_fields": sorted(_CHART_PARTIAL_SET_KEYS),
                }
            )
            continue
        if len(key_path) == 1:
            normalized[root] = value
            continue
        target = normalized.setdefault(root, {})
        if not isinstance(target, dict):
            issues.append({"error_code": "INVALID_CHART_PATCH_PATH", "reason_path": f"{reason_path}.{raw_key}"})
            continue
        cursor = target
        for part in key_path[1:-1]:
            next_value = cursor.setdefault(part, {})
            if not isinstance(next_value, dict):
                issues.append({"error_code": "INVALID_CHART_PATCH_PATH", "reason_path": f"{reason_path}.{raw_key}"})
                break
            cursor = next_value
        else:
            cursor[key_path[-1]] = value
    return normalized, issues


def _normalize_chart_partial_unset(raw_unset: Any, *, reason_path: str) -> tuple[set[str], list[dict[str, Any]]]:
    if not isinstance(raw_unset, list):
        return set(), [{"error_code": "INVALID_CHART_PATCH_UNSET", "reason_path": reason_path, "message": "patch_charts[].unset must be a list"}]
    normalized: set[str] = set()
    issues: list[dict[str, Any]] = []
    for index, raw_key in enumerate(raw_unset):
        root = _canonical_chart_partial_patch_key(str(raw_key or "").strip().split(".")[0])
        if root not in _CHART_PARTIAL_UNSET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_CHART_PATCH_UNSET_FIELD",
                    "reason_path": f"{reason_path}[{index}]",
                    "field": raw_key,
                    "allowed_fields": sorted(_CHART_PARTIAL_UNSET_KEYS),
                }
            )
            continue
        normalized.add(root)
    return normalized, issues


def _chart_upsert_payload_from_existing(
    *,
    chart_id: str,
    base: dict[str, Any],
    config: dict[str, Any],
) -> dict[str, Any]:
    return _compact_dict(
        {
            "chart_id": chart_id,
            "name": str(base.get("chartName") or config.get("chartName") or chart_id).strip() or chart_id,
            "chart_type": _public_chart_type_from_backend(base.get("chartType") or config.get("chartType")),
            "config": deepcopy(config),
        }
    )


def _qingbi_field_type_from_public_field(field_type: str | None) -> str:
    return {
        "single_select": "singleSelect",
        "multi_select": "multiSelect",
        "member": "member",
        "department": "dept",
        "date": "datetime",
        "datetime": "datetime",
        "number": "decimal",
        "amount": "decimal",
        "boolean": "singleSelect",
    }.get(str(field_type or ""), "string")


def _build_public_dimension_fields(
    selectors: list[str],
    *,
    app_key: str,
    field_lookup: dict[str, dict[str, Any]],
    chart_field_lookup: dict[str, Any],
    qingbi_fields_by_id: dict[str, dict[str, Any]],
    chart_type: str = "chart",
) -> list[dict[str, Any]]:
    dimensions: list[dict[str, Any]] = []
    for selector in selectors:
        qingbi_field = _resolve_qingbi_chart_field(selector, chart_field_lookup=chart_field_lookup, chart_type=chart_type, role="dimension")
        field_id = _chart_field_id(qingbi_field)
        form_field = qingbi_field.get("_public_form_field") if isinstance(qingbi_field.get("_public_form_field"), dict) else {}
        dimensions.append(
            {
                "fieldId": field_id,
                "fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
                "fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
                "orderType": "default",
                "alignType": "left",
                "dateFormat": "yyyy-MM-dd",
                "numberFormat": "default",
                "numberConfig": {"format": "splitter", "unit": "DEFAULT", "prefix": "", "suffix": "", "digit": None},
                "digit": None,
                "aggreType": "sum",
                "orderPriority": None,
                "width": None,
                "verticalAlign": "middle",
                "formula": qingbi_field.get("formula"),
                "fieldSource": qingbi_field.get("fieldSource") or "default",
                "status": qingbi_field.get("status"),
                "supId": qingbi_field.get("supId"),
                "beingTable": bool(qingbi_field.get("beingTable", False)),
                "returnType": qingbi_field.get("returnType"),
            }
        )
    return dimensions


def _default_public_total_metric() -> dict[str, Any]:
    return {
        "fieldId": ":-100",
        "fieldName": "数据总量",
        "fieldType": "decimal",
        "orderType": "default",
        "alignType": "left",
        "dateFormat": "yyyy-MM-dd",
        "numberFormat": "default",
        "numberConfig": {"format": "splitter", "unit": "DEFAULT", "prefix": "", "suffix": "", "digit": None},
        "digit": None,
        "aggreType": "sum",
        "orderPriority": None,
        "width": None,
        "verticalAlign": "middle",
        "beingTable": False,
        "supId": None,
    }


_QINGBI_TOTAL_FIELD_ID = ":-100"
_QINGBI_DECIMAL_FIELD_TYPES = {"decimal", "number", "numeric", "amount", "integer", "int", "long", "double", "float"}


class ChartRuleViolation(ValueError):
    def __init__(self, diagnostics: dict[str, Any]) -> None:
        self.diagnostics = diagnostics
        super().__init__(str(diagnostics.get("message") or diagnostics.get("next_action") or "chart rule violation"))


def _chart_field_id(field: dict[str, Any]) -> str:
    return str(field.get("fieldId") or field.get("field_id") or "").strip()


def _chart_field_name(field: dict[str, Any]) -> str | None:
    name = str(field.get("fieldName") or field.get("field_name") or "").strip()
    return name or None


def _chart_fields(payload: dict[str, Any], key: str) -> list[dict[str, Any]]:
    value = payload.get(key)
    if not isinstance(value, list):
        return []
    return [item for item in value if isinstance(item, dict)]


def _chart_field_summary(field: dict[str, Any]) -> dict[str, Any]:
    return _compact_dict(
        {
            "field_id": _chart_field_id(field),
            "field_name": _chart_field_name(field),
            "field_type": field.get("fieldType") or field.get("field_type"),
            "field_source": field.get("fieldSource") or field.get("field_source"),
            "bi_formula_type": field.get("biFormulaType") or field.get("bi_formula_type"),
            "aggre_field_id": field.get("aggreFieldId") or field.get("aggre_field_id"),
        }
    )


def _chart_duplicate_fields(fields: list[dict[str, Any]]) -> list[dict[str, Any]]:
    seen: dict[str, dict[str, Any]] = {}
    duplicates: dict[str, dict[str, Any]] = {}
    for field in fields:
        field_id = _chart_field_id(field)
        if not field_id:
            continue
        if field_id in seen:
            duplicates[field_id] = _chart_field_summary(field)
        else:
            seen[field_id] = field
    return list(duplicates.values())


def _chart_rule_diagnostics(
    *,
    rule_code: str,
    chart_type: str,
    message: str,
    expected: str,
    actual: dict[str, Any],
    next_action: str,
    offending_fields: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    return _compact_dict(
        {
            "rule_code": rule_code,
            "chart_type": chart_type,
            "message": message,
            "expected": expected,
            "actual": actual,
            "offending_fields": offending_fields or [],
            "next_action": next_action,
        }
    )


def _raise_chart_rule(
    *,
    rule_code: str,
    chart_type: str,
    message: str,
    expected: str,
    actual: dict[str, Any],
    next_action: str,
    offending_fields: list[dict[str, Any]] | None = None,
) -> None:
    raise ChartRuleViolation(
        _chart_rule_diagnostics(
            rule_code=rule_code,
            chart_type=chart_type,
            message=message,
            expected=expected,
            actual=actual,
            next_action=next_action,
            offending_fields=offending_fields,
        )
    )


_QINGBI_TOTAL_FIELD_ALIASES = {_QINGBI_TOTAL_FIELD_ID, "数据总量", "data_total", "total", "count"}


def _qingbi_field_que_id(*, app_key: str, field_id: Any) -> int | None:
    raw = str(field_id or "").strip()
    if not raw or raw == _QINGBI_TOTAL_FIELD_ID:
        return None
    if raw.startswith("field_"):
        return _coerce_positive_int(raw.removeprefix("field_"))
    if raw.startswith(f"{app_key}:"):
        return _coerce_positive_int(raw.split(":", 1)[1])
    if ":" in raw:
        return _coerce_positive_int(raw.rsplit(":", 1)[1])
    return _coerce_positive_int(raw)


def _dedupe_qingbi_fields(fields: list[dict[str, Any]]) -> list[dict[str, Any]]:
    deduped: list[dict[str, Any]] = []
    seen: set[str] = set()
    for field in fields:
        field_id = _chart_field_id(field)
        key = field_id or json.dumps(field, sort_keys=True, ensure_ascii=False, default=str)
        if key in seen:
            continue
        seen.add(key)
        deduped.append(field)
    return deduped


def _build_qingbi_chart_field_lookup(
    *,
    app_key: str,
    qingbi_fields: list[dict[str, Any]],
    field_lookup: dict[str, dict[str, Any]],
) -> dict[str, Any]:
    by_selector: dict[str, list[dict[str, Any]]] = {}
    form_by_que_id = dict(field_lookup.get("by_que_id") or {})
    if not form_by_que_id:
        for bucket_name in ("by_name", "by_field_id"):
            bucket = field_lookup.get(bucket_name) or {}
            if not isinstance(bucket, dict):
                continue
            for form_field in bucket.values():
                if not isinstance(form_field, dict):
                    continue
                que_id = _coerce_any_int(form_field.get("que_id"))
                if que_id is not None:
                    form_by_que_id.setdefault(que_id, form_field)

    def add_selector(key: Any, field: dict[str, Any]) -> None:
        normalized = str(key or "").strip()
        if not normalized:
            return
        by_selector.setdefault(normalized, []).append(field)
        lower = normalized.lower()
        if lower != normalized:
            by_selector.setdefault(lower, []).append(field)

    for raw_field in qingbi_fields:
        if not isinstance(raw_field, dict):
            continue
        field_id = _chart_field_id(raw_field)
        if not field_id:
            continue
        field = deepcopy(raw_field)
        que_id = _qingbi_field_que_id(app_key=app_key, field_id=field_id)
        form_field = form_by_que_id.get(que_id) if que_id is not None else None
        if isinstance(form_field, dict):
            field["_public_form_field"] = deepcopy(form_field)
        if not _chart_field_name(field) and isinstance(form_field, dict) and form_field.get("name"):
            field["fieldName"] = form_field.get("name")

        add_selector(field_id, field)
        if que_id is not None:
            add_selector(que_id, field)
            add_selector(f"field_{que_id}", field)
            if isinstance(form_field, dict):
                add_selector(form_field.get("field_id"), field)
        title = _chart_field_name(field)
        if title:
            add_selector(title, field)
    return {"by_selector": by_selector}


def _compact_public_chart_fields_read(
    *,
    app_key: str,
    qingbi_fields: list[dict[str, Any]],
    field_lookup: dict[str, dict[str, Any]],
) -> list[dict[str, Any]]:
    form_by_que_id = field_lookup.get("by_que_id") or {}
    compact_fields: list[dict[str, Any]] = []
    seen: set[str] = set()
    for field in qingbi_fields:
        if not isinstance(field, dict):
            continue
        bi_field_id = _chart_field_id(field)
        if not bi_field_id or bi_field_id in seen:
            continue
        seen.add(bi_field_id)
        que_id = _qingbi_field_que_id(app_key=app_key, field_id=bi_field_id)
        form_field = form_by_que_id.get(que_id) if que_id is not None else None
        public_field_id = (
            str(form_field.get("field_id"))
            if isinstance(form_field, dict) and form_field.get("field_id")
            else f"field_{que_id}"
            if que_id is not None
            else bi_field_id
        )
        title = _chart_field_name(field) or (
            str(form_field.get("name")) if isinstance(form_field, dict) and form_field.get("name") else bi_field_id
        )
        compact_fields.append(
            _compact_dict(
                {
                    "field_id": public_field_id,
                    "que_id": que_id,
                    "bi_field_id": bi_field_id,
                    "title": title,
                    "field_type": field.get("fieldType") or field.get("field_type"),
                    "system_field": bool(que_id is not None and not isinstance(form_field, dict)),
                    "available_for_charts": True,
                    "chart_apply_examples": _chart_apply_examples_for_field(
                        title=title,
                        field_type=field.get("fieldType") or field.get("field_type"),
                    ),
                }
            )
        )
    return compact_fields


def _chart_apply_examples_for_field(*, title: str, field_type: Any) -> dict[str, Any]:
    field_name = str(title or "").strip()
    if not field_name:
        return {}
    examples: dict[str, Any] = {
        "count_by_field": {
            "name": f"按{field_name}分布",
            "chart_type": "bar",
            "group_by": [field_name],
            "metric": "count(*)",
        },
        "filtered_count": {
            "name": f"{field_name}筛选数量",
            "chart_type": "target",
            "metric": "count(*)",
            "where": [{"field": field_name, "op": "eq", "value": "REPLACE_WITH_VALUE"}],
        },
    }
    if str(field_type or "").strip().lower() in _QINGBI_DECIMAL_FIELD_TYPES:
        examples["sum_metric"] = {
            "name": f"{field_name}合计",
            "chart_type": "target",
            "metric": f"sum({field_name})",
        }
    return examples


def _chart_field_candidates(
    selector: Any,
    *,
    chart_field_lookup: dict[str, Any],
) -> list[dict[str, Any]]:
    raw = str(selector or "").strip()
    if not raw:
        return []
    by_selector = chart_field_lookup.get("by_selector") if isinstance(chart_field_lookup.get("by_selector"), dict) else {}
    return _dedupe_qingbi_fields(list(by_selector.get(raw) or by_selector.get(raw.lower()) or []))


def _resolve_qingbi_chart_field(
    selector: Any,
    *,
    chart_field_lookup: dict[str, Any],
    chart_type: str,
    role: str,
) -> dict[str, Any]:
    raw = str(selector or "").strip()
    if not raw:
        _raise_chart_rule(
            rule_code="CHART_FIELD_NOT_IN_QINGBI_SCHEMA",
            chart_type=chart_type,
            message="chart field selector cannot be empty",
            expected="use a field from app_get_fields.chart_fields",
            actual={"selector": raw, "role": role},
            next_action="Call app_get_fields and choose a field from chart_fields for chart dimensions, metrics, filters, or query conditions.",
        )
    if raw in _QINGBI_TOTAL_FIELD_ALIASES or raw.lower() in _QINGBI_TOTAL_FIELD_ALIASES:
        if role == "metric":
            return _default_public_total_metric()
        _raise_chart_rule(
            rule_code="CHART_TOTAL_FIELD_NOT_ALLOWED",
            chart_type=chart_type,
            message="数据总量 is only valid as a metric field, not as a dimension/filter/query field",
            expected="use 数据总量 only in indicator_field_ids or omit metrics for count-style charts",
            actual={"selector": raw, "role": role},
            next_action="Choose a real QingBI field from app_get_fields.chart_fields for dimensions, filters, and query conditions.",
            offending_fields=[{"field_id": _QINGBI_TOTAL_FIELD_ID, "field_name": "数据总量"}],
        )
    candidates = _chart_field_candidates(raw, chart_field_lookup=chart_field_lookup)
    if not candidates:
        _raise_chart_rule(
            rule_code="CHART_FIELD_NOT_IN_QINGBI_SCHEMA",
            chart_type=chart_type,
            message=f"field '{raw}' was not found in QingBI datasource fields for this app",
            expected="chart fields must come from app_get_fields.chart_fields, not record schema or form-only fields",
            actual={"selector": raw, "role": role},
            next_action="Call app_get_fields and choose a field from chart_fields; if the system field is absent there, QingBI cannot use it for this report.",
        )
    if len(candidates) > 1:
        _raise_chart_rule(
            rule_code="CHART_FIELD_AMBIGUOUS",
            chart_type=chart_type,
            message=f"field '{raw}' matched multiple QingBI datasource fields",
            expected="use an unambiguous field selector such as bi_field_id or field_<queId>",
            actual={"selector": raw, "role": role, "candidate_count": len(candidates)},
            next_action="Use one of the returned candidate bi_field_id values or field_<queId> selectors.",
            offending_fields=[_chart_field_summary(item) for item in candidates],
        )
    return deepcopy(candidates[0])


def _check_chart_slot_duplicates(*, chart_type: str, payload: dict[str, Any], slot_names: list[str]) -> None:
    for slot_name in slot_names:
        duplicates = _chart_duplicate_fields(_chart_fields(payload, slot_name))
        if duplicates:
            _raise_chart_rule(
                rule_code="CHART_FIELD_ID_REPEAT",
                chart_type=chart_type,
                message=f"{chart_type} chart has duplicate field ids in {slot_name}",
                expected=f"{slot_name} must not contain duplicated fieldId values",
                actual={"slot": slot_name, "duplicate_count": len(duplicates)},
                offending_fields=duplicates,
                next_action="Use different fields for this slot, or remove the duplicated field before retrying.",
            )


def _histogram_metric_issue(metric: dict[str, Any]) -> dict[str, Any] | None:
    field_id = _chart_field_id(metric)
    if field_id == _QINGBI_TOTAL_FIELD_ID:
        return {
            "rule_code": "HISTOGRAM_DEFAULT_TOTAL_METRIC_UNSUPPORTED",
            "message": "histogram cannot use 数据总量 as its metric",
            "next_action": "Pass one explicit numeric field in indicator_field_ids and set config.aggregate such as sum/avg.",
        }
    field_type = str(metric.get("fieldType") or metric.get("field_type") or "").strip().lower()
    if field_type not in _QINGBI_DECIMAL_FIELD_TYPES:
        return {
            "rule_code": "HISTOGRAM_METRIC_FIELD_TYPE_UNSUPPORTED",
            "message": "histogram metric must be a numeric field",
            "next_action": "Choose one number/amount field as indicator_field_ids for histogram.",
        }
    field_source = str(metric.get("fieldSource") or metric.get("field_source") or "").strip().lower()
    bi_formula_type = str(metric.get("biFormulaType") or metric.get("bi_formula_type") or "").strip().lower()
    aggre_field_id = str(metric.get("aggreFieldId") or metric.get("aggre_field_id") or "").strip()
    if field_source == "formula" and (bi_formula_type in {"chart_agg", "agg"} or aggre_field_id):
        return {
            "rule_code": "HISTOGRAM_AGG_FORMULA_METRIC_UNSUPPORTED",
            "message": "histogram metric cannot be an aggregate formula field",
            "next_action": "Choose a plain numeric field, not an aggregate formula field.",
        }
    return None


def _validate_public_chart_payload_rules(payload: dict[str, Any]) -> None:
    chart_type = str(payload.get("chartType") or "").strip().lower()
    dimensions = _chart_fields(payload, "selectedDimensions")
    metrics = _chart_fields(payload, "selectedMetrics")
    _check_chart_slot_duplicates(
        chart_type=chart_type,
        payload=payload,
        slot_names=[
            "selectedDimensions",
            "selectedMetrics",
            "xDimensions",
            "yDimensions",
            "xMetrics",
            "yMetrics",
            "leftMetrics",
            "rightMetrics",
        ],
    )

    if chart_type == "gauge":
        if dimensions:
            _raise_chart_rule(
                rule_code="GAUGE_DIMENSION_NOT_ALLOWED",
                chart_type=chart_type,
                message="gauge chart must not have dimensions",
                expected="0 dimensions",
                actual={"dimension_count": len(dimensions)},
                offending_fields=[_chart_field_summary(field) for field in dimensions],
                next_action="Remove dimension_field_ids for gauge. The CLI clears public dimensions, but custom selectedDimensions in config must also be removed.",
            )
        if len(metrics) != 2:
            _raise_chart_rule(
                rule_code="GAUGE_METRIC_COUNT_INVALID",
                chart_type=chart_type,
                message="gauge chart requires exactly two metrics",
                expected="exactly 2 non-duplicated metrics; one real metric plus 数据总量 is allowed",
                actual={"metric_count": len(metrics), "metric_field_ids": [_chart_field_id(field) for field in metrics]},
                offending_fields=[_chart_field_summary(field) for field in metrics],
                next_action="Pass two different indicator_field_ids, or pass one explicit real numeric metric so the CLI can pair it with 数据总量.",
            )
    elif chart_type == "histogram":
        if len(dimensions) > 1:
            _raise_chart_rule(
                rule_code="HISTOGRAM_DIMENSION_COUNT_INVALID",
                chart_type=chart_type,
                message="histogram chart supports at most one dimension",
                expected="0 or 1 dimension",
                actual={"dimension_count": len(dimensions)},
                offending_fields=[_chart_field_summary(field) for field in dimensions],
                next_action="Keep at most one dimension_field_ids value for histogram.",
            )
        if len(metrics) != 1:
            _raise_chart_rule(
                rule_code="HISTOGRAM_METRIC_COUNT_INVALID",
                chart_type=chart_type,
                message="histogram chart requires exactly one explicit metric",
                expected="exactly 1 plain numeric metric",
                actual={"metric_count": len(metrics), "metric_field_ids": [_chart_field_id(field) for field in metrics]},
                offending_fields=[_chart_field_summary(field) for field in metrics],
                next_action="Pass exactly one numeric field in indicator_field_ids; histogram cannot rely on the default count metric.",
            )
        issue = _histogram_metric_issue(metrics[0])
        if issue:
            _raise_chart_rule(
                rule_code=str(issue["rule_code"]),
                chart_type=chart_type,
                message=str(issue["message"]),
                expected="one plain decimal metric; not 数据总量 and not aggregate formula",
                actual={"metric": _chart_field_summary(metrics[0])},
                offending_fields=[_chart_field_summary(metrics[0])],
                next_action=str(issue["next_action"]),
            )
    elif chart_type == "heatmap":
        if len(dimensions) != 2 or len(metrics) != 1:
            _raise_chart_rule(
                rule_code="HEATMAP_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="heatmap chart requires two dimensions and one metric",
                expected="2 dimensions and 1 metric",
                actual={"dimension_count": len(dimensions), "metric_count": len(metrics)},
                next_action="Pass exactly two dimension_field_ids and one indicator_field_ids value for heatmap.",
            )
    elif chart_type == "waterfall":
        if len(dimensions) != 1 or len(metrics) != 1:
            _raise_chart_rule(
                rule_code="WATERFALL_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="waterfall chart requires one dimension and one metric",
                expected="1 dimension and 1 metric",
                actual={"dimension_count": len(dimensions), "metric_count": len(metrics)},
                next_action="Pass exactly one dimension_field_ids value and one indicator_field_ids value for waterfall.",
            )
    elif chart_type == "treemap":
        if len(dimensions) < 1 or len(dimensions) > 2 or len(metrics) != 1:
            _raise_chart_rule(
                rule_code="TREEMAP_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="treemap chart requires one or two dimensions and one metric",
                expected="1-2 dimensions and 1 metric",
                actual={"dimension_count": len(dimensions), "metric_count": len(metrics)},
                next_action="Pass one or two dimension_field_ids values and exactly one indicator_field_ids value for treemap.",
            )
    elif chart_type == "map":
        if len(dimensions) != 1 or len(metrics) != 1:
            _raise_chart_rule(
                rule_code="MAP_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="map chart requires one dimension and one metric",
                expected="1 dimension and 1 metric",
                actual={"dimension_count": len(dimensions), "metric_count": len(metrics)},
                next_action="Pass exactly one location/address dimension and one metric for map.",
            )
    elif chart_type == "scatter":
        x_metrics = _chart_fields(payload, "xMetrics")
        y_metrics = _chart_fields(payload, "yMetrics")
        if not dimensions or len(x_metrics) != 1 or len(y_metrics) != 1:
            _raise_chart_rule(
                rule_code="SCATTER_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="scatter chart requires at least one dimension, one x metric, and one y metric",
                expected=">=1 dimensions, exactly 1 x metric and 1 y metric",
                actual={"dimension_count": len(dimensions), "x_metric_count": len(x_metrics), "y_metric_count": len(y_metrics)},
                next_action="Pass at least one dimension_field_ids value and one or two indicator_field_ids values for scatter.",
            )
    elif chart_type == "dualaxes":
        left_metrics = _chart_fields(payload, "leftMetrics")
        right_metrics = _chart_fields(payload, "rightMetrics")
        if not dimensions or (not left_metrics and not right_metrics):
            _raise_chart_rule(
                rule_code="DUALAXES_FIELD_COUNT_INVALID",
                chart_type=chart_type,
                message="dualaxes chart requires at least one dimension and at least one metric axis",
                expected=">=1 dimensions and at least one left/right metric",
                actual={"dimension_count": len(dimensions), "left_metric_count": len(left_metrics), "right_metric_count": len(right_metrics)},
                next_action="Pass at least one dimension_field_ids value and one or two indicator_field_ids values for dualaxes.",
            )


def _explain_chart_backend_validation_error(
    *,
    api_error: QingflowApiError,
    chart_type: str,
    payload: dict[str, Any] | None,
) -> dict[str, Any] | None:
    backend_code = backend_code_int(api_error)
    if backend_code not in {81002, 81005}:
        return None
    chart_type = str(chart_type or (payload or {}).get("chartType") or "").strip().lower()
    if isinstance(payload, dict):
        try:
            _validate_public_chart_payload_rules(payload)
        except ChartRuleViolation as violation:
            return violation.diagnostics
    if backend_code == 81005:
        duplicate_fields: list[dict[str, Any]] = []
        if isinstance(payload, dict):
            for slot_name in [
                "selectedDimensions",
                "selectedMetrics",
                "xDimensions",
                "yDimensions",
                "xMetrics",
                "yMetrics",
                "leftMetrics",
                "rightMetrics",
            ]:
                duplicate_fields.extend(_chart_duplicate_fields(_chart_fields(payload, slot_name)))
        return _chart_rule_diagnostics(
            rule_code="CHART_FIELD_ID_REPEAT",
            chart_type=chart_type,
            message="QingBI rejected the chart because one field id is repeated in a chart slot",
            expected="field ids must be unique within each dimension/metric slot",
            actual={"backend_code": backend_code},
            offending_fields=duplicate_fields,
            next_action="Remove duplicated fields or pass two different explicit metrics before retrying.",
        )
    return _chart_rule_diagnostics(
        rule_code="WRONG_METRIC_COUNT_OR_TYPE",
        chart_type=chart_type,
        message="QingBI rejected the chart because metric count or metric type does not satisfy this chart type",
        expected="use the chart-type metric count/type rules from builder charts documentation",
        actual={"backend_code": backend_code},
        next_action="Check indicator_field_ids count and field types; for histogram use exactly one plain numeric metric, and for gauge use two non-duplicated metrics.",
    )


def _build_public_metric_fields(
    selectors: list[str],
    *,
    app_key: str,
    field_lookup: dict[str, dict[str, Any]],
    chart_field_lookup: dict[str, Any],
    qingbi_fields_by_id: dict[str, dict[str, Any]],
    aggregate: str,
    chart_type: str = "chart",
) -> list[dict[str, Any]]:
    normalized_aggregate = str(aggregate or "count").strip().lower()
    if normalized_aggregate == "count" or not selectors:
        return [_default_public_total_metric()]
    metrics: list[dict[str, Any]] = []
    for selector in selectors:
        qingbi_field = _resolve_qingbi_chart_field(selector, chart_field_lookup=chart_field_lookup, chart_type=chart_type, role="metric")
        metrics.append(_public_qingbi_metric_field(qingbi_field, aggregate=normalized_aggregate))
    return metrics or [_default_public_total_metric()]


def _public_qingbi_metric_field(qingbi_field: dict[str, Any], *, aggregate: str) -> dict[str, Any]:
    field_id = _chart_field_id(qingbi_field)
    if field_id == _QINGBI_TOTAL_FIELD_ID:
        return deepcopy(qingbi_field)
    form_field = qingbi_field.get("_public_form_field") if isinstance(qingbi_field.get("_public_form_field"), dict) else {}
    normalized_aggregate = str(aggregate or "sum").strip().lower()
    aggre_type = {"sum": "sum", "avg": "avg", "average": "avg", "max": "max", "min": "min"}.get(normalized_aggregate, "sum")
    return {
        "fieldId": field_id,
        "fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
        "fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
        "orderType": "default",
        "alignType": "left",
        "dateFormat": "yyyy-MM-dd",
        "numberFormat": "default",
        "numberConfig": {"format": "splitter", "unit": "DEFAULT", "prefix": "", "suffix": "", "digit": None},
        "digit": None,
        "aggreType": aggre_type,
        "orderPriority": None,
        "width": None,
        "verticalAlign": "middle",
        "formula": qingbi_field.get("formula"),
        "fieldSource": qingbi_field.get("fieldSource") or "default",
        "status": qingbi_field.get("status"),
        "supId": qingbi_field.get("supId"),
        "beingTable": bool(qingbi_field.get("beingTable", False)),
        "returnType": qingbi_field.get("returnType"),
        "biFormulaType": qingbi_field.get("biFormulaType"),
        "aggreFieldId": qingbi_field.get("aggreFieldId"),
    }


def _build_public_semantic_metric_fields(
    metrics: list[ChartMetricPatch],
    *,
    app_key: str,
    field_lookup: dict[str, dict[str, Any]],
    chart_field_lookup: dict[str, Any],
    qingbi_fields_by_id: dict[str, dict[str, Any]],
    chart_type: str = "chart",
) -> list[dict[str, Any]]:
    if not metrics:
        return [_default_public_total_metric()]
    selected_metrics: list[dict[str, Any]] = []
    for metric in metrics:
        op = str(metric.op or "count").strip().lower()
        field_name = str(metric.field_name or "").strip()
        if op == "count":
            if field_name:
                _raise_chart_rule(
                    rule_code="CHART_COUNT_FIELD_UNSUPPORTED",
                    chart_type=chart_type,
                    message="count metric currently supports count(*) only",
                    expected='Use metric: "count(*)" or {"op": "count"} for record count.',
                    actual={"metric": metric.model_dump(mode="json")},
                    next_action='Use count(*) for count cards; use sum(field), avg(field), max(field), or min(field) for field aggregation.',
                )
            selected_metrics.append(_default_public_total_metric())
            continue
        qingbi_field = _resolve_qingbi_chart_field(
            field_name,
            chart_field_lookup=chart_field_lookup,
            chart_type=chart_type,
            role="metric",
        )
        metric_payload = _public_qingbi_metric_field(qingbi_field, aggregate=op)
        if metric.alias:
            metric_payload["fieldName"] = metric.alias
        selected_metrics.append(metric_payload)
    return selected_metrics or [_default_public_total_metric()]


def _split_axis_metric_fields(metrics: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    if not metrics:
        default_metric = _default_public_total_metric()
        return [deepcopy(default_metric)], [deepcopy(default_metric)]
    if len(metrics) == 1:
        return [deepcopy(metrics[0])], [deepcopy(metrics[0])]
    return [deepcopy(metrics[0])], [deepcopy(metrics[1])]


def _two_gauge_metric_fields(metrics: list[dict[str, Any]], *, requested_metric_count: int) -> list[dict[str, Any]]:
    if not metrics:
        return []
    if len(metrics) == 1:
        if requested_metric_count <= 0:
            return [deepcopy(metrics[0])]
        return [deepcopy(metrics[0]), _default_public_total_metric()]
    return [deepcopy(metrics[0]), deepcopy(metrics[1])]


def _build_public_chart_filter_matrix(
    rules: list[Any],
    *,
    app_key: str,
    field_lookup: dict[str, dict[str, Any]],
    chart_field_lookup: dict[str, Any],
    qingbi_fields_by_id: dict[str, dict[str, Any]],
    chart_type: str = "chart",
) -> list[list[dict[str, Any]]]:
    if not rules:
        return []
    group: list[dict[str, Any]] = []
    judge_map = {
        ViewFilterOperator.eq.value: "equal",
        ViewFilterOperator.neq.value: "unequal",
        ViewFilterOperator.gte.value: "greaterOrEqual",
        ViewFilterOperator.lte.value: "lessOrEqual",
        ViewFilterOperator.in_.value: "anyMatch",
        ViewFilterOperator.contains.value: "include",
        ViewFilterOperator.is_empty.value: "isNull",
        ViewFilterOperator.not_empty.value: "notNull",
    }
    for rule in rules:
        qingbi_field = _resolve_qingbi_chart_field(
            getattr(rule, "field_name", None),
            chart_field_lookup=chart_field_lookup,
            chart_type=chart_type,
            role="filter",
        )
        field_id = _chart_field_id(qingbi_field)
        form_field = qingbi_field.get("_public_form_field") if isinstance(qingbi_field.get("_public_form_field"), dict) else {}
        operator = str(getattr(rule, "operator", ViewFilterOperator.eq.value).value if hasattr(getattr(rule, "operator", None), "value") else getattr(rule, "operator", ViewFilterOperator.eq.value))
        values = list(getattr(rule, "values", []) or [])
        group.append(
            {
                "fieldId": field_id,
                "fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
                "fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
                "judgeType": judge_map.get(operator, "equal"),
                "judgeValue": _build_qingbi_chart_filter_judge_value(
                    operator=operator,
                    values=values,
                    form_field=form_field,
                    chart_type=chart_type,
                ),
                "judgeValueDetailList": [],
                "matchType": 1,
            }
        )
    return [group] if group else []


def _build_qingbi_chart_filter_judge_value(
    *,
    operator: str,
    values: list[Any],
    form_field: dict[str, Any],
    chart_type: str,
) -> str | None:
    if operator in {ViewFilterOperator.is_empty.value, ViewFilterOperator.not_empty.value}:
        return None
    if operator == ViewFilterOperator.in_.value:
        return "<&&>".join(
            _qingbi_chart_filter_value_to_text(value=value, form_field=form_field, chart_type=chart_type) for value in values
        )
    if not values:
        return ""
    return _qingbi_chart_filter_value_to_text(value=values[0], form_field=form_field, chart_type=chart_type)


def _qingbi_chart_filter_value_to_text(*, value: Any, form_field: dict[str, Any], chart_type: str) -> str:
    option_details = [
        item
        for item in (form_field.get("option_details") or [])
        if isinstance(item, dict) and item.get("id") is not None and item.get("value") is not None
    ]
    if not option_details:
        if isinstance(value, dict):
            for key in ("value", "label", "name", "title"):
                raw = str(value.get(key) or "").strip()
                if raw:
                    return raw
            if value.get("id") is not None:
                return str(value.get("id"))
        return _stringify_condition_value(value)

    option_by_value = {str(item.get("value") or "").strip(): item for item in option_details if str(item.get("value") or "").strip()}
    option_by_id = {str(item.get("id")): item for item in option_details if item.get("id") is not None}

    def resolve(raw: Any) -> str | None:
        text = _stringify_condition_value(raw).strip()
        if not text:
            return None
        matched = option_by_value.get(text) or option_by_id.get(text)
        if not matched:
            return None
        return str(matched.get("value") or "").strip()

    def reject(raw: Any) -> NoReturn:
        allowed = [
            {"id": str(item.get("id")), "value": str(item.get("value"))}
            for item in option_details
        ]
        _raise_chart_rule(
            rule_code="CHART_FILTER_OPTION_VALUE_UNSUPPORTED",
            chart_type=chart_type,
            message="chart filter value is not in the field option list",
            expected="Use an existing option label or option id from the target field schema.",
            actual={
                "field_name": form_field.get("name"),
                "field_id": form_field.get("que_id"),
                "value": raw,
                "allowed_values": allowed,
            },
            offending_fields=[{"field_name": form_field.get("name"), "field_id": form_field.get("que_id"), "options": allowed}],
            next_action="Read schema first, then pass one of the allowed option labels or option ids.",
        )

    if isinstance(value, dict):
        for key in ("value", "label", "name", "title"):
            if value.get(key) is not None:
                resolved = resolve(value.get(key))
                if resolved is not None:
                    return resolved
                reject(value.get(key))
        raw_id = _condition_detail_id_text(value)
        if raw_id:
            resolved = resolve(raw_id)
            if resolved is not None:
                return resolved
            reject(raw_id)
        reject(value)
    if isinstance(value, int):
        resolved = resolve(value)
        if resolved is not None:
            return resolved
        reject(value)
    resolved = resolve(value)
    if resolved is not None:
        return resolved
    reject(value)


def _chart_field_names_by_id_from_public_fields(*, app_key: str, fields: list[dict[str, Any]]) -> dict[str, str]:
    field_name_by_id: dict[str, str] = {}
    for field in fields:
        if not isinstance(field, dict):
            continue
        name = str(field.get("name") or "").strip()
        if not name:
            continue
        que_id = field.get("que_id")
        field_id = str(field.get("field_id") or "").strip()
        for raw_key in (
            que_id,
            field_id,
            f"{app_key}:{que_id}" if que_id is not None else None,
            f"{app_key}:{field_id}" if field_id else None,
        ):
            key = str(raw_key or "").strip()
            if key:
                field_name_by_id.setdefault(key, name)
    return field_name_by_id


def _public_chart_filter_groups_from_qingbi_config(
    config: dict[str, Any],
    *,
    field_name_by_id: dict[str, str] | None = None,
) -> list[list[dict[str, Any]]]:
    groups: list[list[dict[str, Any]]] = []
    raw_groups = config.get("beforeAggregationFilterMatrix")
    if not isinstance(raw_groups, list):
        return groups
    resolved_field_name_by_id = field_name_by_id or {}
    for raw_group in raw_groups:
        if not isinstance(raw_group, list):
            continue
        group: list[dict[str, Any]] = []
        for raw_rule in raw_group:
            if not isinstance(raw_rule, dict):
                continue
            operator = _public_chart_filter_operator_from_judge_type(raw_rule.get("judgeType"))
            field_id = raw_rule.get("fieldId") or raw_rule.get("field_id")
            field_id_text = _stringify_condition_value(field_id).strip() if field_id is not None else ""
            raw_field_name = (
                raw_rule.get("fieldName")
                or raw_rule.get("field_name")
                or raw_rule.get("queTitle")
                or raw_rule.get("title")
            )
            raw_field_name_text = _stringify_condition_value(raw_field_name).strip()
            field_name = (
                resolved_field_name_by_id.get(raw_field_name_text)
                or resolved_field_name_by_id.get(field_id_text)
                or raw_field_name_text
                or field_id_text
            )
            public_rule: dict[str, Any] = {
                "field_name": field_name,
                "operator": operator,
            }
            if field_id is not None:
                public_rule["field_id"] = field_id_text
            values = _public_chart_filter_values_from_rule(raw_rule, operator=operator)
            if operator == "in" or len(values) > 1:
                public_rule["values"] = values
            elif len(values) == 1:
                public_rule["value"] = values[0]
            group.append(public_rule)
        if group:
            groups.append(group)
    return groups


def _chart_apply_retry_context(
    *,
    request: ChartApplyRequest,
    failed_items: list[dict[str, Any]],
    created_ids: list[str],
    updated_ids: list[str],
    removed_ids: list[str],
    reordered: bool,
    write_executed: bool,
) -> dict[str, Any]:
    failed_chart_names = [
        str(item.get("name") or "").strip()
        for item in failed_items
        if str(item.get("name") or "").strip()
    ]
    failed_delete_ids = [
        str(item.get("chart_id") or "").strip()
        for item in failed_items
        if str(item.get("operation") or "") == "delete" and str(item.get("chart_id") or "").strip()
    ]
    reorder_failed = any(str(item.get("operation") or "") == "reorder" for item in failed_items)
    failed_name_set = set(failed_chart_names)
    retry_payload: dict[str, Any] = {"app_key": request.app_key}
    retry_upserts = [
        patch.model_dump(mode="json", exclude_none=True)
        for patch in request.upsert_charts
        if patch.name in failed_name_set
    ]
    if retry_upserts:
        retry_payload["upsert_charts"] = retry_upserts
    if failed_delete_ids:
        retry_payload["remove_chart_ids"] = failed_delete_ids
    if reorder_failed and request.reorder_chart_ids:
        retry_payload["reorder_chart_ids"] = list(request.reorder_chart_ids)
    has_retry_payload = any(key in retry_payload for key in ("upsert_charts", "remove_chart_ids", "reorder_chart_ids"))
    context: dict[str, Any] = {
        "created_chart_ids": list(created_ids),
        "updated_chart_ids": list(updated_ids),
        "removed_chart_ids": list(removed_ids),
        "failed_chart_names": failed_chart_names,
        "failed_delete_chart_ids": failed_delete_ids,
        "readback_first": bool(write_executed),
        "retry_rule": (
            "read app charts before retrying; retry only the failed chart payloads if they are still missing or unverified"
            if write_executed
            else "fix the failed chart payloads and retry only those items"
        ),
    }
    if has_retry_payload:
        context["suggested_retry_payload"] = retry_payload
    return context


def _chart_apply_batching_context(
    *,
    request: ChartApplyRequest,
    upsert_charts: list[ChartUpsertPatch],
    batch_size: int,
) -> dict[str, Any]:
    suggested_batch_payloads: list[dict[str, Any]] = []
    for start in range(0, len(upsert_charts), batch_size):
        batch = upsert_charts[start : start + batch_size]
        suggested_batch_payloads.append(
            {
                "app_key": request.app_key,
                "upsert_charts": [patch.model_dump(mode="json", exclude_none=True) for patch in batch],
            }
        )

    followup_payload: dict[str, Any] = {"app_key": request.app_key}
    if request.remove_chart_ids:
        followup_payload["remove_chart_ids"] = list(request.remove_chart_ids)
    if request.reorder_chart_ids:
        followup_payload["reorder_chart_ids"] = list(request.reorder_chart_ids)
    if len(followup_payload) > 1:
        suggested_batch_payloads.append(followup_payload)

    return {
        "upsert_count": len(upsert_charts),
        "max_upsert_count": batch_size,
        "write_executed": False,
        "readback_first": False,
        "retry_rule": "run suggested_batch_payloads one at a time; do not submit the original large upsert_charts array",
        "suggested_batch_payloads": suggested_batch_payloads,
    }


def _view_apply_failure_diagnostics(
    *,
    patch: ViewUpsertPatch,
    current_fields_by_name: dict[str, dict[str, Any]],
    backend_code: Any,
    operation_phase: str,
) -> dict[str, Any]:
    references: list[tuple[str, str]] = []

    def add_reference(name: Any, source: str) -> None:
        text = str(name or "").strip()
        if text:
            references.append((text, source))

    for column in patch.columns:
        add_reference(column, "columns")
    add_reference(patch.group_by, "group_by")
    add_reference(patch.start_field, "start_field")
    add_reference(patch.end_field, "end_field")
    add_reference(patch.title_field, "title_field")
    for rule in patch.filters:
        add_reference(rule.field_name, "filters")

    seen: set[str] = set()
    field_entries: list[dict[str, Any]] = []
    for field_name, source in references:
        key = f"{field_name}\0{source}"
        if key in seen:
            continue
        seen.add(key)
        field = current_fields_by_name.get(field_name) or {}
        field_entries.append(
            {
                "field_name": field_name,
                "source": source,
                "field_id": field.get("field_id"),
                "que_id": field.get("que_id"),
                "field_type": field.get("type"),
                "que_type": field.get("que_type"),
                "exists_in_schema": bool(field),
            }
        )
    diagnostics: dict[str, Any] = {
        "operation_phase": operation_phase,
        "field_level_diagnostics": field_entries,
    }
    if backend_code_value_int(backend_code) == 40038:
        diagnostics.update(
            {
                "suspected_fields": field_entries,
                "recovery_hint": "Do not delete app fields or recreate the app. First retry the view with the smallest required columns; then add non-critical columns back one by one.",
                "recommended_minimal_retry": {
                    "name": patch.name,
                    "type": patch.type.value,
                    "columns": [field_entries[0]["field_name"]] if field_entries else [],
                    **({"filters": [rule.model_dump(mode="json", exclude_none=True) for rule in patch.filters]} if patch.filters else {}),
                },
            }
        )
    return diagnostics


def _public_chart_group_by_from_qingbi_config(config: dict[str, Any]) -> list[str]:
    fields: list[dict[str, Any]] = []
    for key in ("selectedDimensions", "xDimensions", "yDimensions", "selectedTime"):
        fields.extend(_chart_fields(config, key))
    group_by: list[str] = []
    seen: set[str] = set()
    for field in fields:
        name = _stringify_condition_value(
            field.get("fieldName")
            or field.get("field_name")
            or field.get("queTitle")
            or field.get("title")
            or field.get("fieldId")
            or field.get("field_id")
        ).strip()
        if not name or name in seen:
            continue
        seen.add(name)
        group_by.append(name)
    return group_by


def _public_chart_metrics_from_qingbi_config(config: dict[str, Any]) -> list[dict[str, Any]]:
    fields: list[dict[str, Any]] = []
    for key in ("selectedMetrics", "xMetrics", "yMetrics", "leftMetrics", "rightMetrics"):
        fields.extend(_chart_fields(config, key))
    metrics: list[dict[str, Any]] = []
    seen: set[tuple[str, str]] = set()
    for field in fields:
        field_id = _chart_field_id(field)
        if field_id == _QINGBI_TOTAL_FIELD_ID:
            metric = {"op": "count", "expr": "count(*)"}
        else:
            op = str(field.get("aggreType") or field.get("aggregate") or "sum").strip().lower()
            if op == "average":
                op = "avg"
            field_name = _stringify_condition_value(
                field.get("fieldName")
                or field.get("field_name")
                or field.get("queTitle")
                or field.get("title")
                or field_id
            ).strip()
            metric = {"op": op or "sum", "field_name": field_name}
            if field_id:
                metric["field_id"] = field_id
            metric["expr"] = f"{metric['op']}({field_name})" if field_name else metric["op"]
        identity = (str(metric.get("op") or ""), str(metric.get("field_id") or metric.get("field_name") or metric.get("expr") or ""))
        if identity in seen:
            continue
        seen.add(identity)
        metrics.append(metric)
    return metrics


def _public_chart_filter_operator_from_judge_type(judge_type: Any) -> str:
    raw = _stringify_condition_value(judge_type).strip()
    normalized = re.sub(r"[\s_-]+", "", raw).lower()
    mapping = {
        "equal": "eq",
        "equals": "eq",
        "=": "eq",
        "unequal": "neq",
        "notequal": "neq",
        "!=": "neq",
        "anymatch": "in",
        "oneof": "in",
        "include": "contains",
        "contains": "contains",
        "greaterorequal": "gte",
        "gte": "gte",
        "lessorequal": "lte",
        "lte": "lte",
        "isnull": "is_empty",
        "empty": "is_empty",
        "notnull": "not_empty",
        "notempty": "not_empty",
    }
    if normalized in mapping:
        return mapping[normalized]
    numeric = _coerce_nonnegative_int(judge_type)
    if numeric is not None:
        return _public_view_filter_operator_from_judge_type(numeric)
    return f"judge_type:{judge_type}"


def _public_chart_filter_values_from_rule(rule: dict[str, Any], *, operator: str) -> list[str]:
    if operator in {"is_empty", "not_empty"}:
        return []
    raw_value = rule.get("judgeValue")
    if raw_value is None and "judgeValues" in rule:
        raw_value = rule.get("judgeValues")
    values: list[str] = []
    if isinstance(raw_value, list):
        values = [_stringify_condition_value(value).strip() for value in raw_value]
    elif isinstance(raw_value, str) and "<&&>" in raw_value:
        values = [value.strip() for value in raw_value.split("<&&>")]
    elif raw_value is not None:
        values = [_stringify_condition_value(raw_value).strip()]
    values = [value for value in values if value]
    detail_value_by_id, ordered_detail_values = _condition_detail_value_maps(
        rule,
        detail_keys=("judgeValueDetailList", "judgeValueDetails"),
    )
    if values:
        return [detail_value_by_id.get(value) or value for value in values]
    if ordered_detail_values:
        return ordered_detail_values
    return []


def _condition_detail_id_text(detail: dict[str, Any]) -> str:
    for key in ("id", "key", "optId", "opt_id", "optionId", "option_id", "valueId", "value_id"):
        if detail.get(key) is None:
            continue
        text = _stringify_condition_value(detail.get(key)).strip()
        if text:
            return text
    return ""


def _condition_detail_display_text(detail: dict[str, Any]) -> str:
    for key in ("value", "label", "name", "title", "optValue", "opt_value", "dataValue", "displayValue", "display_value"):
        value = _stringify_condition_value(detail.get(key)).strip()
        if value:
            return value
    return _condition_detail_id_text(detail)


def _condition_detail_value_maps(
    rule: dict[str, Any],
    *,
    detail_keys: tuple[str, ...],
) -> tuple[dict[str, str], list[str]]:
    detail_value_by_id: dict[str, str] = {}
    ordered_detail_values: list[str] = []
    for detail_key in detail_keys:
        details = rule.get(detail_key)
        if not isinstance(details, list):
            continue
        for detail in details:
            if not isinstance(detail, dict):
                continue
            display_value = _condition_detail_display_text(detail)
            if not display_value:
                continue
            ordered_detail_values.append(display_value)
            detail_id = _condition_detail_id_text(detail)
            if detail_id:
                detail_value_by_id[detail_id] = display_value
    return detail_value_by_id, ordered_detail_values


def _build_public_chart_config_payload(
    *,
    patch: ChartUpsertPatch,
    app_key: str,
    field_lookup: dict[str, dict[str, Any]],
    chart_field_lookup: dict[str, Any],
    qingbi_fields_by_id: dict[str, dict[str, Any]],
) -> dict[str, Any]:
    config = deepcopy(patch.config)
    explicit_fields = set(getattr(patch, "model_fields_set", set()) or set())
    semantic_dimension_fields = bool({"dimension_field_ids", "group_by", "rows", "columns"} & explicit_fields)
    semantic_metric_fields = bool(
        {"indicator_field_ids", "metric", "metrics", "x_metric", "y_metric", "left_metric", "right_metric", "value_metric", "target_metric"}
        & explicit_fields
    )
    if semantic_dimension_fields:
        config.pop("selectedDimensions", None)
    if semantic_metric_fields:
        config.pop("selectedMetrics", None)
    if "filters" in explicit_fields:
        config.pop("beforeAggregationFilterMatrix", None)
    aggregate = str(config.pop("aggregate", "count") or "count").lower()
    before_filters = deepcopy(config.pop("beforeAggregationFilterMatrix", []))
    after_filters = deepcopy(config.pop("afterAggregationFilterMatrix", []))
    if patch.filters:
        before_filters = _build_public_chart_filter_matrix(
            patch.filters,
            app_key=app_key,
            field_lookup=field_lookup,
            chart_field_lookup=chart_field_lookup,
            qingbi_fields_by_id=qingbi_fields_by_id,
            chart_type=patch.chart_type.value,
        )
    query_condition_field_ids = []
    for selector in list(config.pop("query_condition_field_ids", []) or []):
        field = _resolve_qingbi_chart_field(
            selector,
            chart_field_lookup=chart_field_lookup,
            chart_type=patch.chart_type.value,
            role="query_condition",
        )
        query_condition_field_ids.append(_chart_field_id(field))
    backend_chart_type = _map_public_chart_type_to_backend(patch.chart_type)
    if backend_chart_type == "gauge" and not patch.indicator_field_ids and not patch.metrics and "selectedMetrics" not in config:
        raise ValueError("gauge charts require at least one metric; pass value_metric or metric and the CLI will pair it with 数据总量")
    selected_dimensions = _build_public_dimension_fields(
        patch.dimension_field_ids,
        app_key=app_key,
        field_lookup=field_lookup,
        chart_field_lookup=chart_field_lookup,
        qingbi_fields_by_id=qingbi_fields_by_id,
        chart_type=patch.chart_type.value,
    )
    if patch.metrics:
        selected_metrics = _build_public_semantic_metric_fields(
            patch.metrics,
            app_key=app_key,
            field_lookup=field_lookup,
            chart_field_lookup=chart_field_lookup,
            qingbi_fields_by_id=qingbi_fields_by_id,
            chart_type=patch.chart_type.value,
        )
    else:
        selected_metrics = _build_public_metric_fields(
            patch.indicator_field_ids,
            app_key=app_key,
            field_lookup=field_lookup,
            chart_field_lookup=chart_field_lookup,
            qingbi_fields_by_id=qingbi_fields_by_id,
            aggregate=aggregate,
            chart_type=patch.chart_type.value,
        )
    payload: dict[str, Any] = {
        "chartName": patch.name,
        "chartType": backend_chart_type,
        "dataSource": {"dataSourceId": app_key, "dataSourceType": "qingflow"},
        "selectedDimensions": selected_dimensions,
        "selectedMetrics": selected_metrics,
        "beforeAggregationFilterMatrix": before_filters,
        "afterAggregationFilterMatrix": after_filters,
        "chartStyleConfigs": deepcopy(config.pop("chartStyleConfigs", [])),
        "conditionFormatMatrix": deepcopy(config.pop("conditionFormatMatrix", [])),
        "displayLimitConfig": deepcopy(config.pop("displayLimitConfig", {"status": 1, "type": "asc", "limit": 20})),
        "rawDataConfigDTO": deepcopy(
            config.pop(
                "rawDataConfigDTO",
                {"beingOpen": False, "authInfo": qingbi_workspace_visible_auth(), "fieldInfoList": []},
            )
        ),
        "queryConditionFieldIds": query_condition_field_ids,
        "queryConditionStatus": bool(config.pop("queryConditionStatus", bool(query_condition_field_ids))),
        "queryConditionExact": bool(config.pop("queryConditionExact", False)),
    }
    if backend_chart_type == "summary":
        payload.pop("selectedDimensions", None)
        payload.setdefault("xDimensions", deepcopy(selected_dimensions))
        y_dimensions = _build_public_dimension_fields(
            patch.columns,
            app_key=app_key,
            field_lookup=field_lookup,
            chart_field_lookup=chart_field_lookup,
            qingbi_fields_by_id=qingbi_fields_by_id,
            chart_type=patch.chart_type.value,
        )
        payload.setdefault("yDimensions", y_dimensions)
    elif backend_chart_type == "scatter":
        x_metrics, y_metrics = _split_axis_metric_fields(selected_metrics)
        payload.pop("selectedMetrics", None)
        payload.setdefault("xMetrics", x_metrics)
        payload.setdefault("yMetrics", y_metrics)
    elif backend_chart_type == "dualaxes":
        left_metrics, right_metrics = _split_axis_metric_fields(selected_metrics)
        payload.pop("selectedMetrics", None)
        payload.setdefault("leftMetrics", left_metrics)
        payload.setdefault("rightMetrics", right_metrics)
    elif backend_chart_type == "gauge":
        payload["selectedDimensions"] = []
        payload["selectedMetrics"] = _two_gauge_metric_fields(selected_metrics, requested_metric_count=len(patch.indicator_field_ids or []))
    for key in (
        "selectedTime",
        "xDimensions",
        "yDimensions",
        "xMetrics",
        "yMetrics",
        "leftMetrics",
        "rightMetrics",
        "pieType",
        "radarType",
    ):
        if key in config:
            payload[key] = deepcopy(config.pop(key))
    payload.update(config)
    _validate_public_chart_payload_rules(payload)
    return payload


def _chart_patch_updates_chart_config(patch: ChartUpsertPatch) -> bool:
    explicit_fields = set(getattr(patch, "model_fields_set", set()) or set())
    return bool(
        {
            "dimension_field_ids",
            "indicator_field_ids",
            "group_by",
            "rows",
            "columns",
            "metric",
            "metrics",
            "x_metric",
            "y_metric",
            "left_metric",
            "right_metric",
            "value_metric",
            "target_metric",
            "filters",
            "config",
        }
        & explicit_fields
    )


def _chart_patch_dataset_source_type(patch: ChartUpsertPatch) -> str:
    config = patch.config if isinstance(patch.config, dict) else {}
    candidates = [
        config.get("dataSourceType"),
        config.get("data_source_type"),
    ]
    data_source = config.get("dataSource")
    if isinstance(data_source, dict):
        candidates.extend([data_source.get("dataSourceType"), data_source.get("data_source_type"), data_source.get("type")])
    data_source = config.get("data_source")
    if isinstance(data_source, dict):
        candidates.extend([data_source.get("dataSourceType"), data_source.get("data_source_type"), data_source.get("type")])
    for candidate in candidates:
        normalized = str(candidate or "").strip().lower()
        if not normalized:
            continue
        if normalized not in {"qingflow", "app"}:
            return normalized
    return ""


def _chart_item_dataset_source_type(item: dict[str, Any]) -> str:
    candidates = [
        item.get("dataSourceType"),
        item.get("data_source_type"),
        item.get("sourceType"),
        item.get("source_type"),
    ]
    data_source = item.get("dataSource")
    if isinstance(data_source, dict):
        candidates.extend([data_source.get("dataSourceType"), data_source.get("data_source_type"), data_source.get("type")])
    for candidate in candidates:
        normalized = str(candidate or "").strip().lower()
        if not normalized:
            continue
        if normalized not in {"qingflow", "app", "bi_qingflow"}:
            return normalized
    return ""


def _build_public_portal_base_payload(
    *,
    dash_name: str,
    package_tag_id: int | None,
    icon: str | None,
    color: str | None,
    auth: dict[str, Any] | None,
    hide_copyright: bool | None,
    dash_global_config: dict[str, Any] | None,
    config: dict[str, Any] | None,
    base_payload: dict[str, Any] | None,
) -> dict[str, Any]:
    data = deepcopy(base_payload) if isinstance(base_payload, dict) else {}
    effective_name = str(dash_name or data.get("dashName") or "").strip() or "未命名门户"
    effective_hide_copyright = hide_copyright if hide_copyright is not None else bool(data.get("hideCopyright", False))
    if icon or color or not data.get("dashIcon"):
        data["dashIcon"] = encode_workspace_icon_with_defaults(
            icon=icon,
            color=color,
            title=effective_name,
            fallback_icon_name="view-grid",
            existing_icon=str(data.get("dashIcon") or "").strip() or None,
        )
    data["dashName"] = effective_name
    data["auth"] = deepcopy(auth if auth is not None else data.get("auth") or default_member_auth())
    data["hideCopyright"] = effective_hide_copyright
    if package_tag_id is not None:
        data["tags"] = [{"tagId": package_tag_id, "ordinal": 1}]
    elif "tags" not in data:
        data["tags"] = []
    global_config = deepcopy(dash_global_config if isinstance(dash_global_config, dict) else data.get("dashGlobalConfig") or {})
    global_config.pop("layout", None)
    global_config["interval"] = global_config.get("interval") or 60
    global_config["beingAutoRefresh"] = bool(global_config.get("beingAutoRefresh", False))
    data["dashGlobalConfig"] = global_config
    if isinstance(config, dict):
        data.update(deepcopy(config))
    return data


def _extract_chart_identifier(chart: Any) -> str:
    if not isinstance(chart, dict):
        return ""
    return str(
        chart.get("chartId")
        or chart.get("biChartId")
        or chart.get("chartKey")
        or ""
    ).strip()


def _chart_type_from_item(chart: Any) -> str:
    if not isinstance(chart, dict):
        return ""
    return _public_chart_type_from_backend(
        chart.get("chartType")
        or chart.get("chart_type")
        or chart.get("chartGraphType")
        or chart.get("type")
    )


def _normalize_backend_chart_type(value: Any) -> str:
    raw = str(value or "").strip()
    if not raw:
        return ""
    by_code = {
        "1": "detail",
        "2": "summary",
        "3": "indicator",
        "4": "columnar",
        "5": "line",
        "6": "pie",
        "7": "funnel",
        "8": "radar",
        "9": "bar",
        "10": "scatter",
        "11": "ring",
        "12": "dualaxes",
        "13": "map",
        "14": "timeline",
        "15": "area",
        "16": "stacked_column",
        "17": "stacked_bar",
        "18": "rose",
        "19": "stacked_area",
        "20": "pct_stack_col",
        "21": "pct_stack_bar",
        "22": "pct_stack_area",
        "23": "waterfall",
        "24": "gauge",
        "25": "heatmap",
        "26": "histogram",
        "27": "treemap",
    }
    aliases = {
        "percent_stacked_column": "pct_stack_col",
        "percent_stacked_bar": "pct_stack_bar",
        "percent_stacked_area": "pct_stack_area",
    }
    normalized = by_code.get(raw, raw.lower())
    return aliases.get(normalized, normalized)


def _public_chart_type_from_backend(value: Any) -> str:
    normalized = _normalize_backend_chart_type(value)
    return {
        "indicator": PublicChartType.target.value,
        "detail": PublicChartType.table.value,
    }.get(normalized, normalized)


def _find_charts_by_name(items: Any, *, chart_name: str, chart_type: str | None = None) -> list[dict[str, Any]]:
    target_name = str(chart_name or "").strip()
    target_type = _normalize_backend_chart_type(chart_type)
    candidates: list[dict[str, Any]] = []
    if not isinstance(items, list) or not target_name:
        return []
    for item in items:
        if not isinstance(item, dict):
            continue
        item_name = str(item.get("chartName") or "").strip()
        if item_name != target_name:
            continue
        item_type = _normalize_backend_chart_type(item.get("chartType"))
        if target_type and item_type and item_type != target_type:
            continue
        candidates.append(deepcopy(item))
    return candidates


def _find_chart_by_name(items: Any, *, chart_name: str, chart_type: str | None = None) -> dict[str, Any] | None:
    candidates = _find_charts_by_name(items, chart_name=chart_name, chart_type=chart_type)
    if not candidates:
        return None
    return deepcopy(candidates[-1])


def _portal_position_payload(position: Any, *, inferred_mobile_y: int = 0) -> dict[str, Any]:
    mobile_provided = bool(getattr(position, "mobile_provided", False))
    mobile_rows = int(getattr(position, "mobile_h", 8)) if mobile_provided else int(getattr(position, "pc_h", 8))
    return {
        "pc": {
            "x": int(getattr(position, "pc_x", 0)),
            "y": int(getattr(position, "pc_y", 0)),
            "cols": int(getattr(position, "pc_w", 12)),
            "rows": int(getattr(position, "pc_h", 8)),
        },
        "mobile": {
            "x": int(getattr(position, "mobile_x", 0)) if mobile_provided else 0,
            "y": int(getattr(position, "mobile_y", 0)) if mobile_provided else int(inferred_mobile_y),
            "cols": int(getattr(position, "mobile_w", 6)) if mobile_provided else 6,
            "rows": mobile_rows,
        },
    }


def _portal_component_position_public(
    source_type: Any,
    *,
    pc_x: int,
    pc_y: int,
    pc_row_height: int,
    mobile_y: int,
    layout_preset: str | None = None,
) -> tuple[dict[str, Any], int, int, int, int]:
    source_name = str(source_type or "").lower()
    if source_name == "filter":
        cols = 24
        rows = 2
    elif source_name == "grid":
        cols = 24
        rows = 4
    elif source_name == "text":
        cols = 24
        rows = 2
    elif source_name == "view":
        cols = 24
        rows = 11
    elif source_name == "link":
        cols = 12
        rows = 2
    else:
        if layout_preset == "dashboard_2col":
            cols = 12
        else:
            cols = 8
        rows = 7
    if cols == 24:
        if pc_x != 0:
            pc_y += pc_row_height
            pc_x = 0
            pc_row_height = 0
        position = {"pc": {"cols": 24, "rows": rows, "x": 0, "y": pc_y}, "mobile": {"cols": 6, "rows": rows, "x": 0, "y": mobile_y}}
        return position, 0, pc_y + rows, 0, mobile_y + rows
    if pc_x + cols > 24:
        pc_y += pc_row_height
        pc_x = 0
        pc_row_height = 0
    position = {"pc": {"cols": cols, "rows": rows, "x": pc_x, "y": pc_y}, "mobile": {"cols": 6, "rows": rows, "x": 0, "y": mobile_y}}
    next_pc_x = pc_x + cols
    next_row_height = max(pc_row_height, rows)
    next_pc_y = pc_y
    if next_pc_x >= 24:
        next_pc_y += next_row_height
        next_pc_x = 0
        next_row_height = 0
    return position, next_pc_x, next_pc_y, next_row_height, mobile_y + rows


def _empty_portal_layout_diagnostics() -> dict[str, Any]:
    return {
        "pc_grid_columns": 24,
        "mobile_grid_columns": 6,
        "section_count": 0,
        "explicit_position_count": 0,
        "max_pc_right": None,
        "standard_template_counts": {"metric_cards": 0, "bi_charts": 0, "views": 0},
        "safe_for_display": True,
        "warnings": [],
    }


def _portal_layout_diagnostics(
    sections: list[PortalSectionPatch],
    components: list[dict[str, Any]],
    *,
    layout_metadata: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    diagnostics = _empty_portal_layout_diagnostics()
    diagnostics["section_count"] = len(sections)
    explicit_count = sum(1 for section in sections if section.position is not None)
    diagnostics["explicit_position_count"] = explicit_count
    pc_positions: list[dict[str, Any]] = []
    warnings: list[dict[str, Any]] = []
    standard_counts = {"metric_cards": 0, "bi_charts": 0, "views": 0}
    has_business_grid = False
    for index, component in enumerate(components):
        if not isinstance(component, dict):
            continue
        position = component.get("position") if isinstance(component.get("position"), dict) else {}
        pc = position.get("pc") if isinstance(position.get("pc"), dict) else {}
        if pc:
            pc_positions.append(pc)
        section = sections[index] if index < len(sections) else None
        metadata = layout_metadata[index] if isinstance(layout_metadata, list) and index < len(layout_metadata) and isinstance(layout_metadata[index], dict) else {}
        source_type = str(getattr(section, "source_type", "") or "").lower() if section is not None else ""
        title = str(getattr(section, "title", "") or "").strip() if section is not None else None
        cols = int(pc.get("cols") or 0)
        rows = int(pc.get("rows") or 0)
        chart_type = str(metadata.get("chart_type") or "").strip().lower()
        role = str(metadata.get("role") or getattr(section, "role", "") or "").strip().lower() if section is not None else ""
        is_metric_chart = chart_type in {"target", "indicator"}
        if source_type == "chart" and (is_metric_chart or role in {"metric", "metrics", "indicator", "kpi"}):
            standard_counts["metric_cards"] += 1
        elif source_type == "chart":
            standard_counts["bi_charts"] += 1
        elif source_type == "view":
            standard_counts["views"] += 1
        min_chart_cols = 6 if is_metric_chart else 8
        min_chart_rows = 5 if is_metric_chart else 7
        if source_type == "grid":
            has_business_grid = True
            grid_config = component.get("gridConfig") if isinstance(component.get("gridConfig"), dict) else {}
            grid_items = grid_config.get("items") if isinstance(grid_config, dict) else None
            if not isinstance(grid_items, list) or not grid_items:
                warnings.append(_warning(
                    "PORTAL_GRID_ITEMS_EMPTY",
                    "grid portal section has no config.items; frontend will show an empty entry container",
                    section_index=index,
                    title=title,
                    fix_hint="Pass config.items with entries such as {type:1,jumpMode:1,linkAppKey,linkFormType,title}.",
                ))
        if source_type == "chart" and (cols < min_chart_cols or rows < min_chart_rows):
            warnings.append(_warning(
                "PORTAL_CHART_CARD_TOO_SMALL",
                (
                    "metric chart portal card is too small; use at least pc.cols >= 6 and pc.rows >= 5"
                    if is_metric_chart
                    else "chart portal card is too small; use at least pc.cols >= 8 and pc.rows >= 7 for non-metric charts"
                ),
                section_index=index,
                title=title,
                chart_type=chart_type or None,
                pc=deepcopy(pc),
            ))
        if source_type == "chart" and role in {"metric", "metrics", "indicator", "kpi"} and not is_metric_chart:
            warnings.append(_warning(
                "PORTAL_METRIC_SECTION_CHART_TYPE_MISMATCH",
                "metric portal section must reference a target/indicator chart; use section.chart to create the missing metric chart while assembling the portal",
                section_index=index,
                title=title,
                role=role,
                chart_type=chart_type or None,
                fix_hint="Use section.chart with chart_type=target or indicator and a metric expression, or change role away from metric.",
            ))
        if section is not None and section.position is not None and not bool(getattr(section.position, "mobile_provided", False)):
            warnings.append(_warning(
                "PORTAL_MOBILE_POSITION_MISSING",
                "pc position was provided without mobile position; mobile layout was generated with 6-column grid",
                section_index=index,
                title=title,
            ))
    max_right = None
    if pc_positions:
        max_right = max(int(position.get("x") or 0) + int(position.get("cols") or 0) for position in pc_positions)
    diagnostics["max_pc_right"] = max_right
    if len(pc_positions) > 1 and max_right is not None and max_right <= 12:
        warnings.append(_warning(
            "PORTAL_LAYOUT_HALF_WIDTH",
            "portal components only occupy the left half of the 24-column pc grid; this looks like a 12-column layout was used",
            max_pc_right=max_right,
            fix_hint="Use x=0/12 with cols=12 for two columns, x=0/8/16 with cols=8 for three columns, or omit position/use layout_preset.",
        ))
    standard_categories_present = sum(1 for count in standard_counts.values() if int(count or 0) > 0)
    _append_portal_standard_count_warnings(
        warnings=warnings,
        standard_counts=standard_counts,
        require_complete_standard=has_business_grid or standard_categories_present == 3,
    )
    diagnostics["standard_template_counts"] = standard_counts
    diagnostics["warnings"] = warnings
    diagnostics["safe_for_display"] = not any(
        item.get("code") in {
            "PORTAL_LAYOUT_HALF_WIDTH",
            "PORTAL_CHART_CARD_TOO_SMALL",
            "PORTAL_GRID_ITEMS_EMPTY",
            "PORTAL_METRIC_SECTION_CHART_TYPE_MISMATCH",
            "PORTAL_STANDARD_METRIC_COUNT_OUT_OF_RANGE",
            "PORTAL_STANDARD_BI_COUNT_OUT_OF_RANGE",
            "PORTAL_STANDARD_VIEW_COUNT_OUT_OF_RANGE",
        }
        for item in warnings
    )
    return diagnostics


def _append_portal_standard_count_warnings(
    *,
    warnings: list[dict[str, Any]],
    standard_counts: dict[str, int],
    require_complete_standard: bool = False,
) -> None:
    metric_count = int(standard_counts.get("metric_cards") or 0)
    bi_count = int(standard_counts.get("bi_charts") or 0)
    view_count = int(standard_counts.get("views") or 0)
    if not require_complete_standard:
        return
    if (metric_count or require_complete_standard) and not 4 <= metric_count <= 6:
        warnings.append(_warning(
            "PORTAL_STANDARD_METRIC_COUNT_OUT_OF_RANGE",
            "standard portal metric area should contain 4-6 metric cards",
            actual_count=metric_count,
            expected_count="4-6",
            fix_hint="Use section.chart for missing target/indicator metric cards, or keep 4 metric cards in one row with pc.cols=6, pc.rows=5.",
        ))
    if (bi_count or require_complete_standard) and not 2 <= bi_count <= 3:
        warnings.append(_warning(
            "PORTAL_STANDARD_BI_COUNT_OUT_OF_RANGE",
            "standard portal BI area should contain 2-3 visualization charts",
            actual_count=bi_count,
            expected_count="2-3",
            fix_hint="Use two half-width charts or three one-third-width charts with pc.rows=7.",
        ))
    if (view_count or require_complete_standard) and not 1 <= view_count <= 2:
        warnings.append(_warning(
            "PORTAL_STANDARD_VIEW_COUNT_OUT_OF_RANGE",
            "standard portal data view area should contain 1-2 business views",
            actual_count=view_count,
            expected_count="1-2",
            fix_hint="Reference 1-2 business views and avoid default 全部数据 / 我的数据 views as the main portal table.",
        ))


def _portal_layout_warning_items(layout_diagnostics: dict[str, Any]) -> list[dict[str, Any]]:
    warnings = layout_diagnostics.get("warnings") if isinstance(layout_diagnostics, dict) else None
    return [deepcopy(item) for item in warnings if isinstance(item, dict)] if isinstance(warnings, list) else []


def _resolve_chart_reference(*, charts: QingbiReportTools, profile: str, ref: Any) -> dict[str, Any]:
    app_key = str(getattr(ref, "app_key", "") or "").strip()
    chart_id = str(getattr(ref, "chart_id", "") or "").strip()
    chart_name = str(getattr(ref, "chart_name", "") or "").strip()
    if chart_id and not app_key:
        return {"chart_id": chart_id, "chart_name": chart_name, "app_key": None, "chart_type": ""}
    items = charts.qingbi_report_list(profile=profile, app_key=app_key).get("items") or []
    if chart_id:
        for item in items:
            if not isinstance(item, dict):
                continue
            item_id = _extract_chart_identifier(item)
            item_name = str(item.get("chartName") or "").strip()
            if item_id == chart_id:
                return {"chart_id": item_id, "chart_name": item_name, "app_key": app_key, "chart_type": _chart_type_from_item(item)}
        raise ValueError(f"chart ref chart_id '{chart_id}' could not be resolved under app '{app_key}'")
    matches = _find_charts_by_name(items, chart_name=chart_name)
    if len(matches) > 1:
        raise ValueError(f"chart ref chart_name '{chart_name}' is ambiguous under app '{app_key}'; supply chart_id")
    if len(matches) == 1:
        item = matches[0]
        return {
            "chart_id": _extract_chart_identifier(item),
            "chart_name": str(item.get("chartName") or "").strip(),
            "app_key": app_key,
            "chart_type": _chart_type_from_item(item),
        }
    raise ValueError(f"chart ref could not be resolved under app '{app_key}'")


def _resolve_view_reference(*, facade: AiBuilderFacade, profile: str, ref: Any) -> dict[str, Any]:
    app_key = str(getattr(ref, "app_key", "") or "").strip()
    view_key = str(getattr(ref, "view_key", "") or "").strip()
    view_name = str(getattr(ref, "view_name", "") or "").strip()
    views, _ = facade._load_views_result(profile=profile, app_key=app_key, tolerate_404=False)
    if view_key:
        for item in views or []:
            if not isinstance(item, dict):
                continue
            item_key = _extract_view_key(item)
            item_name = _extract_view_name(item)
            if item_key == view_key:
                return {"view_key": item_key, "view_name": item_name, "app_key": app_key, "view_type": _normalize_portal_view_type(item)}
        raise ValueError(f"view ref view_key '{view_key}' could not be resolved under app '{app_key}'")
    matched_items = [
        item
        for item in (views or [])
        if isinstance(item, dict) and _extract_view_name(item) == view_name
    ]
    if len(matched_items) > 1:
        raise ValueError(f"view ref view_name '{view_name}' is ambiguous under app '{app_key}'; supply view_key")
    if len(matched_items) == 1:
        item = matched_items[0]
        return {
            "view_key": _extract_view_key(item),
            "view_name": _extract_view_name(item),
            "app_key": app_key,
            "view_type": _normalize_portal_view_type(item),
        }
    raise ValueError(f"view ref could not be resolved under app '{app_key}'")


def _verify_portal_readback(
    *,
    actual: Any,
    expected_payload: dict[str, Any],
    expected_visibility: dict[str, Any] | None,
    expected_section_count: int | None,
    requested_config_keys: set[str],
    verify_dash_name: bool,
    verify_dash_icon: bool,
    verify_auth: bool,
    verify_hide_copyright: bool,
    verify_dash_global_config: bool,
    verify_tags: bool,
) -> tuple[bool, list[str]]:
    mismatches: list[str] = []
    if not isinstance(actual, dict):
        return False, ["portal readback payload is unavailable"]
    components = actual.get("components")
    if expected_section_count is not None and (not isinstance(components, list) or len(components) != expected_section_count):
        mismatches.append(f"components expected {expected_section_count}, got {len(components) if isinstance(components, list) else 'unavailable'}")
    if verify_dash_name and str(actual.get("dashName") or "").strip() != str(expected_payload.get("dashName") or "").strip():
        mismatches.append("dash_name")
    if verify_dash_icon and str(actual.get("dashIcon") or "") != str(expected_payload.get("dashIcon") or ""):
        mismatches.append("dash_icon")
    if verify_auth:
        if expected_visibility is not None:
            actual_visibility = _public_visibility_from_member_auth(actual.get("auth"))
            if not _visibility_matches_expected(actual_visibility, expected_visibility):
                mismatches.append("auth")
        elif not _mapping_contains(actual.get("auth"), expected_payload.get("auth")):
            mismatches.append("auth")
    if verify_hide_copyright and bool(actual.get("hideCopyright", False)) != bool(expected_payload.get("hideCopyright", False)):
        mismatches.append("hide_copyright")
    if verify_dash_global_config and not _mapping_contains(actual.get("dashGlobalConfig") or {}, expected_payload.get("dashGlobalConfig") or {}):
        mismatches.append("dash_global_config")
    if verify_tags:
        expected_tags = [
            _coerce_positive_int((item or {}).get("tagId"))
            for item in (expected_payload.get("tags") or [])
            if isinstance(item, dict)
        ]
        actual_tags = [
            _coerce_positive_int((item or {}).get("tagId"))
            for item in (actual.get("tags") or [])
            if isinstance(item, dict)
        ]
        if [item for item in expected_tags if item is not None] != [item for item in actual_tags if item is not None]:
            mismatches.append("tags")
    for key in sorted(requested_config_keys):
        if deepcopy(actual.get(key)) != deepcopy(expected_payload.get(key)):
            mismatches.append(f"config.{key}")
    return not mismatches, mismatches


def _empty_public_visibility_selectors(*, include_sub_departs: bool | None = None) -> dict[str, Any]:
    payload = {
        "member_uids": [],
        "member_emails": [],
        "member_names": [],
        "dept_ids": [],
        "dept_names": [],
        "role_ids": [],
        "role_names": [],
    }
    if include_sub_departs is not None:
        payload["include_sub_departs"] = bool(include_sub_departs)
    return payload


def _empty_public_external_visibility_selectors() -> dict[str, Any]:
    return {
        "member_ids": [],
        "member_emails": [],
        "dept_ids": [],
    }


def _public_visibility_from_member_auth(raw_auth: Any) -> dict[str, Any]:
    auth = raw_auth if isinstance(raw_auth, dict) and raw_auth else default_member_auth()
    root_type = str(auth.get("type") or "").strip().upper()
    contact_auth = auth.get("contactAuth") if isinstance(auth.get("contactAuth"), dict) else {}
    external_auth = auth.get("externalMemberAuth") if isinstance(auth.get("externalMemberAuth"), dict) else {}
    contact_members = contact_auth.get("authMembers") if isinstance(contact_auth.get("authMembers"), dict) else {}
    external_members = external_auth.get("authMembers") if isinstance(external_auth.get("authMembers"), dict) else {}

    selectors = _empty_public_visibility_selectors(include_sub_departs=contact_members.get("includeSubDeparts"))
    for item in contact_members.get("member") or []:
        if not isinstance(item, dict):
            continue
        uid = _coerce_positive_int(item.get("uid") or item.get("id"))
        if uid is not None:
            selectors["member_uids"].append(uid)
        email = str(item.get("email") or "").strip()
        if email:
            selectors["member_emails"].append(email)
        name = str(item.get("nickName") or item.get("remark") or item.get("name") or "").strip()
        if name:
            selectors["member_names"].append(name)
    for item in contact_members.get("depart") or []:
        if not isinstance(item, dict):
            continue
        dept_id = _coerce_positive_int(item.get("deptId") or item.get("id"))
        if dept_id is not None:
            selectors["dept_ids"].append(dept_id)
        dept_name = str(item.get("deptName") or item.get("departName") or item.get("name") or "").strip()
        if dept_name:
            selectors["dept_names"].append(dept_name)
    for item in contact_members.get("role") or []:
        if not isinstance(item, dict):
            continue
        role_id = _coerce_positive_int(item.get("roleId") or item.get("id"))
        if role_id is not None:
            selectors["role_ids"].append(role_id)
        role_name = str(item.get("roleName") or item.get("name") or "").strip()
        if role_name:
            selectors["role_names"].append(role_name)

    external_selectors = _empty_public_external_visibility_selectors()
    for item in external_members.get("member") or []:
        if not isinstance(item, dict):
            continue
        member_id = _coerce_positive_int(item.get("uid") or item.get("memberId") or item.get("id"))
        if member_id is not None:
            external_selectors["member_ids"].append(member_id)
        member_email = str(item.get("email") or "").strip()
        if member_email:
            external_selectors["member_emails"].append(member_email)
    for item in external_members.get("depart") or []:
        if not isinstance(item, dict):
            continue
        dept_id = _coerce_positive_int(item.get("deptId") or item.get("id"))
        if dept_id is not None:
            external_selectors["dept_ids"].append(dept_id)

    if root_type == "ALL":
        mode = PublicVisibilityMode.everyone.value
        external_mode = PublicExternalVisibilityMode.workspace.value
    else:
        contact_type = str(contact_auth.get("type") or "").strip().upper()
        external_type = str(external_auth.get("type") or "").strip().upper()
        mode = PublicVisibilityMode.specific.value if contact_type == "SPECIFIC" else PublicVisibilityMode.workspace.value
        external_mode = {
            "WORKSPACE_ALL": PublicExternalVisibilityMode.workspace.value,
            "SPECIFIC": PublicExternalVisibilityMode.specific.value,
        }.get(external_type, PublicExternalVisibilityMode.not_.value)

    return {
        "mode": mode,
        "selectors": selectors,
        "external_mode": external_mode,
        "external_selectors": external_selectors,
    }


def _public_visibility_from_chart_visible_auth(raw_auth: Any) -> dict[str, Any]:
    auth = raw_auth if isinstance(raw_auth, dict) and raw_auth else qingbi_workspace_visible_auth()
    root_type = str(auth.get("type") or "").strip().lower()
    contact_auth = auth.get("contactAuth") if isinstance(auth.get("contactAuth"), dict) else {}
    external_auth = auth.get("externalMemberAuth") if isinstance(auth.get("externalMemberAuth"), dict) else {}
    contact_members = contact_auth.get("authMembers") if isinstance(contact_auth.get("authMembers"), dict) else {}
    external_members = external_auth.get("authMembers") if isinstance(external_auth.get("authMembers"), dict) else {}
    member_auth = {
        "type": "ALL" if root_type == "all" else "WORKSPACE",
        "contactAuth": {
            "type": "SPECIFIC" if str(contact_auth.get("type") or "").strip().lower() == "assign" else "WORKSPACE_ALL",
            "authMembers": {
                "member": deepcopy(contact_members.get("member") or []),
                "depart": deepcopy(contact_members.get("depart") or []),
                "role": deepcopy(contact_members.get("role") or []),
                "dynamic": [],
                "includeSubDeparts": contact_members.get("includeSubDeparts"),
            },
        },
        "externalMemberAuth": {
            "type": {
                "assign": "SPECIFIC",
                "all": "WORKSPACE_ALL",
            }.get(str(external_auth.get("type") or "").strip().lower(), "NOT"),
            "authMembers": {
                "member": deepcopy(external_members.get("member") or []),
                "depart": deepcopy(external_members.get("depart") or []),
                "role": [],
                "dynamic": [],
                "includeSubDeparts": None,
            },
        },
    }
    return _public_visibility_from_member_auth(member_auth)


def _visibility_summary(visibility: dict[str, Any]) -> dict[str, Any]:
    if not isinstance(visibility, dict):
        return {}
    return {
        "mode": str(visibility.get("mode") or PublicVisibilityMode.workspace.value),
        "external_mode": str(visibility.get("external_mode") or PublicExternalVisibilityMode.not_.value),
    }


def _visibility_matches_expected(actual: Any, expected: Any) -> bool:
    if not isinstance(actual, dict) or not isinstance(expected, dict):
        return False
    if str(actual.get("mode") or "") != str(expected.get("mode") or ""):
        return False
    if str(actual.get("external_mode") or "") != str(expected.get("external_mode") or ""):
        return False

    actual_selectors = actual.get("selectors") if isinstance(actual.get("selectors"), dict) else {}
    expected_selectors = expected.get("selectors") if isinstance(expected.get("selectors"), dict) else {}
    actual_external = actual.get("external_selectors") if isinstance(actual.get("external_selectors"), dict) else {}
    expected_external = expected.get("external_selectors") if isinstance(expected.get("external_selectors"), dict) else {}

    def sorted_values(source: dict[str, Any], key: str) -> list[str]:
        return sorted(str(item) for item in (source.get(key) or []) if item is not None and str(item).strip())

    for key in ("member_uids", "dept_ids", "role_ids"):
        if sorted_values(actual_selectors, key) != sorted_values(expected_selectors, key):
            return False
    for key in ("member_ids", "dept_ids"):
        if sorted_values(actual_external, key) != sorted_values(expected_external, key):
            return False

    # Backends commonly enrich id-based selectors with names/emails on readback; require
    # caller-specified text selectors only when no id selector anchors that subject.
    text_selector_pairs = (
        (actual_selectors, expected_selectors, "member_uids", "member_emails"),
        (actual_selectors, expected_selectors, "member_uids", "member_names"),
        (actual_selectors, expected_selectors, "dept_ids", "dept_names"),
        (actual_selectors, expected_selectors, "role_ids", "role_names"),
        (actual_external, expected_external, "member_ids", "member_emails"),
    )
    for actual_group, expected_group, id_key, text_key in text_selector_pairs:
        if sorted_values(expected_group, id_key):
            continue
        expected_text = sorted_values(expected_group, text_key)
        if expected_text and sorted_values(actual_group, text_key) != expected_text:
            return False

    if (
        "include_sub_departs" in expected_selectors
        and expected_selectors.get("include_sub_departs") is not None
        and actual_selectors.get("include_sub_departs") != expected_selectors.get("include_sub_departs")
    ):
        return False
    return True


def _mapping_contains(actual: Any, expected: Any) -> bool:
    if isinstance(expected, dict):
        if not isinstance(actual, dict):
            return False
        for key, expected_value in expected.items():
            if key not in actual:
                return False
            if not _mapping_contains(actual.get(key), expected_value):
                return False
        return True
    if isinstance(expected, list):
        if not isinstance(actual, list) or len(actual) < len(expected):
            return False
        return all(_mapping_contains(actual[index], expected[index]) for index in range(len(expected)))
    return deepcopy(actual) == deepcopy(expected)


def _normalize_portal_view_type(view: dict[str, Any]) -> str:
    raw = str(view.get("viewgraphType") or view.get("type") or "").strip()
    mapping = {
        "table": "tableView",
        "tableView": "tableView",
        "card": "cardView",
        "cardView": "cardView",
        "board": "boardView",
        "boardView": "boardView",
        "gantt": "ganttView",
        "ganttView": "ganttView",
        "hierarchy": "hierarchyView",
        "hierarchyView": "hierarchyView",
    }
    return mapping.get(raw, "tableView")


def _is_view_collection_shape(values: Any) -> bool:
    if isinstance(values, list):
        return True
    if isinstance(values, dict):
        return any(isinstance(values.get(key), list) for key in ("list", "viewList", "views", "result"))
    return False


def _extract_view_name(view: dict[str, Any]) -> str:
    return str(view.get("viewgraphName") or view.get("viewName") or view.get("title") or "").strip()


def _extract_view_key(view: dict[str, Any]) -> str:
    return str(view.get("viewgraphKey") or view.get("viewKey") or "").strip()


def _resolve_system_view_list_type(*, view_key: str | None, view_name: str | None) -> int | None:
    normalized_key = str(view_key or "").strip()
    if normalized_key in SYSTEM_VIEW_ID_TO_LIST_TYPE:
        return SYSTEM_VIEW_ID_TO_LIST_TYPE[normalized_key]
    numeric_key = _coerce_positive_int(normalized_key)
    if numeric_key is not None and numeric_key in RECORD_LIST_TYPE_LABELS:
        return numeric_key
    normalized_name = str(view_name or "").strip()
    if not normalized_name:
        return None
    if normalized_name == "全部数据":
        return 8
    if normalized_name == "我发起的":
        return 14
    if normalized_name == "待办":
        return 1
    if normalized_name == "已办":
        return 2
    if normalized_name in {"抄送", "抄送我的"}:
        return 12
    return None


def _build_apply_config_change_ques(
    *,
    current_que_base_infos: list[dict[str, Any]],
    schema: dict[str, Any],
    visible_field_names: list[str],
) -> list[dict[str, Any]]:
    parsed_schema = _parse_schema(schema)
    fields_by_name = {
        str(field.get("name") or ""): field
        for field in parsed_schema.get("fields", [])
        if isinstance(field, dict) and str(field.get("name") or "")
    }
    current_items: list[dict[str, Any]] = [item for item in current_que_base_infos if isinstance(item, dict)]
    current_by_que_id = {
        _coerce_positive_int(item.get("queId")): item
        for item in current_items
        if _coerce_positive_int(item.get("queId")) is not None
    }
    ordered_visible_que_ids: list[int] = []
    for field_name in visible_field_names:
        field = fields_by_name.get(str(field_name or "").strip())
        que_id = _coerce_positive_int(field.get("que_id")) if isinstance(field, dict) else None
        if que_id is not None and que_id not in ordered_visible_que_ids:
            ordered_visible_que_ids.append(que_id)
    selected_que_ids = set(ordered_visible_que_ids)
    ordered_que_ids: list[int] = list(ordered_visible_que_ids)
    for item in current_items:
        que_id = _coerce_positive_int(item.get("queId"))
        if que_id is not None and que_id not in selected_que_ids and que_id not in ordered_que_ids:
            ordered_que_ids.append(que_id)
    for field in parsed_schema.get("fields", []):
        if not isinstance(field, dict):
            continue
        que_id = _coerce_positive_int(field.get("que_id"))
        if que_id is not None and que_id not in ordered_que_ids:
            ordered_que_ids.append(que_id)
    return [
        _normalize_apply_change_que(
            current_by_que_id.get(que_id),
            fallback_field=next(
                (
                    field
                    for field in parsed_schema.get("fields", [])
                    if isinstance(field, dict) and _coerce_positive_int(field.get("que_id")) == que_id
                ),
                None,
            ),
            visible=que_id in selected_que_ids,
        )
        for que_id in ordered_que_ids
    ]


def _normalize_apply_change_que(
    item: dict[str, Any] | None,
    *,
    fallback_field: dict[str, Any] | None,
    visible: bool,
) -> dict[str, Any]:
    que_id = _coerce_positive_int((item or {}).get("queId"))
    if que_id is None and isinstance(fallback_field, dict):
        que_id = _coerce_positive_int(fallback_field.get("que_id"))
    payload: dict[str, Any] = {
        "queId": que_id,
        "beingVisible": visible,
    }
    width = _coerce_positive_int((item or {}).get("width"))
    if width is not None:
        payload["width"] = width
    current_sub_ques = (item or {}).get("subQues")
    if isinstance(current_sub_ques, list):
        payload["subQues"] = [
            _normalize_apply_sub_change_que(sub_item, visible=visible)
            for sub_item in current_sub_ques
            if isinstance(sub_item, dict)
        ]
    elif isinstance(fallback_field, dict) and isinstance(fallback_field.get("subfields"), list):
        payload["subQues"] = [
            {
                "queId": _coerce_positive_int(subfield.get("que_id")),
                "beingVisible": visible,
            }
            for subfield in fallback_field.get("subfields", [])
            if isinstance(subfield, dict) and _coerce_positive_int(subfield.get("que_id")) is not None
        ]
    return payload


def _normalize_apply_sub_change_que(item: dict[str, Any], *, visible: bool) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "queId": _coerce_positive_int(item.get("queId")),
        "beingVisible": visible and bool(item.get("beingVisible", True)),
    }
    width = _coerce_positive_int(item.get("width"))
    if width is not None:
        payload["width"] = width
    return payload


def _extract_apply_visible_field_names(que_base_infos: Any) -> list[str]:
    if not isinstance(que_base_infos, list):
        return []
    visible_names: list[str] = []
    for item in que_base_infos:
        if not isinstance(item, dict):
            continue
        if not bool(item.get("beingVisible", True)):
            continue
        name = str(item.get("queTitle") or "").strip()
        if name:
            visible_names.append(name)
    return visible_names


def _empty_schema_result(title: str) -> dict[str, Any]:
    return {
        "formTitle": title,
        "editVersionNo": 1,
        "formQues": [],
        "questionRelations": [],
    }


def _schema_has_layout_content(schema: dict[str, Any]) -> bool:
    form_ques = schema.get("formQues")
    if not isinstance(form_ques, list):
        return False
    for row in form_ques:
        if isinstance(row, list) and row:
            return True
    return False


def _count_relation_fields(fields: list[dict[str, Any]]) -> int:
    count = 0
    for field in fields:
        if not isinstance(field, dict):
            continue
        if field.get("type") == FieldType.relation.value:
            count += 1
        subfields = field.get("subfields")
        if isinstance(subfields, list):
            count += _count_relation_fields(cast(list[dict[str, Any]], subfields))
    return count


def _resolve_relation_target_field(
    *,
    target_fields: list[dict[str, Any]],
    selector_payload: dict[str, Any] | None,
    fallback_name: str | None = None,
) -> dict[str, Any]:
    if selector_payload is None:
        if fallback_name:
            fallback = next((field for field in target_fields if str(field.get("name") or "") == fallback_name), None)
            if fallback is not None:
                return fallback
        if target_fields:
            return target_fields[0]
        raise ValueError("target relation app has no usable fields")
    selector = FieldSelector.model_validate(selector_payload)
    match_index = _resolve_selector(_build_selector_map(target_fields), selector)
    if match_index is None:
        raise ValueError(
            f"relation selector could not be resolved in target app: {selector.model_dump(mode='json', exclude_none=True)}"
        )
    return target_fields[match_index]


def _relation_mode_from_optional_data_num(value: Any) -> str:
    return PublicRelationMode.multiple.value if _relation_mode_to_optional_data_num(value) == 0 else PublicRelationMode.single.value


def _relation_mode_to_optional_data_num(value: Any) -> int:
    if isinstance(value, bool):
        return 0 if value else 1
    if isinstance(value, int):
        return 0 if value == 0 else 1
    if isinstance(value, str):
        normalized = value.strip().lower()
        if normalized in {
            PublicRelationMode.multiple.value,
            "multi",
            "multi_select",
            "multi-select",
            "many",
            "0",
        }:
            return 0
    return 1


def _normalize_relation_mode(value: Any) -> str:
    return _relation_mode_from_optional_data_num(value)


def _normalize_department_scope_mode(value: Any) -> str | None:
    if value is None:
        return None
    if isinstance(value, int):
        if value == 1:
            return PublicDepartmentScopeMode.all.value
        if value == 2:
            return PublicDepartmentScopeMode.custom.value
        return None
    normalized = str(value).strip().lower()
    if normalized in {"all", "workspace_all", "workspace-all", "default", "default_all", "default-all", "1"}:
        return PublicDepartmentScopeMode.all.value
    if normalized in {"custom", "explicit", "selected", "2"}:
        return PublicDepartmentScopeMode.custom.value
    return normalized or None


def _normalize_department_scope(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    raw_departments = value.get("departments", value.get("depart", value.get("departs")))
    departments: list[dict[str, Any]] = []
    if isinstance(raw_departments, list):
        for item in raw_departments:
            if isinstance(item, dict):
                dept_id = _coerce_positive_int(item.get("dept_id", item.get("deptId", item.get("id"))))
                dept_name = str(
                    item.get("dept_name", item.get("deptName", item.get("name", item.get("value")))) or ""
                ).strip() or None
            else:
                dept_id = _coerce_positive_int(item)
                dept_name = None
            if dept_id is None and dept_name is None:
                continue
            entry: dict[str, Any] = {}
            if dept_id is not None:
                entry["dept_id"] = dept_id
            if dept_name is not None:
                entry["dept_name"] = dept_name
            departments.append(entry)
    normalized_mode = _normalize_department_scope_mode(value.get("mode"))
    if normalized_mode is None:
        normalized_mode = PublicDepartmentScopeMode.custom.value if departments else PublicDepartmentScopeMode.all.value
    return {
        "mode": normalized_mode,
        "departments": departments,
        "include_sub_departs": (
            None
            if value.get("include_sub_departs", value.get("includeSubDeparts")) is None
            else bool(value.get("include_sub_departs", value.get("includeSubDeparts")))
        ),
    }


def _normalize_department_scope_from_question(question: dict[str, Any]) -> dict[str, Any] | None:
    scope_type = _coerce_positive_int(question.get("deptSelectScopeType"))
    scope = question.get("deptSelectScope") if isinstance(question.get("deptSelectScope"), dict) else {}
    if not isinstance(scope, dict):
        scope = {}
    if isinstance(scope.get("dynamic"), list) and scope.get("dynamic"):
        return None
    if isinstance(scope.get("externalDepartList"), list) and scope.get("externalDepartList"):
        return None
    if scope_type == 1:
        return {
            "mode": PublicDepartmentScopeMode.all.value,
            "departments": [],
            "include_sub_departs": None
            if scope.get("includeSubDeparts") is None
            else bool(scope.get("includeSubDeparts")),
        }
    if scope_type == 2:
        departments: list[dict[str, Any]] = []
        for item in cast(list[Any], scope.get("depart") or []):
            if not isinstance(item, dict):
                continue
            dept_id = _coerce_positive_int(item.get("deptId", item.get("id")))
            dept_name = str(item.get("deptName", item.get("name", item.get("value"))) or "").strip() or None
            if dept_id is None and dept_name is None:
                continue
            entry: dict[str, Any] = {}
            if dept_id is not None:
                entry["dept_id"] = dept_id
            if dept_name is not None:
                entry["dept_name"] = dept_name
            departments.append(entry)
        return {
            "mode": PublicDepartmentScopeMode.custom.value,
            "departments": departments,
            "include_sub_departs": None
            if scope.get("includeSubDeparts") is None
            else bool(scope.get("includeSubDeparts")),
        }
    return None


def _serialize_department_scope_for_question(value: Any) -> tuple[int, dict[str, Any]]:
    normalized = _normalize_department_scope(value)
    if normalized is None or normalized.get("mode") == PublicDepartmentScopeMode.all.value:
        scope: dict[str, Any] = {"depart": [], "dynamic": []}
        if normalized is not None and normalized.get("include_sub_departs") is not None:
            scope["includeSubDeparts"] = bool(normalized.get("include_sub_departs"))
        return 1, scope
    departments = []
    for item in cast(list[Any], normalized.get("departments") or []):
        if not isinstance(item, dict):
            continue
        dept_id = _coerce_positive_int(item.get("dept_id"))
        dept_name = str(item.get("dept_name") or "").strip() or None
        if dept_id is None and dept_name is None:
            continue
        entry: dict[str, Any] = {}
        if dept_id is not None:
            entry["deptId"] = dept_id
        if dept_name is not None:
            entry["deptName"] = dept_name
        departments.append(entry)
    scope = {"depart": departments, "dynamic": []}
    if normalized.get("include_sub_departs") is not None:
        scope["includeSubDeparts"] = bool(normalized.get("include_sub_departs"))
    return 2, scope


def _department_scope_equal(left: Any, right: Any) -> bool:
    return _normalize_department_scope(left) == _normalize_department_scope(right)


def _is_relation_target_metadata_read_restricted_api_error(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    return backend_code_int(error) in {40002, 40027, 40161}


def _relation_target_field_matches(left: dict[str, Any], right: dict[str, Any]) -> bool:
    left_field_id = str(left.get("field_id") or "").strip()
    right_field_id = str(right.get("field_id") or "").strip()
    left_que_id = _coerce_positive_int(left.get("que_id"))
    right_que_id = _coerce_positive_int(right.get("que_id"))
    left_name = str(left.get("name") or "").strip()
    right_name = str(right.get("name") or "").strip()
    return (
        (left_field_id and right_field_id and left_field_id == right_field_id)
        or (left_que_id is not None and right_que_id is not None and left_que_id == right_que_id)
        or (left_name and right_name and left_name == right_name)
    )


def _normalize_relation_target_stub(
    selector_payload: dict[str, Any] | None,
    *,
    fallback_name: str | None = None,
) -> dict[str, Any]:
    payload = selector_payload if isinstance(selector_payload, dict) else {}
    name = str(payload.get("name") or fallback_name or "").strip() or None
    if name is None:
        raise ValueError("relation target selector requires a field name when target app metadata cannot be read")
    return {
        "field_id": str(payload.get("field_id") or "").strip() or None,
        "que_id": 0,
        "name": name,
        "type": str(payload.get("type") or "").strip() or None,
    }


def _apply_relation_target_selection(
    *,
    field: dict[str, Any],
    config: dict[str, Any],
    display_field: dict[str, Any],
    visible_fields: list[dict[str, Any]],
) -> None:
    normalized_visible = [deepcopy(item) for item in visible_fields if isinstance(item, dict)]
    if not normalized_visible:
        normalized_visible = [deepcopy(display_field)]
    elif not any(_relation_target_field_matches(item, display_field) for item in normalized_visible):
        normalized_visible = [deepcopy(display_field), *normalized_visible]
    config["target_field_label"] = display_field.get("name")
    config["target_field_type"] = display_field.get("type")
    config["target_field_que_id"] = _coerce_positive_int(display_field.get("que_id")) or 0
    config["refer_field_ids"] = [item.get("field_id") or item.get("name") for item in normalized_visible]
    config["refer_field_que_ids"] = [_coerce_positive_int(item.get("que_id")) or 0 for item in normalized_visible]
    config["refer_field_labels"] = [item.get("name") for item in normalized_visible]
    config["refer_field_types"] = [item.get("type") for item in normalized_visible]
    config["auth_field_ids"] = [item.get("field_id") or item.get("name") for item in normalized_visible]
    config["auth_field_que_ids"] = [_coerce_positive_int(item.get("que_id")) or 0 for item in normalized_visible]
    config["refer_auth_ques"] = [
        {
            "queId": _coerce_positive_int(item.get("que_id")) or 0,
            "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH,
            "_field_id": item.get("field_id") or item.get("name"),
        }
        for item in normalized_visible
        if (_coerce_positive_int(item.get("que_id")) or 0) > 0
    ]
    config["field_name_show"] = bool(field.get("field_name_show", True))
    field["target_field_id"] = display_field.get("field_id") or display_field.get("name")
    field["target_field_que_id"] = _coerce_positive_int(display_field.get("que_id")) or 0
    field["config"] = config
    field["display_field"] = {
        "field_id": display_field.get("field_id"),
        "que_id": _coerce_positive_int(display_field.get("que_id")) or 0,
        "name": display_field.get("name"),
    }
    field["visible_fields"] = [
        {
            "field_id": item.get("field_id"),
            "que_id": _coerce_positive_int(item.get("que_id")) or 0,
            "name": item.get("name"),
        }
        for item in normalized_visible
    ]


def _verify_relation_readback_by_name(
    *,
    verified_fields: list[dict[str, Any]],
    degraded_expectations: list[dict[str, Any]],
) -> bool:
    if not degraded_expectations:
        return True
    verified_by_name = {
        str(field.get("name") or "").strip(): field
        for field in verified_fields
        if isinstance(field, dict) and str(field.get("name") or "").strip()
    }
    for expectation in degraded_expectations:
        field_name = str(expectation.get("field_name") or "").strip()
        actual = verified_by_name.get(field_name)
        if not isinstance(actual, dict):
            return False
        if str(actual.get("type") or "") != FieldType.relation.value:
            return False
        if str(actual.get("target_app_key") or "") != str(expectation.get("target_app_key") or ""):
            return False
        if _normalize_relation_mode(actual.get("relation_mode")) != _normalize_relation_mode(expectation.get("relation_mode")):
            return False
        expected_display_name = str((expectation.get("display_field") or {}).get("name") or "").strip()
        actual_display_name = str((actual.get("display_field") or {}).get("name") or "").strip()
        if expected_display_name != actual_display_name:
            return False
        expected_visible_names = [
            str(item.get("name") or "").strip()
            for item in cast(list[Any], expectation.get("visible_fields") or [])
            if isinstance(item, dict) and str(item.get("name") or "").strip()
        ]
        actual_visible_names = [
            str(item.get("name") or "").strip()
            for item in cast(list[Any], actual.get("visible_fields") or [])
            if isinstance(item, dict) and str(item.get("name") or "").strip()
        ]
        if not _relation_visible_field_names_match(
            expected_visible_names=expected_visible_names,
            actual_visible_names=actual_visible_names,
            display_field_name=expected_display_name,
        ):
            return False
    return True


def _relation_field_names(values: object) -> list[str]:
    names: list[str] = []
    if not isinstance(values, list):
        return names
    for item in values:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name") or "").strip()
        if name:
            names.append(name)
    return names


def _relation_visible_field_names_match(
    *,
    expected_visible_names: list[str],
    actual_visible_names: list[str],
    display_field_name: str | None = None,
) -> bool:
    if expected_visible_names == actual_visible_names:
        return True

    def dedupe(values: list[str]) -> list[str]:
        result: list[str] = []
        for value in values:
            normalized = str(value or "").strip()
            if normalized and normalized not in result:
                result.append(normalized)
        return result

    expected = dedupe(expected_visible_names)
    actual = dedupe(actual_visible_names)
    if expected == actual:
        return True
    if set(expected) != set(actual):
        return False
    display = str(display_field_name or "").strip()
    if display and actual and actual[0] == display and display in expected:
        expected_display_first = [display, *[name for name in expected if name != display]]
        return actual == expected_display_first
    return False


def _relation_field_public_selector(value: object) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    return {
        "name": str(value.get("name") or "").strip() or None,
        "que_id": _coerce_nonnegative_int(value.get("que_id")),
        "field_id": str(value.get("field_id") or "").strip() or None,
    }


def _schema_relation_readback_matrix(
    *,
    expected_fields: list[dict[str, Any]],
    verified_fields: list[dict[str, Any]],
    changed_field_names: set[str],
    degraded_expectations: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    degraded_by_name = {
        str(item.get("field_name") or "").strip(): item
        for item in degraded_expectations
        if isinstance(item, dict) and str(item.get("field_name") or "").strip()
    }
    relation_names = {
        str(field.get("name") or "").strip()
        for field in expected_fields
        if isinstance(field, dict)
        and str(field.get("type") or "") == FieldType.relation.value
        and str(field.get("name") or "").strip() in changed_field_names
    }
    relation_names.update(name for name in degraded_by_name if name)
    if not relation_names:
        return []

    expected_by_name = {
        str(field.get("name") or "").strip(): field
        for field in expected_fields
        if isinstance(field, dict)
        and str(field.get("type") or "") == FieldType.relation.value
        and str(field.get("name") or "").strip()
    }
    verified_by_name = {
        str(field.get("name") or "").strip(): field
        for field in verified_fields
        if isinstance(field, dict) and str(field.get("name") or "").strip()
    }
    rows: list[dict[str, Any]] = []
    for field_name in sorted(relation_names):
        expected = expected_by_name.get(field_name)
        actual = verified_by_name.get(field_name)
        degraded = degraded_by_name.get(field_name)
        expected_target = str((expected or degraded or {}).get("target_app_key") or "").strip() or None
        actual_target = str((actual or {}).get("target_app_key") or "").strip() or None
        expected_mode = _normalize_relation_mode((expected or degraded or {}).get("relation_mode"))
        actual_mode = _normalize_relation_mode((actual or {}).get("relation_mode"))
        expected_display = _relation_field_public_selector((expected or degraded or {}).get("display_field"))
        actual_display = _relation_field_public_selector((actual or {}).get("display_field"))
        expected_visible_names = _relation_field_names((expected or degraded or {}).get("visible_fields"))
        actual_visible_names = _relation_field_names((actual or {}).get("visible_fields"))
        display_name = str((expected_display or {}).get("name") or "").strip() or None

        checks = {
            "field_exists": isinstance(actual, dict),
            "target_app_key": expected_target == actual_target,
            "relation_mode": expected_mode == actual_mode,
            "display_field": (expected_display or {}).get("name") == (actual_display or {}).get("name"),
            "visible_fields": _relation_visible_field_names_match(
                expected_visible_names=expected_visible_names,
                actual_visible_names=actual_visible_names,
                display_field_name=display_name,
            ),
        }
        readback_verified = all(checks.values())
        if not isinstance(actual, dict):
            status = "missing"
        elif readback_verified and degraded is not None:
            status = "matched_by_name"
        elif readback_verified:
            status = "matched"
        else:
            status = "mismatch"
        rows.append(
            {
                "field_name": field_name,
                "readback_status": status,
                "readback_verified": readback_verified,
                "metadata_verified": degraded is None,
                "checks": checks,
                "expected": {
                    "target_app_key": expected_target,
                    "relation_mode": expected_mode,
                    "display_field": expected_display,
                    "visible_fields": expected_visible_names,
                },
                "actual": {
                    "target_app_key": actual_target,
                    "relation_mode": actual_mode,
                    "display_field": actual_display,
                    "visible_fields": actual_visible_names,
                },
                "data_impact": (
                    "none_detected"
                    if readback_verified
                    else "relation config mismatch can affect existing referenced values; inspect existing records before changing target_app_key or display fields"
                ),
            }
        )
    return rows


def _schema_relation_repair_plan(relation_readback_matrix: list[dict[str, Any]]) -> list[dict[str, Any]]:
    plan: list[dict[str, Any]] = []
    for row in relation_readback_matrix:
        if bool(row.get("readback_verified")):
            continue
        expected = row.get("expected") if isinstance(row.get("expected"), dict) else {}
        plan.append(
            {
                "field_name": row.get("field_name"),
                "mode": "update_fields_relation_patch",
                "next_action": "Use app_schema_apply update_fields with selector.name and set target_app_key/relation_mode/display_field/visible_fields, then read back relation_readback_matrix again.",
                "suggested_patch": {
                    "selector": {"name": row.get("field_name")},
                    "set": {
                        "target_app_key": expected.get("target_app_key"),
                        "relation_mode": expected.get("relation_mode"),
                        "display_field": expected.get("display_field"),
                        "visible_fields": [{"name": name} for name in cast(list[Any], expected.get("visible_fields") or [])],
                    },
                },
                "data_impact": row.get("data_impact"),
            }
        )
    return plan


def _relation_target_metadata_skip_outcome(*, degraded_entries: list[dict[str, Any]]) -> PermissionCheckOutcome | None:
    if not degraded_entries:
        return None
    first = degraded_entries[0]
    transport_error = first.get("transport_error") if isinstance(first.get("transport_error"), dict) else None
    outcome = _permission_skip_outcome(
        scope="relation_target_metadata",
        target={
            "app_key": first.get("target_app_key"),
            "field_name": first.get("field_name"),
        },
        required_permission="read_schema",
        transport_error=deepcopy(transport_error) if transport_error is not None else None,
    )
    outcome.details["relation_target_metadata_unverified"] = True
    outcome.verification["relation_target_metadata_verified"] = False
    outcome.warnings.append(
        _warning(
            "RELATION_TARGET_METADATA_UNVERIFIED",
            "relation target metadata could not be verified before schema apply; writing using explicit field names",
            field_name=first.get("field_name"),
            target_app_key=first.get("target_app_key"),
        )
    )
    return outcome


def _hydrate_relation_field_configs(
    facade: "AiBuilderFacade",
    *,
    profile: str,
    fields: list[dict[str, Any]],
) -> RelationHydrationResult:
    resolved_fields = deepcopy(fields)
    target_field_cache: dict[str, list[dict[str, Any]]] = {}
    degraded_entries: list[dict[str, Any]] = []
    for field in resolved_fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.relation.value:
            continue
        if not bool(field.get("_relation_config_explicit")) and isinstance(field.get("_reference_config_template"), dict):
            continue
        target_app_key = str(field.get("target_app_key") or "").strip()
        if not target_app_key:
            continue
        config = deepcopy(field.get("config") or {})
        relation_mode = _normalize_relation_mode(field.get("relation_mode", config.get("optional_data_num")))
        config["optional_data_num"] = _relation_mode_to_optional_data_num(relation_mode)
        field["relation_mode"] = relation_mode
        field["config"] = config
        display_selector = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
        visible_selector_payloads = [item for item in cast(list[Any], field.get("visible_fields") or []) if isinstance(item, dict)]
        if display_selector is None:
            raise ValueError("relation field requires display_field")
        if not visible_selector_payloads:
            raise ValueError("relation field requires visible_fields")
        target_fields = target_field_cache.get(target_app_key)
        if target_fields is None:
            try:
                target_schema, _ = facade._read_schema_with_fallback(profile=profile, app_key=target_app_key)
                target_fields = _parse_schema(target_schema)["fields"]
                target_field_cache[target_app_key] = target_fields
            except (QingflowApiError, RuntimeError) as error:
                api_error = _coerce_api_error(error)
                if not _is_relation_target_metadata_read_restricted_api_error(api_error):
                    raise
                display_field = _normalize_relation_target_stub(
                    display_selector,
                    fallback_name=str((display_selector or {}).get("name") or "").strip() or None,
                )
                visible_fields = [
                    _normalize_relation_target_stub(item, fallback_name=str(item.get("name") or "").strip() or None)
                    for item in visible_selector_payloads
                ]
                _apply_relation_target_selection(
                    field=field,
                    config=config,
                    display_field=display_field,
                    visible_fields=visible_fields,
                )
                degraded_entries.append(
                    {
                        "field_name": field.get("name"),
                        "target_app_key": target_app_key,
                        "display_field": deepcopy(field.get("display_field") or {}),
                        "visible_fields": deepcopy(field.get("visible_fields") or []),
                        "relation_mode": field.get("relation_mode"),
                        "transport_error": _transport_error_payload(api_error),
                    }
                )
                continue
        if not target_fields:
            display_field = _normalize_relation_target_stub(
                display_selector,
                fallback_name=str((display_selector or {}).get("name") or "").strip() or None,
            )
            visible_fields = [
                _normalize_relation_target_stub(item, fallback_name=str(item.get("name") or "").strip() or None)
                for item in visible_selector_payloads
            ]
            _apply_relation_target_selection(
                field=field,
                config=config,
                display_field=display_field,
                visible_fields=visible_fields,
            )
            degraded_entries.append(
                {
                    "field_name": field.get("name"),
                    "target_app_key": target_app_key,
                    "display_field": deepcopy(field.get("display_field") or {}),
                    "visible_fields": deepcopy(field.get("visible_fields") or []),
                    "relation_mode": field.get("relation_mode"),
                    "transport_error": None,
                }
            )
            continue
        display_field = _resolve_relation_target_field(
            target_fields=target_fields,
            selector_payload=display_selector,
            fallback_name=str((display_selector or {}).get("name") or "").strip() or None,
        )
        visible_fields = [
            _resolve_relation_target_field(target_fields=target_fields, selector_payload=item)
            for item in visible_selector_payloads
        ]
        _apply_relation_target_selection(
            field=field,
            config=config,
            display_field=display_field,
            visible_fields=visible_fields,
        )
    return RelationHydrationResult(
        fields=resolved_fields,
        permission_outcome=_relation_target_metadata_skip_outcome(degraded_entries=degraded_entries),
        degraded_expectations=degraded_entries,
    )


def _slugify(text: str, *, default: str) -> str:
    normalized = "".join(ch.lower() if ch.isalnum() else "_" for ch in str(text or ""))
    collapsed = "_".join(part for part in normalized.split("_") if part)
    return collapsed or default


def _infer_field_type(question: dict[str, Any]) -> str:
    que_type = _coerce_positive_int(question.get("queType")) or 0
    if que_type == 8 and question.get("canDecimal") is True:
        return FieldType.amount.value
    if que_type == 4:
        return FieldType.datetime.value if question.get("dateType") not in {0, None} else FieldType.date.value
    return QUESTION_TYPE_TO_FIELD_TYPE.get(que_type, FieldType.text.value)


def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None) -> dict[str, Any]:
    name = str(question.get("queTitle") or "").strip()
    que_id = _coerce_positive_int(question.get("queId"))
    que_type = _coerce_positive_int(question.get("queType"))
    field_type = _infer_field_type(question)
    field_id = field_id_hint or f"field_{que_id or _slugify(name, default='x')}"
    field: dict[str, Any] = {
        "field_id": field_id,
        "name": name,
        "type": field_type,
        "required": bool(question.get("required")),
        "description": question.get("queHint") or None,
        "options": [],
        "option_details": [],
        "target_app_key": None,
        "config": {},
        "subfields": [],
        "que_id": que_id,
        "que_type": que_type,
        "default_type": _coerce_positive_int(question.get("queDefaultType")) if "queDefaultType" in question else None,
    }
    if "queDefaultValue" in question:
        field["default_value"] = question.get("queDefaultValue")
    if field_type in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
        options = question.get("options")
        if isinstance(options, list):
            option_values: list[str] = []
            option_details: list[dict[str, Any]] = []
            for item in options:
                if not isinstance(item, dict) or not item.get("optValue"):
                    continue
                option_value = str(item.get("optValue") or "")
                option_values.append(option_value)
                option_details.append(
                    {
                        "id": item.get("optId"),
                        "value": option_value,
                    }
                )
            field["options"] = option_values
            field["option_details"] = option_details
    if field_type == FieldType.relation:
        reference = question.get("referenceConfig") if isinstance(question.get("referenceConfig"), dict) else {}
        field["target_app_key"] = reference.get("referAppKey")
        field["relation_mode"] = _relation_mode_from_optional_data_num(reference.get("optionalDataNum"))
        refer_questions = reference.get("referQuestions") if isinstance(reference.get("referQuestions"), list) else []
        refer_auth_questions = reference.get("referAuthQues") if isinstance(reference.get("referAuthQues"), list) else []
        refer_auth_by_que_id: dict[int, int] = {}
        for raw_item in refer_auth_questions:
            if not isinstance(raw_item, dict):
                continue
            que_id = _coerce_nonnegative_int(raw_item.get("queId"))
            que_auth = _coerce_nonnegative_int(raw_item.get("queAuth"))
            if que_id is None or que_auth is None or que_id in refer_auth_by_que_id:
                continue
            refer_auth_by_que_id[que_id] = que_auth
        visible_fields: list[dict[str, Any]] = []
        display_field_que_id = _coerce_nonnegative_int(reference.get("referQueId"))
        display_field_name: str | None = None
        for item in refer_questions:
            if not isinstance(item, dict):
                continue
            que_id = _coerce_nonnegative_int(item.get("queId"))
            que_auth = _coerce_nonnegative_int(item.get("queAuth"))
            if que_auth is None and que_id is not None:
                que_auth = refer_auth_by_que_id.get(que_id)
            selector = {
                "que_id": que_id,
                "name": str(item.get("queTitle") or "").strip() or None,
            }
            if que_auth != _REFERENCE_FIELD_HIDDEN_AUTH:
                visible_fields.append(selector)
            if display_field_que_id is not None and selector["que_id"] == display_field_que_id:
                display_field_name = selector["name"]
        if display_field_name is None and visible_fields:
            display_field_name = visible_fields[0].get("name")
            display_field_que_id = visible_fields[0].get("que_id")
        field["display_field"] = None if display_field_name is None and display_field_que_id is None else {
            "que_id": display_field_que_id,
            "name": display_field_name,
        }
        field["visible_fields"] = visible_fields
        field["field_name_show"] = bool(reference.get("fieldNameShow", True))
        field["_reference_config_template"] = deepcopy(reference)
        field["_relation_config_explicit"] = False
    if field_type == FieldType.department:
        department_scope = _normalize_department_scope_from_question(question)
        if department_scope is not None:
            field["department_scope"] = department_scope
    if field_type == FieldType.code_block:
        code_block_config = question.get("codeBlockConfig") if isinstance(question.get("codeBlockConfig"), dict) else {}
        field["code_block_config"] = {
            "config_mode": _coerce_positive_int(code_block_config.get("configMode")) or 1,
            "code_content": str(code_block_config.get("codeContent") or ""),
            "being_hide_on_form": bool(code_block_config.get("beingHideOnForm", False)),
            "result_alias_path": [
                item
                for item in (
                    _normalize_code_block_alias_path_item(alias)
                    for alias in cast(list[Any], code_block_config.get("resultAliasPath") or [])
                )
                if item is not None
            ],
        }
        field["config"] = deepcopy(field["code_block_config"])
        if question.get("autoTrigger") is not None:
            field["auto_trigger"] = bool(question.get("autoTrigger"))
        if question.get("customBtnTextStatus") is not None:
            field["custom_button_text_enabled"] = bool(question.get("customBtnTextStatus"))
        if question.get("customBtnText") is not None:
            field["custom_button_text"] = str(question.get("customBtnText") or "")
    if field_type == FieldType.q_linker:
        remote_lookup_config = _normalize_remote_lookup_config(question.get("remoteLookupConfig") or {}) or {
            "config_mode": 1,
            "url": "",
            "method": "GET",
            "headers": [],
            "body_type": 1,
            "url_encoded_value": [],
            "json_value": None,
            "xml_value": None,
            "result_type": 1,
            "result_format_path": [],
            "query_params": [],
            "auto_trigger": None,
            "custom_button_text_enabled": None,
            "custom_button_text": None,
            "being_insert_value_directly": None,
            "being_hide_on_form": None,
        }
        if question.get("autoTrigger") is not None:
            remote_lookup_config["auto_trigger"] = bool(question.get("autoTrigger"))
        if question.get("customBtnTextStatus") is not None:
            remote_lookup_config["custom_button_text_enabled"] = bool(question.get("customBtnTextStatus"))
        if question.get("customBtnText") is not None:
            remote_lookup_config["custom_button_text"] = str(question.get("customBtnText") or "")
        field["remote_lookup_config"] = remote_lookup_config
        field["config"] = deepcopy(remote_lookup_config)
        if remote_lookup_config.get("auto_trigger") is not None:
            field["auto_trigger"] = bool(remote_lookup_config.get("auto_trigger"))
        if remote_lookup_config.get("custom_button_text_enabled") is not None:
            field["custom_button_text_enabled"] = bool(remote_lookup_config.get("custom_button_text_enabled"))
        if remote_lookup_config.get("custom_button_text") is not None:
            field["custom_button_text"] = str(remote_lookup_config.get("custom_button_text") or "")
    if field_type == FieldType.subtable:
        subfields = []
        for sub_question in question.get("subQuestions", []) or []:
            if not isinstance(sub_question, dict):
                continue
            subfields.append(_parse_field(sub_question))
        field["subfields"] = subfields
    field["_question_template"] = deepcopy(question)
    return field


def _parse_schema(schema: dict[str, Any]) -> dict[str, Any]:
    fields: list[dict[str, Any]] = []
    fields_by_name: dict[str, dict[str, Any]] = {}
    root_rows: list[list[str]] = []
    sections: list[dict[str, Any]] = []
    form_questions = schema.get("formQues") if isinstance(schema.get("formQues"), list) else []
    for row in form_questions:
        if not isinstance(row, list):
            continue
        if len(row) == 1 and isinstance(row[0], dict) and _coerce_positive_int(row[0].get("queType")) == 24:
            section_question = row[0]
            section_rows: list[list[str]] = []
            for inner_row in section_question.get("innerQuestions", []) or []:
                labels = []
                for question in inner_row or []:
                    if not isinstance(question, dict):
                        continue
                    field = _parse_field(question)
                    fields_by_name.setdefault(field["name"], field)
                    labels.append(field["name"])
                if labels:
                    section_rows.append(labels)
            if section_rows:
                parsed_section_id = _coerce_positive_int(section_question.get("queId"))
                raw_section_id = None
                if parsed_section_id is None:
                    raw_section_id = str(section_question.get("sectionId") or "").strip() or None
                    if raw_section_id is None:
                        parsed_section_id = _coerce_positive_int(section_question.get("sectionId"))
                sections.append(
                    {
                        "section_id": raw_section_id
                        or str(parsed_section_id or _slugify(section_question.get("queTitle") or "section", default="section")),
                        "title": section_question.get("queTitle") or "未命名分组",
                        "rows": section_rows,
                    }
                )
            continue
        labels = []
        for question in row:
            if not isinstance(question, dict):
                continue
            field = _parse_field(question)
            fields_by_name.setdefault(field["name"], field)
            labels.append(field["name"])
        if labels:
            root_rows.append(labels)
    fields = list(fields_by_name.values())
    parsed = {"fields": fields, "layout": {"root_rows": root_rows, "sections": sections}}
    _attach_q_linker_binding_readback(parsed=parsed, raw_schema=schema)
    _attach_code_block_binding_readback(parsed=parsed, raw_schema=schema)
    return parsed


def _schema_field_identity(field: dict[str, Any] | None, *, fallback_name: str | None = None) -> dict[str, Any]:
    field = field or {}
    name = str(field.get("name") or fallback_name or "").strip() or None
    field_id = str(field.get("field_id") or "").strip() or None
    que_id = _coerce_positive_int(field.get("que_id") or field.get("queId"))
    return {
        "name": name,
        "field_id": field_id,
        "que_id": que_id,
    }


def _schema_field_diff_details(
    *,
    added: list[str],
    updated: list[str],
    removed: list[str],
    before_fields: list[dict[str, Any]],
    after_fields: list[dict[str, Any]],
) -> dict[str, list[dict[str, Any]]]:
    before_by_name = {str(field.get("name") or ""): field for field in before_fields if str(field.get("name") or "").strip()}
    after_by_name = {str(field.get("name") or ""): field for field in after_fields if str(field.get("name") or "").strip()}
    return {
        "added": [_schema_field_identity(after_by_name.get(name), fallback_name=name) for name in added],
        "updated": [
            _schema_field_identity(after_by_name.get(name) or before_by_name.get(name), fallback_name=name)
            for name in updated
        ],
        "removed": [_schema_field_identity(before_by_name.get(name), fallback_name=name) for name in removed],
    }


def _resolve_layout_sections_to_names(
    requested_sections: list[dict[str, Any]],
    fields: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[Any]]:
    by_name = {str(field.get("name") or ""): str(field.get("name") or "") for field in fields if field.get("name")}
    by_field_id = {
        str(field.get("field_id") or ""): str(field.get("name") or "")
        for field in fields
        if field.get("field_id") and field.get("name")
    }
    by_que_id = {
        int(field.get("que_id")): str(field.get("name") or "")
        for field in fields
        if _coerce_positive_int(field.get("que_id")) is not None and field.get("name")
    }
    normalized_sections: list[dict[str, Any]] = []
    missing_selectors: list[Any] = []
    for section in requested_sections:
        if not isinstance(section, dict):
            continue
        normalized_rows: list[list[str]] = []
        for row in section.get("rows", []) or []:
            if not isinstance(row, list):
                continue
            normalized_row: list[str] = []
            for selector in row:
                resolved_name = _resolve_layout_field_name(selector, by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
                if resolved_name is None:
                    missing_selectors.append(selector)
                    continue
                normalized_row.append(resolved_name)
            if normalized_row:
                normalized_rows.append(normalized_row)
        normalized_section = {
            "section_id": section.get("section_id"),
            "title": section.get("title"),
            "rows": normalized_rows,
        }
        normalized_sections.append(normalized_section)
    return normalized_sections, missing_selectors


def _resolve_layout_field_name(
    selector: Any,
    *,
    by_name: dict[str, str],
    by_field_id: dict[str, str],
    by_que_id: dict[int, str],
) -> str | None:
    if isinstance(selector, str):
        stripped = selector.strip()
        if not stripped:
            return None
        if stripped in by_name:
            return by_name[stripped]
        if stripped in by_field_id:
            return by_field_id[stripped]
        if stripped.isdigit():
            return by_que_id.get(int(stripped))
        return None
    if isinstance(selector, int) and not isinstance(selector, bool):
        return by_que_id.get(selector)
    if isinstance(selector, dict):
        if selector.get("name"):
            return _resolve_layout_field_name(selector.get("name"), by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
        if selector.get("title"):
            return _resolve_layout_field_name(selector.get("title"), by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
        if selector.get("label"):
            return _resolve_layout_field_name(selector.get("label"), by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
        if selector.get("field_id"):
            return _resolve_layout_field_name(selector.get("field_id"), by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
        if selector.get("que_id") is not None:
            return _resolve_layout_field_name(selector.get("que_id"), by_name=by_name, by_field_id=by_field_id, by_que_id=by_que_id)
    return None


_CODE_BLOCK_INPUT_LINE_RE = re.compile(
    r"^\s*const\s+(?P<var>[A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*qf_field\.\{(?P<field>.+?)\$\$[A-Z0-9-]+\$\$\};?\s*$"
)


def _attach_code_block_binding_readback(*, parsed: dict[str, Any], raw_schema: dict[str, Any]) -> None:
    fields = parsed.get("fields") if isinstance(parsed.get("fields"), list) else []
    if not fields:
        return
    by_que_id = {
        _coerce_positive_int(field.get("que_id")): field
        for field in fields
        if isinstance(field, dict) and _coerce_positive_int(field.get("que_id")) is not None
    }
    relations = raw_schema.get("questionRelations") if isinstance(raw_schema.get("questionRelations"), list) else []
    relation_by_source: dict[int, list[dict[str, Any]]] = {}
    for relation in relations:
        if not isinstance(relation, dict) or _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_CODE_BLOCK:
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_que_id = _coerce_positive_int(alias_config.get("queId"))
        target_que_id = _coerce_positive_int(relation.get("queId"))
        alias_name = str(alias_config.get("qlinkerAlias") or relation.get("qlinkerAlias") or "").strip()
        if source_que_id is None or target_que_id is None or not alias_name:
            continue
        relation_by_source.setdefault(source_que_id, []).append(
            {
                "alias": alias_name,
                "alias_id": _coerce_positive_int(alias_config.get("aliasId")),
                "target_que_id": target_que_id,
            }
        )
    for field in fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.code_block.value:
            continue
        code_block_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {}) or {}
        alias_paths = cast(list[dict[str, Any]], code_block_config.get("result_alias_path") or [])
        que_id = _coerce_positive_int(field.get("que_id"))
        relation_items = relation_by_source.get(que_id or -1, [])
        relation_by_alias = {str(item.get("alias") or ""): item for item in relation_items if str(item.get("alias") or "")}
        inputs, code = _parse_code_block_inputs_and_body(str(code_block_config.get("code_content") or ""))
        outputs: list[dict[str, Any]] = []
        metadata_unverified = False
        for alias_item in alias_paths:
            alias_name = str(alias_item.get("alias_name") or "").strip()
            if not alias_name:
                continue
            output_payload: dict[str, Any] = {
                "alias": alias_name,
                "path": str(alias_item.get("alias_path") or ""),
                "alias_id": _coerce_positive_int(alias_item.get("alias_id", alias_item.get("aliasId"))),
            }
            relation_item = relation_by_alias.get(alias_name)
            if relation_item is not None:
                target_field = by_que_id.get(_coerce_positive_int(relation_item.get("target_que_id")))
                if isinstance(target_field, dict):
                    output_payload["target_field"] = {
                        "field_id": target_field.get("field_id"),
                        "que_id": target_field.get("que_id"),
                        "name": target_field.get("name"),
                    }
                else:
                    metadata_unverified = True
            outputs.append(output_payload)
        for alias_name in relation_by_alias:
            if any(str(item.get("alias") or "") == alias_name for item in outputs):
                continue
            metadata_unverified = True
        field["code_block_binding"] = {
            "inputs": inputs,
            "code": code,
            "auto_trigger": field.get("auto_trigger"),
            "custom_button_text_enabled": field.get("custom_button_text_enabled"),
            "custom_button_text": field.get("custom_button_text"),
            "outputs": outputs,
        }
        if metadata_unverified:
            field["metadata_unverified"] = True


def _normalize_q_linker_key_value_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    key = str(value.get("key") or "").strip()
    if not key:
        return None
    item_type = _coerce_positive_int(value.get("type")) or 1
    normalized: dict[str, Any] = {
        "key": key,
        "value": None if value.get("value") is None else str(value.get("value") or ""),
        "type": item_type,
    }
    return normalized


def _normalize_q_linker_alias_path_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    alias_name = str(value.get("alias_name", value.get("aliasName", "")) or "").strip()
    alias_path = str(value.get("alias_path", value.get("aliasPath", "")) or "").strip()
    if not alias_name or not alias_path:
        return None
    return {
        "alias_name": alias_name,
        "alias_path": alias_path,
        "alias_id": _coerce_positive_int(value.get("alias_id", value.get("aliasId"))),
    }


def _normalize_remote_lookup_config(value: Any) -> dict[str, Any] | None:
    if value is None or not isinstance(value, dict):
        return None
    headers = [
        item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("headers") or [])) if item is not None
    ] if isinstance(value.get("headers"), list) else []
    query_params = [
        item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("query_params", value.get("queryParams")) or [])) if item is not None
    ] if isinstance(value.get("query_params", value.get("queryParams")), list) else []
    url_encoded_value = [
        item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("url_encoded_value", value.get("urlEncodedValue")) or [])) if item is not None
    ] if isinstance(value.get("url_encoded_value", value.get("urlEncodedValue")), list) else []
    result_format_path = [
        item for item in (_normalize_q_linker_alias_path_item(raw) for raw in cast(list[Any], value.get("result_format_path", value.get("resultFormatPath")) or [])) if item is not None
    ] if isinstance(value.get("result_format_path", value.get("resultFormatPath")), list) else []
    return {
        "config_mode": _coerce_positive_int(value.get("config_mode", value.get("configMode"))) or 1,
        "url": str(value.get("url") or ""),
        "method": str(value.get("method") or "GET").upper() or "GET",
        "headers": headers,
        "body_type": _coerce_positive_int(value.get("body_type", value.get("bodyType"))) or 1,
        "url_encoded_value": url_encoded_value,
        "json_value": None if value.get("json_value", value.get("jsonValue")) is None else str(value.get("json_value", value.get("jsonValue")) or ""),
        "xml_value": None if value.get("xml_value", value.get("xmlValue")) is None else str(value.get("xml_value", value.get("xmlValue")) or ""),
        "result_type": _coerce_positive_int(value.get("result_type", value.get("resultType"))) or 1,
        "result_format_path": result_format_path,
        "query_params": query_params,
        "auto_trigger": None if value.get("auto_trigger", value.get("autoTrigger")) is None else bool(value.get("auto_trigger", value.get("autoTrigger"))),
        "custom_button_text_enabled": None if value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus")))) is None else bool(value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus"))))),
        "custom_button_text": None if value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) is None else str(value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) or ""),
        "being_insert_value_directly": None if value.get("being_insert_value_directly", value.get("beingInsertValueDirectly")) is None else bool(value.get("being_insert_value_directly", value.get("beingInsertValueDirectly"))),
        "being_hide_on_form": None if value.get("being_hide_on_form", value.get("beingHideOnForm")) is None else bool(value.get("being_hide_on_form", value.get("beingHideOnForm"))),
    }


def _serialize_remote_lookup_key_value_item(value: Any) -> dict[str, Any]:
    normalized = _normalize_q_linker_key_value_item(value) or {"key": "", "value": "", "type": 1}
    return {
        "key": normalized["key"],
        "value": normalized["value"],
        "type": normalized["type"],
    }


def _serialize_q_linker_alias_path_item(value: Any) -> dict[str, Any]:
    normalized = _normalize_q_linker_alias_path_item(value) or {"alias_name": "", "alias_path": "", "alias_id": None, "alias_type": 1}
    payload = {
        "aliasName": normalized["alias_name"],
        "aliasPath": normalized["alias_path"],
        "aliasType": normalized.get("alias_type") or 1,
    }
    if normalized.get("alias_id") is not None:
        payload["aliasId"] = normalized["alias_id"]
    return payload


def _normalize_q_linker_binding_input_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    raw_field = value.get("field")
    if isinstance(raw_field, str):
        raw_field = {"name": raw_field}
    if not isinstance(raw_field, dict):
        return None
    key = str(value.get("key") or "").strip()
    source = str(value.get("source") or "").strip().lower()
    if not key or not source:
        return None
    payload = {
        "field": {
            "field_id": str(raw_field.get("field_id") or "").strip() or None,
            "que_id": _coerce_positive_int(raw_field.get("que_id")),
            "name": str(raw_field.get("name") or "").strip() or None,
        },
        "key": key,
        "source": source,
    }
    if not any(payload["field"].values()):
        return None
    return payload


def _normalize_q_linker_binding_output_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    alias = str(value.get("alias", value.get("alias_name", value.get("aliasName", ""))) or "").strip()
    path = str(value.get("path", value.get("alias_path", value.get("aliasPath", ""))) or "").strip()
    raw_target = value.get("target_field", value.get("targetField"))
    if isinstance(raw_target, str):
        raw_target = {"name": raw_target}
    if not alias or not path:
        return None
    target_field = {
        "field_id": None,
        "que_id": None,
        "name": None,
    }
    if isinstance(raw_target, dict):
        target_field = {
            "field_id": str(raw_target.get("field_id") or "").strip() or None,
            "que_id": _coerce_positive_int(raw_target.get("que_id")),
            "name": str(raw_target.get("name") or "").strip() or None,
        }
        if not any(target_field.values()):
            target_field = None
    else:
        target_field = None
    return {
        "alias": alias,
        "path": path,
        "alias_id": _coerce_positive_int(value.get("alias_id", value.get("aliasId"))),
        "target_field": target_field,
    }


def _normalize_q_linker_request(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    return {
        "url": str(value.get("url") or ""),
        "method": str(value.get("method") or "GET").upper() or "GET",
        "headers": [
            item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("headers") or [])) if item is not None
        ] if isinstance(value.get("headers"), list) else [],
        "query_params": [
            item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("query_params", value.get("queryParams")) or [])) if item is not None
        ] if isinstance(value.get("query_params", value.get("queryParams")), list) else [],
        "body_type": _coerce_positive_int(value.get("body_type", value.get("bodyType"))) or 1,
        "url_encoded_value": [
            item for item in (_normalize_q_linker_key_value_item(raw) for raw in cast(list[Any], value.get("url_encoded_value", value.get("urlEncodedValue")) or [])) if item is not None
        ] if isinstance(value.get("url_encoded_value", value.get("urlEncodedValue")), list) else [],
        "json_value": None if value.get("json_value", value.get("jsonValue")) is None else str(value.get("json_value", value.get("jsonValue")) or ""),
        "xml_value": None if value.get("xml_value", value.get("xmlValue")) is None else str(value.get("xml_value", value.get("xmlValue")) or ""),
        "result_type": _coerce_positive_int(value.get("result_type", value.get("resultType"))) or 1,
        "auto_trigger": None if value.get("auto_trigger", value.get("autoTrigger")) is None else bool(value.get("auto_trigger", value.get("autoTrigger"))),
        "custom_button_text_enabled": None if value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus")))) is None else bool(value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus"))))),
        "custom_button_text": None if value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) is None else str(value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) or ""),
        "being_insert_value_directly": None if value.get("being_insert_value_directly", value.get("beingInsertValueDirectly")) is None else bool(value.get("being_insert_value_directly", value.get("beingInsertValueDirectly"))),
        "being_hide_on_form": None if value.get("being_hide_on_form", value.get("beingHideOnForm")) is None else bool(value.get("being_hide_on_form", value.get("beingHideOnForm"))),
    }


def _normalize_q_linker_binding(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    request = _normalize_q_linker_request(value.get("request"))
    if request is None:
        return None
    inputs = [
        item for item in (_normalize_q_linker_binding_input_item(raw) for raw in cast(list[Any], value.get("inputs") or [])) if item is not None
    ] if isinstance(value.get("inputs"), list) else []
    outputs = [
        item for item in (_normalize_q_linker_binding_output_item(raw) for raw in cast(list[Any], value.get("outputs") or [])) if item is not None
    ] if isinstance(value.get("outputs"), list) else []
    return {
        "inputs": inputs,
        "request": request,
        "outputs": outputs,
    }


def _remote_lookup_config_equal(left: Any, right: Any) -> bool:
    return _normalize_remote_lookup_config(left) == _normalize_remote_lookup_config(right)


def _q_linker_binding_equal(left: Any, right: Any) -> bool:
    return _normalize_q_linker_binding(left) == _normalize_q_linker_binding(right)


def _compose_qlinker_value_reference(*, field_name: str, que_id: int) -> str:
    return f"{{{field_name}$${_encrypt_question_id(que_id)}$$}}"


def _attach_q_linker_binding_readback(*, parsed: dict[str, Any], raw_schema: dict[str, Any]) -> None:
    fields = parsed.get("fields") if isinstance(parsed.get("fields"), list) else []
    if not fields:
        return
    by_que_id = {
        _coerce_positive_int(field.get("que_id")): field
        for field in fields
        if isinstance(field, dict) and _coerce_positive_int(field.get("que_id")) is not None
    }
    relations = raw_schema.get("questionRelations") if isinstance(raw_schema.get("questionRelations"), list) else []
    relation_by_source: dict[int, list[dict[str, Any]]] = {}
    for relation in relations:
        if not isinstance(relation, dict) or _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_Q_LINKER:
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_que_id = _coerce_positive_int(alias_config.get("queId")) or _coerce_positive_int(relation.get("qlinkerQueId"))
        target_que_id = _coerce_positive_int(relation.get("queId"))
        alias_name = str(alias_config.get("qlinkerAlias") or relation.get("qlinkerAlias") or "").strip()
        if source_que_id is None or target_que_id is None or not alias_name:
            continue
        relation_by_source.setdefault(source_que_id, []).append(
            {
                "alias": alias_name,
                "target_que_id": target_que_id,
            }
        )
    for field in fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.q_linker.value:
            continue
        remote_lookup_config = _normalize_remote_lookup_config(field.get("remote_lookup_config") or field.get("config") or {}) or {}
        que_id = _coerce_positive_int(field.get("que_id"))
        relation_items = relation_by_source.get(que_id or -1, [])
        relation_by_alias = {str(item.get("alias") or ""): item for item in relation_items if str(item.get("alias") or "")}
        inputs: list[dict[str, Any]] = []
        static_headers: list[dict[str, Any]] = []
        static_query_params: list[dict[str, Any]] = []
        static_url_encoded: list[dict[str, Any]] = []
        metadata_unverified = False
        for source_name, config_items, static_bucket in (
            ("header", cast(list[dict[str, Any]], remote_lookup_config.get("headers") or []), static_headers),
            ("query_param", cast(list[dict[str, Any]], remote_lookup_config.get("query_params") or []), static_query_params),
            ("url_encoded", cast(list[dict[str, Any]], remote_lookup_config.get("url_encoded_value") or []), static_url_encoded),
        ):
            for item in config_items:
                if _coerce_positive_int(item.get("type")) == 2:
                    source_field = by_que_id.get(_coerce_positive_int(item.get("value")))
                    if isinstance(source_field, dict):
                        inputs.append(
                            {
                                "field": {
                                    "field_id": source_field.get("field_id"),
                                    "que_id": source_field.get("que_id"),
                                    "name": source_field.get("name"),
                                },
                                "key": item.get("key"),
                                "source": source_name,
                            }
                        )
                    else:
                        metadata_unverified = True
                else:
                    static_bucket.append({"key": item.get("key"), "value": item.get("value")})
        outputs: list[dict[str, Any]] = []
        for alias_item in cast(list[dict[str, Any]], remote_lookup_config.get("result_format_path") or []):
            alias_name = str(alias_item.get("alias_name") or "").strip()
            if not alias_name:
                continue
            output_payload: dict[str, Any] = {
                "alias": alias_name,
                "path": str(alias_item.get("alias_path") or ""),
                "alias_id": _coerce_positive_int(alias_item.get("alias_id")),
            }
            relation_item = relation_by_alias.get(alias_name)
            if relation_item is not None:
                target_field = by_que_id.get(_coerce_positive_int(relation_item.get("target_que_id")))
                if isinstance(target_field, dict):
                    output_payload["target_field"] = {
                        "field_id": target_field.get("field_id"),
                        "que_id": target_field.get("que_id"),
                        "name": target_field.get("name"),
                    }
                else:
                    metadata_unverified = True
            else:
                metadata_unverified = True
            outputs.append(output_payload)
        field["q_linker_binding"] = {
            "inputs": inputs,
            "request": {
                "url": remote_lookup_config.get("url"),
                "method": remote_lookup_config.get("method"),
                "headers": static_headers,
                "query_params": static_query_params,
                "body_type": remote_lookup_config.get("body_type"),
                "url_encoded_value": static_url_encoded,
                "json_value": remote_lookup_config.get("json_value"),
                "xml_value": remote_lookup_config.get("xml_value"),
                "result_type": remote_lookup_config.get("result_type"),
                "auto_trigger": remote_lookup_config.get("auto_trigger"),
                "custom_button_text_enabled": remote_lookup_config.get("custom_button_text_enabled"),
                "custom_button_text": remote_lookup_config.get("custom_button_text"),
                "being_insert_value_directly": remote_lookup_config.get("being_insert_value_directly"),
                "being_hide_on_form": remote_lookup_config.get("being_hide_on_form"),
            },
            "outputs": outputs,
        }
        if metadata_unverified:
            field["metadata_unverified"] = True


def _parse_code_block_inputs_and_body(code_content: str) -> tuple[list[dict[str, Any]], str]:
    if not code_content:
        return [], ""
    lines = code_content.splitlines()
    inputs: list[dict[str, Any]] = []
    index = 0
    while index < len(lines):
        line = lines[index]
        if not line.strip():
            index += 1
            continue
        matched = _CODE_BLOCK_INPUT_LINE_RE.match(line)
        if matched is None:
            break
        inputs.append({"field": {"name": matched.group("field")}, "var": matched.group("var")})
        index += 1
    body = "\n".join(lines[index:]).lstrip("\n")
    return inputs, body


def _compact_public_field_read(*, field: dict[str, Any], layout: dict[str, Any], nested: bool = False) -> dict[str, Any]:
    payload = {
        "field_id": field.get("field_id"),
        "que_id": field.get("que_id"),
        "name": field.get("name"),
        "type": field.get("type"),
        "required": bool(field.get("required")),
        "section_id": None if nested else _find_field_section_id(layout, str(field.get("name") or "")),
    }
    if field.get("type") == FieldType.relation.value:
        payload["target_app_key"] = field.get("target_app_key")
        payload["relation_mode"] = _normalize_relation_mode(field.get("relation_mode"))
        payload["display_field"] = deepcopy(field.get("display_field"))
        payload["visible_fields"] = deepcopy(field.get("visible_fields") or [])
    if field.get("type") == FieldType.department.value and field.get("department_scope") is not None:
        payload["department_scope"] = deepcopy(field.get("department_scope"))
    if field.get("type") == FieldType.code_block.value:
        payload["code_block_config"] = deepcopy(field.get("code_block_config") or {})
        if field.get("auto_trigger") is not None:
            payload["auto_trigger"] = bool(field.get("auto_trigger"))
        if field.get("custom_button_text_enabled") is not None:
            payload["custom_button_text_enabled"] = bool(field.get("custom_button_text_enabled"))
        if field.get("custom_button_text") is not None:
            payload["custom_button_text"] = field.get("custom_button_text")
        if field.get("code_block_binding") is not None:
            payload["code_block_binding"] = deepcopy(field.get("code_block_binding"))
        if field.get("metadata_unverified") is not None:
            payload["metadata_unverified"] = bool(field.get("metadata_unverified"))
    if field.get("type") == FieldType.q_linker.value:
        payload["remote_lookup_config"] = deepcopy(field.get("remote_lookup_config") or {})
        if field.get("q_linker_binding") is not None:
            payload["q_linker_binding"] = deepcopy(field.get("q_linker_binding"))
        if field.get("auto_trigger") is not None:
            payload["auto_trigger"] = bool(field.get("auto_trigger"))
        if field.get("custom_button_text_enabled") is not None:
            payload["custom_button_text_enabled"] = bool(field.get("custom_button_text_enabled"))
        if field.get("custom_button_text") is not None:
            payload["custom_button_text"] = field.get("custom_button_text")
        if field.get("metadata_unverified") is not None:
            payload["metadata_unverified"] = bool(field.get("metadata_unverified"))
    if field.get("type") == FieldType.subtable.value:
        payload["subfields"] = [
            _compact_public_field_read(field=subfield, layout=layout, nested=True)
            for subfield in cast(list[dict[str, Any]], field.get("subfields") or [])
            if isinstance(subfield, dict)
        ]
    return payload


def _field_public_ref(field: dict[str, Any] | None, *, fallback_que_id: int | None = None) -> dict[str, Any] | None:
    if not isinstance(field, dict):
        if fallback_que_id is None:
            return None
        return {"field_id": None, "que_id": fallback_que_id, "name": None, "type": None}
    return {
        "field_id": field.get("field_id"),
        "que_id": _coerce_any_int(field.get("que_id")),
        "name": field.get("name"),
        "type": field.get("type"),
    }


def _find_top_level_field_by_que_id(fields: list[dict[str, Any]], que_id: int | None) -> dict[str, Any] | None:
    if que_id is None:
        return None
    for field in fields:
        if _coerce_any_int(field.get("que_id")) == que_id:
            return field
    return None


def _find_top_level_field_by_name(fields: list[dict[str, Any]], field_name: str | None) -> dict[str, Any] | None:
    if not field_name:
        return None
    for field in fields:
        if str(field.get("name") or "") == field_name:
            return field
    return None


def _form_settings_from_schema(schema: dict[str, Any], fields: list[dict[str, Any]]) -> dict[str, Any]:
    data_title_que_id = _coerce_any_int(schema.get("dataTitleQueId", schema.get("data_title_que_id")))
    data_cover_que_id = _coerce_any_int(schema.get("dataCoverQueId", schema.get("data_cover_que_id")))
    return {
        "data_title_field": _field_public_ref(
            _find_top_level_field_by_que_id(fields, data_title_que_id),
            fallback_que_id=data_title_que_id,
        ),
        "data_cover_field": _field_public_ref(
            _find_top_level_field_by_que_id(fields, data_cover_que_id),
            fallback_que_id=data_cover_que_id,
        ),
    }


def _collect_data_display_marker_selection(fields: list[dict[str, Any]]) -> _DataDisplayMarkerSelection:
    title_fields: list[dict[str, Any]] = []
    cover_fields: list[dict[str, Any]] = []

    def visit(field: dict[str, Any], *, parent_name: str | None = None) -> None:
        name = str(field.get("name") or "").strip()
        field_type = str(field.get("type") or "")
        if bool(field.get("as_data_title")):
            if parent_name or field_type == FieldType.subtable.value:
                raise _DataDisplayConfigError(
                    "INVALID_DATA_TITLE_FIELD",
                    "data title must be a top-level non-subtable field",
                    details={"field_name": name, "parent_field_name": parent_name, "field_type": field_type},
                )
            title_fields.append(field)
        if bool(field.get("as_data_cover")):
            if parent_name:
                raise _DataDisplayConfigError(
                    "INVALID_DATA_COVER_FIELD",
                    "data cover must be a top-level attachment field",
                    details={"field_name": name, "parent_field_name": parent_name, "field_type": field_type},
                )
            if field_type != FieldType.attachment.value:
                raise _DataDisplayConfigError(
                    "INVALID_DATA_COVER_FIELD",
                    "data cover must be a top-level attachment field",
                    details={"field_name": name, "field_type": field_type, "expected_type": FieldType.attachment.value},
                )
            cover_fields.append(field)
        for subfield in cast(list[dict[str, Any]], field.get("subfields") or []):
            if isinstance(subfield, dict):
                visit(subfield, parent_name=name)

    for field in fields:
        if isinstance(field, dict):
            visit(field)

    if len(title_fields) > 1:
        raise _DataDisplayConfigError(
            "MULTIPLE_DATA_TITLE_FIELDS",
            "only one field can be marked as data title",
            details={"fields": [_field_public_ref(field) for field in title_fields]},
        )
    if len(cover_fields) > 1:
        raise _DataDisplayConfigError(
            "MULTIPLE_DATA_COVER_FIELDS",
            "only one field can be marked as data cover",
            details={"fields": [_field_public_ref(field) for field in cover_fields]},
        )
    return _DataDisplayMarkerSelection(
        data_title_field_name=str(title_fields[0].get("name") or "") if title_fields else None,
        data_cover_field_name=str(cover_fields[0].get("name") or "") if cover_fields else None,
    )


def _ensure_required_data_title_config(
    *,
    current_schema: dict[str, Any],
    original_fields: list[dict[str, Any]],
    fields: list[dict[str, Any]],
    selection: _DataDisplayMarkerSelection,
) -> None:
    if selection.data_title_field_name:
        return
    current_title_que_id = _coerce_any_int(
        current_schema.get("dataTitleQueId", current_schema.get("data_title_que_id"))
    )
    if _find_top_level_field_by_que_id(fields, current_title_que_id) is not None:
        return
    if current_title_que_id is None and original_fields:
        # Older or mocked schema payloads may omit the form-level title config even
        # though the app already exists with fields. Avoid blocking unrelated edits
        # unless we can prove the final title is missing.
        return
    suggested_fields = [
        str(field.get("name") or "").strip()
        for field in fields
        if isinstance(field, dict)
        and str(field.get("name") or "").strip()
        and str(field.get("type") or "") != FieldType.subtable.value
    ][:8]
    raise _DataDisplayConfigError(
        "MISSING_DATA_TITLE_FIELD",
        "data title is required; mark exactly one top-level field with as_data_title=true",
        details={
            "current_data_title_que_id": current_title_que_id,
            "suggested_field_names": suggested_fields,
            "next_action": "mark one top-level field as the data title, for example {'name': '项目名称', 'as_data_title': true}",
        },
    )


def _field_form_config_identifier(field: dict[str, Any] | None) -> int | None:
    if not isinstance(field, dict):
        return None
    que_id = _coerce_positive_int(field.get("que_id"))
    if que_id is not None:
        return que_id
    que_temp_id = _coerce_any_int(field.get("que_temp_id"))
    if que_temp_id is not None and que_temp_id != 0:
        return que_temp_id
    return None


def _apply_data_display_selection_to_payload(
    payload: dict[str, Any],
    *,
    fields: list[dict[str, Any]],
    selection: _DataDisplayMarkerSelection,
) -> None:
    if selection.data_title_field_name:
        data_title_field = _find_top_level_field_by_name(fields, selection.data_title_field_name)
        data_title_que_id = _field_form_config_identifier(data_title_field)
        if data_title_que_id is not None:
            payload["dataTitleQueId"] = data_title_que_id
    if selection.data_cover_field_name:
        data_cover_field = _find_top_level_field_by_name(fields, selection.data_cover_field_name)
        data_cover_que_id = _field_form_config_identifier(data_cover_field)
        if data_cover_que_id is not None:
            payload["dataCoverQueId"] = data_cover_que_id


def _verify_data_display_readback(
    *,
    form_settings: Any,
    selection: _DataDisplayMarkerSelection,
) -> dict[str, Any]:
    if not selection.has_any:
        return {
            "data_title_field_verified": None,
            "data_cover_field_verified": None,
            "data_display_config_verified": None,
        }
    settings = form_settings if isinstance(form_settings, dict) else {}
    title_ref = settings.get("data_title_field") if isinstance(settings.get("data_title_field"), dict) else None
    cover_ref = settings.get("data_cover_field") if isinstance(settings.get("data_cover_field"), dict) else None
    title_verified = None
    cover_verified = None
    if selection.data_title_field_name:
        title_verified = bool(title_ref and str(title_ref.get("name") or "") == selection.data_title_field_name)
    if selection.data_cover_field_name:
        cover_verified = bool(cover_ref and str(cover_ref.get("name") or "") == selection.data_cover_field_name)
    requested_results = [value for value in (title_verified, cover_verified) if value is not None]
    return {
        "data_title_field_verified": title_verified,
        "data_cover_field_verified": cover_verified,
        "data_display_config_verified": all(requested_results) if requested_results else None,
    }


def _build_selector_map(fields: list[dict[str, Any]]) -> dict[str, int]:
    mapping: dict[str, int] = {}
    for index, field in enumerate(fields):
        field_id = str(field.get("field_id") or "")
        field_name = str(field.get("name") or "")
        que_id = _coerce_nonnegative_int(field.get("que_id"))
        if field_id:
            mapping[f"field_id:{field_id}"] = index
        if field_name:
            mapping[f"name:{field_name}"] = index
        if que_id is not None:
            mapping[f"que_id:{que_id}"] = index
    return mapping


def _resolve_selector(selector_map: dict[str, int], selector: FieldSelector) -> int | None:
    if selector.field_id:
        value = selector_map.get(f"field_id:{selector.field_id}")
        if value is not None:
            return value
    if selector.que_id is not None:
        value = selector_map.get(f"que_id:{selector.que_id}")
        if value is not None:
            return value
    if selector.name:
        value = selector_map.get(f"name:{selector.name}")
        if value is not None:
            return value
    return None


def _resolve_remove_selector(fields: list[dict[str, Any]], patch: FieldRemovePatch) -> int | None:
    selector = FieldSelector(field_id=patch.field_id, que_id=patch.que_id, name=patch.name)
    return _resolve_selector(_build_selector_map(fields), selector)


def _field_patch_to_internal(patch: FieldPatch) -> dict[str, Any]:
    field_id = _slugify(patch.name, default=f"field_{uuid4().hex[:8]}")
    remote_lookup_config = patch.remote_lookup_config.model_dump(mode="json", exclude_none=True) if patch.remote_lookup_config is not None else None
    q_linker_binding = patch.q_linker_binding.model_dump(mode="json", exclude_none=True) if patch.q_linker_binding is not None else None
    code_block_config = patch.code_block_config.model_dump(mode="json", exclude_none=True) if patch.code_block_config is not None else None
    code_block_binding = patch.code_block_binding.model_dump(mode="json", exclude_none=True) if patch.code_block_binding is not None else None
    return {
        "field_id": field_id,
        "name": patch.name,
        "type": patch.type.value,
        "required": patch.required,
        "description": patch.description,
        "options": list(patch.options),
        "target_app_key": patch.target_app_key,
        "display_field": patch.display_field.model_dump(mode="json", exclude_none=True) if patch.display_field is not None else None,
        "visible_fields": [selector.model_dump(mode="json", exclude_none=True) for selector in patch.visible_fields],
        "relation_mode": patch.relation_mode.value if patch.relation_mode is not None else None,
        "department_scope": patch.department_scope.model_dump(mode="json", exclude_none=True) if patch.department_scope is not None else None,
        "remote_lookup_config": remote_lookup_config,
        "_explicit_remote_lookup_config": remote_lookup_config is not None,
        "q_linker_binding": q_linker_binding,
        "code_block_config": code_block_config,
        "code_block_binding": code_block_binding,
        "_explicit_code_block_binding": code_block_binding is not None,
        "config": deepcopy(remote_lookup_config) if remote_lookup_config is not None else (deepcopy(code_block_config) if code_block_config is not None else {}),
        "auto_trigger": patch.auto_trigger,
        "custom_button_text_enabled": patch.custom_button_text_enabled,
        "custom_button_text": patch.custom_button_text,
        "as_data_title": patch.as_data_title,
        "as_data_cover": patch.as_data_cover,
        "subfields": [_field_patch_to_internal(subfield) for subfield in patch.subfields],
        "que_id": None,
        "default_type": 1,
        "default_value": None,
        "_relation_config_explicit": patch.type == PublicFieldType.relation,
    }


def _field_matches_patch(field: dict[str, Any], patch: FieldPatch) -> bool:
    field_display_selector = field.get("display_field")
    patch_display_selector = patch.display_field.model_dump(mode="json", exclude_none=True) if patch.display_field is not None else None
    field_visible_selectors = field.get("visible_fields")
    patch_visible_selectors = [selector.model_dump(mode="json", exclude_none=True) for selector in patch.visible_fields]
    if (
        str(field.get("type") or "") == FieldType.relation.value
        and patch.type == PublicFieldType.relation
        and (field.get("target_app_key") or None) == patch.target_app_key
        and field_display_selector is None
        and not list(field_visible_selectors or [])
    ):
        relation_selector_match = True
    else:
        relation_selector_match = (
            _field_selector_payload_equal(field_display_selector, patch_display_selector)
            and _field_selector_list_equal(field_visible_selectors, patch_visible_selectors)
        )
    return (
        str(field.get("name") or "") == patch.name
        and str(field.get("type") or "") == patch.type.value
        and bool(field.get("required")) == patch.required
        and (field.get("description") or None) == patch.description
        and list(field.get("options") or []) == list(patch.options)
        and (field.get("target_app_key") or None) == patch.target_app_key
        and relation_selector_match
        and _normalize_relation_mode(field.get("relation_mode")) == _normalize_relation_mode(patch.relation_mode.value if patch.relation_mode is not None else None)
        and (
            patch.department_scope is None
            or _department_scope_equal(field.get("department_scope"), patch.department_scope.model_dump(mode="json", exclude_none=True))
        )
        and _remote_lookup_config_equal(field.get("remote_lookup_config"), patch.remote_lookup_config.model_dump(mode="json", exclude_none=True) if patch.remote_lookup_config is not None else None)
        and _q_linker_binding_equal(field.get("q_linker_binding"), patch.q_linker_binding.model_dump(mode="json", exclude_none=True) if patch.q_linker_binding is not None else None)
        and _code_block_config_equal(field.get("code_block_config"), patch.code_block_config.model_dump(mode="json", exclude_none=True) if patch.code_block_config is not None else None)
        and _code_block_binding_equal(field.get("code_block_binding"), patch.code_block_binding.model_dump(mode="json", exclude_none=True) if patch.code_block_binding is not None else None)
        and _optional_bool_equal(field.get("auto_trigger"), patch.auto_trigger)
        and _optional_bool_equal(field.get("custom_button_text_enabled"), patch.custom_button_text_enabled)
        and _optional_string_equal(field.get("custom_button_text"), patch.custom_button_text)
        and len(field.get("subfields") or []) == len(patch.subfields)
    )


def _merge_existing_field_with_patch(field: dict[str, Any], patch: FieldPatch) -> None:
    if str(field.get("type") or "") == FieldType.relation.value and patch.type == PublicFieldType.relation:
        if not str(field.get("target_app_key") or "").strip() and patch.target_app_key:
            field["target_app_key"] = patch.target_app_key
        if not isinstance(field.get("display_field"), dict) and patch.display_field is not None:
            field["display_field"] = patch.display_field.model_dump(mode="json", exclude_none=True)
        if not list(field.get("visible_fields") or []) and patch.visible_fields:
            field["visible_fields"] = [
                selector.model_dump(mode="json", exclude_none=True)
                for selector in patch.visible_fields
            ]
        if field.get("relation_mode") is None and patch.relation_mode is not None:
            field["relation_mode"] = patch.relation_mode.value
    if str(field.get("type") or "") == FieldType.department.value and patch.type == PublicFieldType.department:
        if field.get("department_scope") is None and patch.department_scope is not None:
            field["department_scope"] = patch.department_scope.model_dump(mode="json", exclude_none=True)
    if patch.as_data_title is not None:
        if patch.as_data_title:
            field["as_data_title"] = True
        else:
            field.pop("as_data_title", None)
    if patch.as_data_cover is not None:
        if patch.as_data_cover:
            field["as_data_cover"] = True
        else:
            field.pop("as_data_cover", None)


def _field_selector_payload_equal(left: Any, right: Any) -> bool:
    if left is None and right is None:
        return True
    if not isinstance(left, dict) or not isinstance(right, dict):
        return False
    return (
        str(left.get("field_id") or "") == str(right.get("field_id") or "")
        and _coerce_nonnegative_int(left.get("que_id")) == _coerce_nonnegative_int(right.get("que_id"))
        and str(left.get("name") or "") == str(right.get("name") or "")
    )


def _field_selector_list_equal(left: Any, right: Any) -> bool:
    left_items = left if isinstance(left, list) else []
    right_items = right if isinstance(right, list) else []
    if len(left_items) != len(right_items):
        return False
    return all(_field_selector_payload_equal(item_left, item_right) for item_left, item_right in zip(left_items, right_items))


def _optional_bool_equal(left: Any, right: Any) -> bool:
    if left is None and right is None:
        return True
    return bool(left) == bool(right)


def _optional_string_equal(left: Any, right: Any) -> bool:
    if left is None and right is None:
        return True
    return str(left or "") == str(right or "")


def _normalize_code_block_alias_path_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    payload = {
        "alias_name": str(value.get("alias_name", value.get("aliasName", "")) or "").strip(),
        "alias_path": str(value.get("alias_path", value.get("aliasPath", "")) or "").strip(),
        "alias_type": _coerce_positive_int(value.get("alias_type", value.get("aliasType"))) or 1,
        "alias_id": _coerce_positive_int(value.get("alias_id", value.get("aliasId"))),
        "sub_alias": [],
    }
    sub_alias_raw = value.get("sub_alias", value.get("subAlias"))
    if isinstance(sub_alias_raw, list):
        payload["sub_alias"] = [
            item for item in (_normalize_code_block_alias_path_item(sub_item) for sub_item in sub_alias_raw) if item is not None
        ]
    return payload


def _normalize_code_block_config(value: Any) -> dict[str, Any] | None:
    if value is None:
        return None
    if not isinstance(value, dict):
        return None
    result_alias_path_raw = value.get("result_alias_path", value.get("resultAliasPath", value.get("alias_config", value.get("aliasConfig"))))
    result_alias_path = []
    if isinstance(result_alias_path_raw, list):
        result_alias_path = [
            item for item in (_normalize_code_block_alias_path_item(alias) for alias in result_alias_path_raw) if item is not None
        ]
    return {
        "config_mode": _coerce_positive_int(value.get("config_mode", value.get("configMode"))) or 1,
        "code_content": str(value.get("code_content", value.get("codeContent", "")) or ""),
        "being_hide_on_form": bool(value.get("being_hide_on_form", value.get("beingHideOnForm", False))),
        "result_alias_path": result_alias_path,
    }


def _code_block_config_equal(left: Any, right: Any) -> bool:
    return _normalize_code_block_config(left) == _normalize_code_block_config(right)


def _normalize_code_block_binding_input_item(value: Any) -> dict[str, Any] | None:
    if isinstance(value, str):
        return {"field": {"name": value}, "var": None}
    if not isinstance(value, dict):
        return None
    raw_field = value.get("field")
    if isinstance(raw_field, str):
        raw_field = {"name": raw_field}
    elif not isinstance(raw_field, dict):
        for key in ("field_name", "fieldName", "name", "title", "label"):
            raw_value = value.get(key)
            if isinstance(raw_value, str) and raw_value.strip():
                raw_field = {"name": raw_value.strip()}
                break
    if not isinstance(raw_field, dict):
        return None
    payload = {
        "field": {
            "field_id": str(raw_field.get("field_id") or "").strip() or None,
            "que_id": _coerce_positive_int(raw_field.get("que_id")),
            "name": str(raw_field.get("name") or "").strip() or None,
        },
        "var": str(value.get("var") or "").strip() or None,
    }
    if not any(payload["field"].values()):
        return None
    return payload


def _normalize_code_block_binding_output_item(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    alias = str(value.get("alias", value.get("alias_name", value.get("aliasName", ""))) or "").strip()
    path = str(value.get("path", value.get("alias_path", value.get("aliasPath", ""))) or "").strip()
    raw_target = value.get("target_field", value.get("targetField"))
    if isinstance(raw_target, str):
        raw_target = {"name": raw_target}
    target_field = None
    if isinstance(raw_target, dict):
        target_field = {
            "field_id": str(raw_target.get("field_id") or "").strip() or None,
            "que_id": _coerce_positive_int(raw_target.get("que_id")),
            "name": str(raw_target.get("name") or "").strip() or None,
        }
        if not any(target_field.values()):
            target_field = None
    if not alias or not path:
        return None
    return {
        "alias": alias,
        "path": path,
        "alias_id": _coerce_positive_int(value.get("alias_id", value.get("aliasId"))),
        "target_field": target_field,
    }


def _normalize_code_block_binding(value: Any) -> dict[str, Any] | None:
    if value is None or not isinstance(value, dict):
        return None
    inputs = []
    outputs = []
    if isinstance(value.get("inputs"), list):
        inputs = [
            item
            for item in (_normalize_code_block_binding_input_item(raw_item) for raw_item in cast(list[Any], value.get("inputs") or []))
            if item is not None
        ]
    if isinstance(value.get("outputs"), list):
        outputs = [
            item
            for item in (_normalize_code_block_binding_output_item(raw_item) for raw_item in cast(list[Any], value.get("outputs") or []))
            if item is not None
        ]
    return {
        "inputs": inputs,
        "code": str(value.get("code", value.get("code_content", value.get("codeContent", ""))) or ""),
        "auto_trigger": None if value.get("auto_trigger", value.get("autoTrigger")) is None else bool(value.get("auto_trigger", value.get("autoTrigger"))),
        "custom_button_text_enabled": None if value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus")))) is None else bool(value.get("custom_button_text_enabled", value.get("customButtonTextEnabled", value.get("custom_btn_text_status", value.get("customBtnTextStatus"))))),
        "custom_button_text": None if value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) is None else str(value.get("custom_button_text", value.get("customButtonText", value.get("custom_btn_text", value.get("customBtnText")))) or ""),
        "outputs": outputs,
    }


def _code_block_binding_equal(left: Any, right: Any) -> bool:
    return _normalize_code_block_binding(left) == _normalize_code_block_binding(right)


_SAFE_SUBFIELD_MUTATION_KEYS = frozenset({"name", "required", "description", "subfield_updates", "as_data_title", "as_data_cover"})


def _validate_safe_subfield_mutation(*, payload: dict[str, Any], location: str) -> None:
    unsupported = sorted(key for key in payload if key not in _SAFE_SUBFIELD_MUTATION_KEYS)
    if unsupported:
        raise ValueError(
            f"{location} only supports safe overlay keys: name, required, description, subfield_updates, as_data_title, as_data_cover; "
            f"unsupported keys: {', '.join(unsupported)}"
        )


def _apply_subfield_updates(field: dict[str, Any], raw_updates: list[Any]) -> None:
    if str(field.get("type") or "") != FieldType.subtable.value:
        raise ValueError("subfield_updates can only target subtable fields")
    subfields = [subfield for subfield in cast(list[dict[str, Any]], field.get("subfields") or []) if isinstance(subfield, dict)]
    for index, raw_item in enumerate(raw_updates):
        patch = FieldUpdatePatch.model_validate(raw_item)
        payload = patch.set.model_dump(mode="json", exclude_none=True)
        _validate_safe_subfield_mutation(payload=payload, location=f"subfield_updates[{index}].set")
        target = _resolve_field_selector_with_uniqueness(
            fields=subfields,
            selector_payload=patch.selector.model_dump(mode="json", exclude_none=True),
            location=f"subfield_updates[{index}].selector",
        )
        _apply_field_mutation(target, patch.set)
    field["subfields"] = subfields


def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
    payload = mutation.model_dump(mode="json", exclude_none=True)
    relation_config_explicit = (
        payload.get("type") == FieldType.relation.value
        or any(key in payload for key in ("target_app_key", "display_field", "visible_fields", "relation_mode"))
    )
    question_overlay_keys = set(cast(list[str], field.get("_question_overlay_keys") or []))
    question_rebuild_required = bool(field.get("_question_rebuild_required"))
    if "name" in payload:
        field["name"] = payload["name"]
        question_overlay_keys.add("name")
    if "type" in payload:
        field["type"] = payload["type"]
        question_rebuild_required = True
    if "required" in payload:
        field["required"] = payload["required"]
        question_overlay_keys.add("required")
    if "description" in payload:
        field["description"] = payload["description"]
        question_overlay_keys.add("description")
    if "options" in payload:
        field["options"] = list(payload["options"])
        question_rebuild_required = True
    if "target_app_key" in payload:
        field["target_app_key"] = payload["target_app_key"]
        question_rebuild_required = True
    if "display_field" in payload:
        field["display_field"] = payload["display_field"]
        question_rebuild_required = True
    if "visible_fields" in payload:
        field["visible_fields"] = list(payload["visible_fields"])
        question_rebuild_required = True
    if "relation_mode" in payload:
        field["relation_mode"] = payload["relation_mode"]
        question_rebuild_required = True
    if "department_scope" in payload:
        field["department_scope"] = payload["department_scope"]
        question_rebuild_required = True
    if "remote_lookup_config" in payload:
        field["remote_lookup_config"] = payload["remote_lookup_config"]
        field["config"] = deepcopy(payload["remote_lookup_config"])
        field["_explicit_remote_lookup_config"] = True
        question_rebuild_required = True
    if "q_linker_binding" in payload:
        field["q_linker_binding"] = payload["q_linker_binding"]
        if "remote_lookup_config" not in payload:
            field["_explicit_remote_lookup_config"] = False
        question_rebuild_required = True
    if "code_block_config" in payload:
        field["code_block_config"] = payload["code_block_config"]
        field["config"] = deepcopy(payload["code_block_config"])
        question_rebuild_required = True
    if "code_block_binding" in payload:
        existing_code_block_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {})
        next_code_block_binding = _normalize_code_block_binding(payload["code_block_binding"])
        field["code_block_binding"] = payload["code_block_binding"]
        field["_explicit_code_block_binding"] = True
        if (
            "code_block_config" not in payload
            and existing_code_block_config is not None
            and next_code_block_binding is not None
        ):
            existing_code = _normalize_code_block_output_assignment(
                _strip_code_block_generated_input_prelude(str(existing_code_block_config.get("code_content") or ""))
            )
            next_code = _normalize_code_block_output_assignment(str(next_code_block_binding.get("code") or ""))
            if existing_code != next_code:
                existing_code_block_config["code_content"] = next_code
                field["code_block_config"] = existing_code_block_config
                field["config"] = deepcopy(existing_code_block_config)
        question_rebuild_required = True
    if "auto_trigger" in payload:
        field["auto_trigger"] = payload["auto_trigger"]
        question_rebuild_required = True
    if "custom_button_text_enabled" in payload:
        field["custom_button_text_enabled"] = payload["custom_button_text_enabled"]
        question_rebuild_required = True
    if "custom_button_text" in payload:
        field["custom_button_text"] = payload["custom_button_text"]
        question_rebuild_required = True
    if "as_data_title" in payload:
        if payload["as_data_title"]:
            field["as_data_title"] = True
        else:
            field.pop("as_data_title", None)
    if "as_data_cover" in payload:
        if payload["as_data_cover"]:
            field["as_data_cover"] = True
        else:
            field.pop("as_data_cover", None)
    if "subfields" in payload:
        field["subfields"] = [_field_patch_to_internal(item) for item in payload["subfields"]]
        question_rebuild_required = True
    if "subfield_updates" in payload:
        _apply_subfield_updates(field, payload["subfield_updates"])
    if relation_config_explicit:
        field["_relation_config_explicit"] = True
        question_rebuild_required = True
    elif payload.get("type") and payload.get("type") != FieldType.relation.value:
        field.pop("_relation_config_explicit", None)
        field.pop("_reference_config_template", None)
    if question_overlay_keys:
        field["_question_overlay_keys"] = sorted(question_overlay_keys)
    else:
        field.pop("_question_overlay_keys", None)
    if question_rebuild_required:
        field["_question_rebuild_required"] = True
    else:
        field.pop("_question_rebuild_required", None)


def _resolve_field_selector_with_uniqueness(
    *,
    fields: list[dict[str, Any]],
    selector_payload: dict[str, Any],
    location: str,
) -> dict[str, Any]:
    selector = FieldSelector.model_validate(selector_payload)
    if selector.field_id:
        matched = [field for field in fields if str(field.get("field_id") or "") == str(selector.field_id)]
    elif selector.que_id is not None:
        matched = [field for field in fields if _coerce_nonnegative_int(field.get("que_id")) == _coerce_nonnegative_int(selector.que_id)]
    elif selector.name:
        name = str(selector.name or "").strip()
        matched = [field for field in fields if str(field.get("name") or "").strip() == name]
        if len(matched) > 1:
            raise ValueError(f"{location} matched multiple fields with the same name; use field_id or que_id")
    else:
        matched = []
    if len(matched) != 1:
        raise ValueError(f"{location} could not be resolved")
    return matched[0]


def _default_code_block_input_var(*, field_name: str, que_id: int | None, index: int) -> str:
    seed = _slugify(field_name, default="")
    if seed:
        parts = [part for part in re.split(r"[-_]+", seed) if part]
        candidate = parts[0] + "".join(part.capitalize() for part in parts[1:])
        if candidate and re.match(r"^[A-Za-z_$][A-Za-z0-9_$]*$", candidate):
            return candidate
    return f"field_{que_id or index + 1}"


def _encrypt_question_id(que_id: int) -> str:
    have_prefix = que_id < 0
    abs_que_id = abs(que_id)
    result_length = 4
    ran = abs_que_id * 17
    inp = str(abs_que_id)
    seeds: list[int] = []
    total = 0
    key = 1000013
    for _ in range(max(result_length - len(inp), 0)):
        tmp = (ran % 20) + 71
        ran = ((ran + 13) * 17) % key
        total += tmp
        seeds.append(tmp)
    result = [((total + index + int(char, 16)) % 15) for index, char in enumerate(inp)]
    for seed in seeds:
        position = (ran + seed) % len(result)
        ran = ((ran + 13) * 17) % key
        result.insert(position, seed)
    rendered = "".join(chr(item) if item > 15 else format(item, "X") for item in result)
    return f"-{rendered}" if have_prefix else rendered


def _compose_code_block_input_reference(*, field_name: str, que_id: int) -> str:
    return f"qf_field.{{{field_name}$${_encrypt_question_id(que_id)}$$}}"


def _strip_code_block_generated_input_prelude(code_content: str) -> str:
    _inputs, body = _parse_code_block_inputs_and_body(code_content)
    return body


@dataclass(frozen=True)
class _DataDisplayMarkerSelection:
    data_title_field_name: str | None = None
    data_cover_field_name: str | None = None

    @property
    def has_any(self) -> bool:
        return bool(self.data_title_field_name or self.data_cover_field_name)


@dataclass
class _DataDisplayConfigError(ValueError):
    error_code: str
    message: str
    details: JSONObject = field(default_factory=dict)

    def __str__(self) -> str:
        return self.message


def _normalize_code_block_output_assignment(code_content: str) -> str:
    return re.sub(r"(?<![A-Za-z0-9_$])(?:const|let)\s+qf_output\s*=", "qf_output =", code_content)


@dataclass
class _CodeBlockValidationError(ValueError):
    error_code: str
    message: str
    details: JSONObject = field(default_factory=dict)

    def __str__(self) -> str:
        return self.message


def _code_block_has_effective_output_assignment(code_content: str) -> bool:
    return bool(re.search(r"(?<![A-Za-z0-9_$])qf_output\s*=", str(code_content or "")))


def _field_selector_payload_for_field(field: dict[str, Any]) -> dict[str, Any]:
    payload: dict[str, Any] = {}
    field_id = str(field.get("field_id") or "").strip()
    que_id = _coerce_positive_int(field.get("que_id"))
    name = str(field.get("name") or "").strip()
    if field_id:
        payload["field_id"] = field_id
    elif que_id is not None:
        payload["que_id"] = que_id
    elif name:
        payload["name"] = name
    return payload


def _field_selector_payload_for_selector(value: Any) -> dict[str, Any]:
    if isinstance(value, dict):
        payload = {
            "field_id": str(value.get("field_id") or "").strip() or None,
            "que_id": _coerce_positive_int(value.get("que_id")),
            "name": str(value.get("name") or "").strip() or None,
        }
        return {key: item for key, item in payload.items() if item is not None}
    if isinstance(value, str) and value.strip():
        return {"name": value.strip()}
    return {}


def _code_block_field_display_name(field: dict[str, Any]) -> str:
    name = str(field.get("name") or "").strip()
    if name:
        return name
    field_id = str(field.get("field_id") or "").strip()
    if field_id:
        return field_id
    que_id = _coerce_positive_int(field.get("que_id"))
    if que_id is not None:
        return str(que_id)
    return "未命名代码块"


def _public_code_block_binding_payload(binding: dict[str, Any]) -> dict[str, Any]:
    return {
        "inputs": [
            {
                "field": _field_selector_payload_for_selector(input_item.get("field")),
                "var": input_item.get("var"),
            }
            for input_item in cast(list[dict[str, Any]], binding.get("inputs") or [])
            if _field_selector_payload_for_selector(input_item.get("field"))
        ],
        "code": str(binding.get("code") or ""),
        "auto_trigger": binding.get("auto_trigger"),
        "custom_button_text_enabled": binding.get("custom_button_text_enabled"),
        "custom_button_text": binding.get("custom_button_text"),
        "outputs": [
            {
                "alias": str(output.get("alias") or "").strip(),
                "path": str(output.get("path") or "").strip(),
                "target_field": _field_selector_payload_for_selector(output.get("target_field")),
            }
            for output in cast(list[dict[str, Any]], binding.get("outputs") or [])
            if str(output.get("alias") or "").strip()
            and str(output.get("path") or "").strip()
            and _field_selector_payload_for_selector(output.get("target_field"))
        ],
    }


def _raw_field_selector_present(value: Any) -> bool:
    if isinstance(value, str):
        return bool(value.strip())
    if not isinstance(value, dict):
        return False
    return bool(
        str(value.get("field_id") or "").strip()
        or _coerce_positive_int(value.get("que_id"))
        or str(value.get("name") or value.get("title") or value.get("label") or "").strip()
    )


def _validate_code_block_alias_config(
    *,
    field_name: str,
    raw_binding: dict[str, Any] | None,
    normalized_config: dict[str, Any] | None,
) -> None:
    raw_outputs = raw_binding.get("outputs") if isinstance(raw_binding, dict) and isinstance(raw_binding.get("outputs"), list) else []
    for index, raw_output in enumerate(cast(list[Any], raw_outputs)):
        if not isinstance(raw_output, dict):
            continue
        raw_target = raw_output.get("target_field", raw_output.get("targetField"))
        if not _raw_field_selector_present(raw_target):
            continue
        alias = str(raw_output.get("alias", raw_output.get("alias_name", raw_output.get("aliasName", ""))) or "").strip()
        path = str(raw_output.get("path", raw_output.get("alias_path", raw_output.get("aliasPath", ""))) or "").strip()
        if alias and path:
            continue
        raise _CodeBlockValidationError(
            "CODE_BLOCK_ALIAS_REQUIRED",
            f"code_block field '{field_name}' requires non-empty alias and path for output target binding",
            details={"field_name": field_name, "output_index": index},
        )
    normalized_aliases = cast(list[dict[str, Any]], (normalized_config or {}).get("result_alias_path") or [])
    for index, alias_item in enumerate(normalized_aliases):
        alias_name = str(alias_item.get("alias_name") or "").strip()
        alias_path = str(alias_item.get("alias_path") or "").strip()
        if alias_name and alias_path:
            continue
        raise _CodeBlockValidationError(
            "CODE_BLOCK_ALIAS_REQUIRED",
            f"code_block field '{field_name}' contains an empty output alias configuration",
            details={"field_name": field_name, "alias_index": index},
        )


def _normalize_and_validate_code_block_fields_for_write(
    *,
    fields: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[str]]:
    next_fields = deepcopy(fields)
    normalized_fields: list[str] = []
    for field in next_fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.code_block.value:
            continue
        field_name = _code_block_field_display_name(field)
        raw_binding = field.get("code_block_binding") if isinstance(field.get("code_block_binding"), dict) else None
        binding = _normalize_code_block_binding(raw_binding)
        current_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {}) or None
        _validate_code_block_alias_config(field_name=field_name, raw_binding=raw_binding, normalized_config=current_config)
        normalized = False
        if binding is not None:
            raw_code = str(binding.get("code") or "")
            normalized_code = _normalize_code_block_output_assignment(raw_code)
            if normalized_code != raw_code:
                normalized = True
            binding["code"] = normalized_code
            field["code_block_binding"] = binding
        if current_config is not None:
            raw_code_content = str(current_config.get("code_content") or "")
            normalized_code_content = _normalize_code_block_output_assignment(raw_code_content)
            if normalized_code_content != raw_code_content:
                normalized = True
            current_config["code_content"] = normalized_code_content
            field["code_block_config"] = current_config
            field["config"] = deepcopy(current_config)
        conflicting_binding_and_config = False
        if binding is not None and current_config is not None:
            current_config_code = str(current_config.get("code_content") or "")
            compiled_body = _strip_code_block_generated_input_prelude(current_config_code)
            if current_config_code.strip() and compiled_body != str(binding.get("code") or ""):
                conflicting_binding_and_config = True
        has_outputs = bool((binding or {}).get("outputs")) or bool((current_config or {}).get("result_alias_path"))
        effective_code = ""
        if binding is not None:
            effective_code = str(binding.get("code") or "")
        elif current_config is not None:
            effective_code = _strip_code_block_generated_input_prelude(str(current_config.get("code_content") or ""))
        if has_outputs and not conflicting_binding_and_config and not _code_block_has_effective_output_assignment(effective_code):
            raise _CodeBlockValidationError(
                "CODE_BLOCK_OUTPUT_ASSIGNMENT_MISSING",
                f"code_block field '{field_name}' must assign to qf_output when outputs are configured",
                details={"field_name": field_name},
            )
        if normalized:
            normalized_fields.append(field_name)
    return next_fields, sorted(set(normalized_fields))


def _ensure_code_block_targets_compiled_as_relation_defaults(*, fields: list[dict[str, Any]]) -> None:
    for field in fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.code_block.value:
            continue
        field_name = _code_block_field_display_name(field)
        binding = _normalize_code_block_binding(field.get("code_block_binding"))
        if binding is None:
            continue
        for output_index, output in enumerate(cast(list[dict[str, Any]], binding.get("outputs") or [])):
            target_payload = output.get("target_field")
            if not isinstance(target_payload, dict):
                continue
            target_field = _resolve_field_selector_with_uniqueness(
                fields=fields,
                selector_payload=target_payload,
                location=f"code_block_binding.outputs[{output_index}].target_field",
            )
            if _coerce_any_int(target_field.get("default_type")) == DEFAULT_TYPE_RELATION:
                continue
            raise _CodeBlockValidationError(
                "CODE_BLOCK_TARGET_DEFAULT_INVALID",
                f"code_block field '{field_name}' output target '{_code_block_field_display_name(target_field)}' is not compiled as relation default",
                details={
                    "field_name": field_name,
                    "target_field_name": _code_block_field_display_name(target_field),
                },
            )


def _code_block_repair_suggested_next_call(*, profile: str, app_key: str, field_name: str | None = None) -> JSONObject:
    arguments: JSONObject = {"profile": profile, "app_key": app_key}
    if field_name:
        arguments["field"] = field_name
    return {"tool_name": "app_repair_code_blocks", "arguments": arguments}


def _ensure_field_temp_ids(fields: list[dict[str, Any]]) -> None:
    temp_id = -10000
    for field in fields:
        if not isinstance(field, dict):
            continue
        if _coerce_positive_int(field.get("que_id")) is None and _coerce_any_int(field.get("que_temp_id")) is None:
            field["que_temp_id"] = temp_id
        temp_id -= 100


def _compile_code_block_binding_fields(
    *,
    fields: list[dict[str, Any]],
    current_schema: dict[str, Any],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    next_fields = deepcopy(fields)
    _ensure_field_temp_ids(next_fields)
    by_name = {
        str(field.get("name") or ""): field
        for field in next_fields
        if isinstance(field, dict) and str(field.get("name") or "")
    }
    existing_relations = current_schema.get("questionRelations") if isinstance(current_schema.get("questionRelations"), list) else []
    relation_specs: list[dict[str, Any]] = []
    affected_source_refs: set[int] = set()
    affected_target_refs: set[int] = set()
    for field in next_fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.code_block.value:
            continue
        binding = _normalize_code_block_binding(field.get("code_block_binding"))
        if binding is None:
            continue
        explicit_binding = bool(field.get("_explicit_code_block_binding"))
        binding_outputs = cast(list[dict[str, Any]], binding.get("outputs") or [])
        binding_has_target_bindings = any(
            isinstance(output.get("target_field"), dict) and any(cast(dict[str, Any], output.get("target_field") or {}).values())
            for output in binding_outputs
        )
        current_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {}) or {
            "config_mode": 1,
            "code_content": "",
            "being_hide_on_form": False,
            "result_alias_path": [],
        }
        if binding_outputs and not binding_has_target_bindings and not explicit_binding:
            field["code_block_binding"] = None
            continue
        low_level_code = _strip_code_block_generated_input_prelude(str(current_config.get("code_content") or ""))
        if str(current_config.get("code_content") or "").strip() and low_level_code != str(binding.get("code") or ""):
            raise ValueError(f"code_block field '{field.get('name')}' has conflicting code_block_config.code_content and code_block_binding.code")
        compiled_inputs: list[dict[str, Any]] = []
        input_lines: list[str] = []
        for index, input_item in enumerate(cast(list[dict[str, Any]], binding.get("inputs") or [])):
            source_field = _resolve_field_selector_with_uniqueness(
                fields=next_fields,
                selector_payload=cast(dict[str, Any], input_item.get("field") or {}),
                location=f"code_block_binding.inputs[{index}].field",
            )
            source_que_id = _coerce_positive_int(source_field.get("que_id"))
            if source_que_id is None:
                source_que_id = _coerce_any_int(source_field.get("que_temp_id"))
            if source_que_id is None:
                raise ValueError(f"code_block input field '{source_field.get('name')}' does not have a stable que_id")
            variable_name = str(input_item.get("var") or "").strip() or _default_code_block_input_var(
                field_name=str(source_field.get("name") or ""),
                que_id=source_que_id,
                index=index,
            )
            if not re.match(r"^[A-Za-z_$][A-Za-z0-9_$]*$", variable_name):
                raise ValueError(f"code_block_binding.inputs[{index}].var must be a valid JavaScript identifier")
            compiled_inputs.append({"field": {"name": source_field.get("name"), "que_id": source_que_id}, "var": variable_name})
            input_lines.append(f"const {variable_name} = {_compose_code_block_input_reference(field_name=str(source_field.get('name') or ''), que_id=source_que_id)};")
        compiled_outputs: list[dict[str, Any]] = []
        existing_alias_by_name = {
            str(item.get("alias_name") or ""): item
            for item in cast(list[dict[str, Any]], current_config.get("result_alias_path") or [])
            if str(item.get("alias_name") or "")
        }
        body = _normalize_code_block_output_assignment(str(binding.get("code") or ""))
        compiled_code = "\n".join(input_lines + ([body] if body else [])).strip()
        for output_index, output_item in enumerate(cast(list[dict[str, Any]], binding.get("outputs") or [])):
            alias_name = str(output_item.get("alias") or "").strip()
            alias_path = str(output_item.get("path") or "").strip()
            existing_alias = existing_alias_by_name.get(alias_name) or {}
            alias_id = _coerce_positive_int(existing_alias.get("alias_id"))
            compiled_outputs.append(
                {
                    "alias_name": alias_name,
                    "alias_path": alias_path,
                    "alias_type": 1,
                    "alias_id": alias_id,
                    "sub_alias": [],
                }
            )
            target_payload = output_item.get("target_field")
            if not isinstance(target_payload, dict):
                raise ValueError(f"code_block_binding.outputs[{output_index}].target_field is required")
            target_field = _resolve_field_selector_with_uniqueness(
                fields=next_fields,
                selector_payload=target_payload,
                location=f"code_block_binding.outputs[{output_index}].target_field",
            )
            target_type = str(target_field.get("type") or "")
            if not _integration_output_target_type_supported(target_type):
                raise ValueError(_integration_output_target_type_error("code_block", str(target_field.get("name") or ""), target_type))
            target_que_ref = _coerce_positive_int(target_field.get("que_id"))
            if target_que_ref is None:
                target_que_ref = _coerce_any_int(target_field.get("que_temp_id"))
            compiled_outputs[-1]["target_field"] = {
                "field_id": target_field.get("field_id"),
                "que_id": target_que_ref,
                "name": target_field.get("name"),
            }
        current_config["code_content"] = compiled_code
        current_config["result_alias_path"] = compiled_outputs
        field["code_block_config"] = current_config
        field["config"] = deepcopy(current_config)
        field["auto_trigger"] = binding.get("auto_trigger")
        field["custom_button_text_enabled"] = binding.get("custom_button_text_enabled")
        field["custom_button_text"] = binding.get("custom_button_text")
        field["code_block_binding"] = {
            "inputs": compiled_inputs,
            "code": body,
            "auto_trigger": binding.get("auto_trigger"),
            "custom_button_text_enabled": binding.get("custom_button_text_enabled"),
            "custom_button_text": binding.get("custom_button_text"),
            "outputs": compiled_outputs,
        }
        source_ref = _coerce_positive_int(field.get("que_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(field.get("que_temp_id"))
        if source_ref is not None:
            affected_source_refs.add(source_ref)
        for output_item in compiled_outputs:
            target_payload = output_item.get("target_field")
            if not isinstance(target_payload, dict):
                continue
            target_field = by_name.get(str(target_payload.get("name") or ""))
            if not isinstance(target_field, dict):
                continue
            target_ref = _coerce_positive_int(target_field.get("que_id"))
            if target_ref is None:
                target_ref = _coerce_any_int(target_field.get("que_temp_id"))
            if target_ref is None or source_ref is None:
                continue
            target_field["default_type"] = DEFAULT_TYPE_RELATION
            affected_target_refs.add(target_ref)
            relation_specs.append(
                {
                    "source_field_name": field.get("name"),
                    "source_field_ref": source_ref,
                    "target_field_name": target_field.get("name"),
                    "target_field_ref": target_ref,
                    "alias": output_item.get("alias_name"),
                    "alias_id": _coerce_positive_int(output_item.get("alias_id")),
                }
            )
    current_field_refs: set[int] = set()
    for field in next_fields:
        if not isinstance(field, dict):
            continue
        field_ref = _coerce_positive_int(field.get("que_id"))
        if field_ref is None:
            field_ref = _coerce_any_int(field.get("que_temp_id"))
        if field_ref is not None:
            current_field_refs.add(field_ref)
    carried_relations: list[dict[str, Any]] = []
    for relation in existing_relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_CODE_BLOCK:
            carried_relations.append(deepcopy(relation))
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_ref = _coerce_positive_int(alias_config.get("queId"))
        target_ref = _coerce_positive_int(relation.get("queId"))
        if source_ref not in current_field_refs or target_ref not in current_field_refs:
            continue
        if (source_ref is not None and source_ref in affected_source_refs) or (target_ref is not None and target_ref in affected_target_refs):
            continue
        carried_relations.append(deepcopy(relation))
    carried_relations.extend(_materialize_code_block_question_relations(fields=next_fields, relation_specs=relation_specs))
    existing_target_refs = _collect_code_block_relation_target_refs(cast(list[dict[str, Any]], existing_relations))
    final_target_refs = _collect_code_block_relation_target_refs(carried_relations)
    for field in next_fields:
        if not isinstance(field, dict):
            continue
        field_ref = _coerce_positive_int(field.get("que_id"))
        if field_ref is None:
            field_ref = _coerce_any_int(field.get("que_temp_id"))
        if field_ref is None or field_ref not in existing_target_refs:
            continue
        if field_ref in final_target_refs:
            field["default_type"] = DEFAULT_TYPE_RELATION
            continue
        if _coerce_any_int(field.get("default_type")) == DEFAULT_TYPE_RELATION:
            field["default_type"] = 1
    return next_fields, carried_relations


def _materialize_code_block_question_relations(
    fields: list[dict[str, Any]],
    relation_specs: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    by_name = {str(field.get("name") or ""): field for field in fields if isinstance(field, dict) and str(field.get("name") or "")}
    materialized: list[dict[str, Any]] = []
    for relation in relation_specs:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) == RELATION_TYPE_CODE_BLOCK:
            materialized.append(deepcopy(relation))
            continue
        source_field = by_name.get(str(relation.get("source_field_name") or ""))
        target_field = by_name.get(str(relation.get("target_field_name") or ""))
        if not isinstance(source_field, dict) or not isinstance(target_field, dict):
            continue
        source_ref = _coerce_positive_int(source_field.get("que_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(source_field.get("que_temp_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(relation.get("source_field_ref"))
        target_ref = _coerce_positive_int(target_field.get("que_id"))
        if target_ref is None:
            target_ref = _coerce_any_int(target_field.get("que_temp_id"))
        if target_ref is None:
            target_ref = _coerce_any_int(relation.get("target_field_ref"))
        if source_ref is None or target_ref is None:
            continue
        materialized.append(
            {
                "queId": target_ref,
                "relationType": RELATION_TYPE_CODE_BLOCK,
                "displayedQueId": None,
                "displayedQueInfo": None,
                "qlinkerAlias": relation.get("alias"),
                "aliasConfig": {
                    "queId": source_ref,
                    "queTitle": source_field.get("name"),
                    "qlinkerAlias": relation.get("alias"),
                    "aliasId": _coerce_positive_int(relation.get("alias_id")),
                },
                "matchRuleType": 1,
                "matchRules": [],
                "matchRuleFormula": None,
                "tableMatchRules": [],
                "sortConfig": None,
            }
        )
    return materialized


def _collect_code_block_relation_target_refs(relations: list[dict[str, Any]]) -> set[int]:
    target_refs: set[int] = set()
    for relation in relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_CODE_BLOCK:
            continue
        target_ref = _coerce_any_int(relation.get("queId"))
        if target_ref is not None:
            target_refs.add(target_ref)
    return target_refs


def _code_block_relations_need_source_rebind(question_relations: list[dict[str, Any]]) -> bool:
    for relation in question_relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_CODE_BLOCK:
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_ref = _coerce_any_int(alias_config.get("queId"))
        alias_id = _coerce_positive_int(alias_config.get("aliasId"))
        if (source_ref is not None and source_ref <= 0) or alias_id is None:
            return True
    return False


def _overlay_code_block_binding_fields(*, target_fields: list[dict[str, Any]], source_fields: list[dict[str, Any]]) -> None:
    source_by_name = {
        str(field.get("name") or ""): field
        for field in source_fields
        if isinstance(field, dict) and str(field.get("name") or "")
    }
    for field in target_fields:
        if not isinstance(field, dict):
            continue
        source = source_by_name.get(str(field.get("name") or ""))
        if not isinstance(source, dict):
            continue
        if source.get("code_block_binding") is not None:
            binding = deepcopy(source.get("code_block_binding"))
            if isinstance(binding, dict):
                for input_item in binding.get("inputs") or []:
                    if not isinstance(input_item, dict):
                        continue
                    selector = input_item.get("field")
                    if isinstance(selector, dict):
                        selector["field_id"] = None
                        if _coerce_positive_int(selector.get("que_id")) is None:
                            selector["que_id"] = None
                for output_item in binding.get("outputs") or []:
                    if not isinstance(output_item, dict):
                        continue
                    selector = output_item.get("target_field")
                    if isinstance(selector, dict):
                        selector["field_id"] = None
                        if _coerce_positive_int(selector.get("que_id")) is None:
                            selector["que_id"] = None
            field["code_block_binding"] = binding


def _integration_output_target_type_supported(field_type: str) -> bool:
    return field_type in INTEGRATION_OUTPUT_TARGET_FIELD_TYPES


def _integration_output_target_type_error(binding_kind: str, field_name: str, field_type: str) -> str:
    allowed = ", ".join(INTEGRATION_OUTPUT_TARGET_FIELD_TYPES)
    rendered_type = field_type or "unknown"
    return (
        f"{binding_kind} output target field '{field_name}' uses unsupported field type '{rendered_type}'. "
        f"Allowed target field types: {allowed}"
    )


def _compile_q_linker_binding_fields(
    *,
    fields: list[dict[str, Any]],
    current_schema: dict[str, Any],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    next_fields = deepcopy(fields)
    _ensure_field_temp_ids(next_fields)
    by_name = {
        str(field.get("name") or ""): field
        for field in next_fields
        if isinstance(field, dict) and str(field.get("name") or "")
    }
    existing_relations = current_schema.get("questionRelations") if isinstance(current_schema.get("questionRelations"), list) else []
    relation_specs: list[dict[str, Any]] = []
    affected_source_refs: set[int] = set()
    affected_target_refs: set[int] = set()
    for field in next_fields:
        if not isinstance(field, dict) or field.get("type") != FieldType.q_linker.value:
            continue
        binding = _normalize_q_linker_binding(field.get("q_linker_binding"))
        if binding is None:
            continue
        current_config_source = field.get("remote_lookup_config")
        current_config_raw = _normalize_remote_lookup_config(current_config_source) if current_config_source is not None else None
        explicit_remote_lookup_config = bool(field.get("_explicit_remote_lookup_config"))
        current_config = current_config_raw or {
            "config_mode": 1,
            "url": "",
            "method": "GET",
            "headers": [],
            "body_type": 1,
            "url_encoded_value": [],
            "json_value": None,
            "xml_value": None,
            "result_type": 1,
            "result_format_path": [],
            "query_params": [],
            "auto_trigger": None,
            "custom_button_text_enabled": None,
            "custom_button_text": None,
            "being_insert_value_directly": None,
            "being_hide_on_form": None,
        }
        request = _normalize_q_linker_request(binding.get("request")) or {
            "url": "",
            "method": "GET",
            "headers": [],
            "query_params": [],
            "body_type": 1,
            "url_encoded_value": [],
            "json_value": None,
            "xml_value": None,
            "result_type": 1,
            "auto_trigger": None,
            "custom_button_text_enabled": None,
            "custom_button_text": None,
            "being_insert_value_directly": None,
            "being_hide_on_form": None,
        }
        if int(request.get("config_mode") or 1) != 1:
            raise ValueError(f"q_linker field '{field.get('name')}' currently only supports custom config_mode=1")
        request_view = {
            "config_mode": 1,
            "url": request.get("url"),
            "method": request.get("method"),
            "headers": request.get("headers") or [],
            "body_type": request.get("body_type"),
            "url_encoded_value": request.get("url_encoded_value") or [],
            "json_value": request.get("json_value"),
            "xml_value": request.get("xml_value"),
            "result_type": request.get("result_type"),
            "result_format_path": [],
            "query_params": request.get("query_params") or [],
            "auto_trigger": request.get("auto_trigger"),
            "custom_button_text_enabled": request.get("custom_button_text_enabled"),
            "custom_button_text": request.get("custom_button_text"),
            "being_insert_value_directly": request.get("being_insert_value_directly"),
            "being_hide_on_form": request.get("being_hide_on_form"),
        }
        current_view = {
            "config_mode": current_config.get("config_mode"),
            "url": current_config.get("url"),
            "method": current_config.get("method"),
            "headers": current_config.get("headers") or [],
            "body_type": current_config.get("body_type"),
            "url_encoded_value": current_config.get("url_encoded_value") or [],
            "json_value": current_config.get("json_value"),
            "xml_value": current_config.get("xml_value"),
            "result_type": current_config.get("result_type"),
            "result_format_path": [],
            "query_params": current_config.get("query_params") or [],
            "auto_trigger": current_config.get("auto_trigger"),
            "custom_button_text_enabled": current_config.get("custom_button_text_enabled"),
            "custom_button_text": current_config.get("custom_button_text"),
            "being_insert_value_directly": current_config.get("being_insert_value_directly"),
            "being_hide_on_form": current_config.get("being_hide_on_form"),
        }
        if explicit_remote_lookup_config and current_config_raw is not None and current_view != request_view:
            raise ValueError(f"q_linker field '{field.get('name')}' has conflicting remote_lookup_config and q_linker_binding.request")

        compiled_inputs: list[dict[str, Any]] = []
        compiled_headers = deepcopy(cast(list[dict[str, Any]], request.get("headers") or []))
        compiled_query_params = deepcopy(cast(list[dict[str, Any]], request.get("query_params") or []))
        compiled_url_encoded = deepcopy(cast(list[dict[str, Any]], request.get("url_encoded_value") or []))
        compiled_url = str(request.get("url") or "")
        compiled_json_value = None if request.get("json_value") is None else str(request.get("json_value") or "")
        compiled_xml_value = None if request.get("xml_value") is None else str(request.get("xml_value") or "")
        for index, input_item in enumerate(cast(list[dict[str, Any]], binding.get("inputs") or [])):
            source_field = _resolve_field_selector_with_uniqueness(
                fields=next_fields,
                selector_payload=cast(dict[str, Any], input_item.get("field") or {}),
                location=f"q_linker_binding.inputs[{index}].field",
            )
            source_que_id = _coerce_positive_int(source_field.get("que_id"))
            if source_que_id is None:
                source_que_id = _coerce_any_int(source_field.get("que_temp_id"))
            if source_que_id is None:
                raise ValueError(f"q_linker input field '{source_field.get('name')}' does not have a stable que_id")
            key = str(input_item.get("key") or "").strip()
            source_kind = str(input_item.get("source") or "").strip().lower()
            compiled_inputs.append(
                {
                    "field": {"name": source_field.get("name"), "que_id": source_que_id},
                    "key": key,
                    "source": source_kind,
                }
            )
            kv_item = {"key": key, "value": str(source_que_id), "type": 2}
            if source_kind == "header":
                compiled_headers.append(kv_item)
            elif source_kind == "query_param":
                compiled_query_params.append(kv_item)
            elif source_kind == "url_encoded":
                compiled_url_encoded.append(kv_item)
            elif source_kind == "json_path":
                token = "{{" + key + "}}"
                reference = _compose_qlinker_value_reference(field_name=str(source_field.get("name") or ""), que_id=source_que_id)
                compiled_url = compiled_url.replace(token, reference)
                if compiled_json_value is not None:
                    compiled_json_value = compiled_json_value.replace(token, reference)
                if compiled_xml_value is not None:
                    compiled_xml_value = compiled_xml_value.replace(token, reference)
            else:
                raise ValueError(f"q_linker_binding.inputs[{index}].source must be one of query_param, header, url_encoded, json_path")

        compiled_outputs: list[dict[str, Any]] = []
        existing_alias_by_name = {
            str(item.get("alias_name") or ""): item
            for item in cast(list[dict[str, Any]], current_config.get("result_format_path") or [])
            if str(item.get("alias_name") or "")
        }
        for output_index, output_item in enumerate(cast(list[dict[str, Any]], binding.get("outputs") or [])):
            target_payload = output_item.get("target_field")
            if not isinstance(target_payload, dict):
                raise ValueError(f"q_linker_binding.outputs[{output_index}].target_field is required")
            target_field = _resolve_field_selector_with_uniqueness(
                fields=next_fields,
                selector_payload=target_payload,
                location=f"q_linker_binding.outputs[{output_index}].target_field",
            )
            target_type = str(target_field.get("type") or "")
            if not _integration_output_target_type_supported(target_type):
                raise ValueError(_integration_output_target_type_error("q_linker", str(target_field.get("name") or ""), target_type))
            target_ref = _coerce_positive_int(target_field.get("que_id"))
            if target_ref is None:
                target_ref = _coerce_any_int(target_field.get("que_temp_id"))
            alias_name = str(output_item.get("alias") or "").strip()
            alias_path = str(output_item.get("path") or "").strip()
            compiled_outputs.append(
                {
                    "alias_name": alias_name,
                    "alias_path": alias_path,
                    "alias_id": _coerce_positive_int((existing_alias_by_name.get(alias_name) or {}).get("alias_id")),
                    "target_field": {
                        "field_id": target_field.get("field_id"),
                        "que_id": target_ref,
                        "name": target_field.get("name"),
                    },
                }
            )

        current_config.update(
            {
                "config_mode": 1,
                "url": compiled_url,
                "method": str(request.get("method") or "GET"),
                "headers": compiled_headers,
                "body_type": int(request.get("body_type") or 1),
                "url_encoded_value": compiled_url_encoded,
                "json_value": compiled_json_value,
                "xml_value": compiled_xml_value,
                "result_type": int(request.get("result_type") or 1),
                "result_format_path": compiled_outputs,
                "query_params": compiled_query_params,
                "auto_trigger": request.get("auto_trigger"),
                "custom_button_text_enabled": request.get("custom_button_text_enabled"),
                "custom_button_text": request.get("custom_button_text"),
                "being_insert_value_directly": request.get("being_insert_value_directly"),
                "being_hide_on_form": request.get("being_hide_on_form"),
            }
        )
        field["remote_lookup_config"] = current_config
        field["_explicit_remote_lookup_config"] = False
        field["config"] = deepcopy(current_config)
        field["auto_trigger"] = request.get("auto_trigger")
        field["custom_button_text_enabled"] = request.get("custom_button_text_enabled")
        field["custom_button_text"] = request.get("custom_button_text")
        field["q_linker_binding"] = {
            "inputs": compiled_inputs,
            "request": request_view,
            "outputs": compiled_outputs,
        }
        source_ref = _coerce_positive_int(field.get("que_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(field.get("que_temp_id"))
        if source_ref is not None:
            affected_source_refs.add(source_ref)
        for output_item in compiled_outputs:
            target_payload = output_item.get("target_field")
            if not isinstance(target_payload, dict):
                continue
            target_field = by_name.get(str(target_payload.get("name") or ""))
            if not isinstance(target_field, dict):
                continue
            target_ref = _coerce_positive_int(target_field.get("que_id"))
            if target_ref is None:
                target_ref = _coerce_any_int(target_field.get("que_temp_id"))
            if target_ref is None or source_ref is None:
                continue
            target_field["default_type"] = DEFAULT_TYPE_RELATION
            affected_target_refs.add(target_ref)
            relation_specs.append(
                {
                    "source_field_name": field.get("name"),
                    "source_field_ref": source_ref,
                    "target_field_name": target_field.get("name"),
                    "target_field_ref": target_ref,
                    "alias": output_item.get("alias_name"),
                }
            )

    carried_relations: list[dict[str, Any]] = []
    for relation in existing_relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_Q_LINKER:
            carried_relations.append(deepcopy(relation))
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_ref = _coerce_positive_int(alias_config.get("queId")) or _coerce_positive_int(relation.get("qlinkerQueId"))
        target_ref = _coerce_positive_int(relation.get("queId"))
        if (source_ref is not None and source_ref in affected_source_refs) or (target_ref is not None and target_ref in affected_target_refs):
            continue
        carried_relations.append(deepcopy(relation))
    carried_relations.extend(_materialize_q_linker_question_relations(fields=next_fields, relation_specs=relation_specs))
    existing_target_refs = _collect_q_linker_relation_target_refs(cast(list[dict[str, Any]], existing_relations))
    final_target_refs = _collect_q_linker_relation_target_refs(carried_relations)
    for field in next_fields:
        if not isinstance(field, dict):
            continue
        field_ref = _coerce_positive_int(field.get("que_id"))
        if field_ref is None:
            field_ref = _coerce_any_int(field.get("que_temp_id"))
        if field_ref is None or field_ref not in existing_target_refs:
            continue
        if field_ref in final_target_refs:
            field["default_type"] = DEFAULT_TYPE_RELATION
            continue
        if _coerce_any_int(field.get("default_type")) == DEFAULT_TYPE_RELATION:
            field["default_type"] = 1
    return next_fields, carried_relations


def _materialize_q_linker_question_relations(
    fields: list[dict[str, Any]],
    relation_specs: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    by_name = {str(field.get("name") or ""): field for field in fields if isinstance(field, dict) and str(field.get("name") or "")}
    materialized: list[dict[str, Any]] = []
    for relation in relation_specs:
        if not isinstance(relation, dict):
            continue
        source_field = by_name.get(str(relation.get("source_field_name") or ""))
        target_field = by_name.get(str(relation.get("target_field_name") or ""))
        if not isinstance(source_field, dict) or not isinstance(target_field, dict):
            continue
        source_ref = _coerce_positive_int(source_field.get("que_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(source_field.get("que_temp_id"))
        if source_ref is None:
            source_ref = _coerce_any_int(relation.get("source_field_ref"))
        target_ref = _coerce_positive_int(target_field.get("que_id"))
        if target_ref is None:
            target_ref = _coerce_any_int(target_field.get("que_temp_id"))
        if target_ref is None:
            target_ref = _coerce_any_int(relation.get("target_field_ref"))
        if source_ref is None or target_ref is None:
            continue
        materialized.append(
            {
                "queId": target_ref,
                "relationType": RELATION_TYPE_Q_LINKER,
                "displayedQueId": None,
                "displayedQueInfo": None,
                "qlinkerQueId": source_ref,
                "qlinkerAlias": relation.get("alias"),
                "aliasConfig": {
                    "queId": source_ref,
                    "queTitle": source_field.get("name"),
                    "qlinkerAlias": relation.get("alias"),
                    "aliasId": None,
                },
                "matchRuleType": 1,
                "matchRules": [],
                "matchRuleFormula": None,
                "tableMatchRules": [],
                "sortConfig": None,
            }
        )
    return materialized


def _collect_q_linker_relation_target_refs(relations: list[dict[str, Any]]) -> set[int]:
    target_refs: set[int] = set()
    for relation in relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_Q_LINKER:
            continue
        target_ref = _coerce_any_int(relation.get("queId"))
        if target_ref is not None:
            target_refs.add(target_ref)
    return target_refs


def _q_linker_relations_need_source_rebind(question_relations: list[dict[str, Any]]) -> bool:
    for relation in question_relations:
        if not isinstance(relation, dict):
            continue
        if _coerce_positive_int(relation.get("relationType")) != RELATION_TYPE_Q_LINKER:
            continue
        alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
        source_ref = _coerce_any_int(alias_config.get("queId")) or _coerce_any_int(relation.get("qlinkerQueId"))
        if source_ref is not None and source_ref <= 0:
            return True
    return False


def _overlay_q_linker_binding_fields(*, target_fields: list[dict[str, Any]], source_fields: list[dict[str, Any]]) -> None:
    source_by_name = {
        str(field.get("name") or ""): field
        for field in source_fields
        if isinstance(field, dict) and str(field.get("name") or "")
    }
    for field in target_fields:
        if not isinstance(field, dict):
            continue
        source = source_by_name.get(str(field.get("name") or ""))
        if not isinstance(source, dict):
            continue
        if source.get("q_linker_binding") is not None:
            binding = deepcopy(source.get("q_linker_binding"))
            if isinstance(binding, dict):
                for input_item in binding.get("inputs") or []:
                    if not isinstance(input_item, dict):
                        continue
                    selector = input_item.get("field")
                    if isinstance(selector, dict):
                        selector["field_id"] = None
                        if _coerce_positive_int(selector.get("que_id")) is None:
                            selector["que_id"] = None
                for output_item in binding.get("outputs") or []:
                    if not isinstance(output_item, dict):
                        continue
                    selector = output_item.get("target_field")
                    if isinstance(selector, dict):
                        selector["field_id"] = None
                        if _coerce_positive_int(selector.get("que_id")) is None:
                            selector["que_id"] = None
            field["q_linker_binding"] = binding


def _append_field_to_layout(layout: dict[str, Any], field_name: str) -> dict[str, Any]:
    next_layout = deepcopy(layout)
    if next_layout.get("sections"):
        next_layout["sections"][-1]["rows"].append([field_name])
    else:
        next_layout.setdefault("root_rows", []).append([field_name])
    return next_layout


def _rename_field_in_layout(layout: dict[str, Any], old_name: str, new_name: str) -> dict[str, Any]:
    next_layout = deepcopy(layout)
    for row in next_layout.get("root_rows", []):
        for index, value in enumerate(row):
            if value == old_name:
                row[index] = new_name
    for section in next_layout.get("sections", []):
        for row in section.get("rows", []):
            for index, value in enumerate(row):
                if value == old_name:
                    row[index] = new_name
    return next_layout


def _remove_field_from_layout(layout: dict[str, Any], field_name: str) -> dict[str, Any]:
    next_layout = deepcopy(layout)
    next_layout["root_rows"] = [row for row in ([item for item in row if item != field_name] for row in next_layout.get("root_rows", [])) if row]
    cleaned_sections = []
    for section in next_layout.get("sections", []):
        rows = [row for row in ([item for item in row if item != field_name] for row in section.get("rows", [])) if row]
        if rows:
            section["rows"] = rows
            cleaned_sections.append(section)
    next_layout["sections"] = cleaned_sections
    return next_layout


def _merge_layout(
    *,
    current_layout: dict[str, Any],
    requested_sections: list[dict[str, Any]],
    all_field_names: list[str],
) -> dict[str, Any]:
    all_fields = list(dict.fromkeys(name for name in all_field_names if name))
    referenced = {
        name
        for section in requested_sections
        for row in section.get("rows", [])
        for name in row
        if isinstance(name, str) and name
    }
    requested_section_ids = {
        str(section.get("section_id") or "")
        for section in requested_sections
        if isinstance(section, dict) and section.get("section_id")
    }
    requested_titles = {
        str(section.get("title") or "")
        for section in requested_sections
        if isinstance(section, dict) and section.get("title")
    }
    preserved_root_rows: list[list[str]] = []
    consumed = set(referenced)
    for row in current_layout.get("root_rows", []) or []:
        filtered = [name for name in row if name in all_fields and name not in consumed]
        if filtered:
            preserved_root_rows.append(filtered)
            consumed.update(filtered)
    preserved_sections: list[dict[str, Any]] = []
    for section in current_layout.get("sections", []) or []:
        section_id = str(section.get("section_id") or "")
        title = str(section.get("title") or "")
        if section_id in requested_section_ids or title in requested_titles:
            continue
        rows = []
        for row in section.get("rows", []) or []:
            filtered = [name for name in row if name in all_fields and name not in consumed]
            if filtered:
                rows.append(filtered)
                consumed.update(filtered)
        if rows:
            preserved_sections.append(
                {
                    "section_id": section_id or _slugify(title, default="section"),
                    "title": title or "未命名分组",
                    "rows": rows,
                }
            )
    auto_added_fields = [name for name in all_fields if name not in referenced]
    remaining_fields = [name for name in all_fields if name not in consumed]
    merged_sections = deepcopy(requested_sections) + preserved_sections
    if remaining_fields:
        merged_sections.append(
            {
                "section_id": "ungrouped_fields",
                "title": "未分组字段",
                "rows": [[name] for name in remaining_fields],
            }
        )
    return {
        "layout": {
            "root_rows": preserved_root_rows,
            "sections": merged_sections,
        },
        "auto_added_fields": auto_added_fields,
    }


def _layouts_equal(left: dict[str, Any], right: dict[str, Any]) -> bool:
    return deepcopy(left) == deepcopy(right)


def _layout_field_sequence(layout: dict[str, Any]) -> list[str]:
    sequence: list[str] = []
    for row in layout.get("root_rows", []) or []:
        if isinstance(row, list):
            sequence.extend(str(value) for value in row if isinstance(value, str) and value)
    for section in layout.get("sections", []) or []:
        if not isinstance(section, dict):
            continue
        for row in section.get("rows", []) or []:
            if isinstance(row, list):
                sequence.extend(str(value) for value in row if isinstance(value, str) and value)
    return sequence


def _layouts_semantically_equal(left: dict[str, Any], right: dict[str, Any]) -> bool:
    return _layout_field_sequence(left) == _layout_field_sequence(right)


def _duration_ms(started_at: float) -> int:
    return max(0, int((time.perf_counter() - started_at) * 1000))


def _merge_duration_breakdown(response: JSONObject, **items: int | None) -> None:
    breakdown = response.get("duration_breakdown_ms")
    if not isinstance(breakdown, dict):
        breakdown = {}
        response["duration_breakdown_ms"] = breakdown
    for key, value in items.items():
        if value is None:
            continue
        breakdown[key] = int(value)


def _find_field_section_id(layout: dict[str, Any], field_name: str) -> str | None:
    for section in layout.get("sections", []) or []:
        for row in section.get("rows", []) or []:
            if field_name in row:
                return str(section.get("section_id") or "")
    return None


def _find_unplaced_fields(fields: list[dict[str, Any]], layout: dict[str, Any]) -> list[str]:
    placed = {
        name
        for row in layout.get("root_rows", []) or []
        for name in row
        if isinstance(name, str)
    }
    for section in layout.get("sections", []) or []:
        for row in section.get("rows", []) or []:
            for name in row:
                if isinstance(name, str):
                    placed.add(name)
    return [str(field.get("name") or "") for field in fields if str(field.get("name") or "") and str(field.get("name") or "") not in placed]


def _detect_layout_mode(layout: dict[str, Any]) -> str:
    if layout.get("sections"):
        return "sectioned"
    if layout.get("root_rows"):
        return "flat"
    return "empty"


def _decorate_layout_sections_as_paragraphs(sections: Any) -> list[dict[str, Any]]:
    if not isinstance(sections, list):
        return []
    decorated: list[dict[str, Any]] = []
    for section in sections:
        if not isinstance(section, dict):
            continue
        payload = deepcopy(section)
        payload["type"] = "paragraph"
        payload["paragraph_id"] = payload.get("section_id")
        decorated.append(payload)
    return decorated


def _build_verification_hints(
    *,
    tag_ids: list[int],
    fields: list[dict[str, Any]],
    layout: dict[str, Any],
    views: list[dict[str, Any]],
) -> list[str]:
    hints: list[str] = []
    if not tag_ids:
        hints.append("package attachment not verified")
    if _find_unplaced_fields(fields, layout):
        hints.append("layout has unplaced fields")
    if not views:
        hints.append("no public views detected")
    return hints


def _warning(code: str, message: str, **extra: Any) -> dict[str, Any]:
    warning = {"code": code, "message": message}
    warning.update({key: value for key, value in extra.items() if value is not None})
    return warning


def _warnings_from_verification_hints(hints: list[str]) -> list[dict[str, Any]]:
    mapping = {
        "package attachment not verified": _warning("PACKAGE_ATTACHMENT_UNVERIFIED", "package attachment is not verified"),
        "layout has unplaced fields": _warning("LAYOUT_HAS_UNPLACED_FIELDS", "layout still contains unplaced fields"),
        "no public views detected": _warning("NO_PUBLIC_VIEWS", "no public views were detected"),
        "schema_read_unavailable": _warning("SCHEMA_READ_UNAVAILABLE", "schema summary readback is unavailable"),
        "views_read_unavailable": _warning("VIEWS_READ_UNAVAILABLE", "views summary readback is unavailable"),
        "workflow_read_unavailable": _warning("WORKFLOW_READ_UNAVAILABLE", "workflow summary readback is unavailable"),
    }
    return [deepcopy(mapping[hint]) for hint in hints if hint in mapping]


def _layout_read_warnings(unplaced_fields: list[str]) -> list[dict[str, Any]]:
    if not unplaced_fields:
        return []
    return [_warning("LAYOUT_HAS_UNPLACED_FIELDS", "layout still contains unplaced fields", fields=unplaced_fields)]


def _publish_verify_warnings(*, package_attached: bool | None, views_unavailable: bool, verified: bool) -> list[dict[str, Any]]:
    warnings: list[dict[str, Any]] = []
    if package_attached is False:
        warnings.append(_warning("PACKAGE_NOT_ATTACHED", "target package is not attached after publish verification"))
    if views_unavailable:
        warnings.append(_warning("VIEWS_READBACK_PENDING", "views readback is unavailable during publish verification"))
    if not verified and not warnings:
        warnings.append(_warning("PUBLISH_VERIFY_INCOMPLETE", "publish verification is incomplete"))
    return warnings


def _chart_apply_warnings(
    *,
    failed_items: list[dict[str, Any]],
    readback_unavailable: bool,
    verified: bool,
    delete_readback_issues: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
    warnings: list[dict[str, Any]] = []
    delete_readback_issues = delete_readback_issues or []
    if failed_items:
        warnings.append(_warning("CHART_OPERATION_FAILED", "one or more chart operations failed", failed_count=len(failed_items)))
    still_exists = [item for item in delete_readback_issues if item.get("readback_status") == "still_exists"]
    unavailable = [item for item in delete_readback_issues if item.get("readback_status") == "unavailable"]
    if still_exists:
        warnings.append(
            _warning(
                "CHART_DELETE_READBACK_STILL_EXISTS",
                "one or more delete requests completed, but chart_id readback still found the chart",
                chart_ids=[item.get("chart_id") for item in still_exists if item.get("chart_id")],
            )
        )
    if unavailable:
        warnings.append(
            _warning(
                "CHART_DELETE_READBACK_UNAVAILABLE",
                "one or more delete requests completed, but chart_id readback was unavailable",
                chart_ids=[item.get("chart_id") for item in unavailable if item.get("chart_id")],
            )
        )
    if readback_unavailable:
        warnings.append(_warning("CHART_READBACK_PENDING", "chart readback is unavailable after apply"))
    elif not verified and not failed_items and not delete_readback_issues:
        warnings.append(_warning("CHART_VERIFICATION_INCOMPLETE", "chart apply completed but verification is incomplete"))
    return warnings


def _portal_apply_warnings(
    *,
    publish_failed: bool,
    draft_verified: bool,
    draft_meta_verified: bool,
    live_verified: bool | None,
    live_meta_verified: bool | None,
    publish_requested: bool,
) -> list[dict[str, Any]]:
    warnings: list[dict[str, Any]] = []
    if publish_failed:
        warnings.append(_warning("PORTAL_PUBLISH_FAILED", "portal publish failed after draft update"))
    if not draft_verified or not draft_meta_verified:
        warnings.append(_warning("PORTAL_DRAFT_VERIFICATION_INCOMPLETE", "portal draft verification is incomplete"))
    if publish_requested and (live_verified is False or live_meta_verified is False):
        warnings.append(_warning("PORTAL_LIVE_VERIFICATION_INCOMPLETE", "portal live verification is incomplete"))
    return warnings


def _build_layout_preset_sections(*, preset: LayoutPreset, field_names: list[str]) -> list[dict[str, Any]]:
    ordered = [name for name in field_names if name]
    if preset == LayoutPreset.single_section:
        return [{"section_id": "main", "title": "基础信息", "rows": [ordered[i : i + 2] for i in range(0, len(ordered), 2) if ordered[i : i + 2]]}]
    if preset == LayoutPreset.compact:
        return [{"section_id": "compact", "title": "紧凑布局", "rows": [ordered[i : i + 3] for i in range(0, len(ordered), 3) if ordered[i : i + 3]]}]
    text_like = [name for name in ordered if any(token in name for token in ("说明", "备注", "描述"))]
    contact_like = [name for name in ordered if any(token in name for token in ("电话", "手机", "邮箱", "地址", "联系"))]
    remaining = [name for name in ordered if name not in text_like and name not in contact_like]
    sections: list[dict[str, Any]] = []
    if remaining:
        sections.append({"section_id": "basic", "title": "基础信息", "rows": [remaining[i : i + 2] for i in range(0, len(remaining), 2) if remaining[i : i + 2]]})
    if contact_like:
        sections.append({"section_id": "contact", "title": "联系信息", "rows": [contact_like[i : i + 2] for i in range(0, len(contact_like), 2) if contact_like[i : i + 2]]})
    if text_like:
        sections.append({"section_id": "notes", "title": "补充说明", "rows": [[name] for name in text_like]})
    return sections


def _build_flow_preset(preset: FlowPreset) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    if preset == FlowPreset.basic_fill_then_approve:
        nodes = [
            {"id": "start", "type": "start", "name": "发起"},
            {"id": "fill_1", "type": "fill", "name": "补充信息"},
            {"id": "approve_1", "type": "approve", "name": "审批"},
            {"id": "end", "type": "end", "name": "结束"},
        ]
        transitions = [
            {"from": "start", "to": "fill_1"},
            {"from": "fill_1", "to": "approve_1"},
            {"from": "approve_1", "to": "end"},
        ]
        return nodes, transitions
    nodes = [
        {"id": "start", "type": "start", "name": "发起"},
        {"id": "approve_1", "type": "approve", "name": "审批"},
        {"id": "end", "type": "end", "name": "结束"},
    ]
    transitions = [
        {"from": "start", "to": "approve_1"},
        {"from": "approve_1", "to": "end"},
    ]
    return nodes, transitions


def _merge_flow_graph(
    *,
    base_nodes: list[dict[str, Any]],
    base_transitions: list[dict[str, Any]],
    override_nodes: list[dict[str, Any]],
    override_transitions: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    if not override_nodes and not override_transitions:
        return deepcopy(base_nodes), deepcopy(base_transitions)
    override_nodes, override_transitions = _align_flow_preset_override_ids(
        base_nodes=base_nodes,
        override_nodes=override_nodes,
        override_transitions=override_transitions,
    )
    merged_nodes: list[dict[str, Any]] = []
    override_map = {
        str(node.get("id") or ""): deepcopy(node)
        for node in override_nodes
        if isinstance(node, dict) and str(node.get("id") or "")
    }
    consumed: set[str] = set()
    for node in base_nodes:
        node_id = str(node.get("id") or "")
        if node_id and node_id in override_map:
            merged = deepcopy(node)
            merged.update(override_map[node_id])
            if isinstance(node.get("config"), dict) and isinstance(override_map[node_id].get("config"), dict):
                merged["config"] = {**deepcopy(node["config"]), **deepcopy(override_map[node_id]["config"])}
            if isinstance(node.get("assignees"), dict) and isinstance(override_map[node_id].get("assignees"), dict):
                merged["assignees"] = {**deepcopy(node["assignees"]), **deepcopy(override_map[node_id]["assignees"])}
            if isinstance(node.get("permissions"), dict) and isinstance(override_map[node_id].get("permissions"), dict):
                merged["permissions"] = {**deepcopy(node["permissions"]), **deepcopy(override_map[node_id]["permissions"])}
            merged_nodes.append(merged)
            consumed.add(node_id)
        else:
            merged_nodes.append(deepcopy(node))
    for node in override_nodes:
        node_id = str(node.get("id") or "")
        if node_id and node_id not in consumed:
            merged_nodes.append(deepcopy(node))
    merged_transitions = deepcopy(override_transitions) if override_transitions else deepcopy(base_transitions)
    return merged_nodes, merged_transitions


def _align_flow_preset_override_ids(
    *,
    base_nodes: list[dict[str, Any]],
    override_nodes: list[dict[str, Any]],
    override_transitions: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    """Map simple preset skeleton overrides back onto canonical preset node ids.

    This keeps preset-based plans stable when the caller customizes the single
    approval/fill/copy step semantically but forgets to reuse ids such as
    `approve_1` or `fill_1`.
    """
    patchable_types = {"approve", "fill", "copy"}
    base_by_type: dict[str, list[str]] = {}
    override_by_type: dict[str, list[str]] = {}
    for node in base_nodes:
        if not isinstance(node, dict):
            continue
        node_type = str(node.get("type") or "")
        node_id = str(node.get("id") or "")
        if node_type in patchable_types and node_id:
            base_by_type.setdefault(node_type, []).append(node_id)
    for node in override_nodes:
        if not isinstance(node, dict):
            continue
        node_type = str(node.get("type") or "")
        node_id = str(node.get("id") or "")
        if node_type in patchable_types and node_id:
            override_by_type.setdefault(node_type, []).append(node_id)
    replacement_map: dict[str, str] = {}
    for node_type in patchable_types:
        base_ids = base_by_type.get(node_type) or []
        override_ids = override_by_type.get(node_type) or []
        if len(base_ids) != 1 or len(override_ids) != 1:
            continue
        base_id = base_ids[0]
        override_id = override_ids[0]
        if override_id == base_id:
            continue
        replacement_map[override_id] = base_id
    if not replacement_map:
        return deepcopy(override_nodes), deepcopy(override_transitions)
    aligned_nodes: list[dict[str, Any]] = []
    for node in override_nodes:
        if not isinstance(node, dict):
            continue
        aligned = deepcopy(node)
        node_id = str(aligned.get("id") or "")
        if node_id in replacement_map:
            aligned["id"] = replacement_map[node_id]
        aligned_nodes.append(aligned)
    aligned_transitions: list[dict[str, Any]] = []
    for transition in override_transitions:
        if not isinstance(transition, dict):
            continue
        aligned = deepcopy(transition)
        source = str(aligned.get("from") or "")
        target = str(aligned.get("to") or "")
        if source in replacement_map:
            aligned["from"] = replacement_map[source]
        if target in replacement_map:
            aligned["to"] = replacement_map[target]
        aligned_transitions.append(aligned)
    return aligned_nodes, aligned_transitions


def _extract_directory_items(listed: JSONObject) -> list[dict[str, Any]]:
    if isinstance(listed.get("items"), list):
        return [item for item in listed["items"] if isinstance(item, dict)]
    result = listed.get("result")
    if isinstance(result, dict) and isinstance(result.get("result"), list):
        return [item for item in result["result"] if isinstance(item, dict)]
    if isinstance(result, list):
        return [item for item in result if isinstance(item, dict)]
    return []


def _build_views_preset(preset: ViewsPreset, field_names: list[str]) -> list[dict[str, Any]]:
    ordered = [name for name in field_names if name]
    if preset == ViewsPreset.status_board:
        group_by = next((name for name in ordered if "状态" in name), ordered[0] if ordered else "")
        return [{"name": "按状态看板", "type": "board", "group_by": group_by, "columns": ordered[:3] or ([group_by] if group_by else [])}]
    if preset == ViewsPreset.default_gantt:
        title_like = next((name for name in ordered if "名称" in name or "标题" in name), ordered[0] if ordered else "")
        start_like = next((name for name in ordered if "开始" in name or "起始" in name), "")
        end_like = next((name for name in ordered if "结束" in name or "截止" in name or "完成" in name), "")
        columns = [name for name in (title_like, start_like, end_like) if name]
        return [
            {
                "name": "项目甘特图",
                "type": "gantt",
                "columns": columns,
                "start_field": start_like or None,
                "end_field": end_like or None,
                "title_field": title_like or None,
            }
        ]
    return [{"name": "全部数据", "type": "table", "columns": ordered[: min(5, len(ordered))]}]


def _tag_items_include_app(tag_items: Any, app_key: str) -> bool:
    if not isinstance(tag_items, list):
        return False
    return any(str(item.get("appKey") or "") == app_key for item in tag_items if isinstance(item, dict))


def _public_package_items_from_tag_items(tag_items: Any) -> list[JSONObject]:
    if not isinstance(tag_items, list):
        return []
    public_items: list[JSONObject] = []
    for item in tag_items:
        if not isinstance(item, dict):
            continue
        item_type = _coerce_positive_int(item.get("itemType"))
        if item_type == 1:
            app_key = str(item.get("appKey") or "").strip()
            if not app_key:
                continue
            public_items.append(
                _compact_dict(
                    {
                        "type": "app",
                        "app_key": app_key,
                        "title": str(item.get("title") or item.get("formTitle") or "").strip() or None,
                        "form_id": item.get("formId"),
                    }
                )
            )
            continue
        if item_type == 2:
            dash_key = str(item.get("dashKey") or item.get("pageKey") or "").strip()
            if not dash_key:
                continue
            public_items.append(
                _compact_dict(
                    {
                        "type": "portal",
                        "dash_key": dash_key,
                        "title": str(item.get("title") or item.get("dashName") or "").strip() or None,
                    }
                )
            )
            continue
        if item_type == 3:
            group_id = _coerce_positive_int(item.get("groupId"))
            public_items.append(
                _compact_dict(
                    {
                        "type": "group",
                        "group_id": group_id,
                        "name": str(item.get("title") or item.get("groupName") or "").strip() or None,
                        "items": _public_package_items_from_tag_items(item.get("subItems")),
                    }
                )
            )
    return public_items


def _select_package_layout_tag_items(*, detail: Any, base: Any) -> list[Any] | None:
    base_tag_items = base.get("tagItems") if isinstance(base, dict) and isinstance(base.get("tagItems"), list) else None
    detail_tag_items = detail.get("tagItems") if isinstance(detail, dict) and isinstance(detail.get("tagItems"), list) else None
    if _package_tag_items_include_groups(base_tag_items):
        return deepcopy(base_tag_items)
    if _package_tag_items_include_groups(detail_tag_items):
        return deepcopy(detail_tag_items)
    if detail_tag_items is not None:
        return deepcopy(detail_tag_items)
    if base_tag_items is not None:
        return deepcopy(base_tag_items)
    return None


def _package_tag_items_include_groups(tag_items: Any) -> bool:
    if not isinstance(tag_items, list):
        return False
    return any(isinstance(item, dict) and _coerce_positive_int(item.get("itemType")) == 3 for item in tag_items)


def _flatten_package_resource_identities(items: Any, *, public: bool) -> set[tuple[str, str]]:
    flattened: set[tuple[str, str]] = set()

    def walk(value: Any) -> None:
        if isinstance(value, list):
            for child in value:
                walk(child)
            return
        if not isinstance(value, dict):
            return
        if public:
            item_type = str(value.get("type") or "").strip().lower()
            if item_type == "app" or value.get("app_key") or value.get("appKey"):
                app_key = str(value.get("app_key") or value.get("appKey") or "").strip()
                if app_key:
                    flattened.add(("app", app_key))
            elif item_type == "portal" or value.get("dash_key") or value.get("dashKey"):
                dash_key = str(value.get("dash_key") or value.get("dashKey") or "").strip()
                if dash_key:
                    flattened.add(("portal", dash_key))
            walk(value.get("items"))
            return
        item_type = _coerce_positive_int(value.get("itemType"))
        if item_type == 1:
            app_key = str(value.get("appKey") or "").strip()
            if app_key:
                flattened.add(("app", app_key))
        elif item_type == 2:
            dash_key = str(value.get("dashKey") or value.get("pageKey") or "").strip()
            if dash_key:
                flattened.add(("portal", dash_key))
        walk(value.get("subItems"))

    walk(items)
    return flattened


def _find_duplicate_package_resources(items: Any) -> list[tuple[str, str]]:
    seen: set[tuple[str, str]] = set()
    duplicates: set[tuple[str, str]] = set()

    def walk(value: Any) -> None:
        if isinstance(value, list):
            for child in value:
                walk(child)
            return
        if not isinstance(value, dict):
            return
        item_type = str(value.get("type") or "").strip().lower()
        identity: tuple[str, str] | None = None
        if item_type == "app" or value.get("app_key") or value.get("appKey"):
            app_key = str(value.get("app_key") or value.get("appKey") or "").strip()
            if app_key:
                identity = ("app", app_key)
        elif item_type == "portal" or value.get("dash_key") or value.get("dashKey"):
            dash_key = str(value.get("dash_key") or value.get("dashKey") or "").strip()
            if dash_key:
                identity = ("portal", dash_key)
        if identity is not None:
            if identity in seen:
                duplicates.add(identity)
            seen.add(identity)
        walk(value.get("items"))

    walk(items)
    return sorted(duplicates)


def _collect_backend_package_groups(tag_items: Any) -> dict[int, str]:
    return {
        int(spec["group_id"]): str(spec["name"] or "").strip()
        for spec in _collect_backend_package_group_specs(tag_items)
        if _coerce_positive_int(spec.get("group_id")) is not None
    }


def _collect_backend_package_group_specs(tag_items: Any, *, path: tuple[int, ...] = ()) -> list[JSONObject]:
    if not isinstance(tag_items, list):
        return []
    groups: list[JSONObject] = []
    for index, item in enumerate(tag_items):
        if not isinstance(item, dict):
            continue
        item_type = _coerce_positive_int(item.get("itemType"))
        child_path = (*path, index)
        if item_type == 3:
            group_id = _coerce_positive_int(item.get("groupId"))
            if group_id is None:
                continue
            child_items = item.get("subItems") if isinstance(item.get("subItems"), list) else []
            groups.append(
                {
                    "path": list(child_path),
                    "group_id": group_id,
                    "name": str(item.get("title") or item.get("groupName") or "").strip(),
                    "resource_signature": _package_resource_signature(child_items, public=False),
                }
            )
            groups.extend(_collect_backend_package_group_specs(child_items, path=child_path))
    return groups


def _collect_public_package_group_specs(items: Any, *, path: tuple[int, ...] = ()) -> list[JSONObject]:
    if not isinstance(items, list):
        return []
    groups: list[JSONObject] = []
    for index, item in enumerate(items):
        if not isinstance(item, dict):
            continue
        item_type = str(item.get("type") or "").strip().lower()
        child_path = (*path, index)
        if item_type == "group" or isinstance(item.get("items"), list):
            groups.append(
                {
                    "path": list(child_path),
                    "group_id": _coerce_positive_int(item.get("group_id") or item.get("groupId")),
                    "name": str(item.get("name") or item.get("title") or item.get("group_name") or "").strip(),
                }
            )
            groups.extend(_collect_public_package_group_specs(item.get("items"), path=child_path))
    return groups


def _align_public_package_group_ids(
    items: list[dict[str, Any]],
    *,
    current_group_specs: list[JSONObject],
) -> tuple[list[dict[str, Any]], list[JSONObject]]:
    normalized_items = deepcopy(items)
    used_group_ids: set[int] = set()
    issues: list[JSONObject] = []

    def walk(nodes: list[dict[str, Any]], *, path: tuple[int, ...] = ()) -> None:
        for index, node in enumerate(nodes):
            if not isinstance(node, dict):
                continue
            item_type = str(node.get("type") or "").strip().lower()
            child_path = (*path, index)
            child_items = node.get("items") if isinstance(node.get("items"), list) else []
            if item_type == "group" or isinstance(node.get("items"), list):
                explicit_group_id = _coerce_positive_int(node.get("group_id") or node.get("groupId"))
                if explicit_group_id is not None:
                    node["group_id"] = explicit_group_id
                    used_group_ids.add(explicit_group_id)
                else:
                    group_name = str(node.get("name") or node.get("title") or node.get("group_name") or "").strip()
                    matched_group, ambiguity = _match_existing_package_group(
                        group_name=group_name,
                        child_items=child_items,
                        path=child_path,
                        current_group_specs=current_group_specs,
                        used_group_ids=used_group_ids,
                    )
                    if ambiguity is not None:
                        issues.append(ambiguity)
                    elif matched_group is not None:
                        matched_group_id = _coerce_positive_int(matched_group.get("group_id"))
                        if matched_group_id is not None:
                            node["group_id"] = matched_group_id
                            used_group_ids.add(matched_group_id)
                walk(child_items, path=child_path)

    walk(normalized_items)
    return normalized_items, issues


def _match_existing_package_group(
    *,
    group_name: str,
    child_items: list[dict[str, Any]],
    path: tuple[int, ...],
    current_group_specs: list[JSONObject],
    used_group_ids: set[int],
) -> tuple[JSONObject | None, JSONObject | None]:
    desired_signature = _package_resource_signature(child_items, public=True)
    candidates = [
        spec
        for spec in current_group_specs
        if _coerce_positive_int(spec.get("group_id")) is not None
        and int(spec["group_id"]) not in used_group_ids
        and str(spec.get("name") or "").strip() == group_name
    ]
    exact_matches = [spec for spec in candidates if spec.get("resource_signature") == desired_signature]
    if len(exact_matches) == 1:
        return exact_matches[0], None
    path_matches = [spec for spec in candidates if tuple(spec.get("path") or ()) == path]
    if len(path_matches) == 1:
        return path_matches[0], None
    if len(candidates) == 1:
        return candidates[0], None
    ambiguous_matches = exact_matches if len(exact_matches) > 1 else candidates if len(candidates) > 1 else []
    if ambiguous_matches:
        return None, {
            "path": list(path),
            "name": group_name,
            "candidate_group_ids": [
                int(spec["group_id"])
                for spec in ambiguous_matches
                if _coerce_positive_int(spec.get("group_id")) is not None
            ],
        }
    return None, None


def _package_resource_signature(items: Any, *, public: bool) -> tuple[tuple[str, str], ...]:
    return tuple(sorted(_flatten_package_resource_identities(items, public=public)))


def _publicize_package_list_item(item: dict[str, Any]) -> JSONObject:
    package_id = _coerce_positive_int(item.get("package_id") or item.get("packageId") or item.get("tag_id") or item.get("tagId"))
    raw_package_id = package_id if package_id is not None else item.get("package_id") or item.get("packageId") or item.get("tag_id") or item.get("tagId")
    package_name = str(item.get("package_name") or item.get("packageName") or item.get("tag_name") or item.get("tagName") or "").strip()
    raw_items = item.get("tagItems") if isinstance(item.get("tagItems"), list) else None
    item_count = None
    raw_item_count = item.get("item_count") if "item_count" in item else item.get("itemCount")
    try:
        if raw_item_count is not None:
            coerced_count = int(raw_item_count)
            if coerced_count >= 0:
                item_count = coerced_count
    except (TypeError, ValueError):
        item_count = None
    if item_count is None and raw_items is not None:
        item_count = len(raw_items)
    tag_icon = item.get("tag_icon") if "tag_icon" in item else item.get("tagIcon")
    return {
        "package_id": raw_package_id,
        "package_name": package_name,
        "tag_id": raw_package_id,
        "tag_name": package_name,
        "publish_status": item.get("publish_status") if "publish_status" in item else item.get("publishStatus"),
        "being_trial": item.get("being_trial") if "being_trial" in item else item.get("beingTrial"),
        "item_count": item_count,
        "item_preview": deepcopy(item.get("item_preview") if "item_preview" in item else item.get("itemPreview") or []),
        "tag_icon": tag_icon,
        "icon_config": workspace_icon_config(str(tag_icon).strip() if tag_icon not in (None, "") else None),
        "permissions": {
            "can_add_app": item.get("can_add_app") if "can_add_app" in item else item.get("addAppStatus"),
            "can_edit_app": item.get("can_edit_app") if "can_edit_app" in item else item.get("editAppStatus"),
            "can_delete_app": item.get("can_delete_app") if "can_delete_app" in item else item.get("delAppStatus"),
            "can_edit_package": item.get("can_edit_package") if "can_edit_package" in item else item.get("editTagStatus"),
        },
    }


def _package_list_item_matches_query(item: dict[str, Any], query: str) -> bool:
    needle = str(query or "").strip().casefold()
    if not needle:
        return True
    haystacks = (
        item.get("package_id"),
        item.get("tag_id"),
        item.get("package_name"),
        item.get("tag_name"),
    )
    return any(needle in str(value or "").casefold() for value in haystacks)


def _backend_package_items_from_public_items(items: list[dict[str, Any]], group_ids_by_path: dict[tuple[int, ...], int], *, path: tuple[int, ...] = ()) -> list[JSONObject]:
    backend_items: list[JSONObject] = []
    for index, item in enumerate(items):
        if not isinstance(item, dict):
            raise ValueError(f"items[{index}] must be an object")
        item_type = str(item.get("type") or "").strip().lower()
        child_path = (*path, index)
        if item_type == "app" or item.get("app_key") or item.get("appKey"):
            app_key = str(item.get("app_key") or item.get("appKey") or "").strip()
            if not app_key:
                raise ValueError(f"items[{index}].app_key is required")
            backend_items.append(
                _compact_dict(
                    {
                        "itemType": 1,
                        "appKey": app_key,
                        "title": str(item.get("title") or item.get("name") or "").strip() or None,
                        "formId": item.get("form_id") or item.get("formId"),
                    }
                )
            )
            continue
        if item_type == "portal" or item.get("dash_key") or item.get("dashKey"):
            dash_key = str(item.get("dash_key") or item.get("dashKey") or "").strip()
            if not dash_key:
                raise ValueError(f"items[{index}].dash_key is required")
            backend_items.append(
                _compact_dict(
                    {
                        "itemType": 2,
                        "dashKey": dash_key,
                        "title": str(item.get("title") or item.get("name") or "").strip() or None,
                    }
                )
            )
            continue
        if item_type == "group" or isinstance(item.get("items"), list):
            group_id = group_ids_by_path.get(child_path) or _coerce_positive_int(item.get("group_id") or item.get("groupId"))
            if group_id is None:
                raise ValueError(f"items[{index}].group_id is required after group creation")
            group_name = str(item.get("name") or item.get("title") or item.get("group_name") or "").strip()
            if not group_name:
                raise ValueError(f"items[{index}].name is required")
            child_items = item.get("items") if isinstance(item.get("items"), list) else []
            backend_items.append(
                {
                    "itemType": 3,
                    "groupId": group_id,
                    "title": group_name,
                    "subItems": _backend_package_items_from_public_items(child_items, group_ids_by_path, path=child_path),
                }
            )
            continue
        raise ValueError(f"items[{index}].type must be app, portal, or group")
    return backend_items


def _extract_package_group_id(value: Any) -> int | None:
    if isinstance(value, dict):
        direct = _coerce_positive_int(value.get("groupId") or value.get("group_id") or value.get("id"))
        if direct is not None:
            return direct
        for nested_key in ("result", "data", "group"):
            nested = value.get(nested_key)
            nested_id = _extract_package_group_id(nested)
            if nested_id is not None:
                return nested_id
    if isinstance(value, list):
        for item in value:
            nested_id = _extract_package_group_id(item)
            if nested_id is not None:
                return nested_id
    return None


def _publicize_package_apply_failure(result: JSONObject, *, profile: str, normalized_args: JSONObject) -> JSONObject:
    public_result = deepcopy(result)
    public_result["normalized_args"] = deepcopy(normalized_args)
    suggested = public_result.get("suggested_next_call")
    if isinstance(suggested, dict):
        public_result["suggested_next_call"] = {
            "tool_name": "package_apply",
            "arguments": {"profile": profile, **deepcopy(normalized_args)},
        }
    for key in ("tag_id", "tag_name", "tag_icon"):
        public_result.pop(key, None)
    details = public_result.get("details")
    if isinstance(details, dict) and "tag_id" in details:
        details["package_id"] = details.pop("tag_id")
    return public_result


def _verify_package_attachment(packages: PackageTools, *, profile: str, tag_id: int, app_key: str, attempts: int = 2) -> JSONObject:
    last_result: JSONObject = {"result": {}}
    for _ in range(max(attempts, 1)):
        last_result = packages.package_get(profile=profile, tag_id=tag_id, include_raw=True)
        result = last_result.get("result") if isinstance(last_result.get("result"), dict) else {}
        if _tag_items_include_app(result.get("tagItems"), app_key):
            return last_result
    return last_result


def _field_question_overlay_keys(field: dict[str, Any]) -> set[str]:
    raw_value = field.get("_question_overlay_keys")
    if isinstance(raw_value, set):
        return {str(item) for item in raw_value if isinstance(item, str) and item}
    if isinstance(raw_value, list):
        return {str(item) for item in raw_value if isinstance(item, str) and item}
    return set()


def _field_needs_question_rebuild(field: dict[str, Any]) -> bool:
    return not isinstance(field.get("_question_template"), dict) or bool(field.get("_question_rebuild_required"))


def _extract_template_row_lengths(schema: dict[str, Any]) -> tuple[dict[int, int], dict[str, int]]:
    lengths_by_que_id: dict[int, int] = {}
    lengths_by_title: dict[str, int] = {}

    def remember_row(row: Any) -> None:
        if not isinstance(row, list):
            return
        questions = [question for question in row if isinstance(question, dict)]
        row_length = len(questions)
        if row_length <= 0:
            return
        for question in questions:
            que_id = _coerce_nonnegative_int(question.get("queId"))
            if que_id is not None:
                lengths_by_que_id[que_id] = row_length
            title = str(question.get("queTitle") or "").strip()
            if title:
                lengths_by_title[title] = row_length

    for row in schema.get("formQues", []) or []:
        if not isinstance(row, list):
            continue
        if len(row) == 1 and isinstance(row[0], dict) and _coerce_positive_int(row[0].get("queType")) == 24:
            for inner_row in row[0].get("innerQuestions", []) or []:
                remember_row(inner_row)
            continue
        remember_row(row)
    return lengths_by_que_id, lengths_by_title


def _field_template_row_length(
    field: dict[str, Any],
    *,
    lengths_by_que_id: dict[int, int],
    lengths_by_title: dict[str, int],
) -> int | None:
    que_id = _coerce_nonnegative_int(field.get("que_id"))
    if que_id is not None and que_id in lengths_by_que_id:
        return lengths_by_que_id[que_id]
    template = field.get("_question_template")
    if isinstance(template, dict):
        template_que_id = _coerce_nonnegative_int(template.get("queId"))
        if template_que_id is not None and template_que_id in lengths_by_que_id:
            return lengths_by_que_id[template_que_id]
        template_title = str(template.get("queTitle") or "").strip()
        if template_title and template_title in lengths_by_title:
            return lengths_by_title[template_title]
    field_name = str(field.get("name") or "").strip()
    if field_name and field_name in lengths_by_title:
        return lengths_by_title[field_name]
    return None


def _row_needs_width_reflow(expected_template_lengths: list[int], current_row_length: int) -> bool:
    if current_row_length <= 0:
        return False
    return any(length != current_row_length for length in expected_template_lengths)


_FORM_SAVE_BASE_KEYS = (
    "formDesc",
    "formTheme",
    "formAttach",
    "formStyle",
    "serialNumType",
    "serialNumConfig",
    "dataTitleQueId",
    "dataCoverQueId",
    "attachVisibleOnlyConfig",
    "externalLang",
    "hideCopyright",
)

_QUESTION_RELATION_SAVE_KEYS = (
    "queId",
    "relationType",
    "displayedQueId",
    "qlinkerAlias",
    "displayedQueInfo",
    "aliasConfig",
    "matchRules",
    "tableMatchRules",
    "matchRuleType",
    "matchRuleFormula",
    "sortConfig",
)

_RELATION_QUESTION_SAVE_KEYS = (
    "queId",
    "queTempId",
    "queType",
    "queOriginType",
    "queTitle",
    "queWidth",
    "scanType",
    "status",
    "required",
    "queHint",
    "linkedQuestions",
    "logicalShow",
    "queDefaultType",
    "queDefaultValue",
    "queDefaultValues",
    "subQueWidth",
    "innerQuestions",
    "minOpts",
    "maxOpts",
    "beingHide",
    "beingDesensitized",
    "relationDisplayMode",
    "customRenderConfig",
)

_REFERENCE_CONFIG_SAVE_KEYS = (
    "referAppKey",
    "referQueId",
    "customButtonText",
    "beingTableSource",
    "referMatchRules",
    "canAddData",
    "dataAdditionButtonText",
    "canViewProcessLog",
    "optionalDataNum",
    "beingDataLogVisible",
    "beingDefaultFormulaAutoFillEnabled",
    "defaultValueMatchRules",
    "configShowForm",
    "configSortFieldId",
    "configAsc",
    "dataShowForm",
    "defaultRow",
    "fieldNameShow",
    "dataSortFieldId",
    "dataSortAsc",
)

_REFERENCE_QUESTION_SAVE_KEYS = (
    "queId",
    "queTitle",
    "queType",
    "queAuth",
    "ordinal",
)

_REFERENCE_FILL_RULE_SAVE_KEYS = (
    "queId",
    "relatedQueId",
    "queTitle",
    "relatedQueTitle",
)

_REFERENCE_AUTH_QUESTION_SAVE_KEYS = (
    "queId",
    "queAuth",
)


def _copy_present_keys(
    source: dict[str, Any],
    keys: tuple[str, ...],
    *,
    keep_none_keys: tuple[str, ...] = (),
) -> dict[str, Any]:
    payload: dict[str, Any] = {}
    keep_none = set(keep_none_keys)
    for key in keys:
        if key not in source:
            continue
        value = source.get(key)
        if value is None and key not in keep_none:
            continue
        payload[key] = deepcopy(value)
    return payload


def _looks_like_backend_encoded_formula(value: str) -> bool:
    if len(value) <= 32:
        return False
    encoded = value[16:-16]
    if not encoded:
        return False
    try:
        decoded = base64.b64decode(encoded, validate=True).decode("utf-8")
        unquote_plus(decoded)
    except Exception:
        return False
    return True


def _encode_formula_for_backend_save(value: Any) -> Any:
    if not isinstance(value, str) or not value:
        return value
    if _looks_like_backend_encoded_formula(value):
        return value
    encoded = quote_plus(value, encoding="utf-8")
    b64_value = base64.b64encode(encoded.encode("utf-8")).decode("ascii")
    alphabet = string.ascii_letters + string.digits
    prefix = "".join(random.choice(alphabet) for _ in range(16))
    suffix = "".join(random.choice(alphabet) for _ in range(16))
    return f"{prefix}{b64_value}{suffix}"


def _normalize_formula_defaults_for_save(value: Any) -> None:
    if isinstance(value, list):
        for item in value:
            _normalize_formula_defaults_for_save(item)
        return
    if not isinstance(value, dict):
        return
    if _coerce_any_int(value.get("queDefaultType")) == DEFAULT_TYPE_FORMULA and value.get("queDefaultValue"):
        value["queDefaultValue"] = _encode_formula_for_backend_save(value.get("queDefaultValue"))
    for key in ("subQuestions", "innerQuestions"):
        nested = value.get(key)
        if isinstance(nested, (list, dict)):
            _normalize_formula_defaults_for_save(nested)


def _normalize_reference_question_for_save(value: Any, *, ordinal: int) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    payload = _copy_present_keys(value, _REFERENCE_QUESTION_SAVE_KEYS)
    que_id = _coerce_any_int(value.get("queId"))
    if que_id is not None:
        payload["queId"] = que_id
    if "ordinal" not in payload:
        payload["ordinal"] = _coerce_nonnegative_int(value.get("ordinal"))
    if payload.get("ordinal") is None:
        payload["ordinal"] = ordinal
    if not any(key in payload for key in ("queId", "queTitle", "queType")):
        return None
    return payload


def _normalize_reference_fill_rule_for_save(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    payload = _copy_present_keys(value, _REFERENCE_FILL_RULE_SAVE_KEYS)
    que_id = _coerce_nonnegative_int(value.get("queId"))
    related_que_id = _coerce_nonnegative_int(value.get("relatedQueId", value.get("referQueId")))
    if que_id is not None:
        payload["queId"] = que_id
    if related_que_id is not None:
        payload["relatedQueId"] = related_que_id
    if "relatedQueTitle" not in payload and value.get("referQueTitle") is not None:
        payload["relatedQueTitle"] = str(value.get("referQueTitle") or "")
    if "queId" not in payload or "relatedQueId" not in payload:
        return None
    return payload


def _normalize_reference_auth_question_for_save(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    payload = _copy_present_keys(value, _REFERENCE_AUTH_QUESTION_SAVE_KEYS)
    que_id = _coerce_any_int(value.get("queId"))
    if que_id is not None:
        payload["queId"] = que_id
    que_auth = _coerce_nonnegative_int(value.get("queAuth"))
    if que_auth is not None:
        payload["queAuth"] = que_auth
    sub_ques = [
        item
        for item in (
            _normalize_reference_auth_question_for_save(raw_item)
            for raw_item in cast(list[Any], value.get("subQues") or [])
        )
        if item is not None
    ]
    inner_ques = [
        item
        for item in (
            _normalize_reference_auth_question_for_save(raw_item)
            for raw_item in cast(list[Any], value.get("innerQues") or [])
        )
        if item is not None
    ]
    if sub_ques or "subQues" in value:
        payload["subQues"] = sub_ques
    if inner_ques or "innerQues" in value:
        payload["innerQues"] = inner_ques
    if "queId" not in payload or "queAuth" not in payload:
        return None
    return payload


def _dedupe_reference_auth_questions(auth_questions: list[dict[str, Any]]) -> list[dict[str, Any]]:
    deduped: list[dict[str, Any]] = []
    seen_que_ids: set[int] = set()
    for item in auth_questions:
        normalized_item = _normalize_reference_auth_question_for_save(item)
        if normalized_item is None:
            continue
        que_id = _coerce_any_int(normalized_item.get("queId"))
        if que_id is None or que_id in seen_que_ids:
            continue
        seen_que_ids.add(que_id)
        deduped.append(normalized_item)
    return deduped


_REFERENCE_FIELD_HIDDEN_AUTH = 2
_REFERENCE_FIELD_VISIBLE_AUTH = 3


def _synthesize_reference_auth_questions_for_save(
    *,
    source: dict[str, Any],
    field: dict[str, Any],
) -> list[dict[str, Any]]:
    config = field.get("config") if isinstance(field.get("config"), dict) else {}
    synthesized: list[dict[str, Any]] = []

    if isinstance(config.get("refer_auth_ques"), list):
        synthesized.extend(cast(list[dict[str, Any]], config.get("refer_auth_ques") or []))
    if synthesized:
        return _dedupe_reference_auth_questions(synthesized)

    refer_question_ids_by_name: dict[str, int] = {}
    for raw_item in cast(list[Any], source.get("referQuestions") or []):
        if not isinstance(raw_item, dict):
            continue
        que_id = _coerce_any_int(raw_item.get("queId"))
        name = str(raw_item.get("queTitle") or "").strip()
        if que_id is None or not name or name in refer_question_ids_by_name:
            continue
        refer_question_ids_by_name[name] = que_id

    visible_fields = cast(list[dict[str, Any]], field.get("visible_fields") or [])
    for item in visible_fields:
        if not isinstance(item, dict):
            continue
        que_id = _coerce_any_int(item.get("que_id"))
        if que_id is None:
            name = str(item.get("name") or "").strip()
            que_id = refer_question_ids_by_name.get(name)
        if que_id is None:
            continue
        synthesized.append({"queId": que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
    if synthesized:
        return _dedupe_reference_auth_questions(synthesized)

    auth_field_que_ids = cast(list[Any], config.get("auth_field_que_ids") or [])
    for raw_que_id in auth_field_que_ids:
        que_id = _coerce_any_int(raw_que_id)
        if que_id is None:
            continue
        synthesized.append({"queId": que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
    if synthesized:
        return _dedupe_reference_auth_questions(synthesized)

    for raw_item in cast(list[Any], source.get("referQuestions") or []):
        if not isinstance(raw_item, dict):
            continue
        que_id = _coerce_any_int(raw_item.get("queId"))
        if que_id is None:
            continue
        synthesized.append(
            {
                "queId": que_id,
                "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH,
            }
        )
    if synthesized:
        return _dedupe_reference_auth_questions(synthesized)

    fallback_que_id = _coerce_any_int(field.get("target_field_que_id"))
    if fallback_que_id is not None:
        synthesized.append({"queId": fallback_que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
    return _dedupe_reference_auth_questions(synthesized)


def _reference_question_auth_overrides_for_save(
    *,
    source: dict[str, Any],
    field: dict[str, Any],
) -> dict[int, int]:
    overrides: dict[int, int] = {}
    visible_que_ids: set[int] = set()

    for item in cast(list[Any], field.get("visible_fields") or []):
        if not isinstance(item, dict):
            continue
        que_id = _coerce_any_int(item.get("que_id"))
        if que_id is not None:
            visible_que_ids.add(que_id)

    if not visible_que_ids:
        refer_auth_ques = _synthesize_reference_auth_questions_for_save(source=source, field=field)
        for item in refer_auth_ques:
            que_id = _coerce_any_int(item.get("queId"))
            que_auth = _coerce_nonnegative_int(item.get("queAuth"))
            if que_id is None or que_auth is None:
                continue
            overrides[que_id] = que_auth
        if overrides:
            return overrides

    for raw_item in cast(list[Any], source.get("referQuestions") or []):
        if not isinstance(raw_item, dict):
            continue
        que_id = _coerce_any_int(raw_item.get("queId"))
        if que_id is None:
            continue
        overrides[que_id] = (
            _REFERENCE_FIELD_VISIBLE_AUTH if que_id in visible_que_ids else _REFERENCE_FIELD_HIDDEN_AUTH
        )
    return overrides


def _reference_question_matches_visible_selector(question: dict[str, Any], selector: dict[str, Any]) -> bool:
    question_que_id = _coerce_any_int(question.get("queId"))
    selector_que_id = _coerce_any_int(selector.get("que_id"))
    if question_que_id is not None and selector_que_id is not None and question_que_id == selector_que_id:
        return True
    question_name = str(question.get("queTitle") or "").strip()
    selector_name = str(selector.get("name") or "").strip()
    return bool(question_name and selector_name and question_name == selector_name)


def _build_reference_question_from_visible_selector(
    selector: dict[str, Any],
    *,
    ordinal: int,
) -> dict[str, Any] | None:
    return _normalize_reference_question_for_save(
        {
            "queId": _coerce_any_int(selector.get("que_id")),
            "queTitle": str(selector.get("name") or "").strip() or None,
            "queType": str(selector.get("type") or "2"),
            "ordinal": ordinal,
        },
        ordinal=ordinal,
    )


def _canonicalize_reference_questions_for_save(
    *,
    source: dict[str, Any],
    field: dict[str, Any],
) -> list[dict[str, Any]]:
    relation_config_explicit = bool(field.get("_relation_config_explicit"))
    normalized_source_questions = [
        item
        for item in (
            _normalize_reference_question_for_save(raw_item, ordinal=index)
            for index, raw_item in enumerate(cast(list[Any], source.get("referQuestions") or []), start=1)
        )
        if item is not None
    ]
    if not relation_config_explicit:
        return normalized_source_questions

    display_field = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
    visible_fields = [item for item in cast(list[Any], field.get("visible_fields") or []) if isinstance(item, dict)]
    ordered_visible_selectors: list[dict[str, Any]] = []
    if display_field is not None:
        ordered_visible_selectors.append(display_field)
    for item in visible_fields:
        if any(_relation_target_field_matches(existing, item) for existing in ordered_visible_selectors):
            continue
        ordered_visible_selectors.append(item)

    if not ordered_visible_selectors:
        return normalized_source_questions

    canonical_questions: list[dict[str, Any]] = []
    used_source_indexes: set[int] = set()

    for ordinal, selector in enumerate(ordered_visible_selectors, start=1):
        matched_index: int | None = None
        matched_item: dict[str, Any] | None = None
        for index, item in enumerate(normalized_source_questions):
            if index in used_source_indexes:
                continue
            if _reference_question_matches_visible_selector(item, selector):
                matched_index = index
                matched_item = deepcopy(item)
                break
        if matched_item is None:
            matched_item = _build_reference_question_from_visible_selector(selector, ordinal=ordinal)
        if matched_item is None:
            continue
        matched_item["ordinal"] = ordinal
        canonical_questions.append(matched_item)
        if matched_index is not None:
            used_source_indexes.add(matched_index)

    source_target_app_key = str(source.get("referAppKey") or "").strip()
    target_app_key = str(field.get("target_app_key") or "").strip()
    preserve_remaining_source_questions = not source_target_app_key or source_target_app_key == target_app_key

    if preserve_remaining_source_questions:
        next_ordinal = len(canonical_questions) + 1
        for index, item in enumerate(normalized_source_questions):
            if index in used_source_indexes:
                continue
            remaining_item = deepcopy(item)
            remaining_item["ordinal"] = next_ordinal
            next_ordinal += 1
            canonical_questions.append(remaining_item)

    return canonical_questions


def _canonicalize_reference_auth_questions_for_save(
    *,
    source: dict[str, Any],
    refer_questions: list[dict[str, Any]],
    relation_config_explicit: bool,
) -> list[dict[str, Any]]:
    source_auth_questions = [
        item
        for item in (
            _normalize_reference_auth_question_for_save(raw_item)
            for raw_item in cast(list[Any], source.get("referAuthQues") or [])
        )
        if item is not None
    ]
    source_auth_by_que_id: dict[int, dict[str, Any]] = {}
    for item in source_auth_questions:
        que_id = _coerce_any_int(item.get("queId"))
        if que_id is None or que_id in source_auth_by_que_id:
            continue
        source_auth_by_que_id[que_id] = item

    if not relation_config_explicit:
        auth_questions: list[dict[str, Any]] = []
        seen_que_ids: set[int] = set()
        refer_question_auth_by_que_id: dict[int, int] = {}
        for item in refer_questions:
            que_id = _coerce_any_int(item.get("queId"))
            que_auth = _coerce_nonnegative_int(item.get("queAuth"))
            if que_id is None or que_auth is None or que_id in refer_question_auth_by_que_id:
                continue
            refer_question_auth_by_que_id[que_id] = que_auth

        for item in source_auth_questions:
            que_id = _coerce_any_int(item.get("queId"))
            if que_id is None or que_id in seen_que_ids:
                continue
            payload = deepcopy(item)
            if que_id in refer_question_auth_by_que_id:
                payload["queAuth"] = refer_question_auth_by_que_id[que_id]
            auth_questions.append(payload)
            seen_que_ids.add(que_id)

        for item in refer_questions:
            que_id = _coerce_any_int(item.get("queId"))
            que_auth = _coerce_nonnegative_int(item.get("queAuth"))
            if que_id is None or que_auth is None or que_id in seen_que_ids:
                continue
            payload = deepcopy(source_auth_by_que_id.get(que_id) or {"queId": que_id})
            payload["queId"] = que_id
            payload["queAuth"] = que_auth
            auth_questions.append(payload)
            seen_que_ids.add(que_id)

        return _dedupe_reference_auth_questions(auth_questions)

    auth_questions: list[dict[str, Any]] = []
    for item in refer_questions:
        que_id = _coerce_any_int(item.get("queId"))
        que_auth = _coerce_nonnegative_int(item.get("queAuth"))
        if que_id is None or que_auth is None:
            continue
        payload = deepcopy(source_auth_by_que_id.get(que_id) or {"queId": que_id})
        payload["queId"] = que_id
        payload["queAuth"] = que_auth
        auth_questions.append(payload)
    return _dedupe_reference_auth_questions(auth_questions)


def _enforce_reference_config_consistency_for_save(
    payload: dict[str, Any],
    *,
    field: dict[str, Any],
) -> dict[str, Any]:
    relation_config_explicit = bool(field.get("_relation_config_explicit"))
    refer_questions = [
        item
        for item in (
            _normalize_reference_question_for_save(raw_item, ordinal=index)
            for index, raw_item in enumerate(cast(list[Any], payload.get("referQuestions") or []), start=1)
        )
        if item is not None
    ]
    if not refer_questions:
        return payload

    refer_auth_ques = _dedupe_reference_auth_questions(
        [
            item
            for item in (
                _normalize_reference_auth_question_for_save(raw_item)
                for raw_item in cast(list[Any], payload.get("referAuthQues") or [])
            )
            if item is not None
        ]
    )
    refer_auth_by_que_id: dict[int, int] = {}
    for item in refer_auth_ques:
        que_id = _coerce_any_int(item.get("queId"))
        que_auth = _coerce_nonnegative_int(item.get("queAuth"))
        if que_id is None or que_auth is None or que_id in refer_auth_by_que_id:
            continue
        refer_auth_by_que_id[que_id] = que_auth

    display_field_que_id = _coerce_any_int(payload.get("referQueId"))
    if display_field_que_id is None:
        display_field_que_id = _coerce_any_int(field.get("target_field_que_id"))
        if display_field_que_id is not None:
            payload["referQueId"] = display_field_que_id

    if relation_config_explicit:
        if display_field_que_id is not None and not any(
            _coerce_any_int(item.get("queId")) == display_field_que_id for item in refer_questions
        ):
            display_selector = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
            display_question = (
                _build_reference_question_from_visible_selector(display_selector, ordinal=1)
                if display_selector is not None
                else None
            )
            if display_question is not None:
                display_question["queId"] = display_field_que_id
                display_question["queAuth"] = _REFERENCE_FIELD_VISIBLE_AUTH
                refer_questions = [display_question, *refer_questions]

        if display_field_que_id is not None:
            display_questions = [
                item for item in refer_questions if _coerce_any_int(item.get("queId")) == display_field_que_id
            ]
            trailing_questions = [
                item for item in refer_questions if _coerce_any_int(item.get("queId")) != display_field_que_id
            ]
            refer_questions = [*display_questions, *trailing_questions]

    for ordinal, item in enumerate(refer_questions, start=1):
        que_id = _coerce_any_int(item.get("queId"))
        if que_id is None:
            continue
        item["ordinal"] = ordinal
        item["queAuth"] = refer_auth_by_que_id.get(
            que_id,
            _coerce_nonnegative_int(item.get("queAuth")) or _REFERENCE_FIELD_VISIBLE_AUTH,
        )
        if display_field_que_id is not None and que_id == display_field_que_id:
            item["queAuth"] = _REFERENCE_FIELD_VISIBLE_AUTH

    payload["referQuestions"] = refer_questions
    payload["referAuthQues"] = _canonicalize_reference_auth_questions_for_save(
        source={"referAuthQues": refer_auth_ques},
        refer_questions=refer_questions,
        relation_config_explicit=relation_config_explicit,
    )
    return payload


def _normalize_reference_config_for_save(
    reference: Any,
    *,
    field: dict[str, Any],
) -> dict[str, Any]:
    source = reference if isinstance(reference, dict) else {}
    payload = _copy_present_keys(source, _REFERENCE_CONFIG_SAVE_KEYS)
    if str(field.get("target_app_key") or "").strip():
        payload["referAppKey"] = str(field.get("target_app_key") or "").strip()
    if field.get("target_field_que_id") is not None:
        payload["referQueId"] = _coerce_nonnegative_int(field.get("target_field_que_id"))
    if field.get("field_name_show") is not None:
        payload["fieldNameShow"] = bool(field.get("field_name_show"))

    refer_question_auth_overrides = _reference_question_auth_overrides_for_save(source=source, field=field)
    refer_questions = _canonicalize_reference_questions_for_save(source=source, field=field)
    for index, normalized_item in enumerate(refer_questions, start=1):
        que_id = _coerce_any_int(normalized_item.get("queId"))
        if que_id is not None and que_id in refer_question_auth_overrides:
            normalized_item["queAuth"] = refer_question_auth_overrides[que_id]
        normalized_item["ordinal"] = index
    if refer_questions or "referQuestions" in source:
        payload["referQuestions"] = refer_questions

    refer_fill_rules = [
        item
        for item in (
            _normalize_reference_fill_rule_for_save(raw_item)
            for raw_item in cast(list[Any], source.get("referFillRules") or [])
        )
        if item is not None
    ]
    if refer_fill_rules or "referFillRules" in source:
        payload["referFillRules"] = refer_fill_rules

    refer_auth_ques = _canonicalize_reference_auth_questions_for_save(
        source=source,
        refer_questions=refer_questions,
        relation_config_explicit=bool(field.get("_relation_config_explicit")),
    )
    if not refer_auth_ques:
        refer_auth_ques = _synthesize_reference_auth_questions_for_save(source=source, field=field)
    if refer_auth_ques or "referAuthQues" in source:
        payload["referAuthQues"] = refer_auth_ques

    return _enforce_reference_config_consistency_for_save(payload, field=field)


def _normalize_relation_question_for_save(question: dict[str, Any], *, field: dict[str, Any]) -> dict[str, Any]:
    payload = _copy_present_keys(question, _RELATION_QUESTION_SAVE_KEYS)
    overlay_keys = _field_question_overlay_keys(field)
    que_id = _coerce_nonnegative_int(question.get("queId"))
    if que_id is not None:
        payload["queId"] = que_id
    que_temp_id = _coerce_nonnegative_int(question.get("queTempId"))
    if que_temp_id is not None and "queId" not in payload:
        payload["queTempId"] = que_temp_id
    payload["queType"] = _coerce_positive_int(question.get("queType")) or 25
    payload["queTitle"] = str(field.get("name") or question.get("queTitle") or "")
    if "required" in overlay_keys or "required" in question or field.get("required") is not None:
        payload["required"] = bool(field.get("required", question.get("required", False)))
    if "description" in overlay_keys:
        payload["queHint"] = "" if field.get("description") is None else str(field.get("description"))
    elif "queHint" in question and question.get("queHint") is not None:
        payload["queHint"] = str(question.get("queHint") or "")
    if field.get("default_type") is not None:
        payload["queDefaultType"] = _coerce_positive_int(field.get("default_type")) or 1
    if "default_value" in field:
        payload["queDefaultValue"] = field.get("default_value")
    payload["referenceConfig"] = _normalize_reference_config_for_save(question.get("referenceConfig"), field=field)
    return payload


def _build_form_save_base_payload(current_schema: dict[str, Any], title: str) -> dict[str, Any]:
    payload: dict[str, Any] = {"formTitle": title}
    for key in _FORM_SAVE_BASE_KEYS:
        if key in current_schema:
            payload[key] = deepcopy(current_schema.get(key))
    if "dataTitleQueId" not in payload and "data_title_que_id" in current_schema:
        payload["dataTitleQueId"] = deepcopy(current_schema.get("data_title_que_id"))
    if "dataCoverQueId" not in payload and "data_cover_que_id" in current_schema:
        payload["dataCoverQueId"] = deepcopy(current_schema.get("data_cover_que_id"))
    payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
    payload["formQues"] = []
    payload["questionRelations"] = []
    return payload


def _normalize_question_relations_for_save(question_relations: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
    normalized: list[dict[str, Any]] = []
    for relation in question_relations or []:
        if not isinstance(relation, dict):
            continue
        item: dict[str, Any] = {}
        for key in _QUESTION_RELATION_SAVE_KEYS:
            if key not in relation:
                continue
            value = relation.get(key)
            if value is None:
                continue
            if key == "matchRuleFormula":
                value = _encode_formula_for_backend_save(value)
            item[key] = deepcopy(value)
        if item:
            normalized.append(item)
    return normalized


def _field_rename_maps(fields: list[dict[str, Any]]) -> tuple[dict[int, str], dict[str, str]]:
    by_que_id: dict[int, str] = {}
    by_title: dict[str, str] = {}

    def visit(field: dict[str, Any]) -> None:
        if not isinstance(field, dict):
            return
        template = field.get("_question_template")
        if not isinstance(template, dict):
            for subfield in cast(list[dict[str, Any]], field.get("subfields") or []):
                visit(subfield)
            return
        old_title = str(template.get("queTitle") or "").strip()
        new_title = str(field.get("name") or "").strip()
        if not old_title or not new_title or old_title == new_title:
            for subfield in cast(list[dict[str, Any]], field.get("subfields") or []):
                visit(subfield)
            return
        que_id = _coerce_nonnegative_int(field.get("que_id"))
        if que_id is None:
            que_id = _coerce_nonnegative_int(template.get("queId"))
        if que_id is not None:
            by_que_id[que_id] = new_title
        by_title[old_title] = new_title
        for subfield in cast(list[dict[str, Any]], field.get("subfields") or []):
            visit(subfield)

    for field in fields:
        visit(field)
    return by_que_id, by_title


def _sync_question_title_references(value: Any, *, by_que_id: dict[int, str], by_title: dict[str, str]) -> None:
    if isinstance(value, list):
        for item in value:
            _sync_question_title_references(item, by_que_id=by_que_id, by_title=by_title)
        return
    if not isinstance(value, dict):
        return

    title_keys = ("queTitle", "_field_id")
    que_id = _coerce_nonnegative_int(value.get("queId"))
    replacement = None
    if que_id is not None and que_id in by_que_id:
        replacement = by_que_id[que_id]
    elif que_id is None:
        for key in title_keys:
            current_title = str(value.get(key) or "").strip()
            if current_title and current_title in by_title:
                replacement = by_title[current_title]
                break
    if replacement is not None:
        for key in title_keys:
            current_title = str(value.get(key) or "").strip()
            if (que_id is not None and que_id in by_que_id and key in value) or (current_title and current_title in by_title):
                value[key] = replacement
    sup_id = _coerce_nonnegative_int(value.get("supId"))
    if sup_id is not None and sup_id in by_que_id and "supQueTitle" in value:
        value["supQueTitle"] = by_que_id[sup_id]

    for child_value in value.values():
        if isinstance(child_value, (dict, list)):
            _sync_question_title_references(child_value, by_que_id=by_que_id, by_title=by_title)


def _materialize_preserved_subtable_question(field: dict[str, Any], *, template: dict[str, Any]) -> dict[str, Any] | None:
    materialized_subquestions: list[dict[str, Any]] = []
    for subfield in cast(list[dict[str, Any]], field.get("subfields") or []):
        if not isinstance(subfield, dict):
            continue
        materialized = _materialize_preserved_question(subfield)
        if materialized is None:
            return None
        materialized_subquestions.append(materialized)
    template["subQuestions"] = materialized_subquestions
    template["innerQuestions"] = [deepcopy(materialized_subquestions)]
    return template


def _materialize_preserved_question(field: dict[str, Any]) -> dict[str, Any] | None:
    template = deepcopy(field.get("_question_template"))
    if not isinstance(template, dict):
        return None
    overlay_keys = _field_question_overlay_keys(field)
    if "name" in overlay_keys:
        template["queTitle"] = str(field.get("name") or "")
    if "required" in overlay_keys:
        template["required"] = bool(field.get("required", False))
    if "description" in overlay_keys:
        description = field.get("description")
        template["queHint"] = "" if description is None else str(description)
    if str(field.get("type") or "") == FieldType.subtable.value:
        return _materialize_preserved_subtable_question(field, template=template)
    if str(field.get("type") or "") == FieldType.relation.value:
        return _normalize_relation_question_for_save(template, field=field)
    return template


def _materialize_edit_question(field: dict[str, Any], *, temp_id: int) -> tuple[dict[str, Any], bool]:
    if not _field_needs_question_rebuild(field):
        preserved = _materialize_preserved_question(field)
        if preserved is not None:
            return preserved, True
    return _field_to_question(field, temp_id=temp_id), False


def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]:
    built_question, _next_temp_id = build_question(
        {
            "label": field["name"],
            "type": field["type"],
            "required": field.get("required", False),
            "description": field.get("description"),
            "options": field.get("options", []),
            "target_entity_id": field.get("target_app_key") or "__TARGET_APP_KEY__",
            "target_field_id": field.get("target_field_id"),
            "config": deepcopy(field.get("config") or {}),
            "subfields": [
                {
                    "label": subfield["name"],
                    "type": subfield["type"],
                    "required": subfield.get("required", False),
                    "description": subfield.get("description"),
                    "options": subfield.get("options", []),
                    "target_entity_id": subfield.get("target_app_key") or "__TARGET_APP_KEY__",
                    "subfields": subfield.get("subfields", []),
                }
                for subfield in field.get("subfields", [])
            ],
        },
        temp_id,
    )
    relation_config_explicit = bool(field.get("_relation_config_explicit"))
    relation_question_template = (
        deepcopy(field.get("_question_template"))
        if field.get("type") == FieldType.relation.value and isinstance(field.get("_question_template"), dict)
        else None
    )
    question = (
        relation_question_template
        if relation_question_template is not None and not relation_config_explicit
        else built_question
    )
    if relation_config_explicit and relation_question_template is not None:
        for key in ("queOriginType", "relationDisplayMode", "customRenderConfig"):
            if key in relation_question_template:
                question[key] = deepcopy(relation_question_template[key])
    if _coerce_nonnegative_int(field.get("que_id")) is not None:
        question["queId"] = field["que_id"]
        question.pop("queTempId", None)
    else:
        question["queId"] = 0
        question["queTempId"] = temp_id
        field["que_temp_id"] = temp_id
    question["queType"] = built_question.get("queType", question.get("queType"))
    question["queTitle"] = built_question.get("queTitle", field["name"])
    question["required"] = built_question.get("required", bool(field.get("required", False)))
    question["queHint"] = built_question.get("queHint", field.get("description") or "")
    if field.get("default_type") is not None:
        question["queDefaultType"] = _coerce_positive_int(field.get("default_type")) or 1
    if "default_value" in field:
        question["queDefaultValue"] = field.get("default_value")
    if field.get("type") == FieldType.relation.value:
        preserved_reference = (
            deepcopy(field.get("_reference_config_template"))
            if not relation_config_explicit and isinstance(field.get("_reference_config_template"), dict)
            else None
        )
        if preserved_reference is not None:
            preserved_reference["referAppKey"] = field.get("target_app_key")
            question["referenceConfig"] = preserved_reference
        else:
            existing_reference = (
                deepcopy(relation_question_template.get("referenceConfig"))
                if relation_question_template is not None and isinstance(relation_question_template.get("referenceConfig"), dict)
                else deepcopy(question.get("referenceConfig"))
                if isinstance(question.get("referenceConfig"), dict)
                else {}
            )
            reference = (
                existing_reference
                if relation_config_explicit
                else deepcopy(question.get("referenceConfig"))
                if isinstance(question.get("referenceConfig"), dict)
                else {}
            )
            built_reference = (
                deepcopy(built_question.get("referenceConfig"))
                if isinstance(built_question.get("referenceConfig"), dict)
                else {}
            )
            original_target_app_key = str(existing_reference.get("referAppKey") or "").strip()
            next_target_app_key = str(field.get("target_app_key") or "").strip()
            preserve_existing_reference_questions = (
                relation_config_explicit
                and bool(original_target_app_key)
                and original_target_app_key == next_target_app_key
            )
            if relation_config_explicit:
                for stale_key in ("customButtonText", "customAdvancedSetting", "configShowForm", "dataShowForm"):
                    reference.pop(stale_key, None)
            for key in (
                "referQueId",
                "referQuestions",
                "referAuthQues",
                "optionalDataNum",
                "fieldNameShow",
                "_targetFieldId",
            ):
                if preserve_existing_reference_questions and key in {"referQuestions", "referAuthQues"}:
                    continue
                if key in built_reference:
                    reference[key] = deepcopy(built_reference[key])
            reference["referAppKey"] = field.get("target_app_key")
            reference["_targetEntityId"] = field.get("target_app_key")
            if field.get("target_field_que_id") is not None:
                reference["referQueId"] = field.get("target_field_que_id")
            question["referenceConfig"] = reference
        question = _normalize_relation_question_for_save(question, field=field)
    if field.get("type") == FieldType.department.value:
        scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
        question["deptSelectScopeType"] = scope_type
        question["deptSelectScope"] = scope_payload
    if field.get("type") == FieldType.code_block.value:
        code_block_config = _normalize_code_block_config(field.get("code_block_config") or field.get("config") or {}) or {
            "config_mode": 1,
            "code_content": "",
            "being_hide_on_form": False,
            "result_alias_path": [],
        }
        question["codeBlockConfig"] = {
            "configMode": code_block_config["config_mode"],
            "codeContent": code_block_config["code_content"],
            "resultAliasPath": [
                _serialize_code_block_alias_path_item(item) for item in code_block_config.get("result_alias_path", [])
            ],
            "beingHideOnForm": code_block_config["being_hide_on_form"],
        }
        question["autoTrigger"] = bool(field.get("auto_trigger", False))
        question["customBtnTextStatus"] = bool(field.get("custom_button_text_enabled", False))
        question["customBtnText"] = str(field.get("custom_button_text") or "")
    if field.get("type") == FieldType.q_linker.value:
        remote_lookup_config = _normalize_remote_lookup_config(field.get("remote_lookup_config") or field.get("config") or {}) or {
            "config_mode": 1,
            "url": "",
            "method": "GET",
            "headers": [],
            "body_type": 1,
            "url_encoded_value": [],
            "json_value": None,
            "xml_value": None,
            "result_type": 1,
            "result_format_path": [],
            "query_params": [],
            "auto_trigger": None,
            "custom_button_text_enabled": None,
            "custom_button_text": None,
            "being_insert_value_directly": None,
            "being_hide_on_form": None,
        }
        question["remoteLookupConfig"] = {
            "configMode": remote_lookup_config["config_mode"],
            "url": remote_lookup_config["url"],
            "method": remote_lookup_config["method"],
            "headers": [_serialize_remote_lookup_key_value_item(item) for item in remote_lookup_config.get("headers", [])],
            "bodyType": remote_lookup_config["body_type"],
            "urlEncodedValue": [_serialize_remote_lookup_key_value_item(item) for item in remote_lookup_config.get("url_encoded_value", [])],
            "jsonValue": remote_lookup_config.get("json_value"),
            "xmlValue": remote_lookup_config.get("xml_value"),
            "resultType": remote_lookup_config["result_type"],
            "resultFormatPath": [_serialize_q_linker_alias_path_item(item) for item in remote_lookup_config.get("result_format_path", [])],
            "queryParams": [_serialize_remote_lookup_key_value_item(item) for item in remote_lookup_config.get("query_params", [])],
            "beingInsertValueDirectly": bool(remote_lookup_config.get("being_insert_value_directly", False)),
            "beingHideOnForm": bool(remote_lookup_config.get("being_hide_on_form", False)),
        }
        if remote_lookup_config["config_mode"] == 1:
            question["remoteLookupConfig"]["openAppConfig"] = {"event": {"eventId": 0, "name": "custom"}}
        question["autoTrigger"] = bool(field.get("auto_trigger", remote_lookup_config.get("auto_trigger", False)))
        question["customBtnTextStatus"] = bool(field.get("custom_button_text_enabled", remote_lookup_config.get("custom_button_text_enabled", False)))
        question["customBtnText"] = str(field.get("custom_button_text") or remote_lookup_config.get("custom_button_text") or "")
    return question


def _serialize_code_block_alias_path_item(value: Any) -> dict[str, Any]:
    normalized = _normalize_code_block_alias_path_item(value) or {
        "alias_name": "",
        "alias_path": "",
        "alias_type": 1,
        "alias_id": None,
        "sub_alias": [],
    }
    payload = {
        "aliasName": normalized["alias_name"],
        "aliasPath": normalized["alias_path"],
        "aliasType": normalized["alias_type"],
    }
    if normalized.get("alias_id") is not None:
        payload["aliasId"] = normalized["alias_id"]
    if normalized.get("sub_alias"):
        payload["subAlias"] = [
            _serialize_code_block_alias_path_item(item) for item in normalized.get("sub_alias", [])
        ]
    return payload


def _build_form_payload_from_fields(
    *,
    title: str,
    current_schema: dict[str, Any],
    fields: list[dict[str, Any]],
    layout: dict[str, Any],
    question_relations: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    questions_by_name: dict[str, dict[str, Any]] = {}
    temp_id = -10000
    for field in fields:
        question = _field_to_question(field, temp_id=temp_id)
        questions_by_name[field["name"]] = question
        temp_id -= 100

    form_rows: list[list[dict[str, Any]]] = []
    for row in layout.get("root_rows", []):
        form_rows.append([deepcopy(questions_by_name[name]) for name in row if name in questions_by_name])
    for section in layout.get("sections", []):
        inner_rows = []
        for row in section.get("rows", []):
            questions = [deepcopy(questions_by_name[name]) for name in row if name in questions_by_name]
            if questions:
                _apply_row_widths(questions)
                inner_rows.append(questions)
        if not inner_rows:
            continue
        wrapper = {
            "queId": 0,
            "queTempId": -(20000 + sum(ord(ch) for ch in str(section.get("section_id") or section.get("title") or "section"))),
            "queType": 24,
            "queTitle": section.get("title") or "未命名分组",
            "queWidth": 100,
            "scanType": 1,
            "status": 1,
            "required": False,
            "queHint": "",
            "linkedQuestions": {},
            "logicalShow": True,
            "queDefaultValue": None,
            "queDefaultType": 1,
            "subQueWidth": 2,
            "innerQuestions": inner_rows,
            "beingHide": False,
            "beingDesensitized": False,
        }
        parsed_section_id = _coerce_positive_int(section.get("section_id"))
        if parsed_section_id is not None:
            wrapper["sectionId"] = parsed_section_id
        form_rows.append([wrapper])
    for row in form_rows:
        _apply_row_widths(row)
    payload = default_form_payload(title, form_rows)
    _normalize_formula_defaults_for_save(payload.get("formQues"))
    payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
    payload["questionRelations"] = _normalize_question_relations_for_save(
        question_relations if question_relations is not None else (current_schema.get("questionRelations") or [])
    )
    return payload


def _build_form_payload_for_edit_fields(
    *,
    title: str,
    current_schema: dict[str, Any],
    fields: list[dict[str, Any]],
    layout: dict[str, Any],
    question_relations: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    _, section_templates = _extract_question_templates(current_schema)
    template_row_lengths_by_que_id, template_row_lengths_by_title = _extract_template_row_lengths(current_schema)
    fields_by_name = {
        str(field.get("name") or ""): field
        for field in fields
        if isinstance(field, dict) and str(field.get("name") or "").strip()
    }
    form_rows: list[list[dict[str, Any]]] = []
    temp_id = -10000

    for row in layout.get("root_rows", []) or []:
        questions: list[dict[str, Any]] = []
        expected_template_lengths: list[int] = []
        row_preserved = True
        for name in row:
            field = fields_by_name.get(str(name))
            if field is None:
                continue
            template_row_length = _field_template_row_length(
                field,
                lengths_by_que_id=template_row_lengths_by_que_id,
                lengths_by_title=template_row_lengths_by_title,
            )
            if template_row_length is not None:
                expected_template_lengths.append(template_row_length)
            question, preserved = _materialize_edit_question(field, temp_id=temp_id)
            questions.append(question)
            row_preserved = row_preserved and preserved
            temp_id -= 100
        if not questions:
            continue
        if not row_preserved or _row_needs_width_reflow(expected_template_lengths, len(questions)):
            _apply_row_widths(questions)
        form_rows.append(questions)

    for section in layout.get("sections", []) or []:
        inner_rows: list[list[dict[str, Any]]] = []
        for row in section.get("rows", []) or []:
            questions: list[dict[str, Any]] = []
            expected_template_lengths: list[int] = []
            row_preserved = True
            for name in row:
                field = fields_by_name.get(str(name))
                if field is None:
                    continue
                template_row_length = _field_template_row_length(
                    field,
                    lengths_by_que_id=template_row_lengths_by_que_id,
                    lengths_by_title=template_row_lengths_by_title,
                )
                if template_row_length is not None:
                    expected_template_lengths.append(template_row_length)
                question, preserved = _materialize_edit_question(field, temp_id=temp_id)
                questions.append(question)
                row_preserved = row_preserved and preserved
                temp_id -= 100
            if not questions:
                continue
            if not row_preserved or _row_needs_width_reflow(expected_template_lengths, len(questions)):
                _apply_row_widths(questions)
            inner_rows.append(questions)
        if not inner_rows:
            continue
        template = _select_section_template(section_templates, section)
        wrapper = deepcopy(template) if isinstance(template, dict) else {
            "queId": 0,
            "queTempId": -(20000 + sum(ord(ch) for ch in str(section.get("section_id") or section.get("title") or "section"))),
            "queType": 24,
            "queWidth": 100,
            "scanType": 1,
            "status": 1,
            "required": False,
            "queHint": "",
            "linkedQuestions": {},
            "logicalShow": True,
            "queDefaultValue": None,
            "queDefaultType": 1,
            "subQueWidth": 2,
            "beingHide": False,
            "beingDesensitized": False,
        }
        if section.get("title") is not None:
            wrapper["queTitle"] = section.get("title") or wrapper.get("queTitle") or "未命名分组"
        parsed_section_id = _coerce_positive_int(section.get("section_id"))
        if parsed_section_id is not None:
            wrapper["sectionId"] = parsed_section_id
        elif template is None and _coerce_positive_int(wrapper.get("sectionId")) is None:
            wrapper.pop("sectionId", None)
        wrapper["innerQuestions"] = inner_rows
        form_rows.append([wrapper])

    rename_by_que_id, rename_by_title = _field_rename_maps(fields)
    if rename_by_que_id or rename_by_title:
        _sync_question_title_references(form_rows, by_que_id=rename_by_que_id, by_title=rename_by_title)
    normalized_relations = _normalize_question_relations_for_save(
        question_relations if question_relations is not None else (current_schema.get("questionRelations") or [])
    )
    if rename_by_que_id or rename_by_title:
        _sync_question_title_references(normalized_relations, by_que_id=rename_by_que_id, by_title=rename_by_title)

    payload = _build_form_save_base_payload(current_schema, title)
    payload["formQues"] = form_rows
    _normalize_formula_defaults_for_save(payload.get("formQues"))
    payload["questionRelations"] = normalized_relations
    payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
    return payload


def _apply_row_widths(row: list[dict[str, Any]]) -> None:
    if not row:
        return
    width = int(100 / len(row))
    remainder = 100 - (width * len(row))
    for index, question in enumerate(row):
        question["queWidth"] = width + (1 if index < remainder else 0)


def _summarize_workflow_nodes(result: Any) -> list[dict[str, Any]]:
    nodes = []
    spec: Any = result
    if isinstance(result, dict) and isinstance(result.get("spec"), dict):
        spec = result.get("spec")
    if isinstance(spec, dict) and isinstance(spec.get("nodes"), list):
        for item in spec.get("nodes") or []:
            if not isinstance(item, dict):
                continue
            node_id = item.get("id")
            name = item.get("name") or item.get("title")
            if node_id is None or not name:
                continue
            attrs = item.get("attrs") if isinstance(item.get("attrs"), dict) else {}
            nodes.append(
                {
                    "id": str(node_id),
                    "name": name,
                    "type": item.get("type"),
                    **({"sub_type": attrs.get("subType")} if attrs.get("subType") else {}),
                }
            )
        return nodes
    if isinstance(result, dict):
        iterable = result.values()
    elif isinstance(result, list):
        iterable = result
    else:
        iterable = []
    for item in iterable:
        if not isinstance(item, dict):
            continue
        node_id = _coerce_positive_int(item.get("auditNodeId"))
        name = item.get("auditNodeName")
        if node_id is None or not name:
            continue
        nodes.append({"id": node_id, "name": name, "type": item.get("type"), "deal_type": item.get("dealType")})
    return nodes


def _looks_like_workflow_spec_payload(payload: Any) -> bool:
    if not isinstance(payload, dict):
        return False
    spec = payload.get("spec")
    if isinstance(spec, dict):
        return True
    return any(key in payload for key in ("nodes", "edges", "transitions", "schemaVersion"))


def _workflow_node_iterable(result: Any) -> list[dict[str, Any]]:
    if isinstance(result, dict):
        iterable = result.values()
    elif isinstance(result, list):
        iterable = result
    else:
        iterable = []
    return [item for item in iterable if isinstance(item, dict)]


def _workflow_condition_matrix_value(node: dict[str, Any]) -> list[Any]:
    config = node.get("config")
    if isinstance(config, dict):
        matrix = config.get("autoJudges")
        if isinstance(matrix, list):
            return deepcopy(matrix)
        matrix = config.get("conditionFormatMatrix")
        if isinstance(matrix, list):
            return deepcopy(matrix)
    matrix = node.get("autoJudges")
    if isinstance(matrix, list):
        return deepcopy(matrix)
    matrix = node.get("conditionFormatMatrix")
    if isinstance(matrix, list):
        return deepcopy(matrix)
    return []


def _workflow_branch_structure_verified(*, current_workflow: Any, requested_nodes: list[dict[str, Any]]) -> bool:
    requested_branch_like = [
        node
        for node in requested_nodes
        if str(node.get("type") or "").strip().lower() in {"branch", "condition"}
    ]
    if not requested_branch_like:
        return True
    actual_nodes = _workflow_node_iterable(current_workflow)
    actual_branch_like = [
        node
        for node in actual_nodes
        if _normalize_existing_workflow_node_type(node.get("type"), node.get("dealType")) in {"branch", "condition"}
    ]
    if len(actual_branch_like) < len(requested_branch_like):
        return False
    actual_condition_nodes = [
        node
        for node in actual_nodes
        if _normalize_existing_workflow_node_type(node.get("type"), node.get("dealType")) == "condition"
    ]
    for requested in requested_branch_like:
        if str(requested.get("type") or "").strip().lower() != "condition":
            continue
        expected_matrix = _workflow_condition_matrix_value(requested)
        if not expected_matrix:
            continue
        requested_name = str(requested.get("name") or "").strip()
        match = next(
            (
                node
                for node in actual_condition_nodes
                if str(node.get("auditNodeName") or node.get("nodeName") or "").strip() == requested_name
            ),
            None,
        )
        if match is None or not _workflow_condition_matrix_value(match):
            return False
    return True


def _workflow_nodes_semantically_equal(*, current_workflow: Any, requested_nodes: list[dict[str, Any]]) -> bool:
    current_nodes = _summarize_workflow_nodes(current_workflow)
    current_effective = [
        node
        for node in current_nodes
        if _normalize_existing_workflow_node_type(node.get("type"), node.get("deal_type")) not in {"start", "end"}
    ]
    requested_effective = [
        node for node in requested_nodes if str(node.get("type") or "") not in {"start", "end"}
    ]
    if not current_effective or not requested_effective or len(current_effective) != len(requested_effective):
        return False
    current_signatures = sorted(
        (
            _normalize_existing_workflow_node_type(node.get("type"), node.get("deal_type")),
            str(node.get("name") or "").strip(),
        )
        for node in current_effective
    )
    requested_signatures = sorted(
        (
            str(node.get("type") or "").strip().lower(),
            str(node.get("name") or "").strip(),
        )
        for node in requested_effective
    )
    return current_signatures == requested_signatures


def _normalize_existing_workflow_node_type(raw_type: Any, deal_type: Any) -> str:
    normalized = "" if raw_type is None else str(raw_type).strip().lower()
    if normalized in {"audit", "approve"}:
        return "approve"
    if normalized in {"fill", "copy", "branch", "condition", "webhook", "start", "end"}:
        return normalized
    if normalized in {"1"}:
        return "branch"
    if normalized in {"2"}:
        return "condition"
    if normalized in {"3"} or deal_type == 10:
        return "webhook"
    if normalized in {"0"} and deal_type == 0:
        return "approve"
    if normalized in {"0"} and deal_type == 1:
        return "fill"
    if normalized in {"0"} and deal_type == 2:
        return "copy"
    if normalized in {"0"} or deal_type == 3:
        return "start"
    return normalized or "unknown"


def _summarize_views(result: Any) -> list[dict[str, Any]]:
    if not isinstance(result, list):
        return []
    items = []
    for view in result:
        if not isinstance(view, dict):
            continue
        name = view.get("viewgraphName") or view.get("viewName") or view.get("title")
        view_key = view.get("viewgraphKey") or view.get("viewKey")
        view_type = _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))
        columns = view.get("columnNames") or view.get("columns") or []
        group_by = view.get("groupBy") or view.get("group_by")
        if not any((name, view_type, columns, group_by)) and str(view_key or "").isdigit():
            continue
        if not any((name, view_key, view_type, columns, group_by)):
            continue
        items.append(
            {
                "name": name,
                "view_key": view_key,
                "type": view_type,
                "columns": columns,
                "group_by": group_by,
            }
        )
    return items


def _summarize_views_with_config(views_tool: ViewTools, *, profile: str, views: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    base_items = _summarize_views(views)
    if not base_items:
        return [], []
    config_read_errors: list[dict[str, Any]] = []
    enriched_items: list[dict[str, Any]] = []
    for item in base_items:
        view_key = str(item.get("view_key") or "").strip()
        if not view_key:
            enriched_items.append(item)
            continue
        try:
            config_response = views_tool.view_get_config(profile=profile, viewgraph_key=view_key)
        except (QingflowApiError, RuntimeError) as error:
            api_error = _coerce_api_error(error)
            config_read_errors.append(
                {
                    "view_key": view_key,
                    "name": item.get("name"),
                    "request_id": api_error.request_id,
                    "http_status": api_error.http_status,
                    "backend_code": api_error.backend_code,
                    "category": api_error.category,
                }
            )
            enriched_items.append(item)
            continue
        config = config_response.get("result") if isinstance(config_response.get("result"), dict) else {}
        question_list: list[dict[str, Any]] = []
        try:
            question_response = views_tool.view_list_questions(profile=profile, viewgraph_key=view_key)
            raw_question_list = question_response.get("result")
            if isinstance(raw_question_list, list):
                question_list = [deepcopy(entry) for entry in raw_question_list if isinstance(entry, dict)]
        except (QingflowApiError, RuntimeError):
            question_list = []
        enriched_items.append(_merge_view_summary_with_config(item, config=config, question_list=question_list))
    return enriched_items, config_read_errors


def _merge_view_summary_with_config(
    base: dict[str, Any],
    *,
    config: dict[str, Any],
    question_list: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    summary = deepcopy(base)
    if not isinstance(config, dict) or not config:
        return summary
    if isinstance(config.get("auth"), dict):
        summary["visibility_summary"] = _visibility_summary(_public_visibility_from_member_auth(config.get("auth")))
    legacy_columns = [str(value) for value in (summary.get("columns") or []) if str(value or "").strip()]
    question_entries = _extract_view_question_entries(config.get("viewgraphQuestions"))
    canonical_question_entries = _extract_view_question_entries(question_list)
    question_entries_by_id = {
        field_id: entry
        for entry in question_entries
        if (field_id := _coerce_nonnegative_int(entry.get("field_id"))) is not None
    }
    configured_column_ids = [
        field_id
        for field_id in (_coerce_nonnegative_int(value) for value in (config.get("viewgraphQueIds") or []))
        if field_id is not None
    ]
    configured_columns = _resolve_view_column_names(
        configured_column_ids,
        question_entries_by_id=question_entries_by_id,
        legacy_columns=legacy_columns,
    )
    config_enriched = False
    display_entries = _sort_view_question_entries(
        [entry for entry in question_entries if bool(entry.get("visible", True))],
    )
    public_display_entries = _filter_public_view_display_entries(display_entries, configured_column_ids=configured_column_ids)
    display_column_ids = [
        field_id
        for field_id in (_coerce_nonnegative_int(entry.get("field_id")) for entry in public_display_entries)
        if field_id is not None
    ]
    display_columns = [
        str(entry.get("name") or "").strip()
        for entry in public_display_entries
        if str(entry.get("name") or "").strip()
    ]
    apply_entries = [
        entry
        for entry in public_display_entries
        if _coerce_nonnegative_int(entry.get("field_id")) is not None
        and str(entry.get("name") or "").strip()
        and str(entry.get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
    ]
    apply_column_ids = [
        field_id
        for field_id in (_coerce_nonnegative_int(entry.get("field_id")) for entry in apply_entries)
        if field_id is not None
    ]
    apply_columns = [
        str(entry.get("name") or "").strip()
        for entry in apply_entries
        if str(entry.get("name") or "").strip()
    ]
    if not apply_columns and configured_column_ids:
        apply_columns = [
            str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
            for field_id in configured_column_ids
            if str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
            and str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
        ]
        apply_column_ids = [
            field_id
            for field_id in configured_column_ids
            if str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
            and str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
        ]
    if not display_columns and configured_columns:
        display_columns = configured_columns
        display_column_ids = list(configured_column_ids)
    if display_columns:
        summary["columns"] = display_columns
        summary["display_columns"] = display_columns
        summary["display_column_ids"] = display_column_ids
        config_enriched = True
    elif configured_columns:
        summary["columns"] = configured_columns
        config_enriched = True
    if configured_columns:
        summary["configured_columns"] = configured_columns
        summary["configured_column_ids"] = configured_column_ids
        config_enriched = True
    if apply_columns:
        summary["apply_columns"] = apply_columns
        summary["apply_column_ids"] = apply_column_ids
        config_enriched = True
    if canonical_question_entries:
        canonical_display_entries = _sort_view_question_entries(canonical_question_entries)
        canonical_display_column_ids = [
            field_id
            for field_id in (_coerce_nonnegative_int(entry.get("field_id")) for entry in canonical_display_entries)
            if field_id is not None
        ]
        canonical_display_columns = [
            str(entry.get("name") or "").strip()
            for entry in canonical_display_entries
            if str(entry.get("name") or "").strip()
        ]
        canonical_apply_entries = [
            entry
            for entry in canonical_display_entries
            if _coerce_nonnegative_int(entry.get("field_id")) is not None
            and str(entry.get("name") or "").strip()
            and str(entry.get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
        ]
        summary["columns"] = canonical_display_columns
        summary["display_columns"] = canonical_display_columns
        summary["display_column_ids"] = canonical_display_column_ids
        summary["column_details"] = canonical_display_entries
        summary["apply_columns"] = [
            str(entry.get("name") or "").strip()
            for entry in canonical_apply_entries
            if str(entry.get("name") or "").strip()
        ]
        summary["apply_column_ids"] = [
            field_id
            for field_id in (_coerce_nonnegative_int(entry.get("field_id")) for entry in canonical_apply_entries)
            if field_id is not None
        ]
        config_enriched = True
    elif question_entries:
        summary["column_details"] = public_display_entries or _sort_view_question_entries(question_entries)
        config_enriched = True
    display_config = _extract_view_display_config(
        config,
        question_entries_by_id=question_entries_by_id,
        fallback_group_by=str(summary.get("group_by") or "").strip() or None,
    )
    if display_config:
        summary["display_config"] = display_config
        if not summary.get("group_by") and display_config.get("group_by"):
            summary["group_by"] = display_config.get("group_by")
        config_enriched = True
    query_question_entries_by_id = dict(question_entries_by_id)
    for entry in canonical_question_entries:
        field_id = _coerce_nonnegative_int(entry.get("field_id"))
        if field_id is not None:
            query_question_entries_by_id[field_id] = entry
    if any(key in config for key in ("queryConditionStatus", "queryConditionExact", "hideBeforeQueryCondition", "queryCondition")):
        summary["query_conditions"] = _extract_view_query_conditions_config(
            config,
            question_entries_by_id=query_question_entries_by_id,
        )
        config_enriched = True
    if "viewgraphLimit" in config:
        field_lookup = _view_field_lookup_from_question_entries(
            [*question_entries, *canonical_question_entries]
        )
        summary["filters"] = _public_view_filter_groups_from_match_rules(
            config.get("viewgraphLimit"),
            current_fields_by_name=field_lookup,
        )
        config_enriched = True
    if any(key in config for key in ("asosChartVisible", "asosChartConfig", "asosChartIdList", "limitType")):
        summary["associated_resources_config"] = _extract_view_associated_resources_config(config)
        config_enriched = True
    button_entries, button_source = _extract_view_button_entries(config)
    if button_entries:
        summary["buttons"] = [_normalize_view_button_entry(entry) for entry in button_entries]
        summary["button_count"] = len(button_entries)
        if button_source:
            summary["button_read_source"] = button_source
        config_enriched = True
    elif button_source:
        summary["buttons"] = []
        summary["button_count"] = 0
        summary["button_read_source"] = button_source
        config_enriched = True
    if config_enriched:
        summary["read_source"] = "view_config+question" if canonical_question_entries else "view_config"
    return summary


def _extract_view_question_entries(questions: Any) -> list[dict[str, Any]]:
    if not isinstance(questions, list):
        return []
    entries: list[dict[str, Any]] = []
    fallback_order = 0

    def walk(nodes: Any) -> None:
        nonlocal fallback_order
        if not isinstance(nodes, list):
            return
        for item in nodes:
            if not isinstance(item, dict):
                continue
            children: list[Any] = []
            for child_key in ("innerQues", "subQues", "innerQuestions", "subQuestions"):
                child_value = item.get(child_key)
                if isinstance(child_value, list) and child_value:
                    children.extend(child_value)
            if children:
                walk(children)
                continue
            field_id = _coerce_nonnegative_int(item.get("queId"))
            name = str(item.get("queTitle") or "").strip() or None
            if field_id is None and name is None:
                continue
            visible_raw = item.get("beingListDisplay")
            if visible_raw is None:
                visible_raw = item.get("beingVisible")
            visible = bool(visible_raw) if visible_raw is not None else True
            display_order = _coerce_positive_int(item.get("displayOrdinal"))
            fallback_order += 1
            entry: dict[str, Any] = {
                "field_id": field_id,
                "name": name,
                "visible": visible,
                "display_order": display_order if display_order is not None else fallback_order,
            }
            option_details: list[dict[str, Any]] = []
            option_values: list[str] = []
            raw_options = item.get("options")
            if isinstance(raw_options, list):
                for raw_option in raw_options:
                    if not isinstance(raw_option, dict):
                        continue
                    option_id = raw_option.get("optId")
                    option_value = str(raw_option.get("optValue") or "").strip()
                    if not option_value and option_id is None:
                        continue
                    if option_value:
                        option_values.append(option_value)
                    option_details.append({"id": option_id, "value": option_value or str(option_id)})
            if option_values:
                entry["options"] = option_values
            if option_details:
                entry["option_details"] = option_details
            width = _coerce_positive_int(item.get("width"))
            if width is not None:
                entry["width"] = width
            entries.append(entry)

    walk(questions)
    return entries


def _view_field_lookup_from_question_entries(entries: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
    fields_by_name: dict[str, dict[str, Any]] = {}
    for entry in entries:
        if not isinstance(entry, dict):
            continue
        name = str(entry.get("name") or entry.get("title") or "").strip()
        que_id = _coerce_positive_int(entry.get("field_id") or entry.get("que_id") or entry.get("queId"))
        if not name or que_id is None:
            continue
        field_entry: dict[str, Any] = {"name": name, "que_id": que_id}
        options = entry.get("options")
        if isinstance(options, list) and options:
            field_entry["options"] = [str(value) for value in options if str(value or "").strip()]
        option_details = entry.get("option_details")
        if isinstance(option_details, list) and option_details:
            field_entry["option_details"] = [deepcopy(value) for value in option_details if isinstance(value, dict)]
        fields_by_name.setdefault(name, field_entry)
    return fields_by_name


def _view_field_lookup_from_summary(view: dict[str, Any]) -> dict[str, dict[str, Any]]:
    entries = view.get("column_details")
    if isinstance(entries, list):
        return _view_field_lookup_from_question_entries([entry for entry in entries if isinstance(entry, dict)])
    fields_by_name: dict[str, dict[str, Any]] = {}
    names = view.get("columns")
    ids = view.get("display_column_ids") or view.get("configured_column_ids") or []
    if isinstance(names, list):
        for index, raw_name in enumerate(names):
            name = str(raw_name or "").strip()
            if not name:
                continue
            que_id = _coerce_positive_int(ids[index] if isinstance(ids, list) and index < len(ids) else None)
            fields_by_name.setdefault(name, {"name": name, "que_id": que_id})
    return fields_by_name


def _filter_public_view_display_entries(
    entries: list[dict[str, Any]],
    *,
    configured_column_ids: list[int],
) -> list[dict[str, Any]]:
    configured_set = set(configured_column_ids)
    filtered: list[dict[str, Any]] = []
    for entry in entries:
        name = str(entry.get("name") or "").strip()
        field_id = _coerce_nonnegative_int(entry.get("field_id"))
        if name in _KNOWN_SYSTEM_VIEW_COLUMNS and field_id not in configured_set:
            continue
        filtered.append(entry)
    return filtered or entries


def _sort_view_question_entries(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return sorted(
        entries,
        key=lambda entry: (
            _coerce_positive_int(entry.get("display_order")) if _coerce_positive_int(entry.get("display_order")) is not None else 10**9,
            str(entry.get("name") or ""),
        ),
    )


def _resolve_view_column_names(
    field_ids: list[int],
    *,
    question_entries_by_id: dict[int, dict[str, Any]],
    legacy_columns: list[str],
) -> list[str]:
    names: list[str] = []
    for index, field_id in enumerate(field_ids):
        entry = question_entries_by_id.get(field_id) or {}
        name = str(entry.get("name") or "").strip()
        if not name and index < len(legacy_columns):
            name = str(legacy_columns[index] or "").strip()
        if name:
            names.append(name)
    return names


def _extract_view_display_config(
    config: dict[str, Any],
    *,
    question_entries_by_id: dict[int, dict[str, Any]],
    fallback_group_by: str | None,
) -> dict[str, Any]:
    if not isinstance(config, dict):
        return {}
    display_config: dict[str, Any] = {}
    for source_key, public_key in (
        ("defaultRowHigh", "row_height"),
        ("sortType", "sort_type"),
        ("beingPinNavigate", "pin_navigation"),
        ("beingShowCover", "show_cover"),
        ("beingShowTitleQue", "show_title_field"),
    ):
        if source_key in config:
            display_config[public_key] = config.get(source_key)
    title_field_id = _coerce_positive_int(config.get("titleQue"))
    if title_field_id is not None:
        display_config["title_field_id"] = title_field_id
        title_field = str((question_entries_by_id.get(title_field_id) or {}).get("name") or "").strip()
        if title_field:
            display_config["title_field"] = title_field
    group_field_id = _coerce_positive_int(config.get("groupQueId"))
    if group_field_id is not None:
        display_config["group_by_field_id"] = group_field_id
        group_by = str((question_entries_by_id.get(group_field_id) or {}).get("name") or "").strip()
        if group_by:
            display_config["group_by"] = group_by
    elif fallback_group_by:
        display_config["group_by"] = fallback_group_by
    raw_sorts = config.get("viewgraphSorts")
    if isinstance(raw_sorts, list):
        sorts: list[dict[str, Any]] = []
        for item in raw_sorts:
            if not isinstance(item, dict):
                continue
            sort_field_id = _coerce_positive_int(item.get("queId"))
            if sort_field_id is None:
                continue
            sort_entry: dict[str, Any] = {
                "field_id": sort_field_id,
                "ascending": bool(item.get("beingSortAscend", True)),
            }
            sort_name = str((question_entries_by_id.get(sort_field_id) or {}).get("name") or "").strip()
            if sort_name:
                sort_entry["field"] = sort_name
            sorts.append(sort_entry)
        if sorts:
            display_config["sorts"] = sorts
    return display_config


def _normalize_view_button_type(value: Any) -> str | None:
    normalized = str(value or "").strip().upper()
    if normalized in {"SYSTEM", "CUSTOM"}:
        return normalized
    return None


def _normalize_view_button_config_type(value: Any) -> str | None:
    normalized = str(value or "").strip().upper()
    if normalized == "LIST":
        return "INSIDE"
    if normalized in {"TOP", "DETAIL", "INSIDE"}:
        return normalized
    return None


def _normalize_print_tpls(value: Any) -> list[dict[str, Any]]:
    if not isinstance(value, list):
        return []
    items: list[dict[str, Any]] = []
    for item in value:
        if isinstance(item, dict):
            tpl_id = item.get("printTplId")
            if tpl_id is None:
                tpl_id = item.get("templateId")
            if tpl_id is None:
                tpl_id = item.get("id")
            tpl_name = item.get("printTplName")
            if tpl_name is None:
                tpl_name = item.get("templateName")
            if tpl_name is None:
                tpl_name = item.get("name")
            normalized: dict[str, Any] = {}
            if tpl_id is not None:
                normalized["id"] = str(tpl_id)
            if str(tpl_name or "").strip():
                normalized["name"] = str(tpl_name).strip()
            if normalized:
                items.append(normalized)
            continue
        scalar = str(item or "").strip()
        if scalar:
            items.append({"id": scalar})
    return items


def _normalize_print_tpls_for_compare(value: Any) -> list[str]:
    normalized_items: list[str] = []
    for item in _normalize_print_tpls(value):
        item_id = str(item.get("id") or "").strip()
        item_name = str(item.get("name") or "").strip()
        normalized_items.append(item_id or item_name)
    return normalized_items


def _serialize_print_tpl_ids(value: Any) -> list[str]:
    return [item for item in _normalize_print_tpls_for_compare(value) if item]


def _extract_view_button_entries(config: dict[str, Any]) -> tuple[list[dict[str, Any]], str | None]:
    if not isinstance(config, dict):
        return [], None
    raw_vo = config.get("buttonConfigVO")
    if isinstance(raw_vo, list):
        return [deepcopy(item) for item in raw_vo if isinstance(item, dict)], "buttonConfigVO"
    raw_dtos = config.get("buttonConfigDTOList")
    if isinstance(raw_dtos, list):
        return [deepcopy(item) for item in raw_dtos if isinstance(item, dict)], "buttonConfigDTOList"
    grouped = config.get("buttonConfig")
    if not isinstance(grouped, dict):
        return [], None
    entries: list[dict[str, Any]] = []
    for item in grouped.get("topButtonList") or []:
        if not isinstance(item, dict):
            continue
        entry = deepcopy(item)
        entry.setdefault("configType", "TOP")
        entry.setdefault("beingMain", True)
        entries.append(entry)
    for item in grouped.get("mainButtonDetailList") or []:
        if not isinstance(item, dict):
            continue
        entry = deepcopy(item)
        entry.setdefault("configType", "DETAIL")
        entry["beingMain"] = True
        entries.append(entry)
    for item in grouped.get("moreButtonDetailList") or []:
        if not isinstance(item, dict):
            continue
        entry = deepcopy(item)
        entry.setdefault("configType", "DETAIL")
        entry["beingMain"] = False
        entries.append(entry)
    for item in grouped.get("listButtonList") or []:
        if not isinstance(item, dict):
            continue
        entry = deepcopy(item)
        entry.setdefault("configType", "INSIDE")
        entries.append(entry)
    return entries, "buttonConfig"


def _normalize_view_button_entry(entry: dict[str, Any]) -> dict[str, Any]:
    button_id = _coerce_positive_int(entry.get("buttonId") or entry.get("button_id") or entry.get("id"))
    button_text = str(entry.get("buttonText") or "").strip() or None
    default_button_text = str(entry.get("defaultButtonText") or "").strip() or None
    normalized: dict[str, Any] = {
        "button_type": _normalize_view_button_type(entry.get("buttonType") or entry.get("button_type")),
        "config_type": _normalize_view_button_config_type(entry.get("configType") or entry.get("config_type")),
        "button_id": button_id,
        "button_text": button_text or default_button_text,
        "being_main": bool(entry.get("beingMain", False)),
        "trigger_action": str(entry.get("triggerAction") or "").strip() or None,
        "print_tpls": _normalize_print_tpls(entry.get("printTpls")),
        "button_formula_type": _coerce_positive_int(entry.get("buttonFormulaType")) or 1,
        "button_limit": _normalize_view_filter_groups_for_compare(entry.get("buttonLimit")),
    }
    for public_key, source_key in (
        ("default_button_text", "defaultButtonText"),
        ("button_icon", "buttonIcon"),
        ("background_color", "backgroundColor"),
        ("text_color", "textColor"),
        ("trigger_link_url", "triggerLinkUrl"),
        ("button_formula", "buttonFormula"),
    ):
        value = entry.get(source_key)
        if isinstance(value, str):
            value = value.strip() or None
        if value not in {None, ""}:
            normalized[public_key] = deepcopy(value)
    trigger_add_data_config = entry.get("triggerAddDataConfig")
    if isinstance(trigger_add_data_config, dict):
        normalized["trigger_add_data_config"] = _normalize_custom_button_add_data_config_for_public(trigger_add_data_config)
    trigger_wings_config = entry.get("triggerWingsConfig")
    if isinstance(trigger_wings_config, dict):
        normalized["trigger_wings_config"] = deepcopy(trigger_wings_config)
    return normalized


def _public_view_button_placement(config_type: str | None) -> str | None:
    normalized = _normalize_view_button_config_type(config_type)
    if normalized == "TOP":
        return "header"
    if normalized == "DETAIL":
        return "detail"
    if normalized == "INSIDE":
        return "list"
    return None


def _extract_view_buttons_config(config: dict[str, Any]) -> dict[str, Any]:
    entries, source = _extract_view_button_entries(config)
    placements: dict[str, list[dict[str, Any]]] = {"header": [], "detail": [], "list": []}
    items: list[dict[str, Any]] = []
    for entry in entries:
        normalized = _normalize_view_button_entry(entry)
        placement = _public_view_button_placement(normalized.get("config_type"))
        normalized["placement"] = placement
        items.append(normalized)
        if placement:
            placements.setdefault(placement, []).append(normalized)
    status = "ok" if items else "none"
    result: dict[str, Any] = {
        "status": status,
        "button_count": len(items),
        "placements": placements,
        "items": items,
    }
    if source:
        result["read_source"] = source
    return result


def _normalize_view_buttons_for_compare(value: Any) -> list[dict[str, Any]]:
    if isinstance(value, dict):
        entries, _ = _extract_view_button_entries(value)
    elif isinstance(value, list):
        entries = [item for item in value if isinstance(item, dict)]
    else:
        entries = []
    normalized_entries: list[dict[str, Any]] = []
    for entry in entries:
        normalized = _normalize_view_button_entry(entry)
        is_custom = normalized.get("button_type") == "CUSTOM"
        normalized_entries.append(
            {
                "button_type": normalized.get("button_type"),
                "config_type": normalized.get("config_type"),
                "button_id": normalized.get("button_id") if is_custom else None,
                "button_text": normalized.get("button_text"),
                "button_icon": normalized.get("button_icon"),
                "background_color": normalized.get("background_color"),
                "text_color": normalized.get("text_color"),
                "trigger_action": normalized.get("trigger_action"),
                # Custom button trigger details are verified by dedicated button CRUD readback.
                # View binding verification should focus on binding/display semantics and tolerate resource-owned URLs.
                "trigger_link_url": None if is_custom else normalized.get("trigger_link_url"),
                "being_main": bool(normalized.get("being_main", False)),
                "print_tpls": _normalize_print_tpls_for_compare(normalized.get("print_tpls")),
                "button_formula": str(normalized.get("button_formula") or ""),
                "button_formula_type": _coerce_positive_int(normalized.get("button_formula_type")) or 1,
                "button_limit": deepcopy(normalized.get("button_limit") or []),
            }
        )
    return normalized_entries


_SYSTEM_VIEW_BUTTON_ID_BY_ACTION: dict[tuple[str, str], int] = {
    ("TOP", "set"): 1,
    ("TOP", "switchView"): 2,
    ("TOP", "setRowHeight"): 3,
    ("TOP", "search"): 6,
    ("DETAIL", "share"): 7,
    ("DETAIL", "edit"): 8,
}

_SYSTEM_VIEW_BUTTON_ID_BY_TEXT: dict[tuple[str, str], int] = {
    ("TOP", "字段管理"): 1,
    ("TOP", "视图类型"): 2,
    ("TOP", "行高"): 3,
    ("TOP", "搜索"): 6,
    ("DETAIL", "分享"): 7,
    ("DETAIL", "修改"): 8,
    ("DETAIL", "修改记录"): 8,
}


def _resolve_system_view_button_logical_id(entry: dict[str, Any]) -> int | None:
    config_type = _normalize_view_button_config_type(entry.get("configType") or entry.get("config_type")) or ""
    trigger_action = str(entry.get("triggerAction") or entry.get("trigger_action") or "").strip()
    if config_type and trigger_action:
        mapped = _SYSTEM_VIEW_BUTTON_ID_BY_ACTION.get((config_type, trigger_action))
        if mapped is not None:
            return mapped
    button_text = str(entry.get("buttonText") or entry.get("button_text") or "").strip()
    default_button_text = str(entry.get("defaultButtonText") or entry.get("default_button_text") or "").strip()
    for candidate in (button_text, default_button_text):
        if config_type and candidate:
            mapped = _SYSTEM_VIEW_BUTTON_ID_BY_TEXT.get((config_type, candidate))
            if mapped is not None:
                return mapped
    button_id = _coerce_positive_int(entry.get("buttonId") or entry.get("button_id") or entry.get("id"))
    if button_id is not None and button_id < 1000:
        return button_id
    return None


def _normalize_expected_view_buttons_for_compare(
    value: Any,
    *,
    custom_button_details_by_id: dict[int, dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
    normalized_entries = _normalize_view_buttons_for_compare(value)
    if not custom_button_details_by_id:
        return normalized_entries
    enriched_entries: list[dict[str, Any]] = []
    for item in normalized_entries:
        enriched = deepcopy(item)
        if enriched.get("button_type") == "CUSTOM":
            button_id = _coerce_positive_int(enriched.get("button_id"))
            detail = custom_button_details_by_id.get(button_id or -1)
            if isinstance(detail, dict):
                for key in (
                    "button_text",
                    "button_icon",
                    "background_color",
                    "text_color",
                    "trigger_action",
                ):
                    value = detail.get(key)
                    if enriched.get(key) in {None, ""} and value not in {None, ""}:
                        enriched[key] = deepcopy(value)
        enriched_entries.append(enriched)
    return enriched_entries


def _partition_view_button_summaries(
    items: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
    system_buttons: list[dict[str, Any]] = []
    custom_buttons: list[dict[str, Any]] = []
    other_buttons: list[dict[str, Any]] = []
    for item in items:
        button_type = str(item.get("button_type") or "").strip().upper()
        if button_type == "SYSTEM":
            system_buttons.append(item)
        elif button_type == "CUSTOM":
            custom_buttons.append(item)
        else:
            other_buttons.append(item)
    return system_buttons, custom_buttons, other_buttons


def _canonicalize_view_button_summary_order(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    top_buttons: list[dict[str, Any]] = []
    detail_main_buttons: list[dict[str, Any]] = []
    detail_more_buttons: list[dict[str, Any]] = []
    list_buttons: list[dict[str, Any]] = []
    other_buttons: list[dict[str, Any]] = []
    for item in items:
        config_type = str(item.get("config_type") or "").strip().upper()
        if config_type == "TOP":
            top_buttons.append(item)
        elif config_type == "DETAIL" and bool(item.get("being_main", False)):
            detail_main_buttons.append(item)
        elif config_type == "DETAIL":
            detail_more_buttons.append(item)
        elif config_type == "INSIDE":
            list_buttons.append(item)
        else:
            other_buttons.append(item)
    return [*top_buttons, *detail_main_buttons, *detail_more_buttons, *list_buttons, *other_buttons]


def _view_button_compare_signature(item: dict[str, Any]) -> dict[str, Any]:
    button_type = str(item.get("button_type") or "").strip().upper()
    signature: dict[str, Any] = {
        "button_type": button_type,
        "config_type": str(item.get("config_type") or "").strip().upper(),
        "button_id": item.get("button_id") if button_type == "CUSTOM" else None,
        "being_main": bool(item.get("being_main", False)),
        "print_tpls": list(item.get("print_tpls") or []),
        "button_formula": str(item.get("button_formula") or ""),
        "button_formula_type": _coerce_positive_int(item.get("button_formula_type")) or 1,
        "button_limit": deepcopy(item.get("button_limit") or []),
    }
    if button_type == "SYSTEM":
        signature["trigger_action"] = item.get("trigger_action")
        signature["button_icon"] = item.get("button_icon")
        signature["button_text"] = item.get("button_text")
    return signature


def _sorted_view_button_compare_signatures(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    signatures = [_view_button_compare_signature(item) for item in items]
    return sorted(signatures, key=lambda item: json.dumps(item, ensure_ascii=False, sort_keys=True, default=str))


def _compare_view_button_summaries(
    *,
    expected: list[dict[str, Any]],
    actual: list[dict[str, Any]],
    pending_custom_button_ids: set[int] | None = None,
) -> dict[str, Any]:
    expected = _canonicalize_view_button_summary_order(expected)
    actual = _canonicalize_view_button_summary_order(actual)
    if _sorted_view_button_compare_signatures(actual) == _sorted_view_button_compare_signatures(expected):
        return {
            "verified": True,
            "custom_button_readback_pending": False,
            "pending_custom_buttons": [],
        }
    expected_system, expected_custom, expected_other = _partition_view_button_summaries(expected)
    actual_system, actual_custom, actual_other = _partition_view_button_summaries(actual)
    pending_ids = pending_custom_button_ids or set()
    if pending_ids:
        actual_custom_signatures = {
            json.dumps(_view_button_compare_signature(item), ensure_ascii=False, sort_keys=True, default=str)
            for item in actual_custom
        }
        pending_indexes: set[int] = set()
        pending_custom_buttons: list[dict[str, Any]] = []
        for index, item in enumerate(expected):
            if str(item.get("button_type") or "").strip().upper() != "CUSTOM":
                continue
            button_id = _coerce_positive_int(item.get("button_id"))
            if button_id not in pending_ids:
                continue
            signature = json.dumps(_view_button_compare_signature(item), ensure_ascii=False, sort_keys=True, default=str)
            if signature in actual_custom_signatures:
                continue
            pending_indexes.add(index)
            pending_custom_buttons.append(item)
        if pending_indexes:
            expected_without_pending = [item for index, item in enumerate(expected) if index not in pending_indexes]
            if _sorted_view_button_compare_signatures(actual) == _sorted_view_button_compare_signatures(expected_without_pending):
                return {
                    "verified": False,
                    "custom_button_readback_pending": True,
                    "pending_custom_buttons": deepcopy(pending_custom_buttons),
                }
    custom_button_readback_pending = (
        bool(expected_custom)
        and not actual_custom
        and _sorted_view_button_compare_signatures(actual_system) == _sorted_view_button_compare_signatures(expected_system)
        and _sorted_view_button_compare_signatures(actual_other) == _sorted_view_button_compare_signatures(expected_other)
    )
    return {
        "verified": False,
        "custom_button_readback_pending": custom_button_readback_pending,
        "pending_custom_buttons": deepcopy(expected_custom) if custom_button_readback_pending else [],
    }


def _serialize_existing_view_button_entry(entry: dict[str, Any]) -> dict[str, Any]:
    dto: dict[str, Any] = {}
    button_type = _normalize_view_button_type(entry.get("buttonType") or entry.get("button_type"))
    button_id = _coerce_positive_int(entry.get("buttonId") or entry.get("button_id") or entry.get("id"))
    if button_type == "SYSTEM":
        button_id = _resolve_system_view_button_logical_id(entry)
    if button_id is not None:
        dto["buttonId"] = button_id
    if button_type is not None:
        dto["buttonType"] = button_type
    config_type = _normalize_view_button_config_type(entry.get("configType") or entry.get("config_type"))
    if config_type is not None:
        dto["configType"] = config_type
    dto["beingMain"] = bool(entry.get("beingMain", False))
    dto["buttonLimit"] = deepcopy(entry.get("buttonLimit") or [])
    dto["buttonFormula"] = str(entry.get("buttonFormula") or "")
    dto["buttonFormulaType"] = _coerce_positive_int(entry.get("buttonFormulaType")) or 1
    dto["printTpls"] = _serialize_print_tpl_ids(entry.get("printTpls"))
    for source_key, target_key in (
        ("buttonText", "buttonText"),
        ("defaultButtonText", "defaultButtonText"),
        ("buttonIcon", "buttonIcon"),
        ("backgroundColor", "backgroundColor"),
        ("textColor", "textColor"),
        ("triggerAction", "triggerAction"),
        ("triggerLinkUrl", "triggerLinkUrl"),
    ):
        if source_key in entry:
            dto[target_key] = deepcopy(entry.get(source_key))
    if isinstance(entry.get("triggerAddDataConfig"), dict):
        dto["triggerAddDataConfig"] = deepcopy(entry.get("triggerAddDataConfig"))
    if isinstance(entry.get("triggerWingsConfig"), dict):
        dto["triggerWingsConfig"] = deepcopy(entry.get("triggerWingsConfig"))
    return dto


def _extract_existing_view_button_dtos(config: dict[str, Any]) -> list[dict[str, Any]]:
    if not isinstance(config, dict):
        return []
    entries, source = _extract_view_button_entries(config)
    if source == "buttonConfigDTOList":
        return [deepcopy(item) for item in entries if isinstance(item, dict)]
    return [_serialize_existing_view_button_entry(entry) for entry in entries if isinstance(entry, dict)]


def _resolve_view_button_dtos_for_patch(
    *,
    config: dict[str, Any],
    patch: ViewUpsertPatch,
    explicit_button_dtos: list[dict[str, Any]] | None,
) -> list[dict[str, Any]] | None:
    if patch.buttons is None:
        return _extract_existing_view_button_dtos(config)
    return deepcopy(explicit_button_dtos or [])


def _build_grouped_view_button_config(button_config_dtos: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
    grouped = {"topButtonList": [], "mainButtonDetailList": [], "moreButtonDetailList": [], "listButtonList": []}
    for raw_item in button_config_dtos:
        if not isinstance(raw_item, dict):
            continue
        item = deepcopy(raw_item)
        config_type = _normalize_view_button_config_type(item.get("configType"))
        being_main = bool(item.get("beingMain", False))
        if config_type == "TOP":
            grouped["topButtonList"].append(item)
        elif config_type == "INSIDE":
            grouped["listButtonList"].append(item)
        elif being_main:
            grouped["mainButtonDetailList"].append(item)
        else:
            grouped["moreButtonDetailList"].append(item)
    return grouped


def _build_view_button_dtos(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    bindings: list[ViewButtonBindingPatch],
    valid_custom_button_ids: set[int],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    dtos: list[dict[str, Any]] = []
    issues: list[dict[str, Any]] = []
    for binding in bindings:
        dto, binding_issues = _serialize_view_button_binding(
            binding=binding,
            current_fields_by_name=current_fields_by_name,
            valid_custom_button_ids=valid_custom_button_ids,
        )
        if binding_issues:
            issues.extend(binding_issues)
            continue
        dtos.append(dto)
    return dtos, issues


def _serialize_view_button_binding(
    *,
    binding: ViewButtonBindingPatch,
    current_fields_by_name: dict[str, dict[str, Any]],
    valid_custom_button_ids: set[int],
    allow_unverified_custom_button_id: bool = False,
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
    if (
        binding.button_type == PublicViewButtonType.custom
        and binding.button_id not in valid_custom_button_ids
        and not allow_unverified_custom_button_id
    ):
        return {}, [
            {
                "error_code": "UNKNOWN_CUSTOM_BUTTON",
                "reason_path": "buttons[].button_id",
                "missing_fields": [],
                "details": {"button_id": binding.button_id},
            }
        ]
    translated_limits, limit_issues = _build_view_button_limit_groups(
        current_fields_by_name=current_fields_by_name,
        groups=binding.button_limit,
    )
    if limit_issues:
        return {}, limit_issues
    dto: dict[str, Any] = {
        "buttonId": binding.button_id,
        "buttonType": binding.button_type.value,
        "configType": binding.config_type.value,
        "beingMain": bool(binding.being_main),
        "buttonLimit": translated_limits,
        "buttonFormula": binding.button_formula or "",
        "buttonFormulaType": binding.button_formula_type,
        "printTpls": _serialize_print_tpl_ids(binding.print_tpls),
    }
    if binding.button_type in {PublicViewButtonType.system, PublicViewButtonType.custom}:
        dto["buttonText"] = binding.button_text
        dto["buttonIcon"] = binding.button_icon
        dto["backgroundColor"] = binding.background_color
        dto["textColor"] = binding.text_color
        dto["triggerAction"] = binding.trigger_action
    return dto, []


def _build_view_button_limit_groups(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    groups: list[list[ViewFilterRulePatch]],
) -> tuple[list[list[dict[str, Any]]], list[dict[str, Any]]]:
    translated_groups: list[list[dict[str, Any]]] = []
    issues: list[dict[str, Any]] = []
    for raw_group in groups:
        if not isinstance(raw_group, list):
            continue
        translated_rules: list[dict[str, Any]] = []
        for raw_rule in raw_group:
            if hasattr(raw_rule, "model_dump"):
                raw_rule = raw_rule.model_dump(mode="json")
            if not isinstance(raw_rule, dict):
                continue
            field_name = str(raw_rule.get("field_name") or "").strip()
            field = current_fields_by_name.get(field_name)
            if field is None:
                issues.append(
                    {
                        "error_code": "UNKNOWN_VIEW_FIELD",
                        "missing_fields": [field_name] if field_name else [],
                        "reason_path": "buttons[].button_limit[].field_name",
                    }
                )
                continue
            translated_rule, issue = _translate_view_filter_rule(field=field, rule=raw_rule)
            if issue:
                issue = deepcopy(issue)
                issue["reason_path"] = "buttons[].button_limit[].values"
                issues.append(issue)
                continue
            translated_rules.append(translated_rule)
        if translated_rules:
            translated_groups.append(translated_rules)
    return translated_groups, issues


def _summarize_charts(result: Any) -> list[dict[str, Any]]:
    if not isinstance(result, list):
        return []
    items: list[dict[str, Any]] = []
    for index, chart in enumerate(result):
        if not isinstance(chart, dict):
            continue
        chart_id = _extract_chart_identifier(chart)
        name = str(chart.get("chartName") or "").strip()
        chart_type = _public_chart_type_from_backend(chart.get("chartType"))
        if not any((chart_id, name, chart_type)):
            continue
        items.append(
            {
                "chart_id": chart_id or None,
                "name": name or None,
                "chart_type": chart_type or None,
                "order": index,
            }
        )
    return items


def _normalize_portal_list_items(raw_items: Any) -> list[dict[str, Any]]:
    if not isinstance(raw_items, list):
        return []
    items: list[dict[str, Any]] = []
    for item in raw_items:
        if not isinstance(item, dict):
            continue
        dash_key = str(item.get("dashKey") or "").strip()
        dash_name = str(item.get("dashName") or "").strip()
        dash_icon = str(item.get("dashIcon") or "").strip() or None
        package_tag_ids = [
            tag_id
            for tag_id in (
                _coerce_positive_int((tag or {}).get("tagId"))
                for tag in (item.get("tags") or [])
                if isinstance(tag, dict)
            )
            if tag_id is not None
        ]
        if not any((dash_key, dash_name, dash_icon, package_tag_ids)):
            continue
        items.append(
            {
                "dash_key": dash_key or None,
                "dash_name": dash_name or None,
                "dash_icon": dash_icon,
                "icon_config": workspace_icon_config(dash_icon),
                "package_tag_ids": package_tag_ids,
            }
        )
    return items


def _summarize_portal_sections(components: Any) -> list[dict[str, Any]]:
    if not isinstance(components, list):
        return []
    items: list[dict[str, Any]] = []
    for index, component in enumerate(components):
        if not isinstance(component, dict):
            continue
        source_type = _normalize_portal_component_source_type(component.get("type"))
        title = _extract_portal_component_title(component, source_type=source_type)
        summary: dict[str, Any] = {
            "order": index,
            "source_type": source_type,
            "title": title,
        }
        position = component.get("position")
        if isinstance(position, dict):
            summary["position"] = deepcopy(position)
        if source_type == "chart":
            chart_config = component.get("chartConfig") if isinstance(component.get("chartConfig"), dict) else {}
            summary["chart_ref"] = {
                "chart_id": str(chart_config.get("biChartId") or "").strip() or None,
                "chart_name": str(chart_config.get("chartComponentTitle") or title or "").strip() or None,
            }
        elif source_type == "view":
            view_config = component.get("viewgraphConfig") if isinstance(component.get("viewgraphConfig"), dict) else {}
            summary["view_ref"] = {
                "app_key": str(view_config.get("appKey") or "").strip() or None,
                "view_key": str(view_config.get("viewgraphKey") or "").strip() or None,
                "view_name": str(view_config.get("viewgraphName") or title or "").strip() or None,
            }
        items.append(summary)
    return items


def _normalize_portal_components(components: Any) -> list[dict[str, Any]]:
    if not isinstance(components, list):
        return []
    items: list[dict[str, Any]] = []
    config_key_map = {
        "grid": "gridConfig",
        "link": "linkConfig",
        "text": "textConfig",
        "filter": "filterConfig",
        "chart": "chartConfig",
        "view": "viewgraphConfig",
    }
    for index, component in enumerate(components):
        if not isinstance(component, dict):
            continue
        source_type = _normalize_portal_component_source_type(component.get("type"))
        title = _extract_portal_component_title(component, source_type=source_type)
        summary: dict[str, Any] = {
            "order": index,
            "source_type": source_type,
            "title": title,
        }
        position = component.get("position")
        if isinstance(position, dict):
            summary["position"] = deepcopy(position)
        config_key = config_key_map.get(source_type, "")
        config = component.get(config_key) if isinstance(component.get(config_key), dict) else {}
        if source_type == "chart":
            summary["chart_ref"] = {
                "chart_id": str(config.get("biChartId") or "").strip() or None,
                "chart_name": str(config.get("chartComponentTitle") or title or "").strip() or None,
            }
        elif source_type == "view":
            summary["view_ref"] = {
                "app_key": str(config.get("appKey") or "").strip() or None,
                "view_key": str(config.get("viewgraphKey") or "").strip() or None,
                "view_name": str(config.get("viewgraphName") or title or "").strip() or None,
            }
        elif source_type in {"grid", "link", "text", "filter"} and config:
            summary["config"] = deepcopy(config)
        items.append(summary)
    return items


def _custom_button_view_configs_from_view_summaries(views: list[dict[str, Any]]) -> list[dict[str, Any]]:
    view_configs: list[dict[str, Any]] = []
    for view in views:
        if not isinstance(view, dict):
            continue
        view_key = str(view.get("view_key") or "").strip()
        if not view_key:
            continue
        raw_buttons = view.get("buttons")
        if not isinstance(raw_buttons, list):
            continue
        field_lookup = _view_field_lookup_from_summary(view)
        buttons: list[dict[str, Any]] = []
        for entry in raw_buttons:
            if not isinstance(entry, dict):
                continue
            if _normalize_view_button_type(entry.get("button_type")) != PublicViewButtonType.custom.value:
                continue
            placement = _public_view_button_placement(entry.get("config_type")) or entry.get("placement")
            raw_button_limit = deepcopy(entry.get("button_limit") or [])
            public_button_limit = (
                _public_view_filter_groups_from_match_rules(raw_button_limit, current_fields_by_name=field_lookup)
                if raw_button_limit
                else []
            )
            button = _compact_dict(
                {
                    "button_ref": _coerce_positive_int(entry.get("button_id")),
                    "button_id": _coerce_positive_int(entry.get("button_id")),
                    "button_text": entry.get("button_text"),
                    "placement": placement,
                    "primary": bool(entry.get("being_main", False)),
                    "button_limit": public_button_limit,
                    "button_formula": entry.get("button_formula"),
                    "button_formula_type": _coerce_positive_int(entry.get("button_formula_type")),
                    "print_tpls": deepcopy(entry.get("print_tpls") or []),
                }
            )
            if button:
                buttons.append(button)
        if buttons or "button_count" in view or "button_read_source" in view:
            view_configs.append(
                _compact_dict(
                    {
                        "view_key": view_key,
                        "view_name": view.get("name"),
                        "buttons": buttons,
                        "button_count": len(buttons),
                    }
                )
            )
    return view_configs


def _associated_resource_view_configs_from_view_summaries(views: list[dict[str, Any]]) -> list[dict[str, Any]]:
    view_configs: list[dict[str, Any]] = []
    for view in views:
        if not isinstance(view, dict):
            continue
        view_key = str(view.get("view_key") or "").strip()
        config = view.get("associated_resources_config")
        if not view_key or not isinstance(config, dict):
            continue
        configured_ids = [
            item_id
            for item_id in (_coerce_positive_int(value) for value in (config.get("configured_associated_item_ids") or []))
            if item_id is not None
        ]
        effective_ids = [
            item_id
            for item_id in (_coerce_positive_int(value) for value in (config.get("effective_associated_item_ids") or []))
            if item_id is not None
        ]
        view_configs.append(
            _compact_dict(
                {
                    "view_key": view_key,
                    "view_name": view.get("name"),
                    "visible": bool(config.get("visible", False)),
                    "limit_type": config.get("limit_type"),
                    "associated_item_ids": configured_ids,
                    "effective_associated_item_ids": effective_ids,
                    "items": deepcopy(config.get("items") or []),
                }
            )
        )
    return view_configs


def _portal_chart_ref_matches(component_ref: dict[str, Any], selector: dict[str, Any]) -> bool:
    selector_id = str(_first_present(selector, "chart_id", "chartId", "biChartId", "id") or "").strip()
    selector_key = str(_first_present(selector, "chart_key", "chartKey", "graphKey", "key") or "").strip()
    selector_name = str(_first_present(selector, "chart_name", "chartName", "name", "title") or "").strip()
    component_id = str(_first_present(component_ref, "chart_id", "chartId", "biChartId", "id") or "").strip()
    component_key = str(_first_present(component_ref, "chart_key", "chartKey", "graphKey", "key") or "").strip()
    component_name = str(_first_present(component_ref, "chart_name", "chartName", "name", "title") or "").strip()
    if selector_id and selector_id in {component_id, component_key}:
        return True
    if selector_key and selector_key in {component_key, component_id}:
        return True
    return bool(selector_name and component_name and selector_name == component_name)


def _portal_view_ref_matches(component_ref: dict[str, Any], selector: dict[str, Any]) -> bool:
    selector_app_key = str(_first_present(selector, "app_key", "appKey") or "").strip()
    selector_key = str(_first_present(selector, "view_key", "viewKey", "viewgraphKey", "key") or "").strip()
    selector_name = str(_first_present(selector, "view_name", "viewName", "viewgraphName", "name", "title") or "").strip()
    component_app_key = str(_first_present(component_ref, "app_key", "appKey") or "").strip()
    component_key = str(_first_present(component_ref, "view_key", "viewKey", "viewgraphKey", "key") or "").strip()
    component_name = str(_first_present(component_ref, "view_name", "viewName", "viewgraphName", "name", "title") or "").strip()
    if selector_app_key and component_app_key and selector_app_key != component_app_key:
        return False
    if selector_key and component_key and selector_key == component_key:
        return True
    return bool(selector_name and component_name and selector_name == component_name)


def _normalize_portal_component_source_type(value: Any) -> str:
    raw = str(value or "").strip()
    mapping = {
        "2": "grid",
        "4": "link",
        "5": "text",
        "6": "filter",
        "9": "chart",
        "10": "view",
        "grid": "grid",
        "link": "link",
        "text": "text",
        "filter": "filter",
        "chart": "chart",
        "view": "view",
    }
    return mapping.get(raw, raw.lower() or "unknown")


def _extract_portal_component_title(component: dict[str, Any], *, source_type: str) -> str | None:
    config_key_map = {
        "grid": "gridConfig",
        "link": "linkConfig",
        "text": "textConfig",
        "filter": "filterConfig",
        "chart": "chartConfig",
        "view": "viewgraphConfig",
    }
    config_key = config_key_map.get(source_type, "")
    config = component.get(config_key) if isinstance(component.get(config_key), dict) else {}
    title_candidates = {
        "grid": ["gridTitle"],
        "link": ["title", "linkTitle"],
        "text": ["title", "textTitle"],
        "filter": ["title", "filterTitle"],
        "chart": ["chartComponentTitle", "componentTitle", "title"],
        "view": ["componentTitle", "viewgraphName", "title"],
    }
    for key in title_candidates.get(source_type, []):
        title = str(config.get(key) or "").strip()
        if title:
            return title
    return None


def _entity_spec_from_app(*, base_info: dict[str, Any], schema: dict[str, Any], views: Any) -> dict[str, Any]:
    parsed = _parse_schema(schema)
    fields = []
    for field in parsed["fields"]:
        normalized_type = field["type"]
        if normalized_type == FieldType.relation.value:
            normalized_type = FieldType.text.value
        field_spec = {
            "field_id": field["field_id"],
            "label": field["name"],
            "type": normalized_type,
            "required": field.get("required", False),
        }
        if field.get("description"):
            field_spec["description"] = field["description"]
        if field.get("options"):
            field_spec["options"] = field["options"]
        if field.get("type") == FieldType.subtable.value:
            field_spec["subfields"] = [
                {
                    "field_id": subfield["field_id"],
                    "label": subfield["name"],
                    "type": subfield["type"],
                    "required": subfield.get("required", False),
                }
                for subfield in field.get("subfields", [])
            ]
        fields.append(field_spec)
    root_rows = [{"field_ids": [_field_id_for_name(parsed["fields"], name) for name in row]} for row in parsed["layout"].get("root_rows", [])]
    sections = []
    for section in parsed["layout"].get("sections", []):
        sections.append(
            {
                "section_id": section["section_id"],
                "title": section["title"],
                "rows": [{"field_ids": [_field_id_for_name(parsed["fields"], name) for name in row]} for row in section.get("rows", [])],
            }
        )
    entity = {
        "entity_id": _slugify(base_info.get("formTitle") or base_info.get("appKey") or "app", default="app"),
        "display_name": base_info.get("formTitle") or base_info.get("appKey") or "应用",
        "kind": "transaction",
        "fields": fields,
        "title_field_id": fields[0]["field_id"] if fields else None,
    }
    status_field_id = _infer_status_field_id(fields)
    if status_field_id:
        entity["status_field_id"] = status_field_id
    if root_rows or sections:
        entity["form_layout"] = {"rows": root_rows, "sections": sections}
    return entity


def _field_id_for_name(fields: list[dict[str, Any]], name: str) -> str:
    for field in fields:
        if field["name"] == name:
            return field["field_id"]
    raise KeyError(name)


def _build_public_workflow_spec(*, nodes: list[dict[str, Any]], transitions: list[dict[str, Any]]) -> JSONObject:
    node_map = {str(node.get("id") or ""): node for node in nodes if isinstance(node, dict) and node.get("id")}
    if not node_map:
        return _failed("INVALID_FLOW", "nodes must be a non-empty list", suggested_next_call=None)
    start_nodes = [node_id for node_id, node in node_map.items() if node.get("type") == "start"]
    if len(start_nodes) != 1:
        return _failed("INVALID_FLOW", "flow must contain exactly one start node", suggested_next_call=None)
    inbound: dict[str, list[str]] = {node_id: [] for node_id in node_map}
    outbound: dict[str, list[str]] = {node_id: [] for node_id in node_map}
    for transition in transitions:
        if not isinstance(transition, dict):
            continue
        source = str(transition.get("from") or "")
        target = str(transition.get("to") or "")
        if source not in node_map or target not in node_map:
            return _failed("INVALID_FLOW_EDGE", "transition references unknown node", details={"transition": transition}, suggested_next_call=None)
        outbound[source].append(target)
        inbound[target].append(source)
    branch_lane_nodes: dict[str, dict[str, Any]] = {}
    for node_id, node in node_map.items():
        if str(node.get("type") or "") != "condition":
            continue
        if len(inbound[node_id]) != 1:
            return _failed(
                "INVALID_FLOW_EDGE",
                f"condition lane '{node_id}' must have exactly one inbound transition",
                details={"node_id": node_id, "inbound": inbound[node_id]},
                suggested_next_call=None,
            )
        parent_id = inbound[node_id][0]
        if node_map[parent_id].get("type") != "branch":
            continue
        branch_lane_nodes[node_id] = {
            "branch_parent_id": parent_id,
            "branch_index": outbound[parent_id].index(node_id) + 1,
        }
    internal_nodes = []
    for node_id, node in node_map.items():
        node_type = str(node.get("type") or "")
        if node_type == "end":
            continue
        if node_type != "start" and len(inbound[node_id]) != 1:
            return _failed(
                "INVALID_FLOW_EDGE",
                f"node '{node_id}' must have exactly one inbound transition",
                details={"node_id": node_id, "inbound": inbound[node_id]},
                suggested_next_call=None,
            )
        config_payload = deepcopy(node.get("config") or {}) if isinstance(node.get("config"), dict) else {}
        assignees = node.get("assignees") or {}
        if node_id in branch_lane_nodes:
            lane_meta = branch_lane_nodes[node_id]
            config_payload["__lane_only__"] = True
            payload = {
                "node_id": f"__branch_lane__{lane_meta['branch_parent_id']}__{lane_meta['branch_index']}",
                "name": node.get("name") or node_id,
                "node_type": "condition",
                "parent_node_id": lane_meta["branch_parent_id"],
                "branch_parent_id": lane_meta["branch_parent_id"],
                "branch_index": lane_meta["branch_index"],
            }
        else:
            payload = {
                "node_id": node_id,
                "name": node.get("name") or node_id,
                "node_type": "audit" if node_type == "approve" else node_type,
            }
            if node_type != "start":
                parent_id = inbound[node_id][0]
                lane_parent = branch_lane_nodes.get(parent_id)
                if lane_parent is not None:
                    payload["parent_node_id"] = lane_parent["branch_parent_id"]
                    payload["branch_parent_id"] = lane_parent["branch_parent_id"]
                    payload["branch_index"] = lane_parent["branch_index"]
                else:
                    payload["parent_node_id"] = parent_id
                    if node_map[parent_id].get("type") == "branch":
                        payload["branch_parent_id"] = parent_id
                        payload["branch_index"] = outbound[parent_id].index(node_id) + 1
        permissions = node.get("permissions") or {}
        editable_que_ids = permissions.get("editable_que_ids") or []
        if editable_que_ids:
            config_payload["editableQueIds"] = editable_que_ids
        if config_payload:
            payload["config"] = config_payload
        if assignees:
            payload["assignees"] = deepcopy(assignees)
        internal_nodes.append(payload)
    return {
        "status": "success",
        "workflow": {"enabled": True, "nodes": internal_nodes},
    }


def _build_flow_condition_matrix(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    node: dict[str, Any],
) -> tuple[list[list[dict[str, Any]]], list[dict[str, Any]]]:
    if str(node.get("type") or "") != "condition":
        return [], []
    raw_groups = node.get("condition_groups") or []
    if not isinstance(raw_groups, list):
        return [], []
    groups: list[list[dict[str, Any]]] = []
    issues: list[dict[str, Any]] = []
    for raw_group in raw_groups:
        if not isinstance(raw_group, list):
            continue
        translated_group: list[dict[str, Any]] = []
        for raw_rule in raw_group:
            if not isinstance(raw_rule, dict):
                continue
            field_name = str(raw_rule.get("field_name") or "").strip()
            field = current_fields_by_name.get(field_name)
            if field is None:
                issues.append(
                    {
                        "kind": "condition_fields",
                        "error_code": "UNKNOWN_FLOW_FIELD",
                        "missing_fields": [field_name] if field_name else [],
                    }
                )
                continue
            translated_group.append(_translate_flow_condition_rule(field=field, rule=raw_rule))
        if translated_group:
            groups.append(translated_group)
    return groups, issues


def _translate_flow_condition_rule(*, field: dict[str, Any], rule: dict[str, Any]) -> dict[str, Any]:
    operator = str(rule.get("operator") or "").strip().lower()
    values = list(rule.get("values") or [])
    field_type = str(field.get("type") or FieldType.text.value)
    que_id = _coerce_positive_int(field.get("que_id")) or 0
    que_type = _coerce_positive_int(field.get("que_type")) or FIELD_TYPE_TO_QUESTION_TYPE.get(field_type, 2)
    base: dict[str, Any] = {
        "queId": que_id,
        "queTitle": str(field.get("name") or ""),
        "queType": que_type,
        "matchType": MATCH_TYPE_ACCURACY,
    }
    if operator == "eq":
        base["judgeType"] = JUDGE_EQUAL
        base["judgeValues"] = [_stringify_condition_value(values[0])]
    elif operator == "neq":
        base["judgeType"] = JUDGE_UNEQUAL
        base["judgeValues"] = [_stringify_condition_value(values[0])]
    elif operator == "in":
        base["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
        base["judgeValues"] = [_stringify_condition_value(value) for value in values]
    elif operator == "contains":
        base["judgeType"] = JUDGE_INCLUDE
        base["judgeValues"] = [_stringify_condition_value(values[0])]
    elif operator == "gte":
        base["judgeType"] = JUDGE_GREATER_OR_EQUAL
        base["judgeValues"] = [_stringify_condition_value(values[0])]
    elif operator == "lte":
        base["judgeType"] = JUDGE_LESS_OR_EQUAL
        base["judgeValues"] = [_stringify_condition_value(values[0])]
    elif operator == "is_empty":
        base["judgeType"] = JUDGE_EQUAL
        base["judgeValues"] = []
    elif operator == "not_empty":
        base["judgeType"] = JUDGE_UNEQUAL
        base["judgeValues"] = []
    return base


def _stringify_condition_value(value: Any) -> str:
    if isinstance(value, bool):
        return "true" if value else "false"
    if value is None:
        return ""
    if isinstance(value, (dict, list)):
        return json.dumps(value, ensure_ascii=False)
    return str(value)


def _empty_view_query_conditions_payload() -> dict[str, Any]:
    return {
        "queryConditionStatus": False,
        "queryConditionExact": False,
        "hideBeforeQueryCondition": False,
        "queryCondition": [],
    }


def _build_view_query_conditions_payload(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    query_conditions: Any,
) -> tuple[dict[str, Any] | None, dict[str, Any] | None, list[dict[str, Any]]]:
    if query_conditions is None:
        return None, None, []
    if isinstance(query_conditions, dict):
        enabled = bool(query_conditions.get("enabled", True))
        exact = bool(query_conditions.get("exact", False))
        hide_before_query = bool(query_conditions.get("hide_before_query", False))
        raw_rows = query_conditions.get("rows") or []
    else:
        enabled = bool(getattr(query_conditions, "enabled", True))
        exact = bool(getattr(query_conditions, "exact", False))
        hide_before_query = bool(getattr(query_conditions, "hide_before_query", False))
        raw_rows = getattr(query_conditions, "rows", []) or []
    if enabled and not raw_rows:
        return None, None, [
            {
                "error_code": "QUERY_CONDITION_ROWS_REQUIRED",
                "reason_path": "query_conditions.rows",
                "missing_fields": [],
                "message": "query_conditions.enabled=true requires at least one query field",
            }
        ]
    rows: list[list[int]] = []
    issues: list[dict[str, Any]] = []
    for row_index, row in enumerate(raw_rows):
        if not isinstance(row, list):
            issues.append(
                {
                    "error_code": "INVALID_QUERY_CONDITION_ROW",
                    "reason_path": f"query_conditions.rows[{row_index}]",
                    "missing_fields": [],
                    "message": "query condition rows must be lists of field names or ids",
                }
            )
            continue
        compiled_row: list[int] = []
        for column_index, selector in enumerate(row):
            field, issue = _resolve_query_condition_field(
                current_fields_by_name=current_fields_by_name,
                selector=selector,
                reason_path=f"query_conditions.rows[{row_index}][{column_index}]",
            )
            if issue:
                issues.append(issue)
                continue
            que_id = _coerce_positive_int(field.get("que_id"))
            if que_id is not None:
                compiled_row.append(que_id)
        if compiled_row:
            rows.append(compiled_row)
    if issues:
        return None, None, issues
    payload = {
        "queryConditionStatus": enabled,
        "queryConditionExact": exact,
        "hideBeforeQueryCondition": hide_before_query,
        "queryCondition": rows if enabled else [],
    }
    return payload, _normalize_view_query_conditions_for_compare(payload), []


def _resolve_query_condition_field(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    selector: Any,
    reason_path: str,
) -> tuple[dict[str, Any], dict[str, Any] | None]:
    raw = str(selector or "").strip()
    if not raw:
        return {}, {
            "error_code": "QUERY_CONDITION_FIELD_NOT_FOUND",
            "reason_path": reason_path,
            "missing_fields": [],
            "message": "query condition field selector cannot be empty",
        }
    fields = [field for field in current_fields_by_name.values() if isinstance(field, dict)]
    field = current_fields_by_name.get(raw)
    if field is None:
        for candidate in fields:
            if raw == str(candidate.get("field_id") or "").strip():
                field = candidate
                break
            candidate_que_id = _coerce_positive_int(candidate.get("que_id"))
            if candidate_que_id is not None and raw == str(candidate_que_id):
                field = candidate
                break
    if field is None:
        subfield_parent = _find_subtable_subfield_parent(current_fields_by_name=current_fields_by_name, selector=raw)
        if subfield_parent is not None:
            return {}, {
                "error_code": "INVALID_QUERY_CONDITION_FIELD",
                "reason_path": reason_path,
                "missing_fields": [],
                "field": raw,
                "parent_field": subfield_parent.get("name"),
                "message": "subtable subfields cannot be used as view query conditions",
                "fix_hint": QUERY_CONDITION_FIX_HINT,
                "supported_field_types": QUERY_CONDITION_SUPPORTED_FIELD_TYPES_PUBLIC,
                "unsupported_field_types": QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES_PUBLIC,
            }
        return {}, {
            "error_code": "QUERY_CONDITION_FIELD_NOT_FOUND",
            "reason_path": reason_path,
            "missing_fields": [raw],
            "message": "query condition references an unknown field",
        }
    field_type = str(field.get("type") or "")
    if field_type in QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES:
        return {}, {
            "error_code": "INVALID_QUERY_CONDITION_FIELD",
            "reason_path": reason_path,
            "missing_fields": [],
            "field": field.get("name"),
            "field_type": field_type,
            "message": "this field type is not supported by the frontend query condition panel",
            "fix_hint": QUERY_CONDITION_FIX_HINT,
            "supported_field_types": QUERY_CONDITION_SUPPORTED_FIELD_TYPES_PUBLIC,
            "unsupported_field_types": QUERY_CONDITION_UNSUPPORTED_FIELD_TYPES_PUBLIC,
        }
    que_id = _coerce_positive_int(field.get("que_id"))
    if que_id is None:
        return {}, {
            "error_code": "INVALID_QUERY_CONDITION_FIELD",
            "reason_path": reason_path,
            "missing_fields": [],
            "field": field.get("name"),
            "message": "query condition field has no backend queId",
            "fix_hint": QUERY_CONDITION_FIX_HINT,
            "supported_field_types": QUERY_CONDITION_SUPPORTED_FIELD_TYPES_PUBLIC,
        }
    return field, None


def _find_subtable_subfield_parent(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    selector: str,
) -> dict[str, Any] | None:
    for field in current_fields_by_name.values():
        if not isinstance(field, dict) or str(field.get("type") or "") != FieldType.subtable.value:
            continue
        for subfield in field.get("subfields") or []:
            if not isinstance(subfield, dict):
                continue
            if selector == str(subfield.get("name") or "").strip():
                return field
            if selector == str(subfield.get("field_id") or "").strip():
                return field
            sub_que_id = _coerce_positive_int(subfield.get("que_id"))
            if sub_que_id is not None and selector == str(sub_que_id):
                return field
    return None


def _apply_view_query_conditions_payload(data: dict[str, Any], query_condition_payload: dict[str, Any] | None) -> None:
    if query_condition_payload is None:
        return
    data["queryConditionStatus"] = bool(query_condition_payload.get("queryConditionStatus", False))
    data["queryConditionExact"] = bool(query_condition_payload.get("queryConditionExact", False))
    data["hideBeforeQueryCondition"] = bool(query_condition_payload.get("hideBeforeQueryCondition", False))
    data["queryCondition"] = deepcopy(query_condition_payload.get("queryCondition") or [])


def _extract_view_query_conditions_config(
    config: dict[str, Any],
    *,
    question_entries_by_id: dict[int, dict[str, Any]] | None = None,
) -> dict[str, Any]:
    question_entries_by_id = question_entries_by_id or {}
    normalized = _normalize_view_query_conditions_for_compare(config)
    rows: list[list[str]] = []
    for row in normalized["rows"]:
        public_row: list[str] = []
        for field_id in row:
            name = str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
            public_row.append(name or str(field_id))
        rows.append(public_row)
    public_config = _public_view_query_conditions_from_compare(normalized)
    public_config["rows"] = rows
    public_config["row_field_ids"] = deepcopy(normalized["rows"])
    return public_config


def _public_view_query_conditions_from_compare(normalized: dict[str, Any]) -> dict[str, Any]:
    return {
        "enabled": bool(normalized.get("enabled", False)),
        "exact": bool(normalized.get("exact", False)),
        "hide_before_query": bool(normalized.get("hide_before_query", False)),
        "rows": deepcopy(normalized.get("rows") or []),
    }


def _normalize_view_query_conditions_for_compare(config: Any) -> dict[str, Any]:
    if not isinstance(config, dict):
        config = {}
    raw_rows = config.get("queryCondition")
    rows: list[list[int]] = []
    if isinstance(raw_rows, list):
        for raw_row in raw_rows:
            if not isinstance(raw_row, list):
                continue
            compiled_row = [
                field_id
                for field_id in (_coerce_positive_int(item) for item in raw_row)
                if field_id is not None
            ]
            if compiled_row:
                rows.append(compiled_row)
    enabled = bool(config.get("queryConditionStatus", bool(rows)))
    return {
        "enabled": enabled,
        "exact": bool(config.get("queryConditionExact", False)),
        "hide_before_query": bool(config.get("hideBeforeQueryCondition", False)),
        "rows": rows,
    }


def _build_view_associated_resources_payload(
    *,
    associated_resources: ViewAssociatedResourcesPatch | dict[str, Any] | None,
    available_resources: list[dict[str, Any]],
) -> tuple[dict[str, Any] | None, dict[str, Any] | None, list[dict[str, Any]]]:
    if associated_resources is None:
        return None, None, []
    if isinstance(associated_resources, dict):
        visible = bool(associated_resources.get("visible", associated_resources.get("enabled", True)))
        raw_limit_type = associated_resources.get("limit_type", associated_resources.get("limitType"))
        raw_item_ids = (
            associated_resources.get("associated_item_ids")
            or associated_resources.get("associatedItemIds")
            or associated_resources.get("asosChartIdList")
            or associated_resources.get("items")
            or []
        )
    else:
        visible = bool(getattr(associated_resources, "visible", True))
        raw_limit_type = getattr(associated_resources, "limit_type", None)
        raw_item_ids = getattr(associated_resources, "associated_item_ids", []) or []
    if not visible:
        expected = {
            "visible": False,
            "limit_type": None,
            "configured_associated_item_ids": [],
            "effective_associated_item_ids": [],
            "items": [],
        }
        return {"asosChartVisible": False}, expected, []
    limit_type = str(raw_limit_type or "all").strip().lower()
    if limit_type not in {"all", "select"}:
        return None, None, [
            {
                "error_code": "INVALID_ASSOCIATED_RESOURCE_LIMIT_TYPE",
                "reason_path": "associated_resources.limit_type",
                "missing_fields": [],
                "received": raw_limit_type,
                "expected_values": ["all", "select"],
                "message": "associated_resources.limit_type must be 'all' or 'select'",
            }
        ]
    available_by_id = _associated_resource_index(available_resources)
    if limit_type == "all":
        payload = {
            "asosChartVisible": True,
            "asosChartConfig": {
                "limitType": ASSOCIATED_RESOURCE_LIMIT_TYPE_ALL,
                "asosChartIdList": [],
            },
        }
        expected = _extract_view_associated_resources_config(payload, available_resources=available_resources)
        return payload, expected, []
    item_ids: list[int] = []
    for index, raw_item_id in enumerate(raw_item_ids):
        item_id = _coerce_positive_int(raw_item_id)
        if item_id is None:
            return None, None, [
                {
                    "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
                    "reason_path": f"associated_resources.associated_item_ids[{index}]",
                    "missing_fields": [],
                    "received": raw_item_id,
                    "message": "associated_item_id must be an app-level associated resource id from app_get.associated_resources",
                    "next_action": "prefer app_associated_resources_apply for associated report/view display; it can resolve associated_item_id, chart_id/chart_key, or view_key",
                }
            ]
        item_ids.append(item_id)
    missing_ids = [item_id for item_id in item_ids if item_id not in available_by_id]
    if missing_ids:
        return None, None, [
            {
                "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
                "reason_path": "associated_resources.associated_item_ids",
                "missing_fields": [],
                "invalid_associated_item_ids": missing_ids,
                "available_associated_item_ids": sorted(available_by_id),
                "message": "associated_resource references ids that are not in the app-level associated resource pool",
                "next_action": "prefer app_associated_resources_apply for associated report/view display; it can resolve associated_item_id, chart_id/chart_key, or view_key",
            }
        ]
    payload = {
        "asosChartVisible": True,
        "asosChartConfig": {
            "limitType": ASSOCIATED_RESOURCE_LIMIT_TYPE_SELECT,
            "asosChartIdList": item_ids,
        },
    }
    expected = _extract_view_associated_resources_config(payload, available_resources=available_resources)
    return payload, expected, []


def _apply_view_associated_resources_payload(data: dict[str, Any], associated_resources_payload: dict[str, Any] | None) -> None:
    if associated_resources_payload is None:
        return
    if "asosChartVisible" in associated_resources_payload:
        data["asosChartVisible"] = bool(associated_resources_payload.get("asosChartVisible", False))
    if "asosChartConfig" in associated_resources_payload:
        data["asosChartConfig"] = deepcopy(associated_resources_payload.get("asosChartConfig") or {})


def _extract_view_associated_resources_config(
    config: dict[str, Any],
    *,
    available_resources: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    available_resources = available_resources or []
    available_by_id = _associated_resource_index(available_resources)
    visible = bool(config.get("asosChartVisible", False))
    raw_config = config.get("asosChartConfig")
    if not isinstance(raw_config, dict):
        raw_config = {}
    raw_limit_type = raw_config.get("limitType", config.get("limitType", ASSOCIATED_RESOURCE_LIMIT_TYPE_ALL))
    limit_type = "select" if _coerce_positive_int(raw_limit_type) == ASSOCIATED_RESOURCE_LIMIT_TYPE_SELECT else "all"
    raw_ids = raw_config.get("asosChartIdList", config.get("asosChartIdList", [])) or []
    configured_ids = [
        item_id
        for item_id in (_coerce_positive_int(item) for item in raw_ids)
        if item_id is not None
    ]
    if not visible:
        effective_ids: list[int] = []
    elif limit_type == "all":
        effective_ids = sorted(available_by_id) if available_by_id else []
    else:
        effective_ids = configured_ids
    items = [deepcopy(available_by_id[item_id]) for item_id in effective_ids if item_id in available_by_id]
    return {
        "visible": visible,
        "limit_type": limit_type,
        "configured_associated_item_ids": configured_ids,
        "effective_associated_item_ids": effective_ids,
        "items": items,
    }


def _associated_resources_config_matches(expected: dict[str, Any], actual: dict[str, Any]) -> bool:
    if bool(expected.get("visible", False)) != bool(actual.get("visible", False)):
        return False
    if not bool(expected.get("visible", False)):
        return True
    if str(expected.get("limit_type") or "") != str(actual.get("limit_type") or ""):
        return False
    if sorted(expected.get("configured_associated_item_ids") or []) != sorted(actual.get("configured_associated_item_ids") or []):
        return False
    if sorted(expected.get("effective_associated_item_ids") or []) != sorted(actual.get("effective_associated_item_ids") or []):
        return False
    return True


def _normalize_associated_resources_payload(payload: Any, *, include_raw: bool = False) -> list[dict[str, Any]]:
    if isinstance(payload, dict):
        raw_items = (
            payload.get("associated_resources")
            or payload.get("associatedResources")
            or payload.get("asosCharts")
            or payload.get("items")
            or payload.get("list")
            or payload.get("data")
            or payload.get("result")
            or []
        )
    else:
        raw_items = payload or []
    if isinstance(raw_items, dict):
        raw_items = raw_items.get("items") or raw_items.get("list") or raw_items.get("data") or []
    if not isinstance(raw_items, list):
        return []
    normalized_items: list[dict[str, Any]] = []
    seen_ids: set[int] = set()
    for raw_item in raw_items:
        item = _normalize_associated_resource_item(raw_item, include_raw=include_raw)
        item_id = _coerce_positive_int(item.get("associated_item_id"))
        if item_id is None or item_id in seen_ids:
            continue
        item["associated_item_id"] = item_id
        normalized_items.append(item)
        seen_ids.add(item_id)
    return normalized_items


def _normalize_associated_resource_item(raw_item: Any, *, include_raw: bool = False) -> dict[str, Any]:
    if not isinstance(raw_item, dict):
        return {}
    item_id = _first_present(raw_item, "associated_item_id", "associatedItemId", "asosChartId", "id")
    chart_key = _first_present(raw_item, "chart_key", "chartKey", "graphKey", "chartId")
    view_key = _first_present(raw_item, "view_key", "viewKey", "viewgraphKey", "viewGraphKey")
    graph_type = _normalize_associated_graph_type(raw_item, chart_key=chart_key, view_key=view_key)
    name = _first_present(raw_item, "name", "chartName", "chart_name", "viewName", "view_name", "title")
    source_type = _first_present(raw_item, "source_type", "sourceType", "type")
    target_app_key = _first_present(raw_item, "target_app_key", "targetAppKey", "appKey", "formKey")
    resource_type = "view" if graph_type == "view" else "report"
    item: dict[str, Any] = {
        "associated_item_id": item_id,
        "resource_type": resource_type,
        "graph_type": graph_type,
        "name": name,
    }
    if target_app_key is not None:
        item["target_app_key"] = target_app_key
    if graph_type == "view":
        item["view_key"] = view_key or chart_key
        view_type = _first_present(raw_item, "view_type", "viewType", "viewgraphType", "viewGraphType")
        if view_type is not None:
            item["view_type"] = view_type
    else:
        item["chart_id"] = _first_present(raw_item, "chart_id", "chartId", "idOfChart")
        item["chart_key"] = chart_key
        chart_type = _first_present(raw_item, "chart_type", "chartType", "chartGraphType")
        if chart_type is not None:
            item["chart_type"] = _public_chart_type_from_backend(chart_type)
        item["report_source"] = _public_report_source_from_backend_source(source_type)
    match_rules = _normalize_associated_resource_raw_match_rules(raw_item)
    if match_rules:
        item["match_rules"] = match_rules
    item = _compact_dict(item)
    if include_raw:
        item["_raw"] = deepcopy(raw_item)
    return item


def _normalize_associated_resource_raw_match_rules(raw_item: dict[str, Any]) -> list[dict[str, Any]]:
    raw_match_rules = _first_present(raw_item, "match_rules", "matchRules", "que_relation", "queRelation")
    if not isinstance(raw_match_rules, list):
        return []
    flattened: list[dict[str, Any]] = []
    if raw_match_rules and all(isinstance(group, list) for group in raw_match_rules):
        for group in raw_match_rules:
            flattened.extend(item for item in group if isinstance(item, dict))
    else:
        flattened.extend(item for item in raw_match_rules if isinstance(item, dict))
    return [_normalize_custom_button_match_rule_for_public(rule) for rule in flattened]


def _match_field_name(field_id: Any, *, fields_by_que_id: dict[int, dict[str, Any]], fallback: Any = None) -> str:
    normalized_id = _coerce_any_int(field_id)
    if normalized_id is not None:
        field = fields_by_que_id.get(normalized_id) or {}
        name = str(field.get("name") or field.get("title") or "").strip()
        if name:
            return name
        if normalized_id == -17:
            return "数据ID"
        if normalized_id == 0:
            return "编号"
    fallback_text = str(fallback or "").strip()
    return fallback_text or str(field_id or "").strip()


def _public_associated_resource_match_mappings_from_rules(
    rules: list[dict[str, Any]],
    *,
    source_fields: dict[int, dict[str, Any]],
    target_fields: dict[int, dict[str, Any]],
) -> list[dict[str, Any]]:
    mappings: list[dict[str, Any]] = []
    for rule in rules:
        if not isinstance(rule, dict):
            continue
        target_field_id = _first_present(rule, "que_id", "queId")
        target_field = target_fields.get(_coerce_any_int(target_field_id) or 0) or {}
        target_name = _match_field_name(
            target_field_id,
            fields_by_que_id=target_fields,
            fallback=_first_present(rule, "que_title", "queTitle"),
        )
        is_dynamic = _coerce_any_int(_first_present(rule, "match_type", "matchType")) == MATCH_TYPE_QUESTION
        values: list[str] = []
        if not is_dynamic:
            value_rule = {
                "judgeValues": _first_present(rule, "judge_values", "judgeValues") or [],
                "judgeValueDetails": _first_present(rule, "judge_value_details", "judgeValueDetails") or [],
            }
            values = _public_view_filter_rule_values(value_rule, field=target_field)
        operator = _public_view_filter_operator_from_judge_type(
            _first_present(rule, "judge_type", "judgeType"),
            values=None if is_dynamic else values,
        )
        mapping: dict[str, Any] = {
            "target_field": target_name,
            "operator": operator,
        }
        if is_dynamic:
            source_field_id = _first_present(rule, "judge_que_id", "judgeQueId")
            raw_source_detail = _first_present(rule, "judge_que_detail", "judgeQueDetail")
            source_detail = raw_source_detail if isinstance(raw_source_detail, dict) else {}
            mapping["source_field"] = _match_field_name(
                source_field_id,
                fields_by_que_id=source_fields,
                fallback=_first_present(source_detail, "que_title", "queTitle"),
            )
        else:
            if len(values) == 1:
                mapping["value"] = values[0]
            elif values:
                mapping["values"] = values
        mappings.append(_compact_dict(mapping))
    return mappings


def _normalize_associated_graph_type(raw_item: dict[str, Any], *, chart_key: Any, view_key: Any) -> str:
    raw_graph_type = str(_first_present(raw_item, "graph_type", "graphType", "resourceType", "type") or "").strip().lower()
    raw_source_type = str(_first_present(raw_item, "source_type", "sourceType") or "").strip().lower()
    combined = f"{raw_graph_type} {raw_source_type}"
    if "view" in combined or "viewgraph" in combined:
        return "view"
    if "chart" in combined or "report" in combined or "qingbi" in combined:
        return "chart"
    if view_key:
        return "view"
    if chart_key:
        return "chart"
    return "chart"


def _associated_resource_index(resources: list[dict[str, Any]]) -> dict[int, dict[str, Any]]:
    indexed: dict[int, dict[str, Any]] = {}
    for resource in resources:
        item_id = _coerce_positive_int(resource.get("associated_item_id"))
        if item_id is not None:
            indexed[item_id] = resource
    return indexed


def _strip_internal_associated_resource_raw(resources: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return [
        _compact_dict({key: deepcopy(value) for key, value in item.items() if key != "_raw"})
        for item in resources
        if isinstance(item, dict)
    ]


def _public_associated_graph_type(value: Any) -> str:
    normalized = str(value or "").strip().lower()
    if normalized in {"view", "viewgraph"}:
        return "view"
    if normalized in {"chart", "report", "qingbi"}:
        return "chart"
    return normalized


def _backend_associated_graph_type(value: Any) -> str:
    graph_type = _public_associated_graph_type(value)
    return "VIEW" if graph_type == "view" else "CHART"


def _public_report_source_from_backend_source(source_type: Any) -> str:
    normalized = str(source_type or "").strip().upper()
    if normalized == "BI_DATASET":
        return "dataset"
    return "app"


def _backend_source_type_for_associated_resource_patch(patch: AssociatedResourceUpsertPatch) -> str:
    graph_type = _public_associated_graph_type(patch.graph_type)
    if graph_type == "view":
        return "QINGFLOW"
    raw_source_type = str(patch.source_type or "").strip().upper()
    if raw_source_type in {"BI_QINGFLOW", "BI_DATASET"}:
        return raw_source_type
    report_source = str(patch.report_source or "app").strip().lower()
    return "BI_DATASET" if report_source == "dataset" else "BI_QINGFLOW"


def _backend_source_type_for_associated_resource_item(item: dict[str, Any]) -> str:
    graph_type = str(item.get("graph_type") or item.get("resource_type") or "").strip().lower()
    if graph_type == "view":
        return "QINGFLOW"
    return "BI_DATASET" if str(item.get("report_source") or "").strip().lower() == "dataset" else "BI_QINGFLOW"


def _validate_associated_resource_patch(patch: AssociatedResourceUpsertPatch, *, reason_path: str) -> dict[str, Any] | None:
    graph_type = _public_associated_graph_type(patch.graph_type)
    if graph_type not in {"chart", "view"}:
        return {
            "error_code": "INVALID_ASSOCIATED_RESOURCE_GRAPH_TYPE",
            "reason_path": f"{reason_path}.graph_type",
            "received": patch.graph_type,
            "expected_values": ["chart", "view"],
            "message": "graph_type must be 'chart' or 'view'",
        }
    source_type = str(patch.source_type or "").strip().upper()
    if source_type and source_type not in {"QINGFLOW", "BI_QINGFLOW", "BI_DATASET"}:
        return {
            "error_code": "INVALID_ASSOCIATED_RESOURCE_SOURCE_TYPE",
            "reason_path": f"{reason_path}.source_type",
            "received": patch.source_type,
            "expected_values": ["BI_QINGFLOW", "BI_DATASET"],
            "message": "source_type is a legacy compatibility field; use report_source='app' or 'dataset'",
        }
    report_source = str(patch.report_source or "").strip().lower()
    if report_source and report_source not in {"app", "dataset"}:
        return {
            "error_code": "INVALID_ASSOCIATED_RESOURCE_REPORT_SOURCE",
            "reason_path": f"{reason_path}.report_source",
            "received": patch.report_source,
            "expected_values": ["app", "dataset"],
            "message": "report_source must be 'app' or 'dataset'",
        }
    if graph_type == "view" and report_source:
        return {
            "error_code": "REPORT_SOURCE_ONLY_FOR_REPORT",
            "reason_path": f"{reason_path}.report_source",
            "message": "report_source is only valid for graph_type=chart/report; omit it for views",
        }
    if graph_type == "chart" and source_type == "QINGFLOW":
        return {
            "error_code": "INVALID_ASSOCIATED_RESOURCE_SOURCE_TYPE",
            "reason_path": f"{reason_path}.source_type",
            "received": patch.source_type,
            "expected_values": ["BI_QINGFLOW", "BI_DATASET"],
            "message": "chart/report associated resources must use BI source; omit source_type or use report_source='app'/'dataset'",
            "next_action": "remove source_type; the tool will infer BI_QINGFLOW for chart/report resources",
        }
    if graph_type == "view" and source_type and source_type != "QINGFLOW":
        return {
            "error_code": "INVALID_ASSOCIATED_RESOURCE_SOURCE_TYPE",
            "reason_path": f"{reason_path}.source_type",
            "received": patch.source_type,
            "expected_values": ["QINGFLOW"],
            "message": "view associated resources use the internal Qingflow view source; omit source_type for views",
        }
    if graph_type == "view" and not str(patch.view_key or "").strip():
        return {
            "error_code": "ASSOCIATED_RESOURCE_VIEW_KEY_REQUIRED",
            "reason_path": f"{reason_path}.view_key",
            "message": "graph_type=view requires view_key",
        }
    if graph_type == "chart" and not str(patch.chart_key or "").strip():
        return {
            "error_code": "ASSOCIATED_RESOURCE_CHART_KEY_REQUIRED",
            "reason_path": f"{reason_path}.chart_key",
            "message": "graph_type=chart requires chart_key",
        }
    if not str(patch.target_app_key or "").strip():
        return {
            "error_code": "ASSOCIATED_RESOURCE_TARGET_APP_REQUIRED",
            "reason_path": f"{reason_path}.target_app_key",
            "message": "target_app_key is required",
        }
    return None


def _associated_resource_public_key(patch_or_item: Any) -> str:
    if isinstance(patch_or_item, AssociatedResourceUpsertPatch):
        graph_type = _public_associated_graph_type(patch_or_item.graph_type)
        return str(patch_or_item.view_key if graph_type == "view" else patch_or_item.chart_key or "").strip()
    if isinstance(patch_or_item, dict):
        graph_type = str(patch_or_item.get("graph_type") or "").strip().lower()
        return str(patch_or_item.get("view_key") if graph_type == "view" else patch_or_item.get("chart_key") or "").strip()
    return ""


def _associated_resource_matches_patch(item: dict[str, Any], patch: AssociatedResourceUpsertPatch) -> bool:
    graph_type = _public_associated_graph_type(patch.graph_type)
    item_graph_type = str(item.get("graph_type") or "").strip().lower()
    if item_graph_type != graph_type:
        return False
    if _backend_source_type_for_associated_resource_item(item) != _backend_source_type_for_associated_resource_patch(patch):
        return False
    patch_target_app = str(patch.target_app_key or "").strip()
    item_target_app = str(item.get("target_app_key") or "").strip()
    if patch_target_app and item_target_app and patch_target_app != item_target_app:
        return False
    return _associated_resource_public_key(item) == _associated_resource_public_key(patch)


_ASSOCIATED_RESOURCE_PARTIAL_PATCH_KEY_ALIASES = {
    "graph_type": "graph_type",
    "graphType": "graph_type",
    "resource_type": "graph_type",
    "resourceType": "graph_type",
    "target_app_key": "target_app_key",
    "targetAppKey": "target_app_key",
    "app_key": "target_app_key",
    "appKey": "target_app_key",
    "view_key": "view_key",
    "viewKey": "view_key",
    "viewgraphKey": "view_key",
    "viewGraphKey": "view_key",
    "chart_key": "chart_key",
    "chartKey": "chart_key",
    "report_source": "report_source",
    "reportSource": "report_source",
    "match_rules": "match_rules",
    "matchRules": "match_rules",
    "match_mappings": "match_mappings",
    "matchMappings": "match_mappings",
}

_ASSOCIATED_RESOURCE_PARTIAL_SET_KEYS = {
    "graph_type",
    "target_app_key",
    "view_key",
    "chart_key",
    "report_source",
    "match_rules",
    "match_mappings",
}

_ASSOCIATED_RESOURCE_PARTIAL_UNSET_KEYS = {"match_rules", "match_mappings"}


def _canonical_associated_resource_partial_patch_key(key: Any) -> str:
    raw = str(key or "").strip()
    return _ASSOCIATED_RESOURCE_PARTIAL_PATCH_KEY_ALIASES.get(raw, raw)


def _normalize_associated_resource_partial_set(raw_set: Any, *, reason_path: str) -> tuple[dict[str, Any], list[dict[str, Any]]]:
    if not isinstance(raw_set, dict):
        return {}, [{"error_code": "INVALID_ASSOCIATED_RESOURCE_PATCH_SET", "reason_path": reason_path, "message": "patch_resources[].set must be an object"}]
    normalized: dict[str, Any] = {}
    issues: list[dict[str, Any]] = []
    for raw_key, value in raw_set.items():
        root = _canonical_associated_resource_partial_patch_key(raw_key)
        if root not in _ASSOCIATED_RESOURCE_PARTIAL_SET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_ASSOCIATED_RESOURCE_PATCH_FIELD",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "field": raw_key,
                    "allowed_fields": sorted(_ASSOCIATED_RESOURCE_PARTIAL_SET_KEYS),
                }
            )
            continue
        normalized[root] = value
    return normalized, issues


def _normalize_associated_resource_partial_unset(raw_unset: Any, *, reason_path: str) -> tuple[set[str], list[dict[str, Any]]]:
    if not isinstance(raw_unset, list):
        return set(), [{"error_code": "INVALID_ASSOCIATED_RESOURCE_PATCH_UNSET", "reason_path": reason_path, "message": "patch_resources[].unset must be a list"}]
    normalized: set[str] = set()
    issues: list[dict[str, Any]] = []
    for index, raw_key in enumerate(raw_unset):
        root = _canonical_associated_resource_partial_patch_key(raw_key)
        if root not in _ASSOCIATED_RESOURCE_PARTIAL_UNSET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_ASSOCIATED_RESOURCE_PATCH_UNSET_FIELD",
                    "reason_path": f"{reason_path}[{index}]",
                    "field": raw_key,
                    "allowed_fields": sorted(_ASSOCIATED_RESOURCE_PARTIAL_UNSET_KEYS),
                }
            )
            continue
        normalized.add(root)
    return normalized, issues


def _associated_resource_upsert_payload_from_existing_item(
    item: dict[str, Any],
    *,
    associated_item_id: int,
    client_key: str | None = None,
) -> dict[str, Any]:
    graph_type = str(item.get("graph_type") or item.get("resource_type") or "").strip().lower()
    payload = {
        "associated_item_id": associated_item_id,
        "client_key": client_key,
        "graph_type": "view" if graph_type == "view" else "chart",
        "target_app_key": item.get("target_app_key"),
        "view_key": item.get("view_key"),
        "chart_key": item.get("chart_key"),
        "report_source": item.get("report_source"),
        "match_rules": [],
    }
    raw = item.get("_raw") if isinstance(item.get("_raw"), dict) else {}
    raw_match_rules = raw.get("matchRules") if isinstance(raw, dict) else None
    if isinstance(raw_match_rules, list):
        if raw_match_rules and all(isinstance(group, list) for group in raw_match_rules):
            flattened = [rule for group in raw_match_rules for rule in group if isinstance(rule, dict)]
            payload["match_rules"] = flattened
        else:
            payload["match_rules"] = [rule for rule in raw_match_rules if isinstance(rule, dict)]
    return _compact_dict(payload)


def _resolve_associated_resource_selector(
    selector: Any,
    *,
    existing_resources: list[dict[str, Any]],
    existing_by_id: dict[int, dict[str, Any]],
    reason_path: str,
) -> tuple[int | None, dict[str, Any] | None]:
    item_id = _coerce_positive_int(selector)
    if item_id is not None:
        if item_id in existing_by_id:
            return item_id, None
    raw = str(selector or "").strip()
    if not raw:
        return None, {
            "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
            "reason_path": reason_path,
            "received": selector,
            "available_associated_item_ids": sorted(existing_by_id),
            "message": "associated resource selector cannot be empty",
        }
    matches: list[dict[str, Any]] = []
    for resource in existing_resources:
        if not isinstance(resource, dict):
            continue
        candidates = [
            resource.get("chart_id"),
            resource.get("chart_key"),
            resource.get("view_key"),
            resource.get("associated_item_id"),
        ]
        if raw in {str(candidate).strip() for candidate in candidates if candidate not in {None, ""}}:
            matches.append(resource)
    if len(matches) == 1:
        resolved_id = _coerce_positive_int(matches[0].get("associated_item_id"))
        if resolved_id is not None:
            return resolved_id, None
    if len(matches) > 1:
        return None, {
            "error_code": "AMBIGUOUS_ASSOCIATED_RESOURCE",
            "reason_path": reason_path,
            "received": selector,
            "candidate_associated_item_ids": [
                item_id
                for item_id in (_coerce_positive_int(item.get("associated_item_id")) for item in matches)
                if item_id is not None
            ],
            "message": "selector matches multiple associated resources; pass associated_item_id",
        }
    return None, {
        "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
        "reason_path": reason_path,
        "received": selector,
        "available_associated_item_ids": sorted(existing_by_id),
        "available_chart_ids": sorted(
            {
                str(resource.get("chart_id") or resource.get("chart_key") or "").strip()
                for resource in existing_resources
                if str(resource.get("chart_id") or resource.get("chart_key") or "").strip()
            }
        ),
        "available_view_keys": sorted(
            {
                str(resource.get("view_key") or "").strip()
                for resource in existing_resources
                if str(resource.get("view_key") or "").strip()
            }
        ),
        "message": "selector must be an associated_item_id, QingBI chart_id/chart_key, or Qingflow view_key from the app-level associated resource pool",
    }


def _associated_resource_not_found_issue(reason_path: str, associated_item_id: int, existing_by_id: dict[int, dict[str, Any]]) -> dict[str, Any]:
    return {
        "error_code": "ASSOCIATED_RESOURCE_NOT_FOUND",
        "reason_path": reason_path,
        "associated_item_id": associated_item_id,
        "available_associated_item_ids": sorted(existing_by_id),
        "message": "associated_item_id is not in the app-level associated resource pool",
        "next_action": "call app_get and use associated_resources[].associated_item_id, chart_id/chart_key, or view_key",
    }


def _duplicate_associated_resource_issue(reason_path: str, associated_item_id: int) -> dict[str, Any]:
    return {
        "error_code": "DUPLICATE_ASSOCIATED_RESOURCE_OPERATION",
        "reason_path": reason_path,
        "associated_item_id": associated_item_id,
        "message": "the same associated resource is targeted more than once",
    }


def _associated_resource_patch_has_match_config(patch: AssociatedResourceUpsertPatch) -> bool:
    fields_set = getattr(patch, "model_fields_set", set())
    return bool(patch.match_mappings) or bool(patch.match_rules) or "match_mappings" in fields_set


def _extract_associated_resource_id_from_result(result: Any) -> int | None:
    if isinstance(result, dict):
        for key in ("associated_item_id", "associatedItemId", "asosChartId", "id"):
            item_id = _coerce_positive_int(result.get(key))
            if item_id is not None:
                return item_id
        nested = result.get("result") or result.get("data")
        if nested is not None and nested is not result:
            return _extract_associated_resource_id_from_result(nested)
    if isinstance(result, list) and len(result) == 1:
        return _extract_associated_resource_id_from_result(result[0])
    return None


def _serialize_associated_resource_match_rules(match_rules: list[Any]) -> list[list[dict[str, Any]]]:
    if not match_rules:
        return []
    serialized: list[dict[str, Any]] = []
    for rule in match_rules:
        if isinstance(rule, CustomButtonMatchRulePatch):
            payload = rule.model_dump(mode="json", exclude_none=True)
        elif isinstance(rule, dict):
            payload = rule
        else:
            continue
        serialized.append(_serialize_custom_button_match_rule(payload))
    return [serialized] if serialized else []


def _serialize_associated_resource_create_payload(
    patch: AssociatedResourceUpsertPatch,
    *,
    match_rules_override: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    graph_type = _public_associated_graph_type(patch.graph_type)
    match_rules = match_rules_override if match_rules_override is not None else patch.match_rules
    return {
        "appKey": patch.target_app_key,
        "chartList": [
            {
                "chartKey": patch.view_key if graph_type == "view" else patch.chart_key,
                "sourceType": _backend_source_type_for_associated_resource_patch(patch),
                "graphType": _backend_associated_graph_type(graph_type),
            }
        ],
        "matchRules": _serialize_associated_resource_match_rules(match_rules),
    }


def _serialize_associated_resource_update_payload(
    patch: AssociatedResourceUpsertPatch,
    *,
    associated_item_id: int,
    existing_item: dict[str, Any] | None = None,
    match_rules_override: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    graph_type = _public_associated_graph_type(patch.graph_type)
    raw = existing_item.get("_raw") if isinstance(existing_item, dict) and isinstance(existing_item.get("_raw"), dict) else None
    payload = deepcopy(raw) if isinstance(raw, dict) else {}
    match_rules = match_rules_override if match_rules_override is not None else patch.match_rules
    payload.update(
        {
            "id": associated_item_id,
            "appKey": patch.target_app_key,
            "chartKey": patch.view_key if graph_type == "view" else patch.chart_key,
            "sourceType": _backend_source_type_for_associated_resource_patch(patch),
            "graphType": _backend_associated_graph_type(graph_type),
            "matchRules": _serialize_associated_resource_match_rules(match_rules),
        }
    )
    return _compact_dict(payload)


def _associated_resource_result_entry(
    operation: str,
    index: int,
    patch: AssociatedResourceUpsertPatch,
    *,
    associated_item_id: int | None,
) -> dict[str, Any]:
    graph_type = _public_associated_graph_type(patch.graph_type)
    return _compact_dict(
        {
            "index": index,
            "operation": operation,
            "status": "success" if associated_item_id is not None else "unverified",
            "associated_item_id": associated_item_id,
            "client_key": patch.client_key,
            "resource_type": "view" if graph_type == "view" else "report",
            "graph_type": graph_type,
            "target_app_key": patch.target_app_key,
            "report_source": _public_report_source_from_backend_source(_backend_source_type_for_associated_resource_patch(patch)) if graph_type == "chart" else None,
            "chart_key": patch.chart_key if graph_type == "chart" else None,
            "view_key": patch.view_key if graph_type == "view" else None,
        }
    )


def _build_view_associated_resources_only_update_payload(
    config: Any,
    *,
    associated_resources_payload: dict[str, Any] | None,
) -> dict[str, Any]:
    payload = deepcopy(config) if isinstance(config, dict) else {}
    for key in (
        "appKey",
        "formId",
        "wsId",
        "viewgraphKey",
        "createTime",
        "updateTime",
        "creator",
        "editor",
        "queBaseInfo",
        "buttonConfigVO",
        "buttonConfigDTOList",
        "buttonConfig",
    ):
        payload.pop(key, None)
    payload.pop("id", None)
    payload.setdefault("viewgraphName", str(config.get("viewgraphName") or config.get("viewName") or "") if isinstance(config, dict) else "")
    payload.setdefault("viewgraphType", str(config.get("viewgraphType") or "tableView") if isinstance(config, dict) else "tableView")
    payload.setdefault("viewgraphQueIds", list(config.get("viewgraphQueIds") or []) if isinstance(config, dict) else [])
    payload.setdefault("viewgraphQuestions", deepcopy(config.get("viewgraphQuestions") or []) if isinstance(config, dict) else [])
    payload.setdefault("auth", deepcopy(config.get("auth") or default_member_auth()) if isinstance(config, dict) else default_member_auth())
    payload.setdefault("sortType", "defaultSort")
    payload.setdefault("viewgraphSorts", [{"queId": 0, "beingSortAscend": True, "queType": 8}])
    payload.setdefault("viewgraphLimitType", 1)
    payload.setdefault("viewgraphLimit", [])
    payload.setdefault("viewgraphLimitFormula", "")
    button_config_dtos = _extract_existing_view_button_dtos(config if isinstance(config, dict) else {})
    if button_config_dtos:
        payload["buttonConfigDTOList"] = button_config_dtos
        payload["buttonConfig"] = _build_grouped_view_button_config(button_config_dtos)
    _apply_view_associated_resources_payload(payload, associated_resources_payload)
    return payload


def _build_view_buttons_only_update_payload(config: Any, *, button_config_dtos: list[dict[str, Any]]) -> dict[str, Any]:
    payload = _build_view_associated_resources_only_update_payload(config, associated_resources_payload=None)
    payload["buttonConfigDTOList"] = deepcopy(button_config_dtos)
    payload["buttonConfig"] = _build_grouped_view_button_config(button_config_dtos)
    return payload


_VIEW_PARTIAL_PATCH_KEY_ALIASES = {
    "view_name": "name",
    "viewName": "name",
    "view_key": "view_key",
    "viewKey": "view_key",
    "viewgraphKey": "view_key",
    "type": "type",
    "view_type": "type",
    "viewType": "type",
    "columns": "columns",
    "column_names": "columns",
    "columnNames": "columns",
    "fields": "columns",
    "group_by": "group_by",
    "groupBy": "group_by",
    "filters": "filters",
    "filter_rules": "filters",
    "filterRules": "filters",
    "start_field": "start_field",
    "startField": "start_field",
    "end_field": "end_field",
    "endField": "end_field",
    "title_field": "title_field",
    "titleField": "title_field",
    "buttons": "buttons",
    "visibility": "visibility",
    "query_conditions": "query_conditions",
    "queryConditions": "query_conditions",
    "query_condition": "query_conditions",
    "queryCondition": "query_conditions",
    "associated_resources": "associated_resources",
    "associatedResources": "associated_resources",
    "associated_reports": "associated_resources",
    "associatedReports": "associated_resources",
}


def _canonical_view_partial_patch_key(key: Any) -> str:
    raw = str(key or "").strip()
    return _VIEW_PARTIAL_PATCH_KEY_ALIASES.get(raw, raw)


def _normalize_view_partial_set(raw_set: Any, *, reason_path: str) -> tuple[dict[str, Any], list[dict[str, Any]]]:
    if not isinstance(raw_set, dict):
        return {}, [{"error_code": "INVALID_VIEW_PATCH_SET", "reason_path": reason_path, "message": "patch_views[].set must be an object"}]
    normalized: dict[str, Any] = {}
    issues: list[dict[str, Any]] = []
    for raw_key, value in raw_set.items():
        key_path = [part for part in str(raw_key or "").strip().split(".") if part]
        if not key_path:
            continue
        root = _canonical_view_partial_patch_key(key_path[0])
        if root not in _VIEW_PARTIAL_SET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_VIEW_PATCH_FIELD",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "field": raw_key,
                    "allowed_fields": sorted(_VIEW_PARTIAL_SET_KEYS),
                }
            )
            continue
        if len(key_path) == 1:
            normalized[root] = value
            continue
        target = normalized.setdefault(root, {})
        if not isinstance(target, dict):
            issues.append(
                {
                    "error_code": "INVALID_VIEW_PATCH_PATH",
                    "reason_path": f"{reason_path}.{raw_key}",
                    "message": "cannot combine a scalar replacement and nested patch for the same field",
                }
            )
            continue
        cursor = target
        for part in key_path[1:-1]:
            next_value = cursor.setdefault(part, {})
            if not isinstance(next_value, dict):
                issues.append({"error_code": "INVALID_VIEW_PATCH_PATH", "reason_path": f"{reason_path}.{raw_key}"})
                break
            cursor = next_value
        else:
            cursor[key_path[-1]] = value
    return normalized, issues


def _normalize_view_partial_unset(raw_unset: Any, *, reason_path: str) -> tuple[set[str], list[dict[str, Any]]]:
    if not isinstance(raw_unset, list):
        return set(), [{"error_code": "INVALID_VIEW_PATCH_UNSET", "reason_path": reason_path, "message": "patch_views[].unset must be a list"}]
    normalized: set[str] = set()
    issues: list[dict[str, Any]] = []
    for index, raw_key in enumerate(raw_unset):
        root = _canonical_view_partial_patch_key(str(raw_key or "").strip().split(".")[0])
        if root not in _VIEW_PARTIAL_UNSET_KEYS:
            issues.append(
                {
                    "error_code": "UNSUPPORTED_VIEW_PATCH_UNSET_FIELD",
                    "reason_path": f"{reason_path}[{index}]",
                    "field": raw_key,
                    "allowed_fields": sorted(_VIEW_PARTIAL_UNSET_KEYS),
                }
            )
            continue
        normalized.add(root)
    return normalized, issues


_VIEW_PARTIAL_SET_KEYS = {
    "name",
    "type",
    "columns",
    "group_by",
    "filters",
    "start_field",
    "end_field",
    "title_field",
    "buttons",
    "visibility",
    "query_conditions",
    "associated_resources",
}

_VIEW_PARTIAL_UNSET_KEYS = {"filters", "buttons", "visibility", "query_conditions", "associated_resources"}


def _deep_merge_public_config(target: dict[str, Any], patch: dict[str, Any]) -> None:
    for key, value in patch.items():
        if isinstance(value, dict) and isinstance(target.get(key), dict):
            _deep_merge_public_config(cast(dict[str, Any], target[key]), value)
        else:
            target[key] = deepcopy(value)


def _field_name_by_id(field_names_by_id: dict[int, str], field_id: Any) -> str | None:
    normalized_id = _coerce_nonnegative_int(field_id)
    if normalized_id is None:
        return None
    name = str(field_names_by_id.get(normalized_id) or "").strip()
    return name or None


def _view_upsert_payload_from_existing_view(
    *,
    config: dict[str, Any],
    summary: dict[str, Any],
    view_key: str,
    field_names_by_id: dict[int, str],
) -> dict[str, Any]:
    view_type = _normalize_view_type_name(config.get("viewgraphType") or summary.get("type"))
    configured_columns = [
        name
        for name in (_field_name_by_id(field_names_by_id, item) for item in (config.get("viewgraphQueIds") or []))
        if name and name not in _KNOWN_SYSTEM_VIEW_COLUMNS
    ]
    columns = [
        str(name or "").strip()
        for name in (summary.get("apply_columns") or summary.get("columns") or [])
        if str(name or "").strip() and str(name or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
    ]
    if configured_columns and (not columns or len(configured_columns) >= len(columns)):
        columns = configured_columns
    display_config = summary.get("display_config") if isinstance(summary.get("display_config"), dict) else {}
    group_by = str(summary.get("group_by") or display_config.get("group_by") or "").strip() or _field_name_by_id(field_names_by_id, config.get("groupQueId"))
    title_field = str(display_config.get("title_field") or "").strip() or _field_name_by_id(field_names_by_id, config.get("titleQue"))
    gantt_config = config.get("viewgraphGanttConfigVO") if isinstance(config.get("viewgraphGanttConfigVO"), dict) else {}
    start_field = _field_name_by_id(field_names_by_id, gantt_config.get("startTimeQueId")) if isinstance(gantt_config, dict) else None
    end_field = _field_name_by_id(field_names_by_id, gantt_config.get("endTimeQueId")) if isinstance(gantt_config, dict) else None
    gantt_title = _field_name_by_id(field_names_by_id, gantt_config.get("titleQueId")) if isinstance(gantt_config, dict) else None
    query_conditions = _extract_view_query_conditions_config(config)
    query_conditions.pop("row_field_ids", None)
    associated_resources = _extract_view_associated_resources_config(config)
    payload: dict[str, Any] = {
        "name": str(summary.get("name") or config.get("viewgraphName") or config.get("viewName") or view_key).strip(),
        "view_key": view_key,
        "type": view_type or "table",
        "columns": columns,
        "group_by": group_by,
        "filters": [],
        "query_conditions": query_conditions,
        "associated_resources": {
            "visible": bool(associated_resources.get("visible", False)),
            "limit_type": associated_resources.get("limit_type"),
            "associated_item_ids": list(associated_resources.get("configured_associated_item_ids") or []),
        },
    }
    if title_field:
        payload["title_field"] = title_field
    if view_type == "gantt":
        if start_field:
            payload["start_field"] = start_field
        if end_field:
            payload["end_field"] = end_field
        if gantt_title:
            payload["title_field"] = gantt_title
    return payload


def _first_present(mapping: dict[str, Any], *keys: str) -> Any:
    for key in keys:
        if key in mapping and mapping[key] is not None:
            return mapping[key]
    return None


def _build_view_create_payload(
    *,
    app_key: str,
    base_info: dict[str, Any],
    schema: dict[str, Any],
    patch: ViewUpsertPatch,
    ordinal: int,
    view_filters: list[list[dict[str, Any]]] | None,
    current_fields_by_name: dict[str, dict[str, Any]],
    auth_override: dict[str, Any] | None = None,
    explicit_button_dtos: list[dict[str, Any]] | None = None,
    query_condition_payload: dict[str, Any] | None = None,
    associated_resources_payload: dict[str, Any] | None = None,
) -> JSONObject:
    entity = _entity_spec_from_app(base_info=base_info, schema=schema, views=None)
    parsed_schema = _parse_schema(schema)
    visible_field_names = _resolve_view_visible_field_names(patch)
    field_ids = [_field_id_for_name(parsed_schema["fields"], name) for name in visible_field_names]
    gantt_config = _build_public_gantt_payload(parsed_schema["fields"], extract_field_map(schema), patch)
    view_spec = ViewSpec(
        view_id=_slugify(patch.name, default=f"view_{uuid4().hex[:6]}"),
        name=patch.name,
        type=patch.type.value,
        field_ids=field_ids,
        group_by_field_id=_field_id_for_name(_parse_schema(schema)["fields"], patch.group_by) if patch.group_by else None,
        config=gantt_config,
    )
    from ..solution.spec_models import EntitySpec
    from ..solution.compiler.view_compiler import compile_views

    compiled = compile_views(EntitySpec.model_validate({**entity, "views": [view_spec.model_dump(mode="json")]}))[0]
    field_map = extract_field_map(schema)
    payload = deepcopy(compiled["create_payload"])
    visible_que_ids = [field_map[field_name] for field_name in visible_field_names if field_name in field_map]
    payload["appKey"] = app_key
    payload["ordinal"] = ordinal
    payload["viewgraphQueIds"] = visible_que_ids
    payload["viewgraphQuestions"] = _build_viewgraph_questions(schema, visible_que_ids)
    payload["auth"] = deepcopy(auth_override if isinstance(auth_override, dict) else default_member_auth())
    payload.setdefault("sortType", "defaultSort")
    payload.setdefault("viewgraphSorts", [{"queId": 0, "beingSortAscend": True, "queType": 8}])
    if patch.type.value in {"card", "board", "gantt"}:
        payload["beingShowTitleQue"] = True
        payload["titleQue"] = gantt_config.get("titleQueId") or (visible_que_ids[0] if visible_que_ids else None)
    if patch.type.value == "board":
        payload["groupQueId"] = field_map.get(patch.group_by or "")
    return _hydrate_view_backend_payload(
        payload=payload,
        view_type=patch.type.value,
        visible_que_id_values=visible_que_ids,
        group_que_id=field_map.get(patch.group_by or "") if patch.group_by else None,
        view_filters=view_filters,
        gantt_payload=gantt_config,
        button_config_dtos=explicit_button_dtos,
        query_condition_payload=query_condition_payload,
        associated_resources_payload=associated_resources_payload,
    )


def _build_form_payload_from_existing_schema(
    *,
    current_schema: dict[str, Any],
    layout: dict[str, Any],
) -> dict[str, Any]:
    question_templates, section_templates = _extract_question_templates(current_schema)
    form_rows: list[list[dict[str, Any]]] = []

    for row in layout.get("root_rows", []) or []:
        questions = [deepcopy(question_templates[name]) for name in row if name in question_templates]
        if questions:
            _apply_row_widths(questions)
            form_rows.append(questions)

    for section in layout.get("sections", []) or []:
        inner_rows: list[list[dict[str, Any]]] = []
        for row in section.get("rows", []) or []:
            questions = [deepcopy(question_templates[name]) for name in row if name in question_templates]
            if questions:
                _apply_row_widths(questions)
                inner_rows.append(questions)
        if not inner_rows:
            continue
        template = _select_section_template(section_templates, section)
        wrapper = deepcopy(template) if template else {
            "queId": 0,
            "queTempId": -(20000 + sum(ord(ch) for ch in str(section.get("section_id") or section.get("title") or "section"))),
            "queType": 24,
            "queWidth": 100,
            "scanType": 1,
            "status": 1,
            "required": False,
            "queHint": "",
            "linkedQuestions": {},
            "logicalShow": True,
            "queDefaultValue": None,
            "queDefaultType": 1,
            "subQueWidth": 2,
            "beingHide": False,
            "beingDesensitized": False,
        }
        wrapper["queTitle"] = section.get("title") or wrapper.get("queTitle") or "未命名分组"
        parsed_section_id = _coerce_positive_int(section.get("section_id"))
        if parsed_section_id is not None:
            wrapper["sectionId"] = parsed_section_id
        elif _coerce_positive_int(wrapper.get("sectionId")) is None:
            wrapper.pop("sectionId", None)
        wrapper["innerQuestions"] = inner_rows
        wrapper["queWidth"] = 100
        form_rows.append([wrapper])

    payload = _build_form_save_base_payload(current_schema, str(current_schema.get("formTitle") or "未命名应用"))
    payload["formQues"] = form_rows
    _normalize_formula_defaults_for_save(payload.get("formQues"))
    payload["questionRelations"] = _normalize_question_relations_for_save(current_schema.get("questionRelations") or [])
    payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
    payload.setdefault("formTitle", current_schema.get("formTitle") or "未命名应用")
    return payload


def _flatten_layout_sections(layout: dict[str, Any]) -> dict[str, Any]:
    root_rows = [list(row) for row in layout.get("root_rows", []) or []]
    for section in layout.get("sections", []) or []:
        for row in section.get("rows", []) or []:
            if row:
                root_rows.append(list(row))
    return {"root_rows": root_rows, "sections": []}


def _extract_question_templates(schema: dict[str, Any]) -> tuple[dict[str, dict[str, Any]], list[dict[str, Any]]]:
    question_templates: dict[str, dict[str, Any]] = {}
    section_templates: list[dict[str, Any]] = []
    for row in schema.get("formQues", []) or []:
        if not isinstance(row, list):
            continue
        if len(row) == 1 and isinstance(row[0], dict) and _coerce_positive_int(row[0].get("queType")) == 24:
            section_question = row[0]
            section_templates.append(deepcopy(section_question))
            for inner_row in section_question.get("innerQuestions", []) or []:
                for question in inner_row or []:
                    if isinstance(question, dict):
                        name = str(question.get("queTitle") or "").strip()
                        if name:
                            question_templates[name] = deepcopy(question)
            continue
        for question in row:
            if isinstance(question, dict):
                name = str(question.get("queTitle") or "").strip()
                if name:
                    question_templates[name] = deepcopy(question)
    return question_templates, section_templates


def _select_section_template(section_templates: list[dict[str, Any]], section: dict[str, Any]) -> dict[str, Any] | None:
    section_id = str(section.get("section_id") or "")
    title = str(section.get("title") or "")
    for template in section_templates:
        if not isinstance(template, dict):
            continue
        template_section_id = _coerce_positive_int(template.get("queId"))
        if template_section_id is None:
            template_section_id = _coerce_positive_int(template.get("sectionId"))
        if section_id and template_section_id is not None and str(template_section_id) == section_id:
            return template
        if title and str(template.get("queTitle") or "") == title:
            return template
    return None


def _pick_view_template_key(existing_views: list[dict[str, Any]], *, desired_type: str) -> str | None:
    normalized_desired = _normalize_view_type_name(desired_type)
    fallback_key: str | None = None
    for view in existing_views:
        if not isinstance(view, dict):
            continue
        view_key = str(view.get("viewgraphKey") or view.get("viewKey") or "")
        if not view_key:
            continue
        normalized_type = _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))
        if fallback_key is None:
            fallback_key = view_key
        if normalized_type == normalized_desired:
            return view_key
    return fallback_key if normalized_desired == "table" else None


def _normalize_view_type_name(value: Any) -> str:
    normalized = str(value or "").strip().lower()
    if not normalized:
        return ""
    if "gantt" in normalized:
        return "gantt"
    if "board" in normalized:
        return "board"
    if "card" in normalized:
        return "card"
    return "table"


def _build_view_update_payload(
    *,
    views: ViewTools,
    profile: str,
    source_viewgraph_key: str,
    schema: dict[str, Any],
    patch: ViewUpsertPatch,
    view_filters: list[list[dict[str, Any]]] | None,
    current_fields_by_name: dict[str, dict[str, Any]],
    auth_override: dict[str, Any] | None = None,
    explicit_button_dtos: list[dict[str, Any]] | None = None,
    query_condition_payload: dict[str, Any] | None = None,
    associated_resources_payload: dict[str, Any] | None = None,
) -> JSONObject:
    config_response = views.view_get_config(profile=profile, viewgraph_key=source_viewgraph_key)
    config = config_response.get("result") if isinstance(config_response.get("result"), dict) else {}
    payload = deepcopy(config)
    parsed_schema = _parse_schema(schema)
    field_map = extract_field_map(schema)
    visible_field_names = _resolve_view_visible_field_names(patch)
    visible_que_ids = [_field_id_for_name(parsed_schema["fields"], name) for name in visible_field_names]
    visible_que_id_values = [field_map[name] for name in visible_field_names if name in field_map]
    gantt_payload = _build_public_gantt_payload(parsed_schema["fields"], field_map, patch)

    for key in (
        "appKey",
        "formId",
        "wsId",
        "viewgraphKey",
        "createTime",
        "updateTime",
        "creator",
        "editor",
        "queBaseInfo",
        "buttonConfigVO",
    ):
        payload.pop(key, None)
    payload.pop("id", None)

    payload["viewgraphName"] = patch.name
    payload["auth"] = deepcopy(auth_override if isinstance(auth_override, dict) else payload.get("auth") or default_member_auth())
    if "viewName" in payload:
        payload["viewName"] = patch.name
    payload["viewgraphQueIds"] = visible_que_id_values
    payload["viewgraphQuestions"] = _build_viewgraph_questions(schema, visible_que_id_values)
    payload.setdefault("sortType", "defaultSort")
    payload.setdefault("viewgraphSorts", [{"queId": 0, "beingSortAscend": True, "queType": 8}])
    payload.setdefault("beingPinNavigate", True)
    payload.setdefault("beingShowCover", False)
    payload.setdefault("defaultRowHigh", "compact")
    payload.setdefault("viewgraphLimitType", 1)
    payload.setdefault("viewgraphLimit", deepcopy(view_filters) if view_filters else [])
    button_config_dtos = _resolve_view_button_dtos_for_patch(
        config=config,
        patch=patch,
        explicit_button_dtos=explicit_button_dtos,
    )

    normalized_type = patch.type.value
    existing_type = _normalize_view_type_name(payload.get("viewgraphType") or payload.get("type"))
    if normalized_type == "table":
        payload["viewgraphType"] = "tableView" if existing_type == "table" else payload.get("viewgraphType", "tableView")
        payload.pop("groupQueId", None)
        payload.pop("beingShowTitleQue", None)
        payload.pop("titleQue", None)
    elif normalized_type == "card":
        payload["viewgraphType"] = "cardView"
        payload["beingShowTitleQue"] = True
        payload["titleQue"] = visible_que_id_values[0] if visible_que_id_values else None
        payload.pop("groupQueId", None)
    elif normalized_type == "board":
        payload["viewgraphType"] = "boardView"
        payload["beingShowTitleQue"] = True
        payload["titleQue"] = visible_que_id_values[0] if visible_que_id_values else None
        payload["groupQueId"] = field_map.get(patch.group_by or "")
    elif normalized_type == "gantt":
        payload["viewgraphType"] = "ganttView"
        payload["beingShowTitleQue"] = True
        payload["titleQue"] = gantt_payload.get("titleQueId") or (visible_que_id_values[0] if visible_que_id_values else None)
        payload.pop("groupQueId", None)
    return _hydrate_view_backend_payload(
        payload=payload,
        view_type=normalized_type,
        visible_que_id_values=visible_que_id_values,
        group_que_id=field_map.get(patch.group_by or "") if patch.group_by else None,
        view_filters=view_filters,
        gantt_payload=gantt_payload,
        button_config_dtos=button_config_dtos,
        query_condition_payload=query_condition_payload,
        associated_resources_payload=associated_resources_payload,
    )


_KNOWN_SYSTEM_VIEW_COLUMNS = {
    "编号",
    "当前流程状态",
    "申请人",
    "申请时间",
    "更新时间",
    "流程标题",
    "当前处理人",
    "当前处理节点",
}


def _filter_known_system_view_columns(columns: list[str]) -> list[str]:
    return [name for name in columns if str(name or "").strip() and str(name).strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS]


def _build_minimal_view_payload(
    *,
    app_key: str,
    schema: dict[str, Any],
    patch: ViewUpsertPatch,
    ordinal: int,
    view_filters: list[list[dict[str, Any]]] | None,
    current_fields_by_name: dict[str, dict[str, Any]],
    auth_override: dict[str, Any] | None = None,
    explicit_button_dtos: list[dict[str, Any]] | None = None,
    query_condition_payload: dict[str, Any] | None = None,
    associated_resources_payload: dict[str, Any] | None = None,
) -> JSONObject:
    field_map = extract_field_map(schema)
    parsed_schema = _parse_schema(schema)
    visible_field_names = _resolve_view_visible_field_names(patch)
    visible_que_id_values = [field_map[name] for name in visible_field_names if name in field_map]
    gantt_payload = _build_public_gantt_payload(parsed_schema["fields"], field_map, patch)
    payload: JSONObject = {
        "appKey": app_key,
        "viewgraphName": patch.name,
        "viewgraphType": {
            "table": "tableView",
            "card": "cardView",
            "board": "boardView",
            "gantt": "ganttView",
        }[patch.type.value],
        "ordinal": ordinal,
        "viewgraphQueIds": visible_que_id_values,
        "viewgraphQuestions": _build_viewgraph_questions(schema, visible_que_id_values),
        "auth": deepcopy(auth_override if isinstance(auth_override, dict) else default_member_auth()),
    }
    if patch.type.value in {"card", "board", "gantt"}:
        payload["beingShowTitleQue"] = True
        payload["titleQue"] = gantt_payload.get("titleQueId") or (visible_que_id_values[0] if visible_que_id_values else None)
    if patch.type.value == "board":
        payload["groupQueId"] = field_map.get(patch.group_by or "")
    return _hydrate_view_backend_payload(
        payload=payload,
        view_type=patch.type.value,
        visible_que_id_values=visible_que_id_values,
        group_que_id=field_map.get(patch.group_by or "") if patch.group_by else None,
        view_filters=view_filters,
        gantt_payload=gantt_payload,
        button_config_dtos=explicit_button_dtos,
        query_condition_payload=query_condition_payload,
        associated_resources_payload=associated_resources_payload,
    )


def _hydrate_view_backend_payload(
    *,
    payload: JSONObject,
    view_type: str,
    visible_que_id_values: list[int],
    group_que_id: int | None,
    view_filters: list[list[dict[str, Any]]] | None = None,
    gantt_payload: dict[str, Any] | None = None,
    button_config_dtos: list[dict[str, Any]] | None = None,
    query_condition_payload: dict[str, Any] | None = None,
    associated_resources_payload: dict[str, Any] | None = None,
) -> JSONObject:
    data = deepcopy(payload)
    data.setdefault("beingPinNavigate", True)
    data.setdefault("beingNeedPass", False)
    data.setdefault("beingShowTitleQue", view_type in {"card", "board"})
    data.setdefault("beingShowCover", False)
    data.setdefault("defaultRowHigh", "compact")
    data.setdefault("asosChartVisible", False)
    data.setdefault("viewgraphPass", "")
    data.setdefault("beingGroupColor", False)
    data.setdefault("beingShowQueTitle", True)
    data.setdefault("beingImageAdaption", False)
    data.setdefault("clippingMode", "default")
    data.setdefault("frontCoverQueId", None)
    if view_filters is None:
        data.setdefault("viewgraphLimitType", 1)
        data.setdefault("viewgraphLimit", [])
        data.setdefault("viewgraphLimitFormula", "")
    else:
        data["viewgraphLimitType"] = 1
        data["viewgraphLimit"] = deepcopy(view_filters)
        data["viewgraphLimitFormula"] = ""
    data.setdefault("sortType", "defaultSort")
    if not data.get("viewgraphSorts"):
        sort_que_id = visible_que_id_values[0] if visible_que_id_values else 1
        data["viewgraphSorts"] = [{"queId": sort_que_id, "beingSortAscend": True}]
    data.setdefault("beingAuditRecordVisible", True)
    data.setdefault("beingQrobotRecordVisible", False)
    data.setdefault("beingPrintStatus", False)
    data.setdefault("beingDefaultPrintTplStatus", False)
    data.setdefault("printTpls", [])
    data.setdefault("beingCommentStatus", False)
    data.setdefault("usages", [])
    data.setdefault("dataPermissionType", "CUSTOM")
    if view_filters is None:
        data.setdefault("dataScope", "ALL")
    else:
        data["dataScope"] = "CUSTOM" if view_filters else "ALL"
    data.setdefault("needPass", False)
    data.setdefault("beingWorkflowNodeFutureListVisible", True)
    data.setdefault("asosChartConfig", {"limitType": 1, "asosChartIdList": []})
    data.setdefault("viewgraphGanttConfigVO", None)
    data.setdefault("viewgraphHierarchyConfigVO", None)
    if button_config_dtos is not None:
        data["buttonConfigDTOList"] = deepcopy(button_config_dtos)
        data["buttonConfig"] = _build_grouped_view_button_config(button_config_dtos)
    else:
        data.pop("buttonConfigVO", None)
    if view_type == "table":
        data["viewgraphType"] = "tableView"
        data["beingShowTitleQue"] = False
        data["titleQue"] = None
        data["groupQueId"] = None
    elif view_type == "card":
        data["viewgraphType"] = "cardView"
        data["beingShowTitleQue"] = True
        data["titleQue"] = visible_que_id_values[0] if visible_que_id_values else None
        data["groupQueId"] = None
    elif view_type == "board":
        data["viewgraphType"] = "boardView"
        data["beingShowTitleQue"] = True
        data["titleQue"] = visible_que_id_values[0] if visible_que_id_values else None
        data["groupQueId"] = group_que_id
    elif view_type == "gantt":
        data["viewgraphType"] = "ganttView"
        data["beingShowTitleQue"] = True
        data["titleQue"] = (gantt_payload or {}).get("titleQueId") or (visible_que_id_values[0] if visible_que_id_values else None)
        data["groupQueId"] = None
        data["viewgraphGanttConfigVO"] = deepcopy(gantt_payload) if gantt_payload else None
    if view_filters is not None:
        data["viewgraphLimit"] = deepcopy(view_filters)
        data["viewgraphLimitType"] = 1
        data["viewgraphLimitFormula"] = ""
        data["dataPermissionType"] = "CUSTOM"
        data["dataScope"] = "CUSTOM" if view_filters else "ALL"
    _apply_view_query_conditions_payload(data, query_condition_payload)
    _apply_view_associated_resources_payload(data, associated_resources_payload)
    return data


def _resolve_view_visible_field_names(patch: ViewUpsertPatch) -> list[str]:
    ordered: list[str] = []
    for value in [*patch.columns, patch.title_field, patch.start_field, patch.end_field, patch.group_by]:
        name = str(value or "").strip()
        if name and name not in _KNOWN_SYSTEM_VIEW_COLUMNS and name not in ordered:
            ordered.append(name)
    return ordered


def _build_public_gantt_payload(
    fields: list[dict[str, Any]],
    field_map: dict[str, int],
    patch: ViewUpsertPatch,
) -> dict[str, Any]:
    if patch.type.value != "gantt":
        return {}
    title_field_name = str((patch.title_field or (patch.columns[0] if patch.columns else "")) or "").strip()
    start_field_name = str(patch.start_field or "").strip()
    end_field_name = str(patch.end_field or "").strip()
    return {
        "titleQueId": field_map.get(title_field_name),
        "startTimeQueId": field_map.get(start_field_name),
        "endTimeQueId": field_map.get(end_field_name),
        "defaultTimeDimension": "week",
        "ganttGroupVOList": [],
        "ganttDependencyVO": {
            "dependencyQueId": None,
            "predecessorTaskQueId": None,
            "startEndOptionId": None,
            "startStartOptionId": None,
            "endEndOptionId": None,
            "endStartOptionId": None,
        },
        "ganttAutoCalibrationVO": {
            "autoCalibrationRuleVO": {
                "startStartBegin": False,
                "startEndBegin": False,
                "startEndFinish": False,
                "endStartBegin": True,
                "endStartFinish": True,
                "endEndFinish": False,
            },
            "beingAutoCalibration": False,
            "userAutoCalibration": False,
        },
    }


def _build_view_filter_groups(
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
    filters: list[Any],
) -> tuple[list[list[dict[str, Any]]], list[dict[str, Any]]]:
    translated_rules: list[dict[str, Any]] = []
    issues: list[dict[str, Any]] = []
    for raw_rule in filters:
        if hasattr(raw_rule, "model_dump"):
            raw_rule = raw_rule.model_dump(mode="json")
        if not isinstance(raw_rule, dict):
            continue
        field_name = str(raw_rule.get("field_name") or "").strip()
        field = current_fields_by_name.get(field_name)
        if field is None:
            issues.append(
                {
                    "error_code": "UNKNOWN_VIEW_FIELD",
                    "missing_fields": [field_name] if field_name else [],
                    "reason_path": "filters[].field_name",
                }
            )
            continue
        translated_rule, issue = _translate_view_filter_rule(field=field, rule=raw_rule)
        if issue:
            issues.append(issue)
            continue
        translated_rules.append(translated_rule)
    return ([translated_rules] if translated_rules else []), issues


def _translate_view_filter_rule(*, field: dict[str, Any], rule: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any] | None]:
    operator = str(rule.get("operator") or "").strip().lower()
    values = list(rule.get("values") or [])
    field_type = str(field.get("type") or FieldType.text.value)
    que_id = _coerce_positive_int(field.get("que_id")) or 0
    que_type = _coerce_positive_int(field.get("que_type")) or FIELD_TYPE_TO_QUESTION_TYPE.get(field_type, 2)
    judge_values, judge_value_details, issue = _resolve_view_filter_values(field=field, values=values)
    if issue:
        return {}, issue
    payload: dict[str, Any] = {
        "queId": que_id,
        "queTitle": str(field.get("name") or ""),
        "queType": que_type,
        "matchType": MATCH_TYPE_ACCURACY,
        "judgeValueDetails": judge_value_details,
    }
    if operator == "eq":
        payload["judgeType"] = JUDGE_EQUAL
        payload["judgeValues"] = judge_values[:1] if judge_values else []
    elif operator == "neq":
        payload["judgeType"] = JUDGE_UNEQUAL
        payload["judgeValues"] = judge_values[:1] if judge_values else []
    elif operator == "in":
        payload["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
        payload["judgeValues"] = judge_values
    elif operator == "contains":
        payload["judgeType"] = JUDGE_INCLUDE
        payload["judgeValues"] = judge_values[:1] if judge_values else []
    elif operator == "gte":
        payload["judgeType"] = JUDGE_GREATER_OR_EQUAL
        payload["judgeValues"] = judge_values[:1] if judge_values else []
    elif operator == "lte":
        payload["judgeType"] = JUDGE_LESS_OR_EQUAL
        payload["judgeValues"] = judge_values[:1] if judge_values else []
    elif operator == "is_empty":
        payload["judgeType"] = JUDGE_EQUAL
        payload["judgeValues"] = []
    elif operator == "not_empty":
        payload["judgeType"] = JUDGE_UNEQUAL
        payload["judgeValues"] = []
    return payload, None


def _resolve_view_filter_values(
    *,
    field: dict[str, Any],
    values: list[Any],
) -> tuple[list[str], list[dict[str, Any]], dict[str, Any] | None]:
    field_type = str(field.get("type") or FieldType.text.value)
    if field_type not in {FieldType.single_select.value, FieldType.multi_select.value, FieldType.boolean.value}:
        return [_stringify_condition_value(value) for value in values], _build_view_filter_value_details(values), None

    option_details = [
        item
        for item in (field.get("option_details") or [])
        if isinstance(item, dict) and item.get("id") is not None and item.get("value") is not None
    ]
    if not option_details:
        return [_stringify_condition_value(value) for value in values], _build_view_filter_value_details(values), None

    option_by_value = {str(item.get("value") or "").strip(): item for item in option_details if str(item.get("value") or "").strip()}
    option_by_id = {str(item.get("id")): item for item in option_details if item.get("id") is not None}
    resolved_details: list[dict[str, Any]] = []
    unresolved_values: list[str] = []
    for value in values:
        resolved = _resolve_view_filter_option_detail(value=value, option_by_value=option_by_value, option_by_id=option_by_id)
        if resolved is None:
            unresolved_values.append(_stringify_condition_value(value))
            continue
        resolved_details.append({"id": resolved.get("id"), "value": str(resolved.get("value") or resolved.get("id"))})
    if unresolved_values:
        return (
            [],
            [],
            {
                "error_code": "UNKNOWN_VIEW_FILTER_VALUE",
                "reason_path": "filters[].values",
                "missing_fields": [],
                "allowed_values": {"filter_values": sorted(option_by_value.keys())},
                "details": {
                    "field_name": str(field.get("name") or ""),
                    "unresolved_values": unresolved_values,
                },
            },
        )
    return (
        [str(detail["id"]) for detail in resolved_details if detail.get("id") is not None],
        [_coerce_view_filter_value_detail(detail) for detail in resolved_details],
        None,
    )


def _resolve_view_filter_option_detail(
    *,
    value: Any,
    option_by_value: dict[str, dict[str, Any]],
    option_by_id: dict[str, dict[str, Any]],
) -> dict[str, Any] | None:
    if isinstance(value, dict):
        item_id = _condition_detail_id_text(value)
        if item_id:
            matched = option_by_id.get(item_id)
            if matched is not None:
                return matched
        for key in ("value", "label", "name", "title", "optValue", "opt_value"):
            item_value = _stringify_condition_value(value.get(key)).strip()
            if item_value:
                return option_by_value.get(item_value)
        return None
    if isinstance(value, int):
        return option_by_id.get(str(value))
    normalized = str(value or "").strip()
    if not normalized:
        return None
    return option_by_value.get(normalized) or option_by_id.get(normalized)


def _build_view_filter_value_details(values: list[Any]) -> list[dict[str, Any]]:
    details: list[dict[str, Any]] = []
    for value in values:
        if isinstance(value, dict):
            item_id = _condition_detail_id_text(value)
            if not item_id:
                continue
            item_value = _condition_detail_display_text(value)
            details.append(_coerce_view_filter_value_detail({"id": item_id, "value": item_value if item_value is not None else str(item_id)}))
        elif isinstance(value, int):
            details.append(_coerce_view_filter_value_detail({"id": value, "value": str(value)}))
    return details


def _coerce_view_filter_value_detail(value: dict[str, Any]) -> dict[str, Any]:
    item_id = value.get("id")
    item_value = value.get("value")
    return {
        "id": item_id,
        "value": item_value if item_value is not None else (str(item_id) if item_id is not None else None),
        "dataValue": value.get("dataValue"),
        "email": value.get("email"),
        "optionId": value.get("optionId"),
        "ordinal": value.get("ordinal"),
        "otherInfo": value.get("otherInfo"),
        "pluginValue": value.get("pluginValue"),
        "queId": value.get("queId"),
    }


def _normalize_view_filter_groups_for_compare(groups: Any) -> list[list[dict[str, Any]]]:
    normalized_groups: list[list[dict[str, Any]]] = []
    for raw_group in groups or []:
        if not isinstance(raw_group, list):
            continue
        normalized_rules: list[dict[str, Any]] = []
        for raw_rule in raw_group:
            if not isinstance(raw_rule, dict):
                continue
            normalized_details = []
            for detail in raw_rule.get("judgeValueDetails") or []:
                if not isinstance(detail, dict):
                    continue
                detail_id = _condition_detail_id_text(detail)
                if not detail_id:
                    continue
                normalized_details.append({"id": detail_id, "value": _condition_detail_display_text(detail) or detail_id})
            normalized_rules.append(
                {
                    "queId": _coerce_positive_int(raw_rule.get("queId")) or 0,
                    "judgeType": _coerce_positive_int(raw_rule.get("judgeType")) or 0,
                    "judgeValues": [str(value) for value in (raw_rule.get("judgeValues") or [])],
                    "judgeValueDetails": normalized_details,
                }
            )
        if normalized_rules:
            normalized_groups.append(normalized_rules)
    return normalized_groups


def _view_filter_rule_values_for_signature(rule: dict[str, Any]) -> list[str]:
    values = [str(value) for value in (rule.get("judgeValues") or [])]
    if values:
        return values
    fallback_values: list[str] = []
    for detail in rule.get("judgeValueDetails") or []:
        if not isinstance(detail, dict):
            continue
        item_id = detail.get("id")
        item_value = detail.get("value")
        if item_id is not None:
            fallback_values.append(str(item_id))
        elif item_value is not None:
            fallback_values.append(str(item_value))
    return fallback_values


def _view_filter_option_value_by_id(field: dict[str, Any]) -> dict[str, str]:
    value_by_id: dict[str, str] = {}
    for detail in field.get("option_details") or []:
        if not isinstance(detail, dict):
            continue
        option_id = detail.get("id")
        option_value = str(detail.get("value") or "").strip()
        if option_id is None or not option_value:
            continue
        value_by_id[str(option_id)] = option_value
    return value_by_id


def _public_view_filter_rule_values(rule: dict[str, Any], *, field: dict[str, Any]) -> list[str]:
    value_by_id = _view_filter_option_value_by_id(field)
    detail_value_by_id: dict[str, str] = {}
    ordered_detail_values: list[str] = []
    for detail in rule.get("judgeValueDetails") or []:
        if not isinstance(detail, dict):
            continue
        detail_value = _condition_detail_display_text(detail)
        detail_id = _condition_detail_id_text(detail)
        if detail_value:
            ordered_detail_values.append(detail_value)
            if detail_id:
                detail_value_by_id[detail_id] = detail_value
    values = [str(value) for value in (rule.get("judgeValues") or []) if str(value or "").strip()]
    if values:
        return [value_by_id.get(value) or detail_value_by_id.get(value) or value for value in values]
    return ordered_detail_values


def _view_filter_groups_signature(groups: Any) -> list[list[dict[str, Any]]]:
    signature: list[list[dict[str, Any]]] = []
    for group in _normalize_view_filter_groups_for_compare(groups):
        group_signature: list[dict[str, Any]] = []
        for rule in group:
            group_signature.append(
                {
                    "queId": rule.get("queId"),
                    "judgeType": rule.get("judgeType"),
                    "judgeValues": _view_filter_rule_values_for_signature(rule),
                }
            )
        if group_signature:
            signature.append(group_signature)
    return signature


def _view_filter_groups_equivalent(expected: Any, actual: Any) -> bool:
    return _view_filter_groups_signature(expected) == _view_filter_groups_signature(actual)


def _view_filter_groups_semantic_readback(expected: Any, actual: Any) -> tuple[bool, list[list[dict[str, Any]]], list[dict[str, Any]]]:
    expected_groups = _normalize_view_filter_groups_for_compare(expected)
    actual_groups = _normalize_view_filter_groups_for_compare(actual)
    if _view_filter_groups_signature(expected_groups) == _view_filter_groups_signature(actual_groups):
        return True, actual_groups, []
    if len(expected_groups) != len(actual_groups):
        return False, actual_groups, []
    semantic_groups: list[list[dict[str, Any]]] = []
    lossy_readbacks: list[dict[str, Any]] = []
    for group_index, (expected_group, actual_group) in enumerate(zip(expected_groups, actual_groups)):
        if len(expected_group) != len(actual_group):
            return False, actual_groups, []
        semantic_group: list[dict[str, Any]] = []
        for rule_index, (expected_rule, actual_rule) in enumerate(zip(expected_group, actual_group)):
            if expected_rule.get("queId") != actual_rule.get("queId") or expected_rule.get("judgeType") != actual_rule.get("judgeType"):
                return False, actual_groups, []
            expected_values = _view_filter_rule_values_for_signature(expected_rule)
            actual_values = _view_filter_rule_values_for_signature(actual_rule)
            if expected_values == actual_values:
                semantic_group.append(actual_rule)
                continue
            if (
                expected_values
                and not actual_values
                and actual_rule.get("judgeType") in {JUDGE_INCLUDE, JUDGE_FUZZY_MATCH}
            ):
                semantic_rule = deepcopy(actual_rule)
                semantic_rule["judgeValues"] = expected_values
                semantic_group.append(semantic_rule)
                lossy_readbacks.append(
                    {
                        "group_index": group_index,
                        "rule_index": rule_index,
                        "queId": actual_rule.get("queId"),
                        "judgeType": actual_rule.get("judgeType"),
                        "message": "view filter literal value was accepted by write path but omitted by raw viewConfig readback; semantic readback was reconstructed from the write payload",
                    }
                )
                continue
            return False, actual_groups, []
        semantic_groups.append(semantic_group)
    return True, semantic_groups, lossy_readbacks


def _public_view_filter_groups_from_match_rules(
    groups: Any,
    *,
    current_fields_by_name: dict[str, dict[str, Any]],
) -> list[list[dict[str, Any]]]:
    fields_by_que_id = {
        _coerce_positive_int(field.get("que_id")): field
        for field in current_fields_by_name.values()
        if isinstance(field, dict) and _coerce_positive_int(field.get("que_id")) is not None
    }
    public_groups: list[list[dict[str, Any]]] = []
    for group in _normalize_view_filter_groups_for_compare(groups):
        public_group: list[dict[str, Any]] = []
        for rule in group:
            que_id = _coerce_positive_int(rule.get("queId")) or 0
            field = fields_by_que_id.get(que_id) or {}
            values = _public_view_filter_rule_values(rule, field=field)
            public_rule: dict[str, Any] = {
                "field_name": str(field.get("name") or rule.get("queTitle") or que_id),
                "operator": _public_view_filter_operator_from_judge_type(rule.get("judgeType"), values=values),
            }
            if public_rule["operator"] == "in":
                public_rule["values"] = values
            elif public_rule["operator"] not in {"is_empty", "not_empty"}:
                if len(values) == 1:
                    public_rule["value"] = values[0]
                elif values:
                    public_rule["values"] = values
            public_group.append(public_rule)
        if public_group:
            public_groups.append(public_group)
    return public_groups


def _public_view_filter_operator_from_judge_type(judge_type: Any, *, values: list[str] | None = None) -> str:
    normalized = _coerce_nonnegative_int(judge_type)
    if normalized == JUDGE_EQUAL:
        return "is_empty" if values is not None and not values else "eq"
    if normalized == JUDGE_UNEQUAL:
        return "not_empty" if values is not None and not values else "neq"
    if normalized in {JUDGE_INCLUDE, JUDGE_FUZZY_MATCH}:
        return "contains"
    if normalized in {JUDGE_EQUAL_ANY, JUDGE_INCLUDE_ANY}:
        return "in"
    if normalized == JUDGE_GREATER_OR_EQUAL:
        return "gte"
    if normalized == JUDGE_LESS_OR_EQUAL:
        return "lte"
    return f"judge_type:{judge_type}"


def _infer_status_field_id(fields: list[dict[str, Any]]) -> str | None:
    preferred_names = {"status", "状态", "订单状态", "审批状态", "流程状态"}
    for field in fields:
        name = str(field.get("label") or field.get("name") or "").strip()
        field_id = str(field.get("field_id") or "")
        field_type = str(field.get("type") or "")
        if field_id == "status" or name.lower() == "status" or name in preferred_names:
            return field_id
        if "状态" in name and field_type in {
            FieldType.single_select.value,
            FieldType.multi_select.value,
            FieldType.text.value,
            FieldType.boolean.value,
        }:
            return field_id
    return None


def _normalize_flow_stage_failure(stage: JSONObject, *, profile: str, app_key: str, entity: dict[str, Any]) -> JSONObject:
    stage_error_code = str(stage.get("error_code") or "FLOW_APPLY_FAILED")
    detail_text = _extract_stage_failure_text(stage)
    request_id = stage.get("request_id")
    backend_code = stage.get("backend_code")
    http_status = stage.get("http_status")
    public_stage_result = _public_stage_result(stage)
    if request_id is None and isinstance(public_stage_result.get("errors"), list) and public_stage_result["errors"]:
        first_error = public_stage_result["errors"][0]
        if isinstance(first_error, dict):
            request_id = first_error.get("request_id")
            backend_code = first_error.get("backend_code")
            http_status = first_error.get("http_status")
    lowered_detail = detail_text.lower()
    if "must declare status field" in detail_text:
        return _failed(
            "STATUS_FIELD_REQUIRED",
            detail_text,
            details={
                "app_key": app_key,
                "entity_id": entity.get("entity_id"),
                "existing_fields": entity.get("fields") or [],
                "stage_result": public_stage_result,
            },
            suggested_next_call={
                "tool_name": "app_schema_apply",
                "arguments": {
                    "profile": profile,
                    "app_key": app_key,
                    "add_fields": [
                        {
                            "name": "状态",
                            "type": "single_select",
                            "options": ["草稿", "进行中", "已完成"],
                            "required": True,
                        }
                    ],
                    "update_fields": [],
                    "remove_fields": [],
                },
            },
            request_id=request_id,
            backend_code=backend_code,
            http_status=http_status,
        )
    if (
        "run solution_build_app first" in lowered_detail
        or "run solution_build_app_flow first" in lowered_detail
        or ("is not defined yet" in lowered_detail and "solution_build_" in lowered_detail)
    ):
        return _failed(
            "FLOW_STAGE_CONTEXT_MISSING",
            "workflow apply lost the app context required by the internal flow builder",
            details={
                "app_key": app_key,
                "entity_id": entity.get("entity_id"),
                "existing_fields": entity.get("fields") or [],
                "internal_detail": detail_text,
                "stage_result": public_stage_result,
            },
            suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, "app_key": app_key}},
            request_id=request_id,
            backend_code=backend_code,
            http_status=http_status,
        )
    message = detail_text or "failed to apply workflow patch"
    details = {"app_key": app_key, "entity_id": entity.get("entity_id"), "stage_result": public_stage_result}
    public_http_status = http_status
    if http_status == 404:
        message = "workflow write route is unavailable for this app in the current route"
        details["transport_error"] = {
            "http_status": http_status,
            "backend_code": backend_code,
            "category": "http",
        }
        public_http_status = None
    return _failed(
        stage_error_code,
        message,
        details=details,
        suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, "app_key": app_key}},
        request_id=request_id,
        backend_code=backend_code,
        http_status=public_http_status,
    )


def _extract_stage_failure_text(stage: JSONObject) -> str:
    details = stage.get("details")
    if isinstance(details, dict):
        for key in ("detail", "message", "error", "summary"):
            value = details.get(key)
            if isinstance(value, str) and value.strip():
                return value
        stage_result = details.get("stage_result")
        if isinstance(stage_result, dict):
            for key in ("detail", "message", "error", "summary"):
                value = stage_result.get(key)
                if isinstance(value, str) and value.strip():
                    return value
    errors = stage.get("errors")
    if isinstance(errors, list):
        for item in errors:
            if not isinstance(item, dict):
                continue
            for key in ("detail", "message", "error", "summary"):
                value = item.get(key)
                if isinstance(value, str) and value.strip():
                    return value
            error_payload = item.get("error_payload")
            if isinstance(error_payload, dict):
                value = error_payload.get("message")
                if isinstance(value, str) and value.strip():
                    return value
    for key in ("detail", "message", "error", "summary"):
        value = stage.get(key)
        if isinstance(value, str) and value.strip():
            return value
    return ""


def _public_stage_result(stage: JSONObject) -> JSONObject:
    public: JSONObject = {}
    for key in (
        "build_id",
        "stage",
        "mode",
        "status",
        "error_code",
        "message",
        "detail",
        "request_id",
        "backend_code",
        "http_status",
        "failure_signature",
        "stage_failure_count",
        "build_summary",
    ):
        if key in stage:
            public[key] = deepcopy(stage[key])
    errors = stage.get("errors")
    if isinstance(errors, list):
        public_errors = []
        for item in errors:
            if not isinstance(item, dict):
                continue
            public_errors.append(
                {
                    "step_name": item.get("step_name"),
                    "category": item.get("category"),
                    "detail": item.get("detail"),
                    "request_id": item.get("error_payload", {}).get("request_id") if isinstance(item.get("error_payload"), dict) else None,
                    "backend_code": item.get("error_payload", {}).get("backend_code") if isinstance(item.get("error_payload"), dict) else None,
                    "http_status": item.get("error_payload", {}).get("http_status") if isinstance(item.get("error_payload"), dict) else None,
                }
            )
        public["errors"] = public_errors
    return public
