feat: strengthen BSP and PSP validation and handoff

This commit is contained in:
tangfahong committed 2026-09-24 11:52:22 +08:00
1 parent bcea35a91e
commit 1641fac631
39 files changed
+2695 -90

No files matched your search

@@ -456,7 +456,7 @@ def inspect_project(target: Path, psp_root: Path = DEFAULT_PSP_ROOT) -> dict[str
def prepare(
reference: Path,
reference: Path | None,
target: Path,
manifest_path: Path,
old_tokens: list[str],
@@ -464,9 +464,10 @@ def prepare(
psp_root: Path = DEFAULT_PSP_ROOT,
) -> dict[str, object]:
psp_root = psp_root_directory(psp_root)
reference = ordinary_directory(reference, "reference")
if reference is not None:
reference = ordinary_directory(reference, "reference")
target = project_directory(target, psp_root, "target")
if reference == target or reference in target.parents or target in reference.parents:
if reference is not None and (reference == target or reference in target.parents or target in reference.parents):
raise GuardError("reference、target 不能互相包含")
target_git = git_state(target)
target_repository = repository_identity(target, target_git)
@@ -482,17 +483,22 @@ def prepare(
if manifest_path.exists() or manifest_path.is_symlink():
raise GuardError(f"manifest 已存在,禁止覆盖: {manifest_path}")
links = scan_symlinks(reference)
if links:
raise GuardError("参考 PSP 包含软链接,未复制: " + ", ".join(links))
credentials = scan_credentials(reference)
if credentials:
raise GuardError("参考 PSP 包含疑似凭据文件,未复制: " + ", ".join(credentials))
source_git = git_state(reference)
if reference is not None:
links = scan_symlinks(reference)
if links:
raise GuardError("参考 PSP 包含软链接,未复制: " + ", ".join(links))
credentials = scan_credentials(reference)
if credentials:
raise GuardError("参考 PSP 包含疑似凭据文件,未复制: " + ", ".join(credentials))
source_git = git_state(reference) if reference is not None else None
staging = target.parent / f".{target.name}.psp-staging-{uuid.uuid4().hex[:12]}"
if staging.exists() or staging.is_symlink():
raise GuardError(f"随机 staging 已存在: {staging}")
if source_git.get("dirty") is True and not include_dirty:
if reference is None:
staging.mkdir()
baseline_mode = "empty"
warnings = ["无参考 PSP:必须按 BSP handoff、硬件资料、应用 schema 和精确 Release 建立每个配置;不可将 Release 当完整 PSP"]
elif source_git.get("dirty") is True and not include_dirty:
copy_committed_head(reference, staging, source_git)
baseline_mode = "committed-head"
warnings = ["参考 PSP 工作区有未提交内容;staging 使用已提交 HEAD,不包含这些修改"]
@@ -512,7 +518,7 @@ def prepare(
"schema_version": 2,
"created_at": now_utc(),
"psp_root": str(psp_root),
"reference": str(reference),
"reference": str(reference) if reference is not None else None,
"target": str(target),
"staging": str(staging),
"target_mode": "precreated-empty-repository",
@@ -521,7 +527,7 @@ def prepare(
"reference_baseline": baseline_mode,
"include_dirty_reference": include_dirty,
"old_tokens": sorted(set(token for token in old_tokens if token)),
"excluded_paths": excluded_paths(reference),
"excluded_paths": excluded_paths(reference) if reference is not None else [],
"source_inventory": source_before,
"staging_inventory_before_edits": staging_inventory,
"warnings": warnings,
@@ -529,7 +535,7 @@ def prepare(
write_json_atomic(manifest_path, payload)
return {
"passed": True,
"reference": str(reference),
"reference": str(reference) if reference is not None else None,
"target": str(target),
"staging": str(staging),
"psp_root": str(psp_root),
@@ -550,12 +556,13 @@ def verify(
) -> dict[str, object]:
manifest_path = manifest_path.expanduser().resolve(strict=True)
manifest = load_json(manifest_path)
reference = ordinary_directory(Path(str(manifest.get("reference"))), "reference")
reference = (ordinary_directory(Path(str(manifest["reference"])), "reference")
if manifest.get("reference") is not None else None)
staging = ordinary_directory(Path(str(manifest.get("staging"))), "staging")
source = manifest.get("source_inventory")
if not isinstance(source, dict):
raise GuardError("manifest 缺少 source_inventory")
if not reference_matches_manifest(reference, manifest):
if reference is not None and not reference_matches_manifest(reference, manifest):
raise GuardError("prepare 后参考 PSP 发生变化;必须重新 prepare")
links = scan_symlinks(staging)
credentials = scan_credentials(staging)
@@ -586,7 +593,7 @@ def verify(
"schema_version": 1,
"checked_at": now_utc(),
"manifest": str(manifest_path),
"reference": str(reference),
"reference": str(reference) if reference is not None else None,
"staging": str(staging),
"target": manifest.get("target"),
"allowed_paths": sorted(allowed),
@@ -693,7 +700,8 @@ def audit_final(
raise GuardError("manifest 缺少 psp_root")
psp_root = psp_root_directory(Path(root_value))
target = project_directory(Path(str(manifest.get("target"))), psp_root, "target")
reference = ordinary_directory(Path(str(manifest.get("reference"))), "reference")
reference = (ordinary_directory(Path(str(manifest["reference"])), "reference")
if manifest.get("reference") is not None else None)
source = manifest.get("source_inventory")
if not isinstance(source, dict):
raise GuardError("manifest 缺少 source_inventory")
@@ -732,8 +740,9 @@ def audit_final(
"schema_version": 1,
"checked_at": now_utc(),
"manifest": str(manifest_path),
"reference": str(reference),
"reference_drifted_since_prepare": not reference_matches_manifest(reference, manifest),
"reference": str(reference) if reference is not None else None,
"reference_drifted_since_prepare": (not reference_matches_manifest(reference, manifest)
if reference is not None else False),
"target": str(target),
"allowed_paths": sorted(allowed),
"allowed_residue_paths": sorted(residue_allow),
@@ -782,6 +791,11 @@ def build_parser() -> argparse.ArgumentParser:
prepare_parser.add_argument("--old-token", action="append", default=[])
prepare_parser.add_argument("--include-dirty", action="store_true")
empty_parser = subparsers.add_parser("prepare-empty", help="无参考 PSP:为预创建空仓库准备空 staging")
empty_parser.add_argument("--psp-root", type=Path, default=DEFAULT_PSP_ROOT)
empty_parser.add_argument("--target", type=Path, required=True)
empty_parser.add_argument("--manifest", type=Path, required=True)
verify_parser = subparsers.add_parser("verify", help="审计 staging 的精确差异与残留")
verify_parser.add_argument("--manifest", type=Path, required=True)
verify_parser.add_argument("--allow-path", action="append", default=[])
@@ -817,6 +831,8 @@ def main() -> int:
args.include_dirty,
args.psp_root,
)
elif args.command == "prepare-empty":
result = prepare(None, args.target, args.manifest, [], False, args.psp_root)
elif args.command == "verify":
result = verify(args.manifest, args.allow_path, args.allow_residue, args.report)
elif args.command == "finalize":