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

@@ -161,6 +161,33 @@ def _table_column_info(cursor, table_name: str) -> dict[str, dict[str, object]]:
return info
def _table_column_xinfo(cursor, table_name: str) -> dict[str, dict[str, object]]:
"""PRAGMA table_xinfo 可识别 generated/hidden 字段SQLite 3.31+)。"""
cursor.execute(f"PRAGMA table_xinfo({_quote_identifier(table_name)})")
info: dict[str, dict[str, object]] = {}
for row in cursor.fetchall():
hidden = int(row[6] or 0) if len(row) > 6 else 0
info[str(row[1])] = {
"cid": int(row[0]),
"name": str(row[1]),
"type": str(row[2] or ""),
"notnull": int(row[3] or 0),
"dflt_value": row[4],
"pk": int(row[5] or 0),
"hidden": hidden,
}
return info
def _is_generated_column(xinfo: dict[str, object]) -> bool:
hidden = int(xinfo.get("hidden") or 0)
return hidden in (2, 3)
def _is_blob_column(column_info: dict[str, object]) -> bool:
return "BLOB" in str(column_info.get("type") or "").upper()
def _default_uses_unixepoch(default_value: object) -> bool:
if default_value is None:
return False
@@ -202,6 +229,7 @@ def validate_column_permissions(
warnings: list[str] = []
ordered = physical_columns or list_physical_column_names(cursor, table_name)
column_info = _table_column_info(cursor, table_name)
column_xinfo = _table_column_xinfo(cursor, table_name)
cursor.execute(
"""
@@ -216,13 +244,13 @@ def validate_column_permissions(
for row in cursor.fetchall()
}
for column_name in ordered:
meta = meta_by_column.get(column_name)
if not meta:
for column_name, meta in meta_by_column.items():
physical = column_info.get(column_name) or {}
xinfo = column_xinfo.get(column_name) or physical
if not physical and column_name not in column_xinfo:
continue
physical = column_info.get(column_name) or {}
pk = int(physical.get("pk") or 0)
pk = int(physical.get("pk") or xinfo.get("pk") or 0)
editable = meta["editable"]
if pk and editable is not None and int(editable) != 0:
errors.append(f"{table_name}.{column_name} 是主键editable 应为 0")
@@ -230,6 +258,12 @@ def validate_column_permissions(
if column_name == "id" and editable is not None and int(editable) != 0:
errors.append(f"{table_name}.id 的 editable 应为 0自增主键")
if _is_generated_column(xinfo) and editable is not None and int(editable) != 0:
errors.append(f"{table_name}.{column_name} 是 generated 字段editable 应为 0")
if _is_blob_column(physical or xinfo) and editable is not None and int(editable) != 0:
errors.append(f"{table_name}.{column_name} 是 BLOB 字段editable 应为 0")
opt_errors, _ = validate_options_json(meta.get("options_json"))
for item in opt_errors:
errors.append(f"{table_name}.{column_name}: {item}")