应用检查:静态文字里的数和固定版本里节点配置写着的数(审批金额线、时限、会签人数这类流程规则)对得上算有出处,对不上的列出是哪几个数、说明数据要绑定、规则要先写进工作流;应用润色助手的提示词同步:规则在固定的版本里没有就不照写,在说明里告诉用户(真实模型验收发现)

This commit is contained in:
ThearchyHelios
2026-10-14 23:58:12 +08:00
parent e2841cb027
commit bb85fbc3ce
3 changed files with 68 additions and 8 deletions
+3 -1
View File
@@ -2266,7 +2266,9 @@ APP_SYSTEM_PROMPT = """\
硬性约定:
- 只能用下面组件目录里的组件,props 按目录里写的字段和类型写,不要编造组件和属性
- 绑定(bind)只取钉住的版本里已有的值:绑定的节点、指标、报告都要在钉住的版本里存在,拿不准就先 look 那一版;
文字组件里不要写数字,也不要拼算式——数字只来自绑定,界面层不做任何计算
文字组件里不要写数据的数字,也不要拼算式——数字只来自绑定,界面层不做任何计算。说明流程规则(审批的金额线、时限)
时写的数要和钉住的版本里节点配置写的一致;用户要写的规则在钉住的版本里没有,不要照写,在 done 的说明里告诉用户
流程并不按这条执行、要先改工作流
- 可见性表达式(visible)只能用 run、input、metric、instance、task、user、view 这几个根名字,只能比较、取值、in 和
and / or / not,不能写算术
- 只改呈现:不改钉住的版本(pins)、入口(entries)、视图(views)、岗位(slots),不改表单的数据约束(字段、类型、
+49 -7
View File
@@ -25,6 +25,8 @@ RUN_FIELDS = ("status", "tier", "finished_at")
USER_FIELDS = ("username", "display_name")
_DIGIT = re.compile(r"\d")
#: 文字里的一个数:千分位的逗号算在数里(「5,000」就是 5000)
_NUMBER = re.compile(r"\d[\d,,]*(?:\.\d+)?")
_REPORT_TEXT = re.compile(r"^\s*\{\{\s*nodes\.([A-Za-z0-9_-]+)\.text\s*\}\}\s*$")
_PLACEHOLDER = re.compile(r"\{([A-Za-z0-9_㐀-鿿]+)\}")
@@ -245,6 +247,7 @@ def _lint_components(spec: dict[str, Any], pins: dict[str, Any], views: dict[str
from app.apps.catalog import BY_NAME
constants = _graph_numbers(pins)
for comp, _, at in components(spec):
ctype = comp.get("type")
entry = BY_NAME.get(str(ctype))
@@ -257,20 +260,59 @@ def _lint_components(spec: dict[str, Any], pins: dict[str, Any], views: dict[str
f"{at}.props{('.' + where) if where else ''}")
if comp.get("children") and not entry["children"]:
out.error("app.props_invalid", f"组件「{comp.get('id')}」({entry['label']})不能放子组件", f"{at}.children")
_lint_text(comp, entry, at, level, out)
_lint_text(comp, entry, at, level, out, constants)
_lint_bind(spec, comp, entry, pins, views, at, out)
_lint_visible(comp, views, at, out)
_lint_refs(spec, comp, at, out)
def _lint_text(comp: dict[str, Any], entry: dict[str, Any], at: str, level: str, out: _Issues) -> None:
"""静态文字里有数字:叙述层无算术、数字只来自证据。受管级报错,已发布级警告。"""
def _numbers(text: str) -> list[str]:
return [m.replace(",", "").replace(",", "").rstrip(".") for m in _NUMBER.findall(text)]
def _graph_numbers(pins: dict[str, Any]) -> set[str]:
"""固定的版本里节点配置写着的数:条件里的金额线、时限、会签人数、表单的上下限这些流程规则。只看 config,不看标题。"""
found: set[str] = set()
def walk(value: Any) -> None:
if isinstance(value, bool):
return
if isinstance(value, (int, float)):
found.update(_numbers(f"{value:g}" if isinstance(value, float) else str(value)))
elif isinstance(value, str):
found.update(_numbers(value))
elif isinstance(value, dict):
for v in value.values():
walk(v)
elif isinstance(value, list):
for v in value:
walk(v)
for state in pins.values():
if getattr(state, "row", None) is None:
continue
for node in node_map(state.graph).values():
walk(node.get("config"))
return found
def _lint_text(comp: dict[str, Any], entry: dict[str, Any], at: str, level: str, out: _Issues,
constants: set[str]) -> None:
"""静态文字里有数字:叙述层无算术、数字只来自证据。受管级报错,已发布级警告。
数据要绑定指标或单元格;流程规则(审批的金额线、时限、会签人数)的出处是固定版本里的节点配置——文字里的每个数都在
那里写着才算有出处。规则只写在说明里、流程并不照着执行的,照样报:那句话是假的。"""
props = comp.get("props") or {}
text = props.get("markdown") if comp.get("type") == "text" else props.get("text") if comp.get("type") == "notice" else None
if isinstance(text, str) and _DIGIT.search(text):
add = out.error if level == "governed" else out.warning
add("app.text_number", f"组件「{comp.get('id')}」({entry['label']})的静态文字里写了数字:数字没有出处,"
"请改为绑定指标或单元格", f"{at}.props")
if not isinstance(text, str) or not _DIGIT.search(text):
return
loose = [n for n in dict.fromkeys(_numbers(text)) if n not in constants]
if not loose:
return
add = out.error if level == "governed" else out.warning
add("app.text_number", f"组件「{comp.get('id')}」({entry['label']})的静态文字里写了数字「{'、'.join(loose[:5])}」,"
"没有出处:数据请改为绑定指标或单元格;流程规则(如审批的金额线、时限)要先写进工作流,文字里的数和工作流里的一致"
"才算有出处", f"{at}.props")
def _lint_bind(spec: dict[str, Any], comp: dict[str, Any], entry: dict[str, Any], pins: dict[str, Any],
+16
View File
@@ -374,3 +374,19 @@ class _Grant:
])
def test_app_roles(principal, grants, want):
assert role_of(principal, _App(), grants) == want
async def test_text_numbers_from_the_pinned_process_have_a_source():
"""流程规则的数(金额线、时限)出处是固定版本里的节点配置:对得上就不报;流程里没有的规则写在说明里照样报——那句话是假的。"""
graph = approval()
graph["nodes"][1]["data"]["config"].update(due="PT48H", condition="input.amount > 5000")
wf, info = await make_workflow(graph)
text = lambda md: base_spec(wf, info, pages=[{"id": "home", "title": "首页", "body": [ # noqa: E731
{"id": "t", "type": "text", "props": {"markdown": md}}]}])
async with SessionLocal() as session:
ok = await lint_app(session, text("金额超过 5,000 元由财务审批,48 小时内处理"), {})
bad = await lint_app(session, text("金额超过 8000 元需要总监会签,48 小时内处理"), {})
assert "app.text_number" not in codes(ok)
issue = next(i for i in bad if i["code"] == "app.text_number")
# 只列对不上的那个数,48 小时在节点配置里写着
assert issue["level"] == "error" and "「8000」" in issue["message"] and "48" not in issue["message"]