External identifiers

Attach an industry, vendor, or application identifier to a PostProject object when another system needs to find that object by its own key. An identifier is a scheme, value, and optional qualifier; PostProject stores all three exactly as supplied and never rewrites or normalizes them.

The example attaches a camera serial number to an asset, reads the identifiers attached to that asset, and finds every object carrying the exact scheme and value:

static pp_error_code_t tag_camera_serial(pp_production_t *production,
                                         const pp_uuid_t *asset_id,
                                         pp_error_t **error) {
  const pp_object_ref_t target = {PP_OBJECT_ASSET, *asset_id};
  pp_transaction_t *transaction = NULL;
  pp_external_identifier_set_t *attached = NULL;
  pp_object_ref_set_t *matches = NULL;

  pp_error_code_t status =
      pp_production_begin_transaction(production, &transaction, error);
  if (status == PP_OK) {
    status = pp_transaction_add_external_identifier(
        transaction, &target, "com.example.camera.serial", "A-0007", NULL,
        error);
  }
  if (status == PP_OK) {
    status = pp_transaction_commit(transaction, error);
  }
  if (status == PP_OK) {
    status = pp_production_external_identifiers(production, &target,
                                                &attached, error);
  }
  if (status == PP_OK) {
    status = pp_production_find_by_external_identifier(
        production, "com.example.camera.serial", "A-0007", &matches, error);
  }
  if (status == PP_OK) {
    printf("identifiers: %llu, matching objects: %llu\n",
           (unsigned long long)pp_external_identifier_set_count(attached),
           (unsigned long long)pp_object_ref_set_count(matches));
  }

  pp_object_ref_set_release(matches);
  pp_external_identifier_set_release(attached);
  pp_transaction_release(transaction);
  return status;
}
void tag_camera_serial(postproject::Production &production,
                       const postproject::Uuid &asset_id) {
  const postproject::ObjectRef target{postproject::ObjectKind::asset, asset_id};
  const postproject::ExternalIdentifier identifier{
      "com.example.camera.serial", "A-0007", std::nullopt};

  auto transaction = production.beginTransaction();
  transaction.addExternalIdentifier(target, identifier);
  transaction.commit();

  const auto attached = production.externalIdentifiers(target);
  const auto matches =
      production.findByExternalIdentifier(identifier.scheme, identifier.value);
  require(attached.size() == 1 && matches == std::vector{target},
          "identifier lookup");
}
def tag_camera_serial(production: Production, asset_id: AssetId) -> None:
    identifier = ExternalIdentifier("com.example.camera.serial", "A-0007")
    with production.transaction() as transaction:
        transaction.add_external_identifier(asset_id, identifier)

    attached = production.external_identifiers[asset_id]
    matches = production.objects_by_external_identifier[
        identifier.scheme, identifier.value
    ]
    assert attached == (identifier,)
    assert matches == (asset_id,)
fn tag_camera_serial(production: &mut SqliteProduction, asset_id: AssetId) -> Result<()> {
    let target = ObjectRef::Asset(asset_id);
    let identifier = ExternalIdentifier::new(
        IdentifierScheme::new("com.example.camera.serial")?,
        "A-0007",
        None,
    )?;
    {
        let mut transaction = production.begin_transaction()?;
        transaction.add_external_identifier(target, &identifier)?;
        transaction.commit()?;
    }

    let attached = production.external_identifiers(target)?;
    let matches =
        production.find_by_external_identifier(identifier.scheme(), identifier.value())?;
    assert_eq!(attached, vec![identifier]);
    assert_eq!(matches, vec![target]);
    Ok(())
}
postproject identifier add production.pproj asset "$ASSET_ID" \
  com.example.camera.serial A-0007
postproject identifier list production.pproj asset "$ASSET_ID"
postproject identifier find production.pproj com.example.camera.serial A-0007

Attachment is a transactional mutation like any other: it stays pending until commit and appears in the revision feed. Removing an identifier requires the same exact scheme, value, and qualifier.

Lookup is exact. A scheme is not a namespace prefix, values are compared byte-for-byte, and a lookup may return several objects because an external system can reuse a value. Treat the result as candidates for the integration to interpret, not as proof of identity.

To refer from a host document to a PostProject object, persist a host-object binding instead; external identifiers are lookup aids and assertions, not PostProject identity.