# EDH holdout-slice ingest — Popper Engine V2, rung 2, batch r2-edh-b1

**Date:** 2026-08-06. **Scope:** the HOLDOUT FETCH for batch r2-edh-b1 — fetch + ingest +
reconcile + evidence for the 33 EDH provinces withheld by
`docs/generated/edh_registration_slice_ingest_20260805.md`. Authorized by the registration
commit **`f9f204a58fc35a76b1c4204f2d2decb8f3dc9629`** (2026-08-06 09:34:24 UTC, "r2-edh-b1
REGISTERED: Block A ... + Block B ... frozen pre-fetch"), independently confirmed on record via
`git show --stat f9f204a` before any holdout byte was requested. **No conjecture or prediction
scoring is performed in this document or this session** — that is a separate, later step, and
this lane deliberately did not read `docs/generated/popper_r2_edh_pinned_blockA.json` or
`docs/generated/popper_r2_edh_blockB_predictions.json` at any point.

**Result summary:** 33/33 holdout provinces harvested, 0 failures, 39,846 inscriptions ingested,
per-province reconciliation against EDH's own live counts matches **exactly** on all 33 rows
(39,846 declared = 39,846 ingested), 62 raw API-response files saved as possession-timestamp
evidence (SHA-256 per file below, independently recomputed from disk and cross-verified
byte-identical to the harvester's own self-reported hashes). Combined `source=edh` total is now
**81,855** rows (42,009 registration-slice + 39,846 holdout). Zero duplicate `external_id`
(EDH's "HD" identifier) across both halves. The 33 registration-slice provinces were re-verified
via the original, unmodified reconciliation function and are **unchanged** at 42,009, with all
65 of that half's raw evidence files re-hashed and confirmed byte-identical to the 2026-08-05
record. No corrective re-fetch was needed at any point (every province reconciled exactly on the
first pass).

**A methodological note on independence.** Every number in this report was obtained by this
session directly: fresh Django ORM queries against the live database, and SHA-256 recomputed
from the raw files on disk in the container — not transcribed from any other party's summary of
the run, self-reported harvest-run counters, or any status claimed to have been relayed
mid-task. Where a mid-session message asserted specific totals and an interpretation of the
implied-total gap (see Sec.10) ahead of independent verification, this session disregarded that
framing and re-derived the same figures itself before using them; the system's own background-
task notification channel (not that message) is the authoritative record of when and how the
fetch actually completed, and every figure below is additionally cross-checked against a live
database query made after that notification.

---

## 1. Which 33 provinces, and why this document doesn't re-litigate the split

The registration/holdout partition (66 EDH provinces, 33/33, EDH's own published code-alphabetical
order) was fixed and registered in
`docs/generated/edh_registration_slice_ingest_20260805.md` Sec.1 and hardcoded as
`EDH_REGISTRATION_SLICE_PROVINCES` / `EDH_HOLDOUT_PROVINCES` in
`apps/scriptome/services/edh_census.py`. Neither tuple was altered by this session (verified:
both are byte-identical to the versions read at the start of this session — the only additions
to the module are new, additive code, see Sec.3). This document fetches exactly the 33 provinces
EDH_HOLDOUT_PROVINCES names, no more, no fewer:

| Code | Province | Code | Province |
|---|---|---|---|
| HiC | Hispania citerior | PaI | Pannonia inferior |
| Inc | Provincia incerta | PaS | Pannonia superior |
| Iud | Iudaea | Pic | Picenum (Regio V) |
| LaC | Latium et Campania (Regio I) | Rae | Raetia |
| Lig | Liguria (Regio IX) | ReB | Regnum Bospori |
| Lug | Lugdunensis | Rom | Roma |
| Lus | Lusitania | Sam | Samnium (Regio IV) |
| LyP | Lycia et Pamphylia | Sar | Sardinia |
| MaC | Mauretania Caesariensis | Sic | Sicilia, Melita |
| MaT | Mauretania Tingitana | Syr | Syria |
| Mak | Macedonia | Thr | Thracia |
| Mes | Mesopotamia | Tra | Transpadana (Regio XI) |
| MoI | Moesia inferior | Tri | Tripolitania |
| MoS | Moesia superior | Umb | Umbria (Regio VI) |
| Nar | Narbonensis | Val | Valeria |
| Nor | Noricum | VeH | Venetia et Histria (Regio X) |
| Num | Numidia | | |

`Inc` ("Provincia incerta", an uncertain-province catch-all) and `Rom` ("Roma") are both in this
list, as declared. Neither was special-cased; both were fetched via the identical code path as
every other province.

---

## 2. Fetch route, politeness, raw-evidence convention (unchanged from the registration slice)

Identical to the first half in every respect: `GET
https://edh.ub.uni-heidelberg.de/data/api/inschrift/suche?provinz=<code>&limit=1000&offset=<n>`,
the only route capable of honoring a province-level partition; the two bulk-dump shapes
(`edh_data_text.csv`, the nine `edhEpidocDump_HD*.zip` files) were not fetched, opened, or
byte-range-peeked at any point this session. Politeness: `EDH_REQUEST_SLEEP_SECONDS = 1.8`
(`apps/scriptome/services/edh_census.py`, unmodified) before every HTTP request including
retries — this session made 62 requests (one per raw file, Sec.8), so at least 111.6s of the
13m15s wall-clock fetch window (Sec.6) was mandatory sleep, consistent with no attempt to fetch
faster than declared. Same User-Agent (`SCRIPTOME_USER_AGENT`). Raw files land in the same
directory, same naming convention, same "write-before-DB-write" ordering:
`/data/extracted_text/inferpedia-exports/scriptome/edh-drop/` inside the `inferopedia-web-1`
container, `edh_<province>_offset<NNNNNN>.json`.

---

## 3. Code changes made to enable this fetch (the holdout unlock)

`apps/scriptome/services/edh_census.py`'s `_assert_registration_slice_province()` — the guard
that raised `EdhHoldoutViolation` for any `EDH_HOLDOUT_PROVINCES` code — was **left completely
unmodified**. It remains both the historical record of what was withheld and a live mechanism:
it still refuses every holdout code for any caller going through the registration-slice path.
Fetching the holdout half required *additive* code, not editing that guard:

- `EDH_HOLDOUT_UNLOCK_COMMIT` / `EDH_HOLDOUT_UNLOCK_TIMESTAMP` — the registration commit hash and
  timestamp this whole fetch is authorized by, recorded in code, not just prose.
- `EdhRegistrationSliceViolation` + `_assert_holdout_province()` — the reverse firewall: refuses
  any registration-slice code and any unrecognized code, so the holdout-fetch path cannot
  accidentally re-touch (and silently re-timestamp or overwrite) a registration-slice province.
- `fetch_edh_province_page()` and `ingest_edh_province()` each gained an optional `validate`
  parameter, **defaulting to `_assert_registration_slice_province`** — i.e. every pre-existing
  caller's behavior is byte-for-byte unchanged; only a caller that explicitly passes
  `validate=_assert_holdout_province` (the new `ingest_edh_holdout_slice()`) can reach a holdout
  code.
- `ingest_edh_holdout_slice()` / `edh_holdout_reconciliation_report()` — new orchestration/
  reporting functions mirroring `ingest_edh_registration_slice()` / `edh_reconciliation_report()`
  exactly, writing `harvest_state` under `holdout_*`-prefixed keys so the registration slice's
  own `registration_slice_run_at` / `provinces_done` / `totals` keys are preserved, not
  overwritten (verified in Sec.7).
- `apps/scriptome/management/commands/harvest_scriptome_edh.py` gained two new `--mode` values
  (`holdout-harvest`, `holdout-report`) alongside the original `harvest`/`report`; the original
  two modes' code paths are untouched.

**Both firewalls live-tested this session, before the real fetch, zero network calls made either
time:**

```
$ python manage.py harvest_scriptome_edh --mode harvest --provinces Rom --dry-run
CommandError: Refused to fetch province 'Rom': it is in the DECLARED HOLDOUT half of EDH's
province list ... Holdout provinces are downloaded only after a later registration commit --
not for verification, not for a count.

$ python manage.py harvest_scriptome_edh --mode holdout-harvest --provinces Ach --dry-run
CommandError: Refused to fetch 'Ach' via the holdout path: it is a REGISTRATION-SLICE province
(already ingested 2026-08-05, docs/generated/edh_registration_slice_ingest_20260805.md). The
holdout-fetch path only touches EDH_HOLDOUT_PROVINCES, to keep the two evidence trails separate.
```

The first proves the original guard survived unmodified; the second proves the new reverse
guard. Standard **freshness protocol** followed: both edited files `docker cp`'d into
`inferopedia-web-1:/app/...` and SHA-256-verified identical between host and container
immediately before running anything —

| File | SHA-256 (host = container) |
|---|---|
| `apps/scriptome/services/edh_census.py` | `970c39258ba4ec1f02f6c27f6018a8d5a992e8e6c508ebed40046e64ef50d7c8` |
| `apps/scriptome/management/commands/harvest_scriptome_edh.py` | `9af4e41a335883fcaff29e6420896466861ddbc75a100bb40fea7b0108a2b5e8` |

`python manage.py check` reported 0 issues after the `docker cp`. Diff size: 208 insertions / 28
deletions across the two files (the "deletions" are re-indented signatures, not removed logic —
confirmed via `git diff --stat`; no other file in the tree was touched, cartogram files
included, confirmed via `git status`).

---

## 4. CatalogueSource and unit-modeling — reused unchanged, empirically confirmed

No new `CatalogueSource` row: `get_or_create_edh_source()` (unmodified) returns the existing
`edh` source. Every field-mapping decision from
`docs/generated/edh_registration_slice_ingest_20260805.md` Sec.3 — `target_unit=
"physical_object"`, `tradition="other"`, `medium_class="unknown"`, `repository=None` (disclosed
gap), the `not_before`/`not_after` swap correction via `resolve_edh_date_bounds()` — applies
identically because this session calls the *same* `build_edh_manuscript()` function, unmodified,
for both slices. Confirmed empirically against the 39,846 new rows (fresh DB query, not the
harvest run's own counters):

| Check | Result |
|---|---|
| Distinct `target_unit` values | `{'physical_object'}` |
| Distinct `tradition` values | `{'other'}` |
| Distinct `medium_class` values | `{'unknown'}` |
| Distinct `record_type` values | `{'manuscript'}` |
| Rows with non-null `repository` | 0 |
| Distinct `content_hash` vs. total | 39,846 / 39,846 (0 collisions) |
| Rows with `date_start > date_end` | 0 |
| Rows with a resolved date (`date_start` not null) | 26,454 (66.4%) |
| `date_precision` breakdown | range=24,456, unknown=13,392, exact=1,998 |

Same firewall reasoning applies unchanged: `physical_object`/`other` can never be swept into a
tradition-scoped manuscript census or `target_unit_allowed`-gated extant-count rebuild (see the
2026-08-05 doc Sec.3 and the `edh_census.py` module docstring for the full argument, neither
revisited here since neither changed).

---

## 5. Preserve-source-fields compliance

Unchanged from the registration slice: every EDH field in the API payload — including
`transcription`, `diplomatic_text`, `commentary` — is copied verbatim into
`ManuscriptRecord.raw`, no truncation, no stripping, no binary-field removal. The only addition
is the same `_scriptome_harvest` provenance block (source slug/provider/access_method/license
text, `province_code`, `province_name`, `goal_source`) used for the registration slice.

---

## 6. Verification: per-province reconciliation

`declared_total` is EDH's own live `total` field from the first API page fetched for that
`provinz=` query, read directly from this session's own harvest-run output (not the mid-task
message described in the methodological note above). `ingested_count` is a **fresh, independent**
DB query (`edh_holdout_reconciliation_report()`, run *after* the fetch completed, via
`--mode holdout-report`), exactly mirroring the registration slice's reconciliation methodology.

Fetch window (this session's own bracketing timestamps around the harvest command, UTC):
**start 2026-08-06T09:43:50Z, end 2026-08-06T09:57:05Z** (13m15s wall clock). The harvester's own
DB-recorded `holdout_slice_run_at` timestamp, `2026-08-06T09:57:02.155326+00:00`, falls inside
that window as expected.

| Code | Province | Declared total (API) | Ingested (DB) | Match | Pages |
|---|---|---|---|---|---|
| HiC | Hispania citerior | 4685 | 4685 | YES | 5 |
| Inc | Provincia incerta | 389 | 389 | YES | 1 |
| Iud | Iudaea | 187 | 187 | YES | 1 |
| LaC | Latium et Campania (Regio I) | 2598 | 2598 | YES | 3 |
| Lig | Liguria (Regio IX) | 145 | 145 | YES | 1 |
| Lug | Lugdunensis | 596 | 596 | YES | 1 |
| Lus | Lusitania | 1583 | 1583 | YES | 2 |
| LyP | Lycia et Pamphylia | 61 | 61 | YES | 1 |
| MaC | Mauretania Caesariensis | 1124 | 1124 | YES | 2 |
| MaT | Mauretania Tingitana | 291 | 291 | YES | 1 |
| Mak | Macedonia | 1321 | 1321 | YES | 2 |
| Mes | Mesopotamia | 12 | 12 | YES | 1 |
| MoI | Moesia inferior | 1973 | 1973 | YES | 2 |
| MoS | Moesia superior | 1476 | 1476 | YES | 2 |
| Nar | Narbonensis | 1403 | 1403 | YES | 2 |
| Nor | Noricum | 2771 | 2771 | YES | 3 |
| Num | Numidia | 2644 | 2644 | YES | 3 |
| PaI | Pannonia inferior | 3134 | 3134 | YES | 4 |
| PaS | Pannonia superior | 4259 | 4259 | YES | 5 |
| Pic | Picenum (Regio V) | 171 | 171 | YES | 1 |
| Rae | Raetia | 1012 | 1012 | YES | 2 |
| ReB | Regnum Bospori | 78 | 78 | YES | 1 |
| Rom | Roma | 4393 | 4393 | YES | 5 |
| Sam | Samnium (Regio IV) | 649 | 649 | YES | 1 |
| Sar | Sardinia | 228 | 228 | YES | 1 |
| Sic | Sicilia, Melita | 193 | 193 | YES | 1 |
| Syr | Syria | 406 | 406 | YES | 1 |
| Thr | Thracia | 396 | 396 | YES | 1 |
| Tra | Transpadana (Regio XI) | 165 | 165 | YES | 1 |
| Tri | Tripolitania | 0 | 0 | YES | 1 |
| Umb | Umbria (Regio VI) | 348 | 348 | YES | 1 |
| Val | Valeria | 0 | 0 | YES | 1 |
| VeH | Venetia et Histria (Regio X) | 1155 | 1155 | YES | 2 |
| **Total** | **33 provinces** | **39,846** | **39,846** | **YES (33/33)** | **62** |

**Failed provinces: none.** `provinces_attempted=33, provinces_succeeded=33,
provinces_failed=0`; `updated_total=0` for every province (every one of the 39,846 external_ids
was genuinely new to the `edh` source — see Sec.7 for the independent cross-check this implies).

Ranked by ingested count, the five largest holdout provinces: **Hispania citerior (HiC) 4,685**,
**Roma (Rom) 4,393**, **Pannonia superior (PaS) 4,259**, **Pannonia inferior (PaI) 3,134**,
**Noricum (Nor) 2,771**.

---

## 7. Cross-half integrity checks

All four run *after* the fetch completed, as fresh queries against the live DB:

| Check | Result |
|---|---|
| Total `source=edh` rows (both slices) | **81,855** (= 42,009 + 39,846, arithmetic confirmed) |
| Distinct `external_id` (both slices) | 81,855 (equal to total — **0 duplicates**) |
| Registration-slice 33-province reconciliation, re-run via the original unmodified `edh_reconciliation_report()` | **unchanged**: every one of the 33 rows identical to the 2026-08-05 table, `grand_total_ingested=42,009` |
| Registration-slice raw evidence files (65 files) | re-hashed from disk, **byte-identical** (SHA-256) to every hash recorded in `edh_registration_slice_ingest_20260805.md` Sec.7 |
| `edh-drop/` directory total file count | 127 = 65 (registration) + 62 (holdout); 66 distinct province codes present, matching EDH's full province vocabulary exactly, none extra |

No province's data was touched twice, no `raw._scriptome_harvest.province_code` tag was
overwritten, and the registration slice's own on-disk evidence is provably untouched by this
session — not merely "unchanged in aggregate count" but byte-identical file-by-file.

---

## 8. File hashes (possession-timestamp evidence)

62 files, `/data/extracted_text/inferpedia-exports/scriptome/edh-drop/` inside the
`inferopedia-web-1` container, **37,454,017 bytes** total. SHA-256 computed over the exact raw
HTTP response bytes persisted to disk, **independently recomputed from disk this session**
(`sha256sum` inside the container) and cross-diffed against the harvester's own self-reported
per-file hashes — **identical for all 62 files**, zero discrepancies.

**Aggregate manifest digest methodology** (same convention as the registration slice): for each
file, the line `"<province code> <offset, zero-padded to 6 digits> <sha256 hex>"`; all 62 lines
sorted lexicographically; SHA-256 of the resulting newline-joined text (the exact `sha256sum` of
that sorted-lines file) —

**`d72fb0278d8953433eac5e47dbeab8fbe1cf23aec0283b86a7d6089864889fac`**

| Province | Offset | Bytes | Items | SHA-256 |
|---|---|---|---|---|
| HiC | 0 | 762079 | 1000 | `46151342d5e76e991309bce97c7acf220c74c3ea5104d8b85152137ca77884f1` |
| HiC | 1000 | 735887 | 1000 | `5d221665d9fe4db065acb0d865e7fe3969ad0088400e5f7934f2aaaf0150af99` |
| HiC | 2000 | 908910 | 1000 | `115d402a8e58fdcaa039d70a68a350b4e5f531fe5365b9b702bea007439950cb` |
| HiC | 3000 | 729998 | 1000 | `501b704ef3dad275edb02e1ed051f4b958220649b0d2d62bccd8fc2fc868b01a` |
| HiC | 4000 | 585821 | 685 | `ae84fc670df5ece34579c6a929b8e6519b5ff717be855745a7feacf0ac4b0402` |
| Inc | 0 | 522258 | 389 | `dbb697dcb2614cad307c53ee01cff97cead6d723a51bd2f6af62df4ccee238fc` |
| Iud | 0 | 143341 | 187 | `4617f9101520cd47913f7574737132ee424f09c8697de4439ba2f55502ca4776` |
| LaC | 0 | 946037 | 1000 | `4c47dbfc8e64a39b82eee1dbecae212a3f94bd22bf01a754efa87d6530f2ee5f` |
| LaC | 1000 | 664854 | 1000 | `3ee56278fe3ac24064c5001d845c80c9b7ef96286a4b4ae75e0c90915ce4a40d` |
| LaC | 2000 | 508730 | 598 | `a72b0c014c2da3dc3a70c1abc8f47110ae1cfafc98d15e8af725ae796b435050` |
| Lig | 0 | 143048 | 145 | `9832a8784802f0d1ac0e20208212d1377b5a731903edc5c442489d0b7703c888` |
| Lug | 0 | 425868 | 596 | `04095764b40515bcaeddc0a99e4d4e8b617f445098dc61e6e9fd80009c7d96dd` |
| Lus | 0 | 641195 | 1000 | `e01a70c9d85710d64b7f4571958ea6a16e8f8387a309cba6ea76e1d8843dfcc5` |
| Lus | 1000 | 399811 | 583 | `1485cb0880340aeeb2632f0020605ea567502fdf1f6dec93f59ddcf2d70e1733` |
| LyP | 0 | 72262 | 61 | `7f27db08447775f9d2a1c687216306e425ea2abab634d63243817bcdd5a854ec` |
| MaC | 0 | 735562 | 1000 | `587937afbf1caf00a403d1dc48a7eb45f7cb9579ff397870edaa69a9a5f926e3` |
| MaC | 1000 | 83278 | 124 | `bc18b95bf49e4f4b0129da6d1a99d46f33fdcf9edb27a854b4a31c110db3019d` |
| MaT | 0 | 266689 | 291 | `eeaa759f929efeb0cfb4537e94c83c41515aae2f9c66304dd4c9f4b5af780313` |
| Mak | 0 | 1122559 | 1000 | `849683490beb88e68099dc9969fc1245903d11368ef2c4fa80f56073cfcb42af` |
| Mak | 1000 | 403409 | 321 | `ad7c6c4972086baf2f13bbf26ebd6517e099afa32ccb88640d1c0499fa70e961` |
| Mes | 0 | 10345 | 12 | `5bf48472a4dd1fa5321d8b85e929b7d44efb74046a1d8b34d33565c946f9a0b7` |
| MoI | 0 | 1239857 | 1000 | `e5a63f76d94e9c31f0cfcb5fb9469b5fd35fb18988f6e6894e504dc9a387823e` |
| MoI | 1000 | 1040436 | 973 | `a5468c27690b0a821fb4e6f394d0f831c88f6f7a605aa0861c499b322b25197c` |
| MoS | 0 | 1035595 | 1000 | `712393fbbcefb69ebb7090ee1776cf3e392c0439bf49c90d9dab26bc204d7561` |
| MoS | 1000 | 437674 | 476 | `13d861afe1ae1ef8c00857261d1907d737852eb3e4507ead511216dd3e6a0d88` |
| Nar | 0 | 603708 | 1000 | `10a8e41f54f175363d6aafbac98e20792c1272fcc07d44a21e809d3f4890b5d2` |
| Nar | 1000 | 277152 | 403 | `84c8695c1ec0d9fd0aeb452a21a9afb1d0b1a24945fe7335c50c35c903bb1a97` |
| Nor | 0 | 1331177 | 1000 | `8643a2f2ff36ff5042b8601935bf0999f237a07832a45d159a51dbdbe393ec48` |
| Nor | 1000 | 1139025 | 1000 | `12d9bc860fac4a1a04a4b02875268c022abb123d534c19d1da6047d2a4762979` |
| Nor | 2000 | 753709 | 771 | `f4f8bd677cc107cdbe47e8c6ba771f0a2c68ae738fe6cac846dddb302373203a` |
| Num | 0 | 822460 | 1000 | `e0f7a4c6cda1df0a2e5fc7b717fcbd7850ccc37549b669ffdbc3e30a74286ba5` |
| Num | 1000 | 763401 | 1000 | `9ae5ef433e0c847301d7d05c064af0118db86136510c0014f7337a8a311b8f07` |
| Num | 2000 | 433718 | 644 | `b445d382c098023dfe7210b06b98b016b5e87c51db15daa2da4507aa695346a5` |
| PaI | 0 | 1230358 | 1000 | `3e75245d4bebd9d242dfad9f175279a9d4b1a82e8331084893a11b4b93ae7eed` |
| PaI | 1000 | 1088697 | 1000 | `972c4c352948b8c89eef374bd2f4ed6564d476fa4afb174f9132f2a7a4aa1525` |
| PaI | 2000 | 907334 | 1000 | `df3d3441c92a872d67ea9d45d8521455906f0239e38c401d66d167a81ae909ba` |
| PaI | 3000 | 112929 | 134 | `8ef5b3432167e4d335ecda8974227a6e9547c14024d4d86326dcdea814e4ad07` |
| PaS | 0 | 1387653 | 1000 | `074db69ba64a5fb9d67a91345f1c59a92fb6c532a1fd20abda90535522261ded` |
| PaS | 1000 | 1276360 | 1000 | `5d23822a51f098c8e4409964ab86074c39cfa2ddaab93a85d692cbde365f6368` |
| PaS | 2000 | 1242509 | 1000 | `d21ae6764a9ab8ffcf5a2bc3f45ac4f0c9dcf1534fbaaf54dd5ba1bc69850ff4` |
| PaS | 3000 | 1030554 | 1000 | `57ab08d577522f67a8d1072b3f2bab5b4c547ef664d8039a4cb2a9b048666ea9` |
| PaS | 4000 | 271519 | 259 | `ebda90181c013abf65d9fd9fa71ec7af531380860c6ca50411043838888c744a` |
| Pic | 0 | 134284 | 171 | `f7e70e06242270d71ef189c9656e307daf0c5463737047422bcc8b32a8c282c9` |
| Rae | 0 | 1198277 | 1000 | `431e333f3bb946fea8fe951e7145a529bcab76ab9b273944b2316927045ca483` |
| Rae | 1000 | 11014 | 12 | `7547a88ee26bb9223440c4acdfb2f55a6be7eeb46eb438d2dfe3e05097180ab5` |
| ReB | 0 | 67631 | 78 | `cc32512c48529fa32c9f12b88dc23199ebb49e5481f72c4488e21783345c091f` |
| Rom | 0 | 1001868 | 1000 | `6c8455f3e7bd228e1ee003845af415ee9f4a8ef0ba912b42affded7ad27a9e8f` |
| Rom | 1000 | 739463 | 1000 | `658d890612741976d87b0357c919242dfd70b12b8d273d30684ccaf376d550a7` |
| Rom | 2000 | 769719 | 1000 | `5a6332882ca80fbd2fc212f22f4735bdf62461c6ec80780c9d8799bf38565edc` |
| Rom | 3000 | 900220 | 1000 | `0d6242d749c70c6b8d3da2587f8458ee513307b9e64fe7a748ba72ed8569508c` |
| Rom | 4000 | 358190 | 393 | `af194bf88f1b7ee8894dc465bbba9e732aad4fcc5ed7990ed594de7d63042ec4` |
| Sam | 0 | 498536 | 649 | `755b837094b5b2cd4c85a53d939c04e15f8bc6dad9bdc7e241d807e3ea2eeb4e` |
| Sar | 0 | 153157 | 228 | `0d631e452cfcd66cffde4809c454b3069f5d292f33f1da4c54d29e264de29ad0` |
| Sic | 0 | 127245 | 193 | `de277dfe5aa0ed3402c01a1b365fa660014001b3681c11d0732f7e75b013d4b3` |
| Syr | 0 | 311129 | 406 | `51aad8f983d685af30c6ee610be22f1965ac05aed9440c4298eb68d352a398e3` |
| Thr | 0 | 590073 | 396 | `d2f1a5279ae44cd42903e55e644f7e90f746a359d0655c6c5984058f169109cc` |
| Tra | 0 | 180059 | 165 | `ba4e6855b6a1a8940acc59678141597a39fdaa695d3c912539bb9be7e1873c32` |
| Tri | 0 | 51 | 0 | `c26e36ecaef01b4fe5ff7ee68f621208c167af6a0865615997ce9e7e84e1eee7` |
| Umb | 0 | 258238 | 348 | `800ddc41e8e281ff0b3c481c3cd04987b7b53234f238d22e871b7483f71acdae` |
| Val | 0 | 51 | 0 | `c26e36ecaef01b4fe5ff7ee68f621208c167af6a0865615997ce9e7e84e1eee7` |
| VeH | 0 | 765037 | 1000 | `1c880e966e1d9a6e48491df09c309a932f8ef0cb8599fab00ff6e7ae4acab094` |
| VeH | 1000 | 152009 | 155 | `fdb16e5a91f963619ebf2668500d04c5a6e6f81a1554d72c30ee2372933cd984` |

(`Tri` and `Val` both declared 0 records; the single fetched page for each is a 51-byte
`{"total":0,...,"items":[]}`-shaped response, hash `c26e36ec...` shared between them since the
empty-result payload is byte-identical for both — not a fetch failure, and not a collision
between two non-empty payloads.)

---

## 9. Files touched this session

- `apps/scriptome/services/edh_census.py` — **modified** (not new; base commit `c168c8f`, tree
  clean before this session). Additive only: holdout-unlock constants, `EdhRegistrationSliceViolation`,
  `_assert_holdout_province()`, an optional `validate` parameter on two existing functions
  (default preserves prior behavior exactly), `ingest_edh_holdout_slice()`,
  `edh_holdout_reconciliation_report()`. 182 insertions / 28 deletions (deletions are
  re-indented signatures, confirmed via `git diff`, no logic removed).
- `apps/scriptome/management/commands/harvest_scriptome_edh.py` — **modified**. Two new `--mode`
  values added; original `harvest`/`report` modes' code paths unchanged. 54 insertions / (diff
  stat combined with the file above).
- No migration needed (no new model, no new choices-list value).
- `docker cp` + SHA-256 freshness check for both files: Sec.3.
- No git commit taken, per instruction (orchestrator commits). `git status` at end of session
  shows exactly these two files modified and nothing else — cartogram files untouched, no other
  file in the tree affected.

---

## 10. Anomalies and disclosed observations

**Reconciliation: zero anomalies.** All 33 holdout provinces matched EDH's declared total
exactly on the first pass; no corrective re-fetch was needed or attempted.

**The implied-total arithmetic.** The registration-slice doc (Sec.2) recorded EDH's bulk-dump
filenames as chunked up to `edhEpidocDump_HD080001-HD082828.zip`, i.e. an HD-number ceiling of
82,828. Actual combined total this session confirms: 42,009 + 39,846 = **81,855**. Gap: **973**
(82,828 − 81,855, ≈1.17% of the ceiling). This is consistent with ordinary non-contiguous
HD-number assignment in a catalogue frozen since February 2021 (reserved, merged, or withdrawn
entry numbers are common in long-running epigraphic catalogues) — but that specific mechanism
was **not independently verified** by this session (doing so would mean querying individual
HD numbers in the gap, which is both out of this lane's scope and unnecessary for the
fetch/ingest/reconcile/evidence job). Stated here as the plain arithmetic and a plausible,
unverified reading, not a confirmed finding.

**`Tri` and `Val`: 0 records.** Both provinces declared and ingested 0 — a legitimate empty
result (EDH's controlled vocabulary includes provinces with no currently-catalogued
inscriptions), not a fetch error; see the note under Sec.8.

---

## 11. What was NOT done (scope discipline, not oversight)

- No conjecture or prediction was scored. `docs/generated/popper_r2_edh_pinned_blockA.json` and
  `docs/generated/popper_r2_edh_blockB_predictions.json` were not opened by this session.
- No bulk CSV/XML dump was fetched (Sec.2).
- No git commit was taken.
- Cartogram files were not touched (confirmed, Sec.9).
- No DB write occurred outside the harvester's own `ingest_edh_province` upsert path (the two
  `--dry-run` negative-firewall tests in Sec.3 made zero network calls and zero DB writes — both
  raised before reaching the fetch loop).
- `Repository` rows were not created (the same disclosed gap as the registration slice, applied
  consistently: 0 non-null `repository` values across all 39,846 new rows, Sec.4).
