Adopt media already known to a production

When a host encounters media, it can ask whether the shared production already records that storage evidence. A match lets the host attach its own external identifier to the existing asset instead of creating another logical asset.

Lookup is read-only. It never imports, adopts, merges, relinks, or changes a locator. The host decides what a candidate means and performs any resulting mutation in an explicit transaction.

Find candidates

PostProject offers two bounded lookups:

  • Locator identity matches one canonical current URI. For an image sequence, the identity also includes its exact prefix, suffix, and frame padding. A directory URI without that naming does not match a sequence.

  • Fingerprint evidence matches the exact algorithm, algorithm version, and opaque value of a resource’s current effective fingerprint. Foreign host algorithms are valid inputs when the host knows their precise semantics.

Each result identifies the matching resource, its parent representation, and its owning asset. Follow next_cursor with the same query and limit until it is absent.

static pp_error_code_t find_known_media(pp_production_t *production,
                                        const char *media_path,
                                        const pp_asset_id_t *expected_asset,
                                        pp_error_t **error) {
  char *uri = NULL;
  pp_fingerprint_t *fingerprint = NULL;
  pp_known_media_set_t *by_locator = NULL;
  pp_known_media_set_t *by_content = NULL;
  const char *algorithm = NULL;
  const uint8_t *value = NULL;
  uint16_t version = 0;
  uint64_t value_length = 0;
  pp_asset_id_t asset;
  pp_representation_id_t representation;
  pp_resource_id_t resource;

  pp_error_code_t status = pp_file_path_to_locator(media_path, &uri, error);
  if (status == PP_OK) {
    status = pp_production_find_known_media_by_locator(
        production, uri, NULL, 100, NULL, &by_locator, error);
  }
  if (status == PP_OK &&
      (pp_known_media_set_count(by_locator) != 1 ||
       pp_known_media_set_next_cursor(by_locator) != NULL)) {
    status = PP_ERROR_INTERNAL;
  }
  if (status == PP_OK) {
    status = pp_known_media_set_get(by_locator, 0, &asset, &representation,
                                    &resource, error);
  }
  if (status == PP_OK && memcmp(&asset, expected_asset, sizeof asset) != 0) {
    status = PP_ERROR_INTERNAL;
  }

  if (status == PP_OK) {
    status = pp_fingerprint_file(media_path, &fingerprint, error);
  }
  if (status == PP_OK) {
    status = pp_fingerprint_get(fingerprint, &algorithm, &version, &value,
                                &value_length, error);
  }
  if (status == PP_OK) {
    status = pp_production_find_known_media_by_fingerprint(
        production, algorithm, version, value, value_length, 100, NULL,
        &by_content, error);
  }
  if (status == PP_OK && pp_known_media_set_count(by_content) == 1) {
    status = pp_known_media_set_get(by_content, 0, &asset, &representation,
                                    &resource, error);
  } else if (status == PP_OK) {
    /* Multiple matches are candidates; the host must never guess one. */
    status = PP_ERROR_INTERNAL;
  }
  if (status == PP_OK && memcmp(&asset, expected_asset, sizeof asset) != 0) {
    status = PP_ERROR_INTERNAL;
  }

  pp_string_release(uri);
  pp_fingerprint_release(fingerprint);
  pp_known_media_set_release(by_locator);
  pp_known_media_set_release(by_content);
  return status;
}
postproject::AssetId find_known_media(const std::string &production_path,
                                   const std::string &media_path) {
  // A second host or process opens the same production explicitly.
  const auto production = postproject::Production::open(production_path).value();
  const auto fingerprint = postproject::fingerprintFile(media_path).value();
  const auto by_locator =
      production.findKnownMedia(std::filesystem::path(media_path), 100).value();
  const auto by_content =
      production.findKnownMedia(fingerprint, 100).value();

  // Several results are candidates for the host to present, never a winner.
  require(!by_locator.next_cursor && !by_content.next_cursor &&
              by_locator.items.size() == 1 && by_content.items.size() == 1,
          "one known-media candidate");
  require(by_locator.items.front().asset_id ==
              by_content.items.front().asset_id,
          "locator and content identify the same asset");
  return by_locator.items.front().asset_id;
}
def find_known_media(
    production_path: Path, media_path: Path, fingerprint: Fingerprint
) -> AssetId:
    # A second host or process opens the same production explicitly.
    with Production.open(production_path) as production:
        by_locator = production.find_known_media(media_path, limit=100)
        by_content = production.find_known_media(fingerprint, limit=100)

        # Several results are candidates for the host to present, never a winner.
        assert by_locator.items == by_content.items
        assert by_locator.next_cursor is None and by_content.next_cursor is None
        assert len(by_locator.items) == 1
        return by_locator.items[0].asset_id
fn find_known_media(
    production_path: &Path,
    locator_uri: &str,
    fingerprint: &ResourceFingerprint,
) -> Result<AssetId> {
    // Another host or process opens the same production explicitly.
    let production = SqliteProduction::open(production_path)?;
    let page = QueryPageRequest::new(100, None)?;
    let locator = LocatorIdentity::new(locator_uri, None)?;
    let by_locator = production.find_known_media_by_locator(&locator, &page)?;
    let by_content = production.find_known_media_by_fingerprint(fingerprint, &page)?;

    // Never silently choose when either query returns several candidates.
    assert_eq!(by_locator.items(), by_content.items());
    assert_eq!(by_locator.items().len(), 1);
    Ok(by_locator.items()[0].asset().id())
}
# Another host or process receives the same explicit production path.
LOCATOR_URI=$(jq -r .uri import.json)
postproject --json media find-by-locator knowledge.pproj "$LOCATOR_URI" \
  --limit 100

FINGERPRINT=$(postproject --json media show knowledge.pproj "$ASSET_ID" |
  jq -c '.representations[0].resources[0].fingerprints[0]')
postproject --json media find-by-fingerprint knowledge.pproj \
  "$(jq -r .algorithm <<<"$FINGERPRINT")" \
  "$(jq -r .version <<<"$FINGERPRINT")" \
  "$(jq -r .value_hex <<<"$FINGERPRINT")" --limit 100
# Both commands return every candidate; the host decides whether to adopt one.

Python find_known_media(path, limit=100) and C++ findKnownMedia(std::filesystem::path, 100) canonicalize existing paths using the same rules as import. Pass an explicit LocatorIdentity for a URI, including paths unavailable on this machine, or a fingerprint for exact content evidence. C and Rust retain their typed locator/fingerprint queries; the CLI accepts paths directly. Image-sequence callers must carry the sequence naming from recognition or host state rather than guessing from the directory.

Decide explicitly

Treat the result count as part of the workflow:

  1. With no matches, the host may follow its normal import path.

  2. With one match, the host may offer to adopt that asset.

  3. With several matches, present all candidates or apply a host-owned decision that the user can inspect. Content equality alone does not prove that two logical assets are the same.

  4. On adoption, attach the host’s own qualified external identifier to the selected asset.

  5. If the host has learned another valid location, confirm that locator explicitly as described in media resolution.

Do not replace another application’s identifier. Different hosts can attach different qualified identifiers to the same PostProject asset.

Current evidence only

Normal locator lookup excludes retired locators. A retired path can later name unrelated content, so historical paths are not safe adoption evidence.

Fingerprint lookup uses the resource’s current effective recorded fingerprint, not every past observation. After a new fingerprint observation replaces the effective value, the former value no longer matches this query. Historical audit information remains in revisions but does not silently affect adoption.

Locator/fingerprint lookup does not verify current bytes. Path conversion checks the filesystem to canonicalize the path; it does not hash its content. Use content verification when the host needs current filesystem evidence.

Concurrent first encounters

Two processes can encounter previously unknown media at the same time. Both may see no candidates and commit separate logical assets. This is intentional: PostProject does not impose global content deduplication, and identical bytes can represent distinct logical production objects.

If avoiding duplicates matters to a host workflow, coordinate that decision at the host level and re-run lookup before import. Do not silently merge assets afterward.

Scope of shared access

Multiple processes on one machine may open the same local .pproj file and observe committed changes through the revision feed. This does not make a production file a network service: network filesystems, remote revision streams, and access-control boundaries are outside this package’s shared-file contract.