feat(gitea): add package release promotion receipts

This commit is contained in:
ogt
2026-07-10 12:29:56 +08:00
parent ee7701f813
commit ec9478235d
5 changed files with 150 additions and 17 deletions

View File

@@ -887,6 +887,7 @@ jobs:
if [ "${AWOOOI_CD_TEST_PROFILE:-full}" = "controlled-runtime" ]; then
echo "✅ controlled-runtime profile: running focused replay/auto-approve/copy tests"
python3.11 ../../scripts/ci/generate-cicd-artifact-receipts.py --repo-root ../.. --out-dir /tmp/awoooi-cicd-artifacts
echo "package-release-promotion source_contract=awoooi-ci-package-release-promotion.json"
python3.11 -m py_compile \
src/core/config.py \
src/db/base.py \
@@ -1103,6 +1104,7 @@ jobs:
2>&1 | tee /tmp/pytest-output.txt; PYTEST_EXIT=${PIPESTATUS[0]}
else
python3.11 ../../scripts/ci/generate-cicd-artifact-receipts.py --repo-root ../.. --out-dir /tmp/awoooi-cicd-artifacts
echo "package-release-promotion source_contract=awoooi-ci-package-release-promotion.json"
echo "✅ full profile: running learning-chain E2E in isolated pytest process"
DATABASE_URL="${DATABASE_URL:-postgresql+asyncpg://ci:ci@localhost/ci}" \
PYTHONFAULTHANDLER=1 python3.11 -m pytest tests/test_learning_chain_e2e.py -v --tb=short -p no:cacheprovider \

View File

@@ -196,6 +196,12 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]:
or "harbor" in lowered_text
or "/packages/" in lowered_text
),
"uses_package_release_promotion_contract": (
"package-release-promotion" in lowered_text
or "package_release_promotion" in lowered_text
or "release_channel_contract" in lowered_text
or "rollback_artifact_pattern" in lowered_text
),
"uses_argocd_gitops": "argocd" in lowered_text,
"uses_notification_or_webhook": (
"telegram" in lowered_text or "webhook" in lowered_text
@@ -292,6 +298,9 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]:
"registry_or_package_channel_workflow_count": _count_where(
rows, "uses_registry_or_package_channel"
),
"package_release_promotion_workflow_count": _count_where(
rows, "uses_package_release_promotion_contract"
),
"argocd_gitops_workflow_count": _count_where(rows, "uses_argocd_gitops"),
"notification_or_webhook_workflow_count": _count_where(
rows, "uses_notification_or_webhook"
@@ -342,6 +351,10 @@ def build_gitea_capability_ci_cd_optimization_readback(
professional_feature_rollups = _dict(
workflow_source_inventory.get("professional_feature_rollups")
)
package_release_contract_count = _int(
professional_feature_rollups.get("package_release_promotion_workflow_count")
)
package_release_contract_ready = package_release_contract_count > 0
full_dr_non_restore_gap_ids = [
gap for gap in full_dr_gap_ids if gap != "restore_drill_approval_gate"
]
@@ -497,19 +510,39 @@ def build_gitea_capability_ci_cd_optimization_readback(
"releases_tags",
"release_management",
"Releases and tags",
"controlled_apply_candidate",
"release/tag convention is not yet enforced across all products",
["GET /api/v1/agents/p0-cicd-baseline-source-readiness"],
"source_ready"
if package_release_contract_ready
else "controlled_apply_candidate",
"package/release promotion source contract is present"
if package_release_contract_ready
else "release/tag convention is not yet enforced across all products",
[
".gitea/workflows/cd.yaml",
"GET /api/v1/agents/p0-cicd-baseline-source-readiness",
],
"add release train tags, changelog receipt, and deploy marker correlation",
{
"package_release_promotion_workflow_count": package_release_contract_count
},
),
_capability(
"packages_registry",
"artifact_management",
"Packages / container and build artifacts",
"controlled_apply_candidate",
"package registry policy needs product-by-product adoption",
["GET /api/v1/agents/package-supply-chain-inventory"],
"source_ready"
if package_release_contract_ready
else "controlled_apply_candidate",
"package registry promotion source contract includes SBOM/provenance/rollback refs"
if package_release_contract_ready
else "package registry policy needs product-by-product adoption",
[
".gitea/workflows/cd.yaml",
"GET /api/v1/agents/package-supply-chain-inventory",
],
"promote build artifacts through Gitea Packages or internal registry with SBOM refs",
{
"package_release_promotion_workflow_count": package_release_contract_count
},
),
_capability(
"gitea_actions_workflows",
@@ -832,11 +865,21 @@ def build_gitea_capability_ci_cd_optimization_readback(
),
_optimization(
"package_registry_release_promotion",
"controlled_apply_candidate",
"source_ready"
if package_release_contract_ready
else "controlled_apply_candidate",
"Promote release artifacts through package/release channels",
"artifact channel is not yet standardized for every product",
["GET /api/v1/agents/package-supply-chain-inventory"],
"package/release promotion contract is wired into CI artifact receipts"
if package_release_contract_ready
else "artifact channel is not yet standardized for every product",
[
".gitea/workflows/cd.yaml",
"GET /api/v1/agents/package-supply-chain-inventory",
],
"add package registry, release tag, changelog, and rollback artifact receipts",
{
"package_release_promotion_workflow_count": package_release_contract_count
},
),
_optimization(
"branch_protection_required_checks",
@@ -1033,7 +1076,9 @@ def build_gitea_capability_ci_cd_optimization_readback(
_professional_practice(
"gitea_125_packages_release_registry",
"Gitea 1.25 Packages",
"controlled_apply_candidate"
"source_ready"
if package_release_contract_ready
else "controlled_apply_candidate"
if _int(
professional_feature_rollups.get(
"registry_or_package_channel_workflow_count"
@@ -1042,7 +1087,9 @@ def build_gitea_capability_ci_cd_optimization_readback(
> 0
else "action_required",
"Package registry, release artifacts, and rollback artifacts",
"registry channel is present, but Gitea package/release promotion is not standardized"
"Gitea package/release promotion source contract is wired into CI receipts"
if package_release_contract_ready
else "registry channel is present, but Gitea package/release promotion is not standardized"
if _int(
professional_feature_rollups.get(
"registry_or_package_channel_workflow_count"
@@ -1061,7 +1108,8 @@ def build_gitea_capability_ci_cd_optimization_readback(
professional_feature_rollups.get(
"registry_or_package_channel_workflow_count"
)
)
),
"package_release_promotion_workflow_count": package_release_contract_count,
},
),
_professional_practice(

View File

@@ -159,6 +159,7 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None:
assert (
professional_feature_rollups["registry_or_package_channel_workflow_count"] == 4
)
assert professional_feature_rollups["package_release_promotion_workflow_count"] == 1
assert professional_feature_rollups["sbom_or_provenance_workflow_count"] == 1
assert professional_feature_rollups["artifact_transfer_workflow_count"] == 1
assert (
@@ -272,11 +273,9 @@ def test_gitea_capability_readback_prioritizes_professional_cicd_gaps() -> None:
payload["professional_ci_cd_practice_rows"]
)
assert payload["rollups"]["p0_professional_ci_cd_practice_gap_count"] == 0
assert payload["rollups"]["market_professional_ci_cd_gap_count"] == 1
assert payload["rollups"]["market_professional_ci_cd_gap_count"] == 0
assert payload["rollups"]["all_gitea_features_fully_utilized"] is False
assert payload["rollups"]["professional_ci_cd_practice_gap_ids"] == [
"gitea_125_packages_release_registry",
]
assert payload["rollups"]["professional_ci_cd_practice_gap_ids"] == []
practice_by_id = {
row["id"]: row for row in payload["professional_ci_cd_practice_rows"]
@@ -288,6 +287,10 @@ def test_gitea_capability_readback_prioritizes_professional_cicd_gaps() -> None:
practice_by_id["gitea_125_secret_variable_redaction"]["status"]
== "source_ready"
)
assert (
practice_by_id["gitea_125_packages_release_registry"]["status"]
== "source_ready"
)
assert (
practice_by_id["gitea_125_actions_compatibility_gap_guard"]["status"]
== "source_ready"

View File

@@ -23,6 +23,7 @@ SCHEMA_VERSION = "awoooi_cicd_artifact_receipts_v1"
SBOM_SCHEMA_VERSION = "awoooi_cicd_sbom_v1"
PROVENANCE_SCHEMA_VERSION = "awoooi_cicd_provenance_v1"
RETENTION_SCHEMA_VERSION = "awoooi_cicd_artifact_retention_v1"
PACKAGE_RELEASE_SCHEMA_VERSION = "awoooi_cicd_package_release_promotion_v1"
PYPROJECT_PATHS = [
"apps/api/pyproject.toml",
@@ -270,12 +271,53 @@ def build_receipts(repo_root: Path, out_dir: Path) -> dict[str, Any]:
retention,
)
package_release = {
"schema_version": PACKAGE_RELEASE_SCHEMA_VERSION,
"source_control_authority": "gitea",
"release_channel_contract": {
"primary_package_registry": "gitea_packages",
"container_registry": "registry.wooo.work/awoooi",
"release_tag_pattern": "awoooi-api-{short_sha}",
"rollback_artifact_pattern": "awoooi-api-{previous_short_sha}",
"changelog_receipt_required": True,
"sbom_ref_required": True,
"provenance_ref_required": True,
"deploy_marker_ref_required": True,
},
"promotion_subject": {
"name": "awoooi",
"source_sha": source_sha,
"run_id": run_id,
"run_attempt": run_attempt,
"sbom_sha256": sbom_sha,
"provenance_sha256": provenance_sha,
"artifact_retention_sha256": retention_sha,
},
"rollback_receipt_contract": {
"rollback_source": "previous_deploy_marker",
"rollback_requires_image_digest": True,
"rollback_requires_package_or_release_ref": True,
"rollback_requires_post_apply_readback": True,
},
"operation_boundaries": {
"package_registry_write_attempted": False,
"gitea_release_write_attempted": False,
"tag_write_attempted": False,
"secret_value_read_allowed": False,
"github_api_allowed": False,
},
}
package_release_sha = _write_json(
out_dir / "awoooi-ci-package-release-promotion.json",
package_release,
)
manifest = {
"schema_version": SCHEMA_VERSION,
"source_control_authority": "gitea",
"source_sha": source_sha,
"run_id": run_id,
"receipt_count": 3,
"receipt_count": 4,
"receipts": [
{
"id": "sbom",
@@ -292,6 +334,11 @@ def build_receipts(repo_root: Path, out_dir: Path) -> dict[str, Any]:
"path": str(out_dir / "awoooi-ci-artifact-retention-manifest.json"),
"sha256": retention_sha,
},
{
"id": "package_release_promotion",
"path": str(out_dir / "awoooi-ci-package-release-promotion.json"),
"sha256": package_release_sha,
},
],
"ready": True,
"operation_boundaries": {

View File

@@ -76,9 +76,21 @@ dev = ["pytest>=8"]
encoding="utf-8"
)
)
package_release = json.loads(
(out_dir / "awoooi-ci-package-release-promotion.json").read_text(
encoding="utf-8"
)
)
assert manifest["schema_version"] == "awoooi_cicd_artifact_receipts_v1"
assert manifest["ready"] is True
assert manifest["receipt_count"] == 4
assert [receipt["id"] for receipt in manifest["receipts"]] == [
"sbom",
"provenance",
"artifact_retention",
"package_release_promotion",
]
assert manifest["operation_boundaries"] == {
"secret_values_collected": False,
"github_api_used": False,
@@ -97,3 +109,24 @@ dev = ["pytest>=8"]
assert (
retention["operation_boundaries"]["package_registry_write_attempted"] is False
)
assert (
package_release["schema_version"] == "awoooi_cicd_package_release_promotion_v1"
)
assert (
package_release["release_channel_contract"]["primary_package_registry"]
== "gitea_packages"
)
assert (
package_release["rollback_receipt_contract"][
"rollback_requires_post_apply_readback"
]
is True
)
assert (
package_release["operation_boundaries"]["package_registry_write_attempted"]
is False
)
assert (
package_release["operation_boundaries"]["gitea_release_write_attempted"]
is False
)