Provenance integration¶
Record a completed operation, such as a transcode or render, as an activity that consumed input representations and produced output representations. Each activity has a stable ID and an open-world kind; each input and output edge may carry an open-world role.
An activity can optionally carry:
start and finish timestamps;
a bounded tool name, version, and absolute URI;
a bounded agent name and/or external identifier;
external identifiers for the activity itself, such as render-job IDs; and
typed metadata parameters that target the activity.
Record an activity¶
The example records a render that consumed the original camera file and produced an image sequence, then queries the provenance graph from both ends:
static pp_error_code_t record_render(pp_production_t *production,
const pp_uuid_t *source_id,
const pp_uuid_t *render_id,
pp_error_t **error) {
const pp_activity_edge_t inputs[] = {{*source_id, "org.postproject:primary"}};
const pp_activity_edge_t outputs[] = {{*render_id, NULL}};
pp_transaction_t *transaction = NULL;
pp_activity_set_t *producers = NULL;
pp_object_ref_set_t *ancestors = NULL;
pp_uuid_t activity_id;
pp_error_code_t status =
pp_production_begin_transaction(production, &transaction, error);
if (status == PP_OK) {
status = pp_transaction_create_activity(
transaction, "org.postproject:render", inputs, 1, outputs, 1, NULL,
NULL, "Example Renderer", "2.1", "https://example.com/renderer", NULL,
NULL, NULL, NULL, &activity_id, error);
}
if (status == PP_OK) {
status = pp_transaction_commit(transaction, error);
}
if (status == PP_OK) {
status = pp_production_activities_producing(production, render_id,
&producers, error);
}
if (status == PP_OK) {
status = pp_production_provenance_ancestors(production, render_id,
&ancestors, error);
}
if (status == PP_OK) {
printf("producers: %llu, ancestors: %llu\n",
(unsigned long long)pp_activity_set_count(producers),
(unsigned long long)pp_object_ref_set_count(ancestors));
}
pp_object_ref_set_release(ancestors);
pp_activity_set_release(producers);
pp_transaction_release(transaction);
return status;
}
void record_render(postproject::Production &production,
const postproject::Uuid &source_id,
const postproject::Uuid &render_id) {
postproject::ActivitySpec activity{};
activity.kind = "org.postproject:render";
activity.inputs = {{source_id, "org.postproject:primary"}};
activity.outputs = {{render_id, std::nullopt}};
activity.tool = postproject::ToolIdentity{
"Example Renderer", "2.1", "https://example.com/renderer"};
auto transaction = production.beginTransaction();
const auto activity_id = transaction.createActivity(activity);
transaction.commit();
const auto producers = production.activitiesProducing(render_id);
require(producers.size() == 1 && producers.front().id == activity_id,
"producing activity");
require(production.ancestors(render_id) == std::vector{source_id},
"provenance ancestors");
require(production.descendants(source_id) == std::vector{render_id},
"provenance descendants");
}
def record_render(
production: Production,
source_id: RepresentationId,
render_id: RepresentationId,
) -> None:
with production.transaction() as transaction:
activity_id = transaction.create_activity(
ActivitySpec(
"org.postproject:render",
inputs=(ActivityEdge(source_id, "org.postproject:primary"),),
outputs=(ActivityEdge(render_id),),
tool=ToolIdentity(
"Example Renderer", "2.1", "https://example.com/renderer"
),
)
)
(producer,) = production.activities_producing[render_id]
assert producer.id == activity_id
assert production.activities_consuming[source_id] == (producer,)
assert production.provenance_ancestors[render_id] == (source_id,)
assert production.provenance_descendants[source_id] == (render_id,)
fn record_render(
production: &mut SqliteProduction,
source_id: RepresentationId,
render_id: RepresentationId,
) -> Result<()> {
let activity = Activity::new(
ActivityId::new(),
ActivityKind::new("org.postproject:render")?,
vec![ActivityInput::new(
source_id,
Some(ActivityRole::new("org.postproject:primary")?),
)],
vec![ActivityOutput::new(render_id, None)],
)?
.with_tool(ToolIdentity::new(
"Example Renderer",
Some("2.1".to_owned()),
Some("https://example.com/renderer".to_owned()),
)?);
{
let mut transaction = production.begin_transaction()?;
transaction.create_activity(&activity)?;
transaction.commit()?;
}
assert_eq!(
production.activities_consuming(source_id)?,
vec![activity.clone()]
);
assert_eq!(production.activities_producing(render_id)?, vec![activity]);
assert_eq!(production.ancestors(render_id)?, vec![source_id]);
assert_eq!(production.descendants(source_id)?, vec![render_id]);
Ok(())
}
postproject activity add production.pproj org.postproject:render \
--input "$ORIGINAL_ID=org.postproject:primary" \
--output "$SEQUENCE_ID" \
--tool-name "Example Renderer" --tool-version 2.1 \
--tool-uri https://example.com/renderer
postproject activity producing production.pproj "$SEQUENCE_ID"
postproject activity ancestors production.pproj "$SEQUENCE_ID"
The activity, its edges, and other mutations in the same transaction commit or roll back together. Every referenced representation must already exist in the transaction’s view. A duplicate activity is rejected as already existing, an absent representation as not found, and an edge that would create a generation cycle as a conflict.
Queries¶
The graph can be read in four directions:
activities producing a representation;
activities consuming a representation;
transitive ancestors, following inputs; and
transitive descendants, following outputs.
All activities can also be listed in stable identity order. Edges inside an activity are canonicalized by representation ID and role, and traversal returns unique representation IDs in stable order. A representation without provenance has empty results; an unknown representation is an error.
Mapping guidance¶
An activity maps strongly at a conceptual level to a W3C PROV Activity, while representations often map to PROV Entities. This is not a normative PROV implementation. MovieLabs OMC task and relationship concepts may be carried by adapters, but PostProject does not infer revision, variant, or alternative semantics from processing lineage.
Do not parse the private SQLite tables or encode tool parameters as an ad hoc JSON column; use the public domain contracts and metadata model.