Expose SBOM controlled source evidence
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
This commit is contained in:
parent
c1d5e4a9dd
commit
34867d784f
3 changed files with 61 additions and 10 deletions
|
|
@ -762,6 +762,22 @@ def _sbom_catchup_report(instr: Any, catchup: dict) -> InstructionResult:
|
||||||
updated = [r for r in catchup.get("updated", []) if isinstance(r, dict)]
|
updated = [r for r in catchup.get("updated", []) if isinstance(r, dict)]
|
||||||
skipped = [r for r in catchup.get("skipped", []) if isinstance(r, dict)]
|
skipped = [r for r in catchup.get("skipped", []) if isinstance(r, dict)]
|
||||||
limit = catchup.get("limit")
|
limit = catchup.get("limit")
|
||||||
|
selected_repos = []
|
||||||
|
controlled_source_count = 0
|
||||||
|
for repo in selected:
|
||||||
|
item = {
|
||||||
|
"repo_slug": repo.get("repo_slug"),
|
||||||
|
"sbom_age_days": repo.get("sbom_age_days"),
|
||||||
|
"has_sbom": repo.get("has_sbom"),
|
||||||
|
"last_sbom_at": repo.get("last_sbom_at"),
|
||||||
|
"checkout_available": repo.get("checkout_available"),
|
||||||
|
}
|
||||||
|
source_ref = _safe_sbom_source_ref(repo.get("source_ref"))
|
||||||
|
if source_ref is not None:
|
||||||
|
item["source_ref"] = source_ref
|
||||||
|
controlled_source_count += 1
|
||||||
|
selected_repos.append(item)
|
||||||
|
|
||||||
report: dict[str, Any] = {
|
report: dict[str, Any] = {
|
||||||
"summary": (
|
"summary": (
|
||||||
f"SBOM catch-up: {len(selected)} selected (limit {limit}), "
|
f"SBOM catch-up: {len(selected)} selected (limit {limit}), "
|
||||||
|
|
@ -773,19 +789,11 @@ def _sbom_catchup_report(instr: Any, catchup: dict) -> InstructionResult:
|
||||||
"deterministic": True,
|
"deterministic": True,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"selected_count": len(selected),
|
"selected_count": len(selected),
|
||||||
|
"controlled_source_count": controlled_source_count,
|
||||||
"stale_count": catchup.get("stale_count"),
|
"stale_count": catchup.get("stale_count"),
|
||||||
"never_count": catchup.get("never_count"),
|
"never_count": catchup.get("never_count"),
|
||||||
"total_count": catchup.get("total_count"),
|
"total_count": catchup.get("total_count"),
|
||||||
"selected_repos": [
|
"selected_repos": selected_repos,
|
||||||
{
|
|
||||||
"repo_slug": r.get("repo_slug"),
|
|
||||||
"sbom_age_days": r.get("sbom_age_days"),
|
|
||||||
"has_sbom": r.get("has_sbom"),
|
|
||||||
"last_sbom_at": r.get("last_sbom_at"),
|
|
||||||
"checkout_available": r.get("checkout_available"),
|
|
||||||
}
|
|
||||||
for r in selected
|
|
||||||
],
|
|
||||||
"updated_repos": [r.get("repo_slug") for r in updated],
|
"updated_repos": [r.get("repo_slug") for r in updated],
|
||||||
"skipped_repos": [
|
"skipped_repos": [
|
||||||
{"repo_slug": r.get("repo_slug"), "reason": r.get("reason")}
|
{"repo_slug": r.get("repo_slug"), "reason": r.get("reason")}
|
||||||
|
|
@ -803,6 +811,23 @@ def _sbom_catchup_report(instr: Any, catchup: dict) -> InstructionResult:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_sbom_source_ref(raw: Any) -> dict[str, str] | None:
|
||||||
|
"""Keep only the reviewable controlled-source identity in progress evidence."""
|
||||||
|
if not isinstance(raw, dict) or raw.get("kind") != "forgejo-archive-v1":
|
||||||
|
return None
|
||||||
|
repository = raw.get("repository")
|
||||||
|
revision = raw.get("revision")
|
||||||
|
if not isinstance(repository, str) or not repository.strip():
|
||||||
|
return None
|
||||||
|
if not isinstance(revision, str) or re.fullmatch(r"[0-9a-f]{40}", revision) is None:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"kind": "forgejo-archive-v1",
|
||||||
|
"repository": repository,
|
||||||
|
"revision": revision,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _validate_output(
|
def _validate_output(
|
||||||
raw_output: Any,
|
raw_output: Any,
|
||||||
instr: Any,
|
instr: Any,
|
||||||
|
|
|
||||||
|
|
@ -640,6 +640,12 @@ def test_deterministic_report_names_selected_repos() -> None:
|
||||||
"sbom_age_days": 9999,
|
"sbom_age_days": 9999,
|
||||||
"has_sbom": False,
|
"has_sbom": False,
|
||||||
"checkout_available": True,
|
"checkout_available": True,
|
||||||
|
"source_ref": {
|
||||||
|
"kind": "forgejo-archive-v1",
|
||||||
|
"repository": "coulomb/never-scanned",
|
||||||
|
"revision": "a" * 40,
|
||||||
|
"archive_url": "must-not-enter-progress-evidence",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"repo_slug": "no-checkout",
|
"repo_slug": "no-checkout",
|
||||||
|
|
@ -647,6 +653,11 @@ def test_deterministic_report_names_selected_repos() -> None:
|
||||||
"sbom_age_days": 112,
|
"sbom_age_days": 112,
|
||||||
"has_sbom": True,
|
"has_sbom": True,
|
||||||
"checkout_available": False,
|
"checkout_available": False,
|
||||||
|
"source_ref": {
|
||||||
|
"kind": "forgejo-archive-v1",
|
||||||
|
"repository": "coulomb/no-checkout",
|
||||||
|
"revision": "not-a-full-sha",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"selected_count": 2,
|
"selected_count": 2,
|
||||||
|
|
@ -663,6 +674,7 @@ def test_deterministic_report_names_selected_repos() -> None:
|
||||||
report = result.report
|
report = result.report
|
||||||
assert report["deterministic"] is True
|
assert report["deterministic"] is True
|
||||||
assert report["never_count"] == 93
|
assert report["never_count"] == 93
|
||||||
|
assert report["controlled_source_count"] == 1
|
||||||
assert [r["repo_slug"] for r in report["selected_repos"]] == [
|
assert [r["repo_slug"] for r in report["selected_repos"]] == [
|
||||||
"never-scanned",
|
"never-scanned",
|
||||||
"no-checkout",
|
"no-checkout",
|
||||||
|
|
@ -670,6 +682,12 @@ def test_deterministic_report_names_selected_repos() -> None:
|
||||||
assert "93 never scanned of 111 repos" in report["summary"]
|
assert "93 never scanned of 111 repos" in report["summary"]
|
||||||
assert report["updated_repos"] == []
|
assert report["updated_repos"] == []
|
||||||
assert report["skipped_repos"] == []
|
assert report["skipped_repos"] == []
|
||||||
|
assert report["selected_repos"][0]["source_ref"] == {
|
||||||
|
"kind": "forgejo-archive-v1",
|
||||||
|
"repository": "coulomb/never-scanned",
|
||||||
|
"revision": "a" * 40,
|
||||||
|
}
|
||||||
|
assert "source_ref" not in report["selected_repos"][1]
|
||||||
|
|
||||||
|
|
||||||
def test_deterministic_report_carries_t02_ingest_outcomes() -> None:
|
def test_deterministic_report_carries_t02_ingest_outcomes() -> None:
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,11 @@ processed exactly that frozen set, spawned zero tasks, and produced three
|
||||||
provenance-bearing `no-manifest` snapshots. `never_count` advanced 94 to 91.
|
provenance-bearing `no-manifest` snapshots. `never_count` advanced 94 to 91.
|
||||||
Keep open for the first unassisted weekday fire and sustainable projection of
|
Keep open for the first unassisted weekday fire and sustainable projection of
|
||||||
each newly exposed oldest-N batch.
|
each newly exposed oldest-N batch.
|
||||||
|
|
||||||
|
Preflight 2026-08-23: after the controlled batch advanced, the newly exposed
|
||||||
|
oldest three (`citation-work`, `clay-borg`, `config-atlas`) each carried a
|
||||||
|
matching `forgejo-archive-v1` reference and a 40-character revision, confirming
|
||||||
|
projection is not limited to the attended batch. The deterministic catch-up
|
||||||
|
report now retains only the allowlisted kind/repository/revision fields and a
|
||||||
|
`controlled_source_count`, so the first unassisted fire can prove provenance
|
||||||
|
directly in State Hub without copying Nexus payloads or archive URLs.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue