Files
OneOS1.2/.cursor/skills/yunxiao-requirement-lifecycle/scripts/oneos_profile_tool.py

315 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Validate and render a OneOS delivery-model Yunxiao project profile."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
TARGET_LIFECYCLE = [
"待处理", "已确认", "分析中", "分析完成", "设计中", "设计完成",
"待开发", "开发中", "开发完成", "待测试", "测试中", "测试完成",
"发布中", "发布完成", "发布失败", "已关闭",
]
REQUIRED_TASK_LABELS = {"交付", "开发", "测试", "发版"}
REQUIRED_CONTROL_IDS = {
*(f"Y{index:02d}" for index in range(1, 17)),
"Y20", "Y21", "Y22",
*(f"Y{index:02d}" for index in range(30, 36)),
"Y40",
"A01", "A02", "A02B", *(f"A{index:02d}" for index in range(3, 11)),
*(f"TC{index:02d}" for index in range(1, 4)),
*(f"DF{index:02d}" for index in range(1, 4)),
*(f"CI{index:02d}" for index in range(1, 4)),
"L01", "L02",
}
VALID_MODES = {
"manual", "native_rule", "integration_bridge", "disabled_legacy", "not_supported"
}
VALID_PHASES = {"P0", "P1", "P2", "P3"}
VALID_TASK_WORKFLOW_MODES = {"A", "B"}
VALID_TASK_IDENTITY_MODES = {"work_item_type", "task_label", "title_prefix_process_control"}
VALID_LEGACY_DISPOSITIONS = {"keep_until_ready", "disable", "replace", "already_disabled"}
SENSITIVE_FRAGMENTS = (
"password", "passwd", "secret", "token", "cookie", "credential",
"private_key", "otp", "密码", "密钥",
)
def is_text(value: Any) -> bool:
return isinstance(value, str) and bool(value.strip())
def require_text(container: dict[str, Any], key: str, label: str, errors: list[str]) -> None:
if not is_text(container.get(key)):
errors.append(f"{label}.{key} 不能为空")
def walk_sensitive_keys(value: Any, prefix: str = "") -> list[str]:
found: list[str] = []
if isinstance(value, dict):
for key, nested in value.items():
current = f"{prefix}.{key}" if prefix else str(key)
if any(fragment in str(key).lower() for fragment in SENSITIVE_FRAGMENTS):
found.append(current)
found.extend(walk_sensitive_keys(nested, current))
elif isinstance(value, list):
for index, nested in enumerate(value):
found.extend(walk_sensitive_keys(nested, f"{prefix}[{index}]"))
return found
def validate_controls(controls: Any, label: str, errors: list[str]) -> None:
if not isinstance(controls, list):
errors.append(f"{label} 必须是数组")
return
seen: set[str] = set()
for index, control in enumerate(controls):
item_label = f"{label}[{index}]"
if not isinstance(control, dict):
errors.append(f"{item_label} 必须是对象")
continue
control_id = control.get("id")
if not is_text(control_id):
errors.append(f"{item_label}.id 不能为空")
continue
if control_id in seen:
errors.append(f"{label} 控制项重复:{control_id}")
seen.add(control_id)
if control_id not in REQUIRED_CONTROL_IDS:
errors.append(f"{label} 未知控制项:{control_id}")
mode = control.get("mode")
if mode not in VALID_MODES:
errors.append(f"{item_label}.mode 不合法")
if not isinstance(control.get("enabled"), bool):
errors.append(f"{item_label}.enabled 必须是布尔值")
if mode in {"disabled_legacy", "not_supported"} and control.get("enabled") is not False:
errors.append(f"{item_label}{mode} 模式要求 enabled=false")
if control_id in {"L01", "L02"}:
if mode != "disabled_legacy" or control.get("enabled") is not False:
errors.append(f"{item_label} 必须停用,防止发布完成绕过验收")
for key in ("owner", "fallback", "evidence"):
require_text(control, key, item_label, errors)
missing = sorted(REQUIRED_CONTROL_IDS - seen)
if missing:
errors.append(f"{label} 缺少控制项:" + "".join(missing))
def validate_project(project: Any, index: int, errors: list[str]) -> None:
label = f"projects[{index}]"
if not isinstance(project, dict):
errors.append(f"{label} 必须是对象")
return
require_text(project, "name", label, errors)
if project.get("migration_phase") not in VALID_PHASES:
errors.append(f"{label}.migration_phase 必须是 P0/P1/P2/P3")
statuses = project.get("requirement_statuses_observed")
if not isinstance(statuses, list) or not statuses or not all(is_text(v) for v in statuses):
errors.append(f"{label}.requirement_statuses_observed 必须是非空文本数组")
if project.get("task_workflow_mode") not in VALID_TASK_WORKFLOW_MODES:
errors.append(f"{label}.task_workflow_mode 必须是 A 或 B")
if project.get("task_identity_mode") not in VALID_TASK_IDENTITY_MODES:
errors.append(f"{label}.task_identity_mode 不合法")
if not isinstance(project.get("native_rule_can_filter_related_task_identity"), bool):
errors.append(f"{label}.native_rule_can_filter_related_task_identity 必须是布尔值")
for key in (
"main_task_prefix", "development_task_prefix", "test_task_prefix", "release_task_prefix"
):
require_text(project, key, label, errors)
repositories = project.get("repositories")
if not isinstance(repositories, list) or not repositories:
errors.append(f"{label}.repositories 至少包含一个仓库")
else:
for repo_index, repo in enumerate(repositories):
repo_label = f"{label}.repositories[{repo_index}]"
if not isinstance(repo, dict):
errors.append(f"{repo_label} 必须是对象")
continue
for key in ("name", "role", "integration_branch", "evidence"):
require_text(repo, key, repo_label, errors)
if repo.get("integration_branch") not in {"dev", "develop"}:
errors.append(f"{repo_label}.integration_branch 只能是实际存在的 dev/develop")
for key in ("codeup_integrated", "required_for_mr_gate"):
if not isinstance(repo.get(key), bool):
errors.append(f"{repo_label}.{key} 必须是布尔值")
test_plan = project.get("test_plan")
if not isinstance(test_plan, dict):
errors.append(f"{label}.test_plan 必须是对象")
else:
require_text(test_plan, "name", f"{label}.test_plan", errors)
require_text(test_plan, "evidence", f"{label}.test_plan", errors)
for key, required in (
("iteration_formally_related", True),
("cases_partitioned_by_requirement", True),
("fixed_defect_is_terminal", False),
("tester_retest_required", True),
):
if test_plan.get(key) is not required:
errors.append(f"{label}.test_plan.{key} 必须为 {str(required).lower()}")
release = project.get("release")
if not isinstance(release, dict):
errors.append(f"{label}.release 必须是对象")
else:
require_text(release, "evidence", f"{label}.release", errors)
for key in (
"release_task_formally_related_to_iteration",
"production_evidence_separate_from_test",
"acceptance_required_after_release",
"production_pipeline_unchanged",
):
if release.get(key) is not True:
errors.append(f"{label}.release.{key} 必须为 true")
legacy_rules = project.get("legacy_rules")
if not isinstance(legacy_rules, list):
errors.append(f"{label}.legacy_rules 必须是数组")
else:
for legacy_index, rule in enumerate(legacy_rules):
item_label = f"{label}.legacy_rules[{legacy_index}]"
if not isinstance(rule, dict):
errors.append(f"{item_label} 必须是对象")
continue
for key in ("name", "reason", "evidence"):
require_text(rule, key, item_label, errors)
if rule.get("disposition") not in VALID_LEGACY_DISPOSITIONS:
errors.append(f"{item_label}.disposition 不合法")
if not isinstance(rule.get("enabled"), bool):
errors.append(f"{item_label}.enabled 必须是布尔值")
validate_controls(project.get("controls"), f"{label}.controls", errors)
def validate(data: dict[str, Any]) -> list[str]:
errors: list[str] = []
if data.get("schema_version") != 1:
errors.append("schema_version 必须为 1")
if data.get("flow_model") != "oneos_delivery":
errors.append("flow_model 必须为 oneos_delivery")
for key in ("organization", "work_item_type"):
require_text(data, key, "profile", errors)
if data.get("target_lifecycle") != TARGET_LIFECYCLE:
errors.append("target_lifecycle 必须与OneOS终态16状态顺序一致")
labels = data.get("task_labels")
if not isinstance(labels, list) or not REQUIRED_TASK_LABELS.issubset(set(labels)):
errors.append("task_labels 必须包含:交付、开发、测试、发版")
policies = data.get("policies")
if not isinstance(policies, dict):
errors.append("policies 必须是对象")
else:
true_keys = (
"require_formal_relations", "require_current_state_condition",
"require_nonzero_development_tasks",
"require_signed_timestamped_replay_protected_callbacks",
)
false_keys = ("allow_automatic_final_close_without_acceptance", "test_success_is_production_release")
for key in true_keys:
if policies.get(key) is not True:
errors.append(f"policies.{key} 必须为 true")
for key in false_keys:
if policies.get(key) is not False:
errors.append(f"policies.{key} 必须为 false")
parts = policies.get("bridge_idempotency_key_components")
if not isinstance(parts, list) or not parts or not all(is_text(v) for v in parts):
errors.append("policies.bridge_idempotency_key_components 不能为空")
projects = data.get("projects")
if not isinstance(projects, list) or not projects:
errors.append("projects 至少包含一个项目")
else:
for index, project in enumerate(projects):
validate_project(project, index, errors)
sensitive = walk_sensitive_keys(data)
if sensitive:
errors.append("配置中禁止出现敏感字段:" + "".join(sensitive))
if "请填写" in json.dumps(data, ensure_ascii=False):
errors.append("仍存在“请填写”占位符")
return errors
def render(data: dict[str, Any]) -> str:
lines = [
"# OneOS云效终态交付模型执行计划", "",
f"- 组织:{data['organization']}",
f"- 工作项类型:{data['work_item_type']}",
f"- 项目数:{len(data['projects'])}",
"- 完整性口径每项目48项Y/A/测试/缺陷/集成/旧规则控制。",
"- 安全边界:不修改生产流水线;发布完成后保留产品验收门禁。", "",
]
for project in data["projects"]:
missing_statuses = [s for s in TARGET_LIFECYCLE if s not in project["requirement_statuses_observed"]]
lines.extend([
f"## 项目:{project['name']}", "",
f"- 迁移阶段:{project['migration_phase']}",
f"- 任务工作流方案:{project['task_workflow_mode']}",
f"- 任务身份方式:{project['task_identity_mode']}",
f"- 原生规则可过滤关联任务身份:{'' if project['native_rule_can_filter_related_task_identity'] else ''}",
f"- 缺少目标状态:{''.join(missing_statuses) if missing_statuses else ''}", "",
"### 旧规则处置", "",
"| 规则 | 处置 | 启用 | 原因 | 证据 |", "|---|---|---|---|---|",
])
for rule in project["legacy_rules"]:
lines.append(
f"| {rule['name']} | {rule['disposition']} | {'' if rule['enabled'] else ''} | "
f"{rule['reason']} | {rule['evidence']} |"
)
lines.extend([
"", "### 48项控制台账", "",
"| ID | 方式 | 启用 | 负责人 | 回退方案 | 证据 |", "|---|---|---|---|---|---|",
])
for control in sorted(project["controls"], key=lambda item: item["id"]):
lines.append(
f"| {control['id']} | {control['mode']} | {'' if control['enabled'] else ''} | "
f"{control['owner']} | {control['fallback']} | {control['evidence']} |"
)
lines.extend([
"", "### 迁移门禁", "",
"- [ ] 目标状态与流转边已补齐。",
"- [ ] 主任务可可靠识别或已明确交桥接。",
"- [ ] 多仓库需求与开发任务均正式关联分支/MR。",
"- [ ] Testhub按需求分包且缺陷复测闭环。",
"- [ ] test、生产发布和产品验收证据分离。",
"- [ ] L01、L02已停用旧规则已有备份和回滚。", "",
])
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
validate_parser = subparsers.add_parser("validate")
validate_parser.add_argument("profile", type=Path)
render_parser = subparsers.add_parser("render")
render_parser.add_argument("profile", type=Path)
render_parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
try:
with args.profile.open("r", encoding="utf-8-sig") as handle:
data = json.load(handle)
if not isinstance(data, dict):
raise ValueError("配置根节点必须是JSON对象")
errors = validate(data)
except (OSError, json.JSONDecodeError, ValueError) as error:
print(f"配置读取失败:{error}", file=sys.stderr)
return 2
if errors:
for error in errors:
print(f"[错误] {error}", file=sys.stderr)
return 1
if args.command == "validate":
print(f"[通过] OneOS配置有效{len(data['projects'])}个项目每项目48项控制。")
return 0
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(render(data), encoding="utf-8")
print(f"[完成] 已生成执行计划:{args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())