adopt-map 0.4.2__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: adopt-map
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: adopt map: deterministic identity extraction from a repository. Build 1.
5
5
  Project-URL: Homepage, https://github.com/onboardux/onboard-core
6
6
  Project-URL: Source, https://github.com/onboardux/onboard-core
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adopt-map"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "adopt map: deterministic identity extraction from a repository. Build 1."
5
5
  requires-python = ">=3.12"
6
6
  license = "Apache-2.0"
@@ -140,7 +140,9 @@ class ChangeEntry:
140
140
 
141
141
  @dataclass(frozen=True, slots=True)
142
142
  class Rebaseline:
143
- """A referent whose extractor version changed: re-recorded, never judged."""
143
+ """A referent whose recorded digest came from a different instrument than the
144
+ one this run keeps -- another extractor version, or another extractor:
145
+ re-recorded, never judged."""
144
146
 
145
147
  uri: str
146
148
  identity_id: str
@@ -218,7 +220,16 @@ def compute(
218
220
  (class, URI), so two runs over one tree produce the same batch order and
219
221
  a reviewer's queue does not reshuffle between sessions.
220
222
  """
221
- seen = {entry.uri: entry for entry in observed}
223
+ # Every sighting of a URI, in the order the extractors produced them. Two
224
+ # extractors can observe one referent -- a key a `.env.example` declares and a
225
+ # settings class reads -- and each digests only its own attributes, so their
226
+ # digests differ by construction. The last sighting is the one this diff keeps
227
+ # and records; a stored digest is compared with the sighting by the extractor
228
+ # that recorded it (`_counterpart`).
229
+ sightings: dict[str, list[ObservedIdentity]] = {}
230
+ for sighting in observed:
231
+ sightings.setdefault(sighting.uri, []).append(sighting)
232
+ seen = {uri: found[-1] for uri, found in sightings.items()}
222
233
  stored_by_uri = {entry.uri: entry for entry in stored}
223
234
  delta = file_delta if file_delta is not None else FileDelta(available=False)
224
235
 
@@ -293,9 +304,14 @@ def compute(
293
304
  )
294
305
  continue
295
306
 
296
- if _versions_differ(entry.extractor_version, arrival.extractor_version):
307
+ counterpart = _counterpart(sightings[entry.uri], entry.extractor)
308
+ if counterpart is None or _versions_differ(
309
+ entry.extractor_version, counterpart.extractor_version
310
+ ):
297
311
  # The fence. Every digest differs across an upgrade by construction,
298
- # so this is the one comparison that must never be made.
312
+ # so this is the one comparison that must never be made -- and the
313
+ # same holds across extractors: the extractor that recorded this
314
+ # referent did not see it this run, and another one did.
299
315
  rebaselines.append(
300
316
  Rebaseline(
301
317
  uri=entry.uri,
@@ -309,7 +325,28 @@ def compute(
309
325
  )
310
326
  continue
311
327
 
312
- if arrival.digest != entry.digest:
328
+ if counterpart.digest == entry.digest and counterpart is not arrival:
329
+ # Unchanged, but recorded from a sighting this diff does not keep:
330
+ # `map` records the first extractor to see a referent, and this diff
331
+ # keeps the last. Re-record the kept one, so the next run compares
332
+ # like with like. Comparing across the two instead reported every key
333
+ # declared in both `.env.example` and a settings class as
334
+ # SEMANTICS_CHANGED on the first refresh after `map`, staling every
335
+ # note bound to it over an edit nobody made.
336
+ rebaselines.append(
337
+ Rebaseline(
338
+ uri=entry.uri,
339
+ identity_id=entry.identity_id,
340
+ from_version=entry.extractor_version,
341
+ to_version=arrival.extractor_version,
342
+ digest=arrival.digest,
343
+ extractor=arrival.extractor,
344
+ source_path=arrival.source_path,
345
+ )
346
+ )
347
+ continue
348
+
349
+ if counterpart.digest != entry.digest:
313
350
  changes.append(
314
351
  ChangeEntry(
315
352
  uri=entry.uri,
@@ -393,6 +430,27 @@ def compute(
393
430
  )
394
431
 
395
432
 
433
+ def _counterpart(
434
+ found: Sequence[ObservedIdentity], extractor: str | None
435
+ ) -> ObservedIdentity | None:
436
+ """This run's sighting to compare a stored digest with: the one by the
437
+ extractor that recorded it, or `None` when that extractor did not see the
438
+ referent this run.
439
+
440
+ A digest covers only the attributes its extractor extracts, so two
441
+ extractors' digests of one referent never agree -- comparing across them
442
+ reports a change on every run where the recorded and the kept sighting come
443
+ from different extractors. A store that names no extractor compares with the
444
+ kept sighting, as it always has.
445
+ """
446
+ if extractor is None:
447
+ return found[-1]
448
+ for sighting in reversed(found):
449
+ if sighting.extractor == extractor:
450
+ return sighting
451
+ return None
452
+
453
+
396
454
  def _versions_differ(stored: str | None, observed: str | None) -> bool:
397
455
  """Whether the fence should fire.
398
456
 
File without changes
File without changes
File without changes