What
perimeter.acquire is consumed by wildfire-service-territory-overlap, which pins a commit and on 2026-09-05 audited the seam, recording four gaps in its docs/UPSTREAM.md: the walk sends only perimeter's User-Agent, so CAL FIRE's logs cannot see who is calling; the paged walk cannot return geometry, so the consumer copied the offset loop; completeness is one record count read once before the walk, so the consumer added a post-walk recount and identifier ordering and uniqueness checks; and the package ships no py.typed, so a strict consumer carries two mypy overrides. This closes all four here so the consumer's compensations can be deleted: user_agent on layer_record_count and fetch_layer; geometry, format and spatial-reference arguments (or an exposed page iterator); a second count after the walk with strictly increasing unique identifiers as an acquisition guard; and src/perimeter/py.typed.
Why it matters
The relationship is "consume, do not re-implement", and every gap left open is a copied loop that will drift. Each change strengthens perimeter's own guarantee that a short download is not an acquisition (the post-walk count catches a truncated walk the pre-walk count cannot), and none touches the measurement or the pages. The network path stays hand-run and out of CI; tests substitute the socket as they do today.
Scope
acquire.py: user_agent: str = USER_AGENT threaded through _get; fetch_layer(..., return_geometry=False, out_fields=..., out_sr=...) or iter_pages(...); post-walk recount and identifier checks that refuse to write on failure, recorded in acquisition.json.
py.typed; pyproject.toml package data.
- A CHANGELOG entry naming the consumer's audit, so its
docs/UPSTREAM.md rows become closable at the next pin.
Out of scope
- Changing what perimeter itself acquires by default.
- Publishing to PyPI under a new name (a separate decision;
perimeter is taken).
Done when
- A test asserts the User-Agent a caller supplies is what
_get sends, and the default still names this project.
- A simulated walk returning fewer records than the post-walk count refuses to write and names both counts; a walk with a repeated identifier refuses.
mypy --strict over a minimal consumer importing perimeter.acquire passes with no overrides.
- Geometry requested through the library round-trips a fixture feature's coordinates unchanged.
Pointers
Proposed with AI assistance.
What
perimeter.acquireis consumed bywildfire-service-territory-overlap, which pins a commit and on 2026-09-05 audited the seam, recording four gaps in itsdocs/UPSTREAM.md: the walk sends only perimeter's User-Agent, so CAL FIRE's logs cannot see who is calling; the paged walk cannot return geometry, so the consumer copied the offset loop; completeness is one record count read once before the walk, so the consumer added a post-walk recount and identifier ordering and uniqueness checks; and the package ships nopy.typed, so a strict consumer carries two mypy overrides. This closes all four here so the consumer's compensations can be deleted:user_agentonlayer_record_countandfetch_layer; geometry, format and spatial-reference arguments (or an exposed page iterator); a second count after the walk with strictly increasing unique identifiers as an acquisition guard; andsrc/perimeter/py.typed.Why it matters
The relationship is "consume, do not re-implement", and every gap left open is a copied loop that will drift. Each change strengthens perimeter's own guarantee that a short download is not an acquisition (the post-walk count catches a truncated walk the pre-walk count cannot), and none touches the measurement or the pages. The network path stays hand-run and out of CI; tests substitute the socket as they do today.
Scope
acquire.py:user_agent: str = USER_AGENTthreaded through_get;fetch_layer(..., return_geometry=False, out_fields=..., out_sr=...)oriter_pages(...); post-walk recount and identifier checks that refuse to write on failure, recorded inacquisition.json.py.typed;pyproject.tomlpackage data.docs/UPSTREAM.mdrows become closable at the next pin.Out of scope
perimeteris taken).Done when
_getsends, and the default still names this project.mypy --strictover a minimal consumer importingperimeter.acquirepasses with no overrides.Pointers
src/perimeter/acquire.py,tests/test_acquire.py,pyproject.tomlwildfire-service-territory-overlap/docs/UPSTREAM.md(audit of 2026-09-05 atdac60195), itsacquire.py, and its issue Add an artifact diff command so a deliberate refresh is compared value by value #58Proposed with AI assistance.