feat: 发布固件底层开发 Skill 套件
This commit is contained in:
commit
c7ae3b4ce1
72 files changed
+10212
No files matched your search
+494
@@ -0,0 +1,494 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render a PSP evidence handoff for user review or Etunel Hub; never send it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
STATUSES = ("candidate", "packaged", "device_verified", "app_integrated", "accepted")
|
||||
|
||||
|
||||
class HandoffError(RuntimeError):
|
||||
"""Handoff evidence is incomplete or inconsistent."""
|
||||
|
||||
|
||||
def now_utc() -> str:
|
||||
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def load_object(path: Path, name: str) -> dict[str, Any]:
|
||||
payload = json.loads(path.expanduser().read_text(encoding="utf-8"))
|
||||
if not isinstance(payload, dict):
|
||||
raise HandoffError(f"{name} JSON 根节点必须是对象: {path}")
|
||||
return payload
|
||||
|
||||
|
||||
def write_json_new(path: Path, payload: dict[str, object]) -> None:
|
||||
path = path.expanduser().resolve(strict=False)
|
||||
if path.exists() or path.is_symlink():
|
||||
raise HandoffError(f"交接文件已存在,禁止覆盖: {path}")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False
|
||||
) as handle:
|
||||
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
||||
handle.write("\n")
|
||||
temporary = Path(handle.name)
|
||||
os.replace(temporary, path)
|
||||
|
||||
|
||||
def run_git(path: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
["git", "-C", str(path), *args],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def psp_git_state(path: Path) -> dict[str, object]:
|
||||
path = path.expanduser().resolve(strict=True)
|
||||
if not path.is_dir() or path.is_symlink():
|
||||
raise HandoffError(f"PSP 仓库必须是普通目录: {path}")
|
||||
top = run_git(path, "rev-parse", "--show-toplevel")
|
||||
commit = run_git(path, "rev-parse", "HEAD")
|
||||
status = run_git(path, "status", "--porcelain", "--untracked-files=all", "--", ".")
|
||||
if top.returncode or commit.returncode or status.returncode:
|
||||
raise HandoffError("PSP 必须是可读取 HEAD 和工作区状态的 Git worktree")
|
||||
lines = [line for line in status.stdout.splitlines() if line.strip()]
|
||||
commit_value = commit.stdout.strip()
|
||||
tags = run_git(path, "tag", "--points-at", commit_value)
|
||||
if tags.returncode != 0:
|
||||
raise HandoffError(f"无法读取 PSP 状态 tag: {tags.stderr.strip()}")
|
||||
return {
|
||||
"path": str(path),
|
||||
"repository_root": str(Path(top.stdout.strip()).resolve()),
|
||||
"commit": commit_value,
|
||||
"dirty": bool(lines),
|
||||
"status": lines,
|
||||
"tags": sorted(line for line in tags.stdout.splitlines() if line),
|
||||
}
|
||||
|
||||
|
||||
def annotated_tag(path: Path, name: str) -> bool:
|
||||
result = run_git(path, "cat-file", "-t", f"refs/tags/{name}")
|
||||
return result.returncode == 0 and result.stdout.strip() == "tag"
|
||||
|
||||
|
||||
def shared_repository_url(value: str) -> bool:
|
||||
stripped = value.strip()
|
||||
return bool(stripped) and not stripped.startswith(("/", "./", "../", "~", "file:"))
|
||||
|
||||
|
||||
def verify_remote(repository_url: str, commit: str) -> dict[str, object]:
|
||||
checked_at = now_utc()
|
||||
environment = os.environ.copy()
|
||||
environment["GIT_TERMINAL_PROMPT"] = "0"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "ls-remote", "--heads", "--tags", repository_url],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
timeout=30,
|
||||
env=environment,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"passed": False,
|
||||
"checked_at": checked_at,
|
||||
"repository_url": repository_url,
|
||||
"commit": commit,
|
||||
"refs": [],
|
||||
"errors": ["git ls-remote 超时"],
|
||||
}
|
||||
except OSError as exc:
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"passed": False,
|
||||
"checked_at": checked_at,
|
||||
"repository_url": repository_url,
|
||||
"commit": commit,
|
||||
"refs": [],
|
||||
"errors": [f"无法执行 git ls-remote: {exc}"],
|
||||
}
|
||||
refs: list[str] = []
|
||||
for line in result.stdout.splitlines():
|
||||
fields = line.split("\t", 1)
|
||||
if len(fields) == 2 and fields[0] == commit:
|
||||
refs.append(fields[1])
|
||||
errors: list[str] = []
|
||||
if result.returncode != 0:
|
||||
errors.append(f"git ls-remote 失败: {result.stderr.strip() or result.returncode}")
|
||||
elif not refs:
|
||||
errors.append("共享远端 heads/tags 中不存在当前完整 PSP commit")
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"passed": not errors,
|
||||
"checked_at": checked_at,
|
||||
"repository_url": repository_url,
|
||||
"commit": commit,
|
||||
"refs": sorted(refs),
|
||||
"errors": errors,
|
||||
}
|
||||
|
||||
|
||||
def require_passed(payload: dict[str, Any] | None, name: str, errors: list[str]) -> None:
|
||||
if payload is None:
|
||||
errors.append(f"缺少 {name} 证据")
|
||||
elif payload.get("passed") is not True and payload.get("result") != "PASS":
|
||||
errors.append(f"{name} 结果不是 PASS")
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def validate_packaging_record(payload: dict[str, Any] | None, errors: list[str]) -> None:
|
||||
require_passed(payload, "PSP 打包", errors)
|
||||
if payload is None:
|
||||
return
|
||||
if payload.get("schema_version") != 2:
|
||||
errors.append("PSP 打包证据必须由契约版工具生成(schema_version=2)")
|
||||
contract = payload.get("contract")
|
||||
if not isinstance(contract, dict):
|
||||
errors.append("PSP 打包证据缺少固定契约 path/hash/id")
|
||||
else:
|
||||
path_value = contract.get("path")
|
||||
expected_hash = contract.get("sha256")
|
||||
if not isinstance(path_value, str) or not isinstance(expected_hash, str):
|
||||
errors.append("PSP 打包契约 path/hash 非法")
|
||||
else:
|
||||
contract_path = Path(path_value)
|
||||
if not contract_path.is_file() or contract_path.is_symlink():
|
||||
errors.append("PSP 打包契约文件当前不可读")
|
||||
elif sha256_file(contract_path) != expected_hash:
|
||||
errors.append("PSP 打包契约文件 hash 已变化")
|
||||
image = payload.get("image")
|
||||
if not isinstance(image, dict):
|
||||
errors.append("PSP 打包证据缺少 image")
|
||||
else:
|
||||
path_value = image.get("path")
|
||||
expected_hash = image.get("sha256")
|
||||
if not isinstance(path_value, str) or not isinstance(expected_hash, str):
|
||||
errors.append("PSP 烧录包 path/hash 非法")
|
||||
else:
|
||||
image_path = Path(path_value)
|
||||
if not image_path.is_file() or image_path.is_symlink():
|
||||
errors.append("PSP 烧录包当前不可读")
|
||||
elif sha256_file(image_path) != expected_hash:
|
||||
errors.append("PSP 烧录包 hash 与打包证据不一致")
|
||||
|
||||
|
||||
def evidence_commit(payload: dict[str, Any] | None) -> str | None:
|
||||
if payload is None:
|
||||
return None
|
||||
direct = payload.get("psp_commit")
|
||||
if isinstance(direct, str):
|
||||
return direct
|
||||
expected = payload.get("expected_psp_commit")
|
||||
if isinstance(expected, str):
|
||||
return expected
|
||||
psp = payload.get("psp")
|
||||
if isinstance(psp, dict) and isinstance(psp.get("commit"), str):
|
||||
return psp["commit"]
|
||||
return None
|
||||
|
||||
|
||||
def validate_device(
|
||||
payload: dict[str, Any] | None,
|
||||
packaging: dict[str, Any] | None,
|
||||
commit: str,
|
||||
errors: list[str],
|
||||
) -> None:
|
||||
if payload is None:
|
||||
errors.append("缺少设备验证证据")
|
||||
return
|
||||
if payload.get("result") != "PASS":
|
||||
errors.append("设备验证结果不是 PASS")
|
||||
if payload.get("evidence_source") != "USER_REPORTED":
|
||||
errors.append("当前人工烧录阶段的 evidence_source 必须为 USER_REPORTED")
|
||||
if payload.get("psp_commit") != commit:
|
||||
errors.append("设备验证的 PSP commit 与当前仓库不一致")
|
||||
unexecuted = payload.get("unexecuted")
|
||||
if not isinstance(unexecuted, list) or unexecuted:
|
||||
errors.append("设备验证仍有未执行的适用检查")
|
||||
checks = payload.get("checks")
|
||||
if not isinstance(checks, list) or not checks:
|
||||
errors.append("设备验证缺少非空 checks")
|
||||
elif any(
|
||||
not isinstance(item, dict) or item.get("result") not in {"PASS", "N/A"}
|
||||
for item in checks
|
||||
):
|
||||
errors.append("设备验证 checks 含失败或非法结果")
|
||||
if packaging:
|
||||
image = packaging.get("image")
|
||||
if not isinstance(image, dict):
|
||||
errors.append("打包证据缺少 image")
|
||||
else:
|
||||
if payload.get("image_path") != image.get("path"):
|
||||
errors.append("设备验证镜像路径与打包结果不一致")
|
||||
if payload.get("image_sha256") != image.get("sha256"):
|
||||
errors.append("设备验证镜像 hash 与打包结果不一致")
|
||||
|
||||
|
||||
def validate_application(
|
||||
payload: dict[str, Any] | None, commit: str, errors: list[str]
|
||||
) -> None:
|
||||
if payload is None:
|
||||
errors.append("缺少应用集成证据")
|
||||
return
|
||||
if payload.get("result") != "PASS":
|
||||
errors.append("应用集成结果不是 PASS")
|
||||
if payload.get("psp_commit") != commit:
|
||||
errors.append("应用集成的 PSP commit 与当前仓库不一致")
|
||||
if not isinstance(payload.get("application_commit"), str) or not payload.get(
|
||||
"application_commit"
|
||||
):
|
||||
errors.append("应用集成缺少 application_commit")
|
||||
unexecuted = payload.get("unexecuted")
|
||||
if not isinstance(unexecuted, list) or unexecuted:
|
||||
errors.append("应用集成仍有未执行的适用检查")
|
||||
approval = payload.get("internal_approved")
|
||||
if approval is not True:
|
||||
errors.append("应用集成缺少应用负责人确认")
|
||||
|
||||
|
||||
def validate_approval(payload: dict[str, Any] | None, errors: list[str]) -> None:
|
||||
if payload is None:
|
||||
errors.append("缺少嵌入式底层负责人确认")
|
||||
return
|
||||
if payload.get("internal_approved") is not True:
|
||||
errors.append("internal_approved 不是 true")
|
||||
for field in ("confirmed_by", "confirmed_at", "confirmed_scope"):
|
||||
if not isinstance(payload.get(field), str) or not payload.get(field):
|
||||
errors.append(f"负责人确认缺少 {field}")
|
||||
|
||||
|
||||
def render(
|
||||
project: str,
|
||||
chip: str,
|
||||
status: str,
|
||||
psp_repo: Path,
|
||||
repository_url: str | None,
|
||||
reference_psp: str,
|
||||
reference_commit: str | None,
|
||||
static_path: Path,
|
||||
bsp_lock_path: Path,
|
||||
packaging_path: Path | None,
|
||||
device_path: Path | None,
|
||||
application_path: Path | None,
|
||||
etunel_context_path: Path | None,
|
||||
approval_path: Path | None,
|
||||
media_version: str | None,
|
||||
ai_plugin_version: str | None,
|
||||
risks: list[str],
|
||||
warnings: list[str],
|
||||
unexecuted: list[str],
|
||||
remote_verification: dict[str, object] | None,
|
||||
for_etunel: bool,
|
||||
) -> dict[str, object]:
|
||||
errors: list[str] = []
|
||||
git = psp_git_state(psp_repo)
|
||||
commit = str(git["commit"])
|
||||
if git["repository_root"] != git["path"]:
|
||||
errors.append("PSP 必须是独立 Git 仓库根目录")
|
||||
if git["dirty"] is True:
|
||||
errors.append("PSP 工作区不干净,不能形成不可变交接")
|
||||
|
||||
static = load_object(static_path, "static validation")
|
||||
bsp_lock = load_object(bsp_lock_path, "BSP lock")
|
||||
expected_bsp_lock = Path(str(git["path"])) / "metadata" / "bsp.lock.json"
|
||||
if bsp_lock_path.expanduser().resolve(strict=True) != expected_bsp_lock:
|
||||
errors.append("BSP lock 必须是当前 PSP 仓库的 metadata/bsp.lock.json")
|
||||
if isinstance(static.get("psp_root"), str) and Path(static["psp_root"]).resolve(
|
||||
strict=False
|
||||
) != Path(str(git["path"])):
|
||||
errors.append("静态验证结果不属于当前 PSP 仓库")
|
||||
if isinstance(static.get("project"), str) and static["project"] != project:
|
||||
errors.append("静态验证结果的项目名与交接项目不一致")
|
||||
packaging = load_object(packaging_path, "packaging") if packaging_path else None
|
||||
device = load_object(device_path, "device validation") if device_path else None
|
||||
application = (
|
||||
load_object(application_path, "application integration") if application_path else None
|
||||
)
|
||||
etunel_context = (
|
||||
load_object(etunel_context_path, "Etunel context") if etunel_context_path else None
|
||||
)
|
||||
approval = load_object(approval_path, "owner approval") if approval_path else None
|
||||
|
||||
require_passed(static, "PSP 静态验证", errors)
|
||||
if bsp_lock.get("schema_version") != 1:
|
||||
errors.append("BSP lock schema_version 必须为 1")
|
||||
if status in {"packaged", "device_verified", "app_integrated", "accepted"}:
|
||||
validate_packaging_record(packaging, errors)
|
||||
if packaging and evidence_commit(packaging) != commit:
|
||||
errors.append("打包证据的 PSP commit 与当前仓库不一致")
|
||||
if status in {"device_verified", "accepted"}:
|
||||
validate_device(device, packaging, commit, errors)
|
||||
if status in {"app_integrated", "accepted"}:
|
||||
validate_application(application, commit, errors)
|
||||
if for_etunel or status == "accepted":
|
||||
validate_approval(approval, errors)
|
||||
if for_etunel:
|
||||
if status not in {"packaged", "accepted"}:
|
||||
errors.append("Etunel 应用交接只允许 packaged 或 accepted 状态")
|
||||
if not repository_url or not shared_repository_url(repository_url):
|
||||
errors.append("Etunel 交接必须提供团队共享 PSP 仓库地址")
|
||||
if remote_verification is None or remote_verification.get("passed") is not True:
|
||||
errors.append("Etunel 交接缺少通过的共享远端 commit 可达性验证")
|
||||
elif (
|
||||
remote_verification.get("repository_url") != repository_url
|
||||
or remote_verification.get("commit") != commit
|
||||
):
|
||||
errors.append("远端可达性验证与当前仓库地址或 PSP commit 不一致")
|
||||
if etunel_context is None:
|
||||
errors.append("Etunel 交接缺少有效上下文")
|
||||
else:
|
||||
for field in ("work_id", "subtask_id", "phase", "wave", "role"):
|
||||
if not isinstance(etunel_context.get(field), str) or not etunel_context.get(field):
|
||||
errors.append(f"Etunel 上下文缺少 {field}")
|
||||
if status == "accepted":
|
||||
accepted_tag = f"accepted/{commit[:12]}"
|
||||
if accepted_tag not in git["tags"] or not annotated_tag(Path(str(git["path"])), accepted_tag):
|
||||
errors.append(f"当前 commit 缺少 annotated accepted 状态 tag: {accepted_tag}")
|
||||
revoked_tag = f"revoked/{commit[:12]}"
|
||||
if revoked_tag in git["tags"]:
|
||||
errors.append(f"当前 PSP commit 已被 revoked: {revoked_tag}")
|
||||
|
||||
evidence = {
|
||||
"static_validation": static,
|
||||
"packaging": packaging,
|
||||
"device_validation": device,
|
||||
"application_integration": application,
|
||||
}
|
||||
result: dict[str, object] = {
|
||||
"schema_version": 1,
|
||||
"handoff_type": "psp-to-application",
|
||||
"generated_at": now_utc(),
|
||||
"passed": not errors,
|
||||
"project": project,
|
||||
"chip": chip,
|
||||
"status": status,
|
||||
"psp": {
|
||||
"repository_url": repository_url,
|
||||
"remote_verification": remote_verification,
|
||||
**git,
|
||||
},
|
||||
"reference_psp": {
|
||||
"source": reference_psp,
|
||||
"commit": reference_commit,
|
||||
},
|
||||
"bsp_lock": bsp_lock,
|
||||
"component_baseline": {
|
||||
"media_version": media_version,
|
||||
"ai_plugin_version": ai_plugin_version,
|
||||
},
|
||||
"evidence": evidence,
|
||||
"etunel": {
|
||||
"context": etunel_context,
|
||||
"remote_commit_reachable": bool(
|
||||
remote_verification and remote_verification.get("passed") is True
|
||||
),
|
||||
"prepared_for_send": for_etunel and not errors,
|
||||
"sent": False,
|
||||
},
|
||||
"owner_approval": approval,
|
||||
"self_check_result": "PASS" if not errors else "FAIL",
|
||||
"unexecuted": unexecuted,
|
||||
"warnings": warnings,
|
||||
"risks": risks,
|
||||
"errors": errors,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project", required=True)
|
||||
parser.add_argument("--chip", required=True)
|
||||
parser.add_argument("--status", required=True, choices=STATUSES)
|
||||
parser.add_argument("--psp-repo", type=Path, required=True)
|
||||
parser.add_argument("--repository-url")
|
||||
parser.add_argument("--reference-psp", required=True)
|
||||
parser.add_argument("--reference-commit")
|
||||
parser.add_argument("--static-validation", type=Path, required=True)
|
||||
parser.add_argument("--bsp-lock", type=Path, required=True)
|
||||
parser.add_argument("--packaging-result", type=Path)
|
||||
parser.add_argument("--device-validation", type=Path)
|
||||
parser.add_argument("--application-integration", type=Path)
|
||||
parser.add_argument("--etunel-context", type=Path)
|
||||
parser.add_argument("--owner-approval", type=Path)
|
||||
parser.add_argument("--media-version")
|
||||
parser.add_argument("--ai-plugin-version")
|
||||
parser.add_argument("--risk", action="append", default=[])
|
||||
parser.add_argument("--warning", action="append", default=[])
|
||||
parser.add_argument("--unexecuted", action="append", default=[])
|
||||
parser.add_argument("--for-etunel", action="store_true")
|
||||
parser.add_argument(
|
||||
"--verify-remote",
|
||||
action="store_true",
|
||||
help="用 git ls-remote 证明当前 commit 可从 repository-url 获取",
|
||||
)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = build_parser().parse_args()
|
||||
try:
|
||||
remote_verification = None
|
||||
if args.verify_remote:
|
||||
if not args.repository_url:
|
||||
raise HandoffError("--verify-remote 必须同时提供 --repository-url")
|
||||
current_commit = str(psp_git_state(args.psp_repo)["commit"])
|
||||
remote_verification = verify_remote(args.repository_url, current_commit)
|
||||
result = render(
|
||||
args.project,
|
||||
args.chip,
|
||||
args.status,
|
||||
args.psp_repo,
|
||||
args.repository_url,
|
||||
args.reference_psp,
|
||||
args.reference_commit,
|
||||
args.static_validation,
|
||||
args.bsp_lock,
|
||||
args.packaging_result,
|
||||
args.device_validation,
|
||||
args.application_integration,
|
||||
args.etunel_context,
|
||||
args.owner_approval,
|
||||
args.media_version,
|
||||
args.ai_plugin_version,
|
||||
args.risk,
|
||||
args.warning,
|
||||
args.unexecuted,
|
||||
remote_verification,
|
||||
args.for_etunel,
|
||||
)
|
||||
write_json_new(args.output, result)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result.get("passed") is True else 2
|
||||
except (HandoffError, OSError, json.JSONDecodeError) as exc:
|
||||
print(json.dumps({"passed": False, "error": str(exc)}, ensure_ascii=False, indent=2))
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user