@empressaio/setback-corpus
v1.4.0
Published
Per-jurisdiction setback tables shared by hauska-engine and legacy-design-tools -- single published source of truth, replacing two independently-maintained local copies.
Maintainers
Readme
@empressaio/setback-corpus
Per-jurisdiction setback tables shared by hauska-engine and
legacy-design-tools — a single published source of truth, replacing two
independently-maintained local copies.
Why this exists
Before 2026-09-07, hauska-engine (packages/adapters/src/local/setbacks/)
and legacy-design-tools (lib/adapters/src/local/setbacks/) each carried
their own hand-curated setback tables. The two corpora were never synced:
some cities (Kyle, Round Rock) had independently authored, genuinely
different tables in each repo; most other overlaps were byte-identical or
near-identical, just copy-pasted at some point in the past without a shared
source. Nine cities existed in only one repo or the other. This is exactly
the two/three-readers drift problem the atom-first architecture exists to
prevent, just at the data layer instead of the code layer.
This package is the fix: one corpus, one publish pipeline, both repos (and eventually Property Explorer's own small local copy) import from here and retire their local JSON files.
What's in scope, what isn't
This package is pure data: SetbackTable/SetbackDistrict types, the
JSON tables themselves, and a plain keyed lookup (getSetbackTable). It
deliberately does not include:
- City-specific serving/routing policy — which GIS zoning code maps to which table, which districts are repealed, which need a live per-parcel record instead of the ordinance chart (e.g. Bastrop's BDC districts). That's real, consumer-specific logic and stays in each consumer's own wrapper around this package.
- The citation-verification gate (
gate.ts,runSetbackGate) — currently lives inlegacy-design-tools(added in that repo's PR #628). Porting it onto this merged corpus is a separate, tracked follow-up, not part of this package yet.
The merge, 2026-09-07
29 jurisdictions total (union of both corpora). Of 13 real overlaps by
jurisdictionKey:
- 9 were byte-identical (austin-tx, bastrop-city-tx, bastrop-tx, grand-county-ut, idaho-unincorporated, lemhi-county-id, pflugerville-tx, san-antonio-tx, utah-unincorporated) — no reconciliation needed.
- 1 cosmetic-only (bastrop-development-code — LDT's copy carried one
extra
effectiveDatefield, no value conflict). - 1 label-only (elgin-development-code — engine used a non-canonical
"transcribed"verification_state; LDT's port already relabeled it"asserted"with identical numeric values; standardized on LDT's label). - 1 clean superset (kyle-tx — LDT has 9 districts vs. engine's 5, but all 5 shared districts agree value-for-value; LDT's version used as-is).
- 1 genuine, unresolved reconciliation (round-rock-tx — see below).
Round Rock is the one open item. The two source tables disagree for
real on side_corner_ft (two different, defensible readings of the
front-lot-line rule), the max_height_ft non-binding sentinel (999 vs 100
— both correctly flag not_specified: true), and SF-3's side_ft/rear_ft
(zero-lot-line vs. Standard-Lot sub-column of the same ordinance cell).
hauska-engine's version is used here as the interim default (it's what's
live in production today); the file's own note field carries the full
disagreement record. Do not treat this as settled — it needs a real
reconciliation pass against the live ordinance table, not a preference call.
P-258 lane-c additions, 2026-09-16 (the merge-time counts above are historical)
61 rows were added to eight existing tables by the P-258 setback acquisition
campaign's lane-c (a ninth, bastrop-tx, received a note-only change),
district-miss rows only, largest-parcel-count first, with per-value provenance
on every added field and one commit per city:
dripping-springs-tx +9 (15), round-rock-tx +14 (24), san-marcos-tx +19 (27),
kyle-tx +7 (16), georgetown-tx +4 (17), buda-tx +4 (12), cedar-park-tx +1
(17), pflugerville-tx +3 (13), bastrop-tx +0 (note only, 10 rows unchanged).
bastrop-tx gained no rows on purpose: its lane-c codes (GC/MU/PI/IND/P-OS)
are routed by the consumer's wrapper to bastrop-development-code, whose
rows are hash-locked against a mirror lock in hauska-engine, so this lane
recorded the ordinance values it read as evidence in that file's note
instead of adding unreachable rows. The counts quoted in the merge record
above describe the merge, not the current tables.
One declared non-additive change from that lane: four PRE-EXISTING
san-marcos-tx rows (CC/GC/NC/OP legacy) had max_height_ft moved 100 -> 999,
because 100 can be misread as a real height limit and 999 cannot; the move is
declared in that file's note in a marked P-258 LANE-C CORRECTION block, and a
district-by-district parse of both revisions of every table in both repos found
no other pre-existing row altered. The same lane also corrected a false claim
(it, and round-rock-tx's RULING 4, had said the 999 invariant is enforced in
code by "rule G7"): rule G7 does exist -- in this package's src/setbacks/gate.ts
-- but not in legacy-design-tools' own gate, and it is not applied to the
shipped tables, so 76 pre-existing rows still carrying a not_specified 100
elsewhere in the corpus would block under it if it were.
P-299 (2026-09-17) closed that measurement and the hole behind it, in
1.4.0. All 155 flagged height rows were reconciled, with this repo's own
not_specified-meaning instrument (corpus_not_specified_audit.py, OPS-21-R1 /
P-146) as the classifier: 40 rows had the FLAG removed and kept their value —
the flag was asserting an absence the instrument states — and the rest now
carry the canonical 999. Of those 40, 6 rows' own quotes state the feet figure
(Pflugerville's six); the other 34 rows' quotes name the tabulated height column
without restating the number, so their basis is the instrument's
REAL_VALUE_FLAGGED classification, and each file's note says which rows rest
on which basis, because it is not the same for all of them. Austin's SF-1 keeps
its 35 ft. Rule G8
blocks the direction that actually leaked (a 999 with no flag, which a
number-trusting consumer reads as a 999-ft limit), and the gate now runs over
every gated table in CI. The same release adds the fourth verification state,
transcription-read, for the 1 190 value slots (170 district rows across 12
tables) whose own note records that the value was read through a COPY of the
instrument -- a third-party mirror, a browser render, or a transcription --
with a 0.75 confidence ceiling enforced by rule G9. See CHANGELOG 1.4.0 for
the per-table list and each file's P-299 OT-1 / P-299 OT-2 note block.
Sourced from hauska-engine (2 files, not in LDT's corpus):
waco-tx, round-rock-tx (interim default, see above).
Sourced from legacy-design-tools (27 files, all overlaps + LDT-only):
everything else, including killeen-tx, belton-tx, seguin-tx,
cibolo-tx (researched 2026-09-06/07, primary-source-verified — no
code-section atom corpus existed for these at research time) and
georgetown-tx/san-marcos-tx (real field-level merges of prior and new
research within LDT's own corpus, preserved as-is here).
Usage
import { getSetbackTable, SETBACK_JURISDICTION_KEYS } from "@empressaio/setback-corpus";
const table = getSetbackTable("round-rock-tx");
// table.districts -> SetbackDistrict[]Adding a jurisdiction
- Drop a
<jurisdiction-key>.jsoninsrc/setbacks/(seeschema.jsonfor the shape). - Append the import + registry entry in
src/setbacks/index.ts. - Bump the package version (additive minor) and add a
CHANGELOG.mdentry.
