Project a turn's provenance into the named-subgraph JSON-LD. Builds `urn:atomic:provgraph: ` with `@graph = [prov:Activity, prov:SoftwareAgent, prov:Person]`, exactly per the doc §"A provenance graph" but with the hard-scope agent-id override (`urn:atomic:agent: `, never a DID) and `used: []` (flywheel deferred). Deterministic: fixed key insertion order + JCS canonicalizati
(input: &ProvActivityInput)
| 132 | /// Deterministic: fixed key insertion order + JCS canonicalization at sign time |
| 133 | /// make the signed bytes stable across calls. |
| 134 | pub fn project(input: &ProvActivityInput) -> Value { |
| 135 | let agent_id = agent_urn(&input.agent_slug); |
| 136 | |
| 137 | // --- prov:Activity --------------------------------------------------- |
| 138 | let mut activity = Map::new(); |
| 139 | activity.insert("@type".into(), json!("prov:Activity")); |
| 140 | activity.insert("@id".into(), json!(activity_urn(&input.activity_id))); |
| 141 | if let Some(started) = &input.started_at { |
| 142 | activity.insert("prov:startedAtTime".into(), json!(started)); |
| 143 | } |
| 144 | if let Some(ended) = &input.ended_at { |
| 145 | activity.insert("prov:endedAtTime".into(), json!(ended)); |
| 146 | } |
| 147 | // wasAssociatedWith (the agent) / actedOnBehalfOf (the person). |
| 148 | activity.insert("associatedWith".into(), json!(agent_id)); |
| 149 | activity.insert("actedOnBehalfOf".into(), json!(input.person_did)); |
| 150 | activity.insert("generated".into(), json!(input.generated)); |
| 151 | // Flywheel `used` edge — emitted only when non-empty (unknown omitted, never |
| 152 | // invented). Empty until the capture records structured inputs. |
| 153 | if !input.used.is_empty() { |
| 154 | activity.insert("used".into(), json!(input.used)); |
| 155 | } |
| 156 | // turnParent — omit the key entirely when there is no parent turn. |
| 157 | if let Some(parent) = &input.turn_parent { |
| 158 | activity.insert("turnParent".into(), json!(parent)); |
| 159 | } |
| 160 | |
| 161 | // --- prov:SoftwareAgent (NON-VERIFIABLE descriptive label) ----------- |
| 162 | let mut agent = Map::new(); |
| 163 | agent.insert("@type".into(), json!("prov:SoftwareAgent")); |
| 164 | agent.insert("@id".into(), json!(agent_id)); |
| 165 | agent.insert("actedOnBehalfOf".into(), json!(input.person_did)); |
| 166 | // Human label as rdfs:label (there is no prov:label in PROV-O); the ctx maps |
| 167 | // the `label` term to rdfs:label. |
| 168 | agent.insert("label".into(), json!(input.agent_display_name)); |
| 169 | if let Some(vendor) = &input.agent_vendor { |
| 170 | agent.insert("vendor".into(), json!(vendor)); |
| 171 | } |
| 172 | |
| 173 | // --- prov:Person (the real did:atomic signer) ------------------------ |
| 174 | let mut person = Map::new(); |
| 175 | person.insert("@type".into(), json!("prov:Person")); |
| 176 | person.insert("@id".into(), json!(input.person_did)); |
| 177 | |
| 178 | let mut root = Map::new(); |
| 179 | root.insert("@context".into(), json!(CONTEXT_URL)); |
| 180 | root.insert("@id".into(), json!(provgraph_urn(&input.change_id_base32))); |
| 181 | root.insert( |
| 182 | "@graph".into(), |
| 183 | json!([ |
| 184 | Value::Object(activity), |
| 185 | Value::Object(agent), |
| 186 | Value::Object(person), |
| 187 | ]), |
| 188 | ); |
| 189 | Value::Object(root) |
| 190 | } |
| 191 |