cloudronin's picture
push build context (uofa source + packs + space app)
a28ec65 verified
Raw
History Blame Contribute Delete
2.93 kB
"""PROV-DM provenance for SIP evidence bundles (SIP §6).
The interrogation run is a ``prov:Activity``; the surrogate, benchmark, and
reference are ``prov:Entity`` instances ``prov:used`` by the run; the evidence
bundle is a ``prov:Entity`` ``prov:wasGeneratedBy`` the run; each measurement
library is a ``prov:SoftwareAgent`` the run ``prov:wasAssociatedWith``.
This is what keeps UofA's ``W-PROV-01`` chain check from firing spuriously on
SIP-sourced evidence: every entity has an upstream edge, so the core
provenance BFS finds no orphan. ``find_orphan_entities`` lets a SIP-side test
assert that invariant *without* importing the rule engine (firewall: no shared
judgment code).
Pure stdlib — no measurement or framework dependency.
"""
from __future__ import annotations
def build_provenance(
*,
run_id: str,
generated_at: str,
surrogate_ref: str,
benchmark_ref: str,
reference_ref: str,
bundle_id: str,
libraries: list[tuple[str, str]],
) -> dict:
"""Assemble the PROV-DM block embedded in a SIP bundle.
``libraries`` is a list of ``(name, version)`` pairs — one SoftwareAgent
each. Returns a plain dict (no verdict tokens; the schema's provenance
denylist forbids any from sneaking in).
"""
agents = [
{"id": f"sip:agent/{name}@{version}", "type": "prov:SoftwareAgent",
"label": f"{name} {version}"}
for name, version in libraries
]
return {
"@context": "http://www.w3.org/ns/prov#",
"activity": {
"id": run_id,
"type": "prov:Activity",
"startedAtTime": generated_at,
"used": [surrogate_ref, benchmark_ref, reference_ref],
"wasAssociatedWith": [a["id"] for a in agents],
},
"entities": [
{"id": surrogate_ref, "type": "prov:Entity", "role": "surrogate"},
{"id": benchmark_ref, "type": "prov:Entity", "role": "benchmark"},
{"id": reference_ref, "type": "prov:Entity", "role": "reference"},
{"id": bundle_id, "type": "prov:Entity", "role": "bundle",
"wasGeneratedBy": run_id},
],
"agents": agents,
}
def find_orphan_entities(provenance: dict) -> list[str]:
"""Return entity ids with no upstream PROV edge (the W-PROV-01 condition).
An entity is connected if the run ``prov:used`` it or it
``prov:wasGeneratedBy`` the run. A well-formed SIP provenance block has no
orphans, so W-PROV-01 stays silent at v2 ingest.
"""
activity = provenance.get("activity", {})
used = set(activity.get("used", []))
run_id = activity.get("id")
orphans = []
for entity in provenance.get("entities", []):
eid = entity.get("id")
connected = eid in used or (
entity.get("wasGeneratedBy") == run_id and run_id is not None
)
if not connected:
orphans.append(eid)
return orphans