fix: harden skill template contracts and scaffolding
All checks were successful
技能自动化发布 / release (push) Successful in 4s

Align scaffold slug replacement, Action manifest strict boundaries, metadata prune API, field permission validation, and task_logs readonly semantics with documented template contracts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-11 11:55:46 +08:00
parent 35c20fc0f2
commit 766d162245
17 changed files with 536 additions and 72 deletions

View File

@@ -47,20 +47,20 @@ CREATE TABLE IF NOT EXISTS _jiangchang_columns (
TASK_LOGS_TABLE_METADATA = {
"table_name": TASK_LOGS_TABLE,
"display_name": "任务日志",
"description": "记录技能任务的执行状态和结果",
"description": "记录技能任务的执行状态和结果(系统自动写入,宿主数据管理只读展示)",
"visible": 1,
"readonly": 0,
"readonly": 1,
}
TASK_LOGS_COLUMN_METADATA: tuple[dict[str, object], ...] = (
{"column_name": "id", "display_name": "编号", "description": "任务日志唯一编号", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "task_type", "display_name": "任务类型", "description": "任务的业务类型", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "target_id", "display_name": "目标编号", "description": "任务目标对象编号", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "input_id", "display_name": "输入编号", "description": "输入对象编号", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "input_title", "display_name": "输入标题", "description": "输入对象标题", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "status", "display_name": "状态", "description": "任务当前状态", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "error_msg", "display_name": "错误信息", "description": "任务失败时的错误说明", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "result_summary", "display_name": "结果摘要", "description": "任务执行结果摘要", "visible": 1, "searchable": 1, "editable": 1},
{"column_name": "task_type", "display_name": "任务类型", "description": "任务的业务类型", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "target_id", "display_name": "目标编号", "description": "任务目标对象编号", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "input_id", "display_name": "输入编号", "description": "输入对象编号", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "input_title", "display_name": "输入标题", "description": "输入对象标题", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "status", "display_name": "状态", "description": "任务当前状态", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "error_msg", "display_name": "错误信息", "description": "任务失败时的错误说明", "visible": 1, "searchable": 1, "editable": 0},
{"column_name": "result_summary", "display_name": "结果摘要", "description": "任务执行结果摘要", "visible": 1, "searchable": 1, "editable": 0},
{
"column_name": "created_at",
"display_name": "创建时间",
@@ -157,27 +157,22 @@ def seed_task_logs_display_metadata(cursor) -> None:
visible=int(meta["visible"]),
readonly=int(meta["readonly"]),
)
for column in TASK_LOGS_COLUMN_METADATA:
upsert_column_metadata(
cursor,
table_name=TASK_LOGS_TABLE,
column_name=str(column["column_name"]),
display_name=str(column["display_name"]),
description=str(column["description"]) if column.get("description") else None,
visible=int(column["visible"]),
searchable=int(column["searchable"]),
editable=int(column["editable"]),
display_type=str(column["display_type"]) if column.get("display_type") else None,
options_json=str(column["options_json"]) if column.get("options_json") else None,
)
seed_column_metadata_batch(
cursor,
table_name=TASK_LOGS_TABLE,
columns=TASK_LOGS_COLUMN_METADATA,
valid_column_names=[str(column["column_name"]) for column in TASK_LOGS_COLUMN_METADATA],
)
def seed_column_metadata_batch(
cursor,
*,
table_name: str,
columns: tuple[dict[str, object], ...],
columns: Iterable[dict[str, object]],
valid_column_names: Iterable[str] | None = None,
) -> None:
"""批量 upsert 字段元数据;传入 valid_column_names 时会在 upsert 后清理陈旧字段元数据。"""
for column in columns:
upsert_column_metadata(
cursor,
@@ -191,6 +186,8 @@ def seed_column_metadata_batch(
display_type=str(column["display_type"]) if column.get("display_type") else None,
options_json=str(column["options_json"]) if column.get("options_json") else None,
)
if valid_column_names is not None:
prune_stale_column_metadata(cursor, table_name, valid_column_names)
def prune_stale_column_metadata(cursor, table_name: str, valid_column_names: Iterable[str]) -> None: