From 9f3be5ebbb2ecd3d2527d3ace70f587d6d412119 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 12 Jul 2026 00:32:34 +0200 Subject: [PATCH] =?UTF-8?q?fix(cicd):=20=E6=A0=A1=E5=87=86=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BA=A4=E4=BB=98=E6=9D=83=E5=A8=81=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/native/cicd/pac-status-evaluator.cjs | 14 ++ scripts/src/platform-infra-gitea-remote.sh | 182 +++++++++++++++--- ...tform-infra-gitea-status-evaluator.test.ts | 118 +++++++++++- .../src/platform-infra-pac-provenance.test.ts | 42 ++++ .../platform_infra_gitea_status_evaluator.py | 95 ++++++++- 5 files changed, 409 insertions(+), 42 deletions(-) diff --git a/scripts/native/cicd/pac-status-evaluator.cjs b/scripts/native/cicd/pac-status-evaluator.cjs index 82c6e755..f14ffb46 100644 --- a/scripts/native/cicd/pac-status-evaluator.cjs +++ b/scripts/native/cicd/pac-status-evaluator.cjs @@ -76,6 +76,16 @@ function normalizeAdmissionPolicySpec(value) { if (Object.prototype.hasOwnProperty.call(spec, "matchConstraints")) { const matchConstraints = { ...record(spec.matchConstraints) }; if (!Object.prototype.hasOwnProperty.call(matchConstraints, "matchPolicy")) matchConstraints.matchPolicy = "Equivalent"; + for (const selector of ["namespaceSelector", "objectSelector"]) { + if (isEmptyRecord(matchConstraints[selector])) delete matchConstraints[selector]; + } + if (Array.isArray(matchConstraints.resourceRules)) { + matchConstraints.resourceRules = matchConstraints.resourceRules.map((value) => { + const rule = { ...record(value) }; + if (rule.scope === "*") delete rule.scope; + return rule; + }); + } spec.matchConstraints = matchConstraints; } return spec; @@ -91,6 +101,10 @@ function normalizeAdmissionBindingSpec(value) { return spec; } +function isEmptyRecord(value) { + return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0; +} + function evaluatePacAdmissionState(inputValue) { const input = record(inputValue); const policy = record(input.policy); diff --git a/scripts/src/platform-infra-gitea-remote.sh b/scripts/src/platform-infra-gitea-remote.sh index f665c42d..0e85acab 100644 --- a/scripts/src/platform-infra-gitea-remote.sh +++ b/scripts/src/platform-infra-gitea-remote.sh @@ -632,7 +632,7 @@ PY python3 - "$status_rc" "$tmp/repo-meta.json" "$tmp" <<'PY' import json, os, sys sys.path.insert(0, sys.argv[3]) -from platform_infra_gitea_status_evaluator import select_snapshot +from platform_infra_gitea_status_evaluator import parse_ls_remote_lines, select_snapshot status_rc = int(sys.argv[1]) meta = json.load(open(sys.argv[2], encoding="utf-8")) tmp = sys.argv[3] @@ -643,12 +643,11 @@ def text(path, limit=1200): return "" repos = [] for repo in meta: - out = text(os.path.join(tmp, f"{repo['key']}.ls.out"), 20000) - refs = {} - for line in out.splitlines(): - parts = line.split() - if len(parts) == 2: - refs[parts[1]] = parts[0] + try: + with open(os.path.join(tmp, f"{repo['key']}.ls.out"), encoding="utf-8", errors="replace") as handle: + refs = parse_ls_remote_lines(handle) + except FileNotFoundError: + refs = {} branch_ref = f"refs/heads/{repo['branch']}" branch_commit = refs.get(branch_ref) selected_snapshot = select_snapshot(branch_commit, repo["snapshotPrefix"], refs) @@ -755,7 +754,7 @@ NODE python3 - "$tmp/repos.json" "$bridge_ready" "$service_exists" "$mirror_status_rc" "$bridge_logs_rc" "$inbox_status_rc" "$tmp/mirror-status.json" "$tmp/mirror-status.err" "$tmp/bridge.log" "$tmp/bridge.err" "$tmp/inbox-status.json" "$tmp/inbox-status.err" <<'PY' import json, os, sys, urllib.error, urllib.parse, urllib.request sys.path.insert(0, os.environ["tmp"]) -from platform_infra_gitea_status_evaluator import evaluate_repository +from platform_infra_gitea_status_evaluator import evaluate_repository, select_committed_head_record, select_target_delivery repos = json.load(open(sys.argv[1], encoding="utf-8")) bridge_ready = sys.argv[2] service_exists = bool(sys.argv[3]) @@ -782,9 +781,9 @@ def request(api_path): req.add_header("X-GitHub-Api-Version", "2022-11-28") try: with urllib.request.urlopen(req, timeout=30) as resp: - return {"ok": True, "status": resp.status, "body": resp.read().decode("utf-8", errors="replace")} + return {"ok": True, "status": resp.status, "body": resp.read().decode("utf-8", errors="replace"), "link": resp.headers.get("Link")} except urllib.error.HTTPError as exc: - return {"ok": False, "status": exc.code, "body": exc.read().decode("utf-8", errors="replace")[:1000]} + return {"ok": False, "status": exc.code, "body": exc.read().decode("utf-8", errors="replace")[:1000], "link": exc.headers.get("Link") if exc.headers else None} def github_head(repository, branch): result = request(f"/repos/{repository}/git/ref/heads/{urllib.parse.quote(branch, safe='')}") try: @@ -792,14 +791,42 @@ def github_head(repository, branch): except Exception: payload = {} return {"ok": result.get("ok"), "status": result.get("status"), "sha": payload.get("object", {}).get("sha"), "error": None if result.get("ok") else result.get("body", "")[:300]} -def hook_deliveries(repository, hook_id): - if not hook_id: - return {"ok": False, "status": None, "latest": None, "latestPush": None, "error": "hook not found"} - result = request(f"/repos/{repository}/hooks/{hook_id}/deliveries?per_page=8") +def hook_delivery_detail(repository, hook_id, delivery): + delivery_id = delivery.get("id") if isinstance(delivery, dict) else None + if not delivery_id: + return {"ok": False, "error": "delivery id missing"} + result = request(f"/repos/{repository}/hooks/{hook_id}/deliveries/{delivery_id}") try: - rows = json.loads(result.get("body") or "[]") + body = json.loads(result.get("body") or "{}") except Exception: - rows = [] + body = {} + request_payload = body.get("request", {}).get("payload", {}) if isinstance(body.get("request"), dict) else {} + payload_repository = request_payload.get("repository", {}) if isinstance(request_payload.get("repository"), dict) else {} + return { + "ok": result.get("ok"), + "repository": payload_repository.get("full_name"), + "ref": request_payload.get("ref"), + "requestedCommit": request_payload.get("after"), + "error": None if result.get("ok") else result.get("body", "")[:300], + } +def next_delivery_cursor(link): + if not isinstance(link, str): + return None + for part in link.split(","): + if 'rel="next"' not in part: + continue + start = part.find("<") + end = part.find(">", start + 1) + if start < 0 or end < 0: + continue + query = urllib.parse.parse_qs(urllib.parse.urlparse(part[start + 1:end]).query) + values = query.get("cursor") or [] + if values: + return values[0] + return None +def hook_deliveries(repository, expected_ref, github_head, head_delivery_id, hook_id): + if not hook_id: + return {"ok": False, "status": None, "latest": None, "latestPush": None, "targetPush": None, "rows": [], "error": "hook not found"} def compact(item): if not isinstance(item, dict): return None @@ -813,9 +840,71 @@ def hook_deliveries(repository, hook_id): "deliveredAt": item.get("delivered_at"), "duration": item.get("duration"), } - latest = compact(rows[0]) if rows else None - latest_push = next((compact(item) for item in rows if isinstance(item, dict) and item.get("event") == "push"), None) - return {"ok": result.get("ok"), "status": result.get("status"), "latest": latest, "latestPush": latest_push, "error": None if result.get("ok") else result.get("body", "")[:300]} + compact_rows = [] + cursor = None + list_error = None + last_status = None + history_has_more = False + max_pages = 10 if head_delivery_id else 1 + for _ in range(max_pages): + query = "?per_page=100" + ("&cursor=" + urllib.parse.quote(cursor, safe="") if cursor else "") + result = request(f"/repos/{repository}/hooks/{hook_id}/deliveries{query}") + last_status = result.get("status") + if not result.get("ok"): + list_error = result.get("body", "")[:300] + break + try: + page_rows = json.loads(result.get("body") or "[]") + except Exception: + page_rows = [] + compact_rows.extend(item for item in (compact(value) for value in page_rows if isinstance(value, dict)) if item is not None) + if head_delivery_id and any(str(item.get("deliveryId")) == str(head_delivery_id) for item in compact_rows): + break + cursor = next_delivery_cursor(result.get("link")) + history_has_more = cursor is not None + if cursor is None: + break + latest = compact_rows[0] if compact_rows else None + latest_push = next((item for item in compact_rows if item.get("event") == "push"), None) + target_push = None + detail_error = None + if head_delivery_id: + delivery_candidates = [item for item in compact_rows if str(item.get("deliveryId")) == str(head_delivery_id)] + if not delivery_candidates: + detail_error = "committed head delivery guid not found in bounded github history" + else: + delivery_candidates = [item for item in compact_rows if item.get("event") == "push"][:100] + detailed_candidates = [] + for delivery in delivery_candidates: + detail = hook_delivery_detail(repository, hook_id, delivery) + if not detail.get("ok"): + detail_error = detail.get("error") or "delivery detail unavailable" + break + detailed = {**delivery, **{key: detail.get(key) for key in ("repository", "ref", "requestedCommit")}} + detailed_candidates.append(detailed) + target_push = select_target_delivery( + detailed_candidates, + repository, + expected_ref, + github_head if head_delivery_id else None, + head_delivery_id, + ) + if target_push is not None: + break + if head_delivery_id is None and target_push is None and detail_error is None: + detail_error = "target branch delivery not found in bounded github history" + return { + "ok": list_error is None and detail_error is None, + "status": last_status, + "latest": latest, + "latestPush": latest_push, + "targetPush": target_push, + "rows": compact_rows, + "historyHasMore": history_has_more, + "error": detail_error or list_error, + } +def newest_delivery(rows): + return max(rows, key=lambda item: (str(item.get("deliveredAt") or ""), str(item.get("id") or "")), default=None) try: mirror_status = json.load(open(mirror_status_path, encoding="utf-8")) except Exception: @@ -861,22 +950,55 @@ for repo in repos: hooks = json.loads(result.get("body") or "[]") except Exception: hooks = [] - match = next((item for item in hooks if (item.get("config") or {}).get("url") == url), None) - hook_id = match.get("id") if match else None + matches = [item for item in hooks if (item.get("config") or {}).get("url") == url] head = github_head(repository, branch) - delivery = hook_deliveries(repository, hook_id) + head_record = select_committed_head_record(repo["key"], head.get("sha"), inbox_records) + head_delivery_id = head_record.get("deliveryId") if isinstance(head_record, dict) else None + observations = [] + for match in matches: + hook_id = match.get("id") + delivery = hook_deliveries(repository, f"refs/heads/{branch}", head.get("sha"), head_delivery_id, hook_id) + observations.append({"hook": match, "delivery": delivery}) + all_deliveries = [ + {**item, "hookId": observation["hook"].get("id")} + for observation in observations + for item in observation["delivery"].get("rows", []) + ] + push_deliveries = [item for item in all_deliveries if item.get("event") == "push"] + latest_delivery = newest_delivery(all_deliveries) + latest_push_delivery = newest_delivery(push_deliveries) + target_push_deliveries = [ + {**observation["delivery"]["targetPush"], "hookId": observation["hook"].get("id")} + for observation in observations + if observation["delivery"].get("targetPush") is not None + ] + selected_push_delivery = newest_delivery(target_push_deliveries) + selected_hook_id = selected_push_delivery.get("hookId") if selected_push_delivery else (matches[0].get("id") if matches else None) + selected_match = next((item for item in matches if item.get("id") == selected_hook_id), None) + topology_ready = len(matches) == 1 and matches[0].get("active") is True + topology_reason = None if topology_ready else ( + "github-hook-missing" if len(matches) == 0 + else "github-hook-duplicate-exact-url" if len(matches) > 1 + else "github-hook-inactive" + ) + delivery_errors = [observation["delivery"].get("error") for observation in observations if observation["delivery"].get("error")] mirror = mirror_by_key.get(repo["key"], {}) - latest_push_delivery = delivery.get("latestPush") hook = { - "hookId": hook_id, - "hookReady": bool(match and match.get("active") is True), - "active": match.get("active") if match else None, + "hookId": selected_hook_id, + "hookReady": topology_ready, + "active": selected_match.get("active") if selected_match else None, "status": result.get("status"), - "latestDelivery": delivery.get("latest"), + "latestDelivery": latest_delivery, "latestPushDelivery": latest_push_delivery, - "latest": delivery.get("latest"), - "latestPush": delivery.get("latestPush"), - "error": result.get("body", "")[:500] if not result.get("ok") else delivery.get("error"), + "selectedPushDelivery": selected_push_delivery, + "deliverySelectionReason": ("committed-head-guid-repository-branch-after" if head_delivery_id else "latest-repository-branch-push-delivery") if selected_push_delivery else "target-branch-push-delivery-unavailable", + "matchingHookCount": len(matches), + "matchingHookIds": [item.get("id") for item in matches], + "hookTopologyReason": topology_reason, + "latest": latest_delivery, + "latestPush": latest_push_delivery, + "selectedPush": selected_push_delivery, + "error": result.get("body", "")[:500] if not result.get("ok") else (delivery_errors[0] if delivery_errors else topology_reason), } rows.append(evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records, inbox_status.get("journal"))) bridge = { diff --git a/scripts/src/platform-infra-gitea-status-evaluator.test.ts b/scripts/src/platform-infra-gitea-status-evaluator.test.ts index a499d2d6..7ae08db8 100644 --- a/scripts/src/platform-infra-gitea-status-evaluator.test.ts +++ b/scripts/src/platform-infra-gitea-status-evaluator.test.ts @@ -5,6 +5,76 @@ import { resolve } from "node:path"; const evaluator = resolve(import.meta.dir, "platform_infra_gitea_status_evaluator.py"); describe("Gitea source authority status evaluator", () => { + test("parses branch and exact snapshot from ls-remote output larger than the display tail budget", () => { + const head = "2".repeat(40); + const prefix = "refs/unidesk/snapshots/gitea-actions/unidesk-master-nc01"; + const historical = Array.from({ length: 420 }, (_, index) => { + const sha = index.toString(16).padStart(40, "0"); + return `${sha}\t${prefix}/${sha}`; + }); + const output = [ + `${head}\trefs/heads/master`, + `${head}\t${prefix}/${head}`, + ...historical, + ].join("\n"); + expect(Buffer.byteLength(output)).toBeGreaterThan(20_000); + const refs = evaluate({ action: "parse-ls-remote", output }); + expect(refs["refs/heads/master"]).toBe(head); + expect(refs[`${prefix}/${head}`]).toBe(head); + }); + + test("anchors the current committed head beyond more than fifty unrelated feature pushes", () => { + const head = "7".repeat(40); + const committed = committedInbox("master-durable-guid", head); + const headRecord = evaluate({ + action: "select-committed-head-record", + repoKey: "fixture", + githubHead: head, + records: [committedInbox("old-guid", "6".repeat(40)), committed], + }); + expect(headRecord.deliveryId).toBe("master-durable-guid"); + expect(evaluate({ + action: "select-committed-head-record", + repoKey: "fixture", + githubHead: "8".repeat(40), + records: [committed], + })).toBeNull(); + const featureDeliveries = Array.from({ length: 75 }, (_, index) => ({ + id: String(10_000 - index), + deliveryId: `feature-${index}`, + deliveredAt: `2026-07-11T23:${String(index % 60).padStart(2, "0")}:00Z`, + repository: "pikasTech/fixture", + ref: `refs/heads/feature-${index}`, + requestedCommit: index.toString(16).padStart(40, "0"), + })); + const masterDelivery = { + id: "42", + deliveryId: "master-durable-guid", + deliveredAt: "2026-07-11T22:00:00Z", + repository: "pikasTech/fixture", + ref: "refs/heads/main", + requestedCommit: head, + }; + const failedRedelivery = { + id: "43", + deliveryId: "failed-redelivery-same-head", + deliveredAt: "2026-07-11T23:59:59Z", + statusCode: 502, + repository: "pikasTech/fixture", + ref: "refs/heads/main", + requestedCommit: head, + }; + const selected = evaluate({ + action: "select-target-delivery", + deliveries: [...featureDeliveries, failedRedelivery, masterDelivery], + repository: "pikasTech/fixture", + ref: "refs/heads/main", + requestedCommit: head, + deliveryId: headRecord.deliveryId, + }); + expect(selected).toEqual(masterDelivery); + }); + test("selects only the exact branch-derived immutable snapshot", () => { const head = "b".repeat(40); const result = evaluate({ @@ -48,7 +118,48 @@ describe("Gitea source authority status evaluator", () => { expect(row.deliveryAccepted).toBe(false); expect(row.correlatedBridgeEvent).toBeNull(); expect(row.bridgeEventStale).toBe(true); - expect(row.staleReason).toBe("latest-push-delivery-failed-or-missing"); + expect(row.staleReason).toBe("target-branch-delivery-failed"); + }); + + test("selects the exact durable proof across duplicate exact-url hooks without hiding topology drift", () => { + const head = "8".repeat(40); + const row = evaluate({ + action: "evaluate-repository", + repo: { + key: "fixture", + upstream: { repository: "pikasTech/fixture", branch: "main" }, + snapshot: { prefix: "refs/snapshots/source" }, + }, + hook: { + hookId: 22, + hookReady: false, + active: true, + status: 200, + latest: { hookId: 11, deliveryId: "duplicate-unpersisted", event: "push", statusCode: 202 }, + latestPush: { hookId: 11, deliveryId: "duplicate-unpersisted", event: "push", statusCode: 202 }, + selectedPush: { hookId: 22, deliveryId: "durable-exact", event: "push", statusCode: 202, repository: "pikasTech/fixture", ref: "refs/heads/main", requestedCommit: head }, + deliverySelectionReason: "committed-durable-proof-for-github-head", + matchingHookCount: 2, + matchingHookIds: [11, 22], + hookTopologyReason: "github-hook-duplicate-exact-url", + }, + head: { ok: true, sha: head }, + mirror: { + branchCommit: head, + snapshotCommit: head, + snapshotRef: `refs/snapshots/source/${head}`, + }, + bridgeEvents: [terminalEvent("durable-exact", head)], + inboxRecords: [committedInbox("durable-exact", head)], + }); + expect(row.latestPushDelivery.deliveryId).toBe("duplicate-unpersisted"); + expect(row.selectedPushDelivery.deliveryId).toBe("durable-exact"); + expect(row.deliveryId).toBe("durable-exact"); + expect(row.deliverySelectionReason).toBe("committed-durable-proof-for-github-head"); + expect(row.durableInboxCommitted).toBe(true); + expect(row.bridgeEventStale).toBe(false); + expect(row.hookReady).toBe(false); + expect(row.hookTopologyReason).toBe("github-hook-duplicate-exact-url"); }); test("MATCH=false is always STALE=true and exact delivery correlation is repo isolated", () => { @@ -98,6 +209,7 @@ describe("Gitea source authority status evaluator", () => { branchCommit: head, snapshotCommit: head, deliveryId: "delivery-old", + requestedCommit: older, statusCode: 202, bridgeEvents: [event], inboxRecords: [committedInbox("delivery-old", older, head, "superseded-with-immutable-snapshot")], @@ -182,6 +294,7 @@ function evaluateRow(input: { inboxRecords?: Array>; deliveredAt?: string; journalCreatedAt?: string; + requestedCommit?: string; }): Record { return evaluate({ action: "evaluate-repository", @@ -196,7 +309,7 @@ function evaluateRow(input: { active: true, status: 200, latest: { deliveryId: input.deliveryId, event: "push", statusCode: input.statusCode, deliveredAt: input.deliveredAt }, - latestPush: { deliveryId: input.deliveryId, event: "push", statusCode: input.statusCode, deliveredAt: input.deliveredAt }, + latestPush: { deliveryId: input.deliveryId, event: "push", statusCode: input.statusCode, deliveredAt: input.deliveredAt, repository: "pikasTech/fixture", ref: "refs/heads/main", requestedCommit: input.requestedCommit ?? input.head }, }, head: { ok: true, sha: input.head }, mirror: { @@ -214,6 +327,7 @@ function committedInbox(deliveryId: string, sourceCommit: string, authorityCommi return { deliveryId, repo: "fixture", + requestedCommit: sourceCommit, state: "committed", result: { sourceCommit, diff --git a/scripts/src/platform-infra-pac-provenance.test.ts b/scripts/src/platform-infra-pac-provenance.test.ts index 7a20cb7b..11c558be 100644 --- a/scripts/src/platform-infra-pac-provenance.test.ts +++ b/scripts/src/platform-infra-pac-provenance.test.ts @@ -98,6 +98,22 @@ test("live admission evaluator requires exact desired hashes, observed generatio }; const input = { policy, binding, role: rbac[0], roleBinding: rbac[1], namespace: "agentrun-ci", defaultCanMutate: false, expected }; expect(evaluator.evaluatePacAdmissionState(input)).toMatchObject({ ready: true, activeAfter: "2026-01-01T00:00:01Z", defaultCanMutate: false }); + const apiDefaultedPolicy = { + ...policy, + spec: { + ...policy.spec, + matchConstraints: { + ...policy.spec.matchConstraints, + namespaceSelector: {}, + objectSelector: {}, + resourceRules: policy.spec.matchConstraints.resourceRules.map((rule: any) => ({ ...rule, scope: "*" })), + }, + }, + }; + expect(evaluator.evaluatePacAdmissionState({ ...input, policy: apiDefaultedPolicy })).toMatchObject({ + ready: true, + liveSpecSha256: expected.configSha256, + }); expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: true })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-serviceaccount-can-mutate-tekton-runs"]) }); expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: null })).toMatchObject({ ready: false, defaultCanMutate: null, reasons: expect.arrayContaining(["default-serviceaccount-mutation-probe-unavailable"]) }); expect(evaluator.evaluatePacAdmissionState({ ...input, role: { ...rbac[0], rules: [rbac[0].rules[0]] } })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-role-rules-mismatch"]) }); @@ -121,6 +137,32 @@ test("live admission evaluator requires exact desired hashes, observed generatio ...input, binding: { ...binding, spec: { ...binding.spec, matchResources: { namespaceSelector: { matchLabels: { disabled: "true" } } } } }, })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]) }); + expect(evaluator.evaluatePacAdmissionState({ + ...input, + policy: { + ...apiDefaultedPolicy, + spec: { + ...apiDefaultedPolicy.spec, + matchConstraints: { + ...apiDefaultedPolicy.spec.matchConstraints, + namespaceSelector: { matchLabels: { disabled: "true" } }, + }, + }, + }, + })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]) }); + expect(evaluator.evaluatePacAdmissionState({ + ...input, + policy: { + ...apiDefaultedPolicy, + spec: { + ...apiDefaultedPolicy.spec, + matchConstraints: { + ...apiDefaultedPolicy.spec.matchConstraints, + resourceRules: apiDefaultedPolicy.spec.matchConstraints.resourceRules.map((rule: any) => ({ ...rule, scope: "Namespaced" })), + }, + }, + }, + })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]) }); }); test("AgentRun owning renderer emits ordered terminal roles over the shared workspace without changing build and publish steps", () => { diff --git a/scripts/src/platform_infra_gitea_status_evaluator.py b/scripts/src/platform_infra_gitea_status_evaluator.py index b82ba33b..0e3f7f82 100644 --- a/scripts/src/platform_infra_gitea_status_evaluator.py +++ b/scripts/src/platform_infra_gitea_status_evaluator.py @@ -1,8 +1,47 @@ #!/usr/bin/env python3 import json +import re import sys +def parse_ls_remote_lines(lines): + refs = {} + for line in lines: + parts = line.split() + if len(parts) == 2: + refs[parts[1]] = parts[0] + return refs + + +def select_committed_head_record(repo_key, github_head, records): + candidates = [] + for record in records: + if not isinstance(record, dict) or record.get("repo") != repo_key or record.get("state") != "committed": + continue + result = record.get("result") if isinstance(record.get("result"), dict) else {} + if record.get("requestedCommit") != github_head or result.get("sourceCommit") != github_head: + continue + if result.get("authorityCommit") not in (None, github_head): + continue + candidates.append(record) + return max(candidates, key=lambda item: (int(item.get("acceptedSequence") or 0), str(item.get("committedAt") or "")), default=None) + + +def select_target_delivery(deliveries, repository, ref, requested_commit=None, delivery_id=None): + candidates = [] + for delivery in deliveries: + if not isinstance(delivery, dict): + continue + if delivery.get("repository") != repository or delivery.get("ref") != ref: + continue + if requested_commit is not None and delivery.get("requestedCommit") != requested_commit: + continue + if delivery_id is not None and _delivery_id(delivery) != str(delivery_id): + continue + candidates.append(delivery) + return max(candidates, key=lambda item: (str(item.get("deliveredAt") or ""), str(item.get("id") or "")), default=None) + + def select_snapshot(branch_commit, snapshot_prefix, refs): if not branch_commit: return { @@ -30,10 +69,21 @@ def evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records=N snapshot_ref = mirror.get("snapshotRef") latest_delivery = hook.get("latest") latest_push_delivery = hook.get("latestPush") - delivery_id = _delivery_id(latest_push_delivery) + selected_push_delivery = hook.get("selectedPush") or latest_push_delivery + delivery_id = _delivery_id(selected_push_delivery) + delivery_repository = selected_push_delivery.get("repository") if isinstance(selected_push_delivery, dict) else None + delivery_ref = selected_push_delivery.get("ref") if isinstance(selected_push_delivery, dict) else None + delivery_requested_commit = selected_push_delivery.get("requestedCommit") if isinstance(selected_push_delivery, dict) else None + delivery_scope_matches = bool( + delivery_repository == repo["upstream"]["repository"] + and delivery_ref == f"refs/heads/{repo['upstream']['branch']}" + and isinstance(delivery_requested_commit, str) + and re.fullmatch(r"[0-9a-f]{40,64}", delivery_requested_commit) + ) delivery_accepted = bool( - isinstance(latest_push_delivery, dict) - and latest_push_delivery.get("statusCode") in (200, 202) + isinstance(selected_push_delivery, dict) + and delivery_scope_matches + and selected_push_delivery.get("statusCode") in (200, 202) ) correlated_inbox_records = [ record for record in inbox_records @@ -41,7 +91,7 @@ def evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records=N and record.get("deliveryId") == delivery_id ] inbox_record = correlated_inbox_records[-1] if correlated_inbox_records else None - pre_durable_bootstrap = _is_pre_durable_bootstrap(latest_push_delivery, inbox_record, journal) + pre_durable_bootstrap = _is_pre_durable_bootstrap(selected_push_delivery, inbox_record, journal) inbox_state = inbox_record.get("state") if inbox_record else ("pre-durable-bootstrap" if pre_durable_bootstrap else None) inbox_result = inbox_record.get("result") if isinstance(inbox_record, dict) and isinstance(inbox_record.get("result"), dict) else {} correlated = [ @@ -63,15 +113,17 @@ def evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records=N inbox_committed = bool( inbox_record and inbox_state == "committed" + and inbox_record.get("requestedCommit") == delivery_requested_commit + and inbox_result.get("sourceCommit") == delivery_requested_commit and ( - inbox_result.get("sourceCommit") == github_head + delivery_requested_commit == github_head or ( inbox_result.get("disposition") == "superseded-with-immutable-snapshot" and inbox_result.get("authorityCommit") == github_head ) ) ) - durable_proof_acceptable = inbox_committed or pre_durable_bootstrap + durable_proof_acceptable = inbox_committed or (pre_durable_bootstrap and delivery_requested_commit == github_head) stale = not ( authority_matches and delivery_accepted @@ -81,6 +133,7 @@ def evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records=N hook, head, delivery_accepted, + delivery_scope_matches, inbox_record, pre_durable_bootstrap, github_head, @@ -106,7 +159,16 @@ def evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records=N "authorityMatches": authority_matches, "latestDelivery": latest_delivery, "latestPushDelivery": latest_push_delivery, + "selectedPushDelivery": selected_push_delivery, + "deliverySelectionReason": hook.get("deliverySelectionReason") or "latest-push-delivery", + "matchingHookCount": hook.get("matchingHookCount"), + "matchingHookIds": hook.get("matchingHookIds") or [], + "hookTopologyReason": hook.get("hookTopologyReason"), "deliveryId": delivery_id, + "deliveryRepository": delivery_repository, + "deliveryRef": delivery_ref, + "deliveryRequestedCommit": delivery_requested_commit, + "deliveryScopeMatches": delivery_scope_matches, "correlatedBridgeEvent": correlated_event, "durableInboxRecord": inbox_record, "durableInboxState": inbox_state, @@ -126,13 +188,15 @@ def _delivery_id(delivery): return str(delivery.get("deliveryId") or delivery.get("guid") or delivery.get("id") or "") -def _stale_reason(hook, head, delivery_accepted, inbox_record, pre_durable_bootstrap, github_head, branch_matches, snapshot_matches): +def _stale_reason(hook, head, delivery_accepted, delivery_scope_matches, inbox_record, pre_durable_bootstrap, github_head, branch_matches, snapshot_matches): if hook.get("hookReady") is not True: return "github-hook-not-ready" if head.get("ok") is not True or not github_head: return "github-head-unavailable" + if not delivery_scope_matches: + return "target-branch-delivery-scope-mismatch-or-missing" if not delivery_accepted: - return "latest-push-delivery-failed-or-missing" + return "target-branch-delivery-failed" if not inbox_record and not pre_durable_bootstrap: return "no-correlated-durable-inbox-record" if not pre_durable_bootstrap: @@ -140,12 +204,17 @@ def _stale_reason(hook, head, delivery_accepted, inbox_record, pre_durable_boots if inbox_state != "committed": return f"durable-inbox-{inbox_state or 'unknown'}-not-committed" result = inbox_record.get("result") if isinstance(inbox_record.get("result"), dict) else {} + delivery_requested_commit = (hook.get("selectedPush") or hook.get("latestPush") or {}).get("requestedCommit") inbox_proves_head = ( - result.get("sourceCommit") == github_head + inbox_record.get("requestedCommit") == delivery_requested_commit + and result.get("sourceCommit") == delivery_requested_commit + and ( + delivery_requested_commit == github_head or ( result.get("disposition") == "superseded-with-immutable-snapshot" and result.get("authorityCommit") == github_head ) + ) ) if not inbox_proves_head: return "correlated-inbox-proof-head-mismatch" @@ -183,7 +252,13 @@ def _error(hook, head, stale_reason): def main(): payload = json.load(sys.stdin) action = payload.get("action") - if action == "select-snapshot": + if action == "parse-ls-remote": + result = parse_ls_remote_lines(str(payload.get("output") or "").splitlines()) + elif action == "select-committed-head-record": + result = select_committed_head_record(payload.get("repoKey"), payload.get("githubHead"), payload.get("records", [])) + elif action == "select-target-delivery": + result = select_target_delivery(payload.get("deliveries", []), payload.get("repository"), payload.get("ref"), payload.get("requestedCommit"), payload.get("deliveryId")) + elif action == "select-snapshot": result = select_snapshot(payload.get("branchCommit"), payload["snapshotPrefix"], payload.get("refs", {})) elif action == "evaluate-repository": result = evaluate_repository(payload["repo"], payload["hook"], payload["head"], payload.get("mirror", {}), payload.get("bridgeEvents", []), payload.get("inboxRecords", []), payload.get("journal"))