Graticula · Phase 1 — Implementation

Control Room

Everything below is read out of the repository at build time — the ADR headers, open-questions.md, architecture-debt.md, architecture-assumptions.md and git. Nothing on this page is typed by hand, because the two versions that were went stale within a day.

Generated from commit 90f51d7 · 2026-09-12 16:05

Where it stands

64
ADRs
12 accepted outright
195/238
ADR conditions discharged
41 live · 2 deferred with their decision
0
open questions
157 answered
0
open debts
248 repaid · 9 waiting on a trigger
7/9
review gates run
1 need an outside reviewer
16/87
assumptions validated
the rest are unproven
2658
tests
285 source files, 119,638 lines

What is left

Three buckets, sorted by who can move them. The sort is derived: an item is the owner's when the question register names them, and an outside reviewer's when the gate says in its own words that it needs somebody who did not write this. Everything else is ours, and it is the only bucket that shrinks by working.

Owner0 open

Nothing is waiting on the owner.

An outside reviewer1 gates

Operations — Needs a deployment somebody operates.

§67 also still owes a review round by somebody who did not participate. Rounds 1 and 2 were self-review; round 3 was commissioned and briefed by the author.

Ours45 items

41 ADR conditions outstanding across 31 decisions — the largest commitment on this page.

0 debts with their repayment triggers already written down.

3 capability areas not started: Publishing, Observability, Resource governance.

1 review gates nobody else is blocking: Extensibility.

Open conditions, by decision

A condition is discharged when its text is struck through in the ADR itself. Nothing here is marked done by this page — it is counted from the decision that made the promise.

ADR-013 · Feature Service Data Model: Identi
3
ADR-028 · Style documents: the one thing abo
3
ADR-007 · Service Runtime
2
ADR-010 · Caching
2
ADR-017 · Admin API Shape
2
ADR-029 · Affinity routing is not the defaul
2
ADR-052 · The canonical symbology document i
2
ADR-055 · A connection is fields, and this s
2
ADR-002 · Primary Data Architecture
1
ADR-005 · API Architecture
1
ADR-006 · Plugin Model
1
ADR-009 · Raster Engine
1
ADR-011 · Job System
1
ADR-015 · Authentication
1
ADR-021 · Tiles are encoded by PostGIS, and
1
ADR-023 · The REST Services Directory: an HT
1
ADR-024 · Shapefile import, and the exceptio
1
ADR-025 · Governance: what a gift owes, and
1
ADR-026 · Serving through a platform-store o
1
ADR-027 · Glyphs and sprites: the tile servi
1
ADR-032 · The product is named Graticula
1
ADR-038 · How a geodatabase becomes one serv
1
ADR-039 · WFS 2.0, read-only, is the first s
1
ADR-046 · Admission control bounds the queue
1
ADR-047 · The outbound licence is Elastic Li
1
ADR-048 · CI does not run the real-data suit
1
ADR-054 · The symbology document is not boun
1
ADR-056 · An item is its own thing, and a se
1
ADR-057 · Composing and publishing a service
1
ADR-059 · Quiescing a data source
1
ADR-060 · The axis order comes from the auth
1

Review gates (§66)

Each gate runs against the whole architecture, not per ADR, and a failure reopens decisions rather than being noted and passed over. A gate ticked without evidence is worse than one left open: it turns an unknown into a false assurance and nothing afterwards re-examines it.

GateRunResult, or what it is waiting for
CorrectnessFAILFAIL, and all five findings are repaired.
SimplicityFAILFAIL, narrowly, and not for the reason this row expected.
PerformanceFAILFAIL, on a finding about behaviour rather than speed.
FailureFAILFAIL, and all ten findings are now repaired — six on the day, the last four on 2026-08-23, each measured before and after.
Operationsnot runNeeds a deployment somebody operates.
SecurityFAILFAIL on one finding, repaired the same day.
Extensibilitynot runNo plugin code exists, so the gate has nothing to examine.
LicensingPASSPASS.
ConsistencyFAILFAIL, and all four findings are now answered — three on the day, the fourth on 2026-08-23.

Decisions

Every architectural decision is an ADR. Status and confidence are read from each file's own header, so a decision cannot quietly change grade here without changing there.

ADRDecisionStatusConfidenceConditions
001Core LanguageACCEPTED
2026-08-25, by owner decision: "ben Q-01'i kabul ettim .NET olarak".
MEDIUM — 2026-08-25.
002Primary Data ArchitectureREOPENED → re-decided, ACCEPTED WITH…
REOPENED → re-decided, ACCEPTED WITH CONDITIONS
MEDIUM2/3
003Geometry EngineACCEPTED WITH CONDITIONS
single-provider scope.
HIGH for the split, which is measured.
004Rendering EngineACCEPTED
un-deferred 2026-08-20 by ADR-041, which is where the decision now lives.
005API ArchitectureACCEPTED WITH CONDITIONS
re-closed 2026-08-20.
MEDIUM-HIGH3/4
006Plugin ModelACCEPTED WITH CONDITIONS
re-closed 2026-08-15, see §0.
MEDIUM-HIGH1/2
007Service RuntimeACCEPTED WITH CONDITIONSMEDIUM — structure well supported, routing unproven2/4
008Query EngineACCEPTED WITH CONDITIONSMEDIUM3/3
009Raster EngineACCEPTED WITH CONDITIONS
re-closed 2026-08-15, see §0.
MEDIUM1/3
010CachingACCEPTED WITH CONDITIONSMEDIUM2/4
011Job SystemACCEPTED WITH CONDITIONSMEDIUM-HIGH3/4
012ClusteringDEFERRED
013Feature Service Data Model: Identity, Relationships and AttachmentsACCEPTED WITH CONDITIONSMEDIUM4/7
014TLS and CertificatesACCEPTED WITH CONDITIONSMEDIUM-HIGH4/4
015AuthenticationACCEPTED WITH CONDITIONSMEDIUM4/5
016Packaging, Deployment and UpgradeACCEPTED WITH CONDITIONSMEDIUM4/4
017Admin API ShapeACCEPTED WITH CONDITIONSMEDIUM — the shape is decided, the surface is not1/3
018Authorization: user types, roles and privilegesREOPENED 2026-08-14 by owner direction →…
REOPENED 2026-08-14 by owner direction → re-decided, ACCEPTED WITH CONDITIONS
MEDIUM5/5
019One deployable, three fused tiersACCEPTED WITH CONDITIONSMEDIUM-HIGH for the decision · LOW for the trigger conditions in §53/3
020An admin console, and what "stop a service" meansACCEPTED WITH CONDITIONSMEDIUM-HIGH for the console · MEDIUM for the status model3/3
021Tiles are encoded by PostGIS, and we do not write an encoderACCEPTED WITH CONDITIONSHIGH for the decision · MEDIUM for the condition in §72/3
022GeometryServer ships in halves, and the projection engine is the datastore'sACCEPTED WITH CONDITIONS · §2 SUPERSEDED by…
ACCEPTED WITH CONDITIONS · §2 SUPERSEDED by §2b, 2026-08-15
HIGH for the split · HIGH for the engine · MEDIUM for the vertex cap4/4
023The REST Services Directory: an HTML face for the APIACCEPTED WITH CONDITIONSHIGH6/7
024Shapefile import, and the exception it costsACCEPTED WITH CONDITIONSHIGH4/5
025Governance: what a gift owes, and what it does notACCEPTED WITH CONDITIONSHIGH3/4
026Serving through a platform-store outage: public-only, while blindACCEPTED WITH CONDITIONSHIGH for the policy · MEDIUM for the window4/5
027Glyphs and sprites: the tile service could not draw a labelACCEPTED WITH CONDITIONSHIGH for the decision · MEDIUM for the coverage4/5
028Style documents: the one thing about a map a server cannot guessACCEPTED WITH CONDITIONSHIGH2/5
029Affinity routing is not the default, and A-003 stops being load-bearingACCEPTED WITH CONDITIONSHIGH for the reversal · MEDIUM for what replaces it at scale2/4
030Reading the reference implementation, not only its documentation~~ACCEPTED WITH CONDITIONS~~ WITHDRAWN
the permission this ADR granted ended 2026-09-09 by owner decision.
MEDIUM2/3
031A service's capabilities are configured, and configuration only ever restrictsACCEPTED WITH CONDITIONSMEDIUM6/6
032The product is named GraticulaACCEPTEDHIGH3/4
033Symbology: one canonical document per layer, and both protocol faces derive from itACCEPTED WITH CONDITIONSMEDIUM-HIGH for the canonical model · MEDIUM for the derivation's fidelity5/5
034Two surfaces over one API: Server for the operator, Studio for the publisherACCEPTED WITH CONDITIONSHIGH for the split · MEDIUM for where each screen lands5/5
035Role privileges are editable, except the administrator'sUNKNOWN7/7
036Groups, and the two axes an authorization answer now hasUNKNOWN5/5
037Job workers come in two kindsACCEPTED WITH CONDITIONSMEDIUM6/6
038How a geodatabase becomes one service with many layersUNKNOWN5/6
039WFS 2.0, read-only, is the first surface after v1UNKNOWN5/6
040The portal surface is how ArcGIS Pro connectsUNKNOWN4/4
041The map renderer, and the two faces that hang off itACCEPTED WITH CONDITIONSMEDIUM-HIGH for the placement and the library · MEDIUM for labels · HIGH for throughput,…6/6
042OGC API Features, and the inversion it unwindsACCEPTED WITH CONDITIONSHIGH for the decision, which was already taken · MEDIUM for the conformance claim until…5/5
043ImageServer, and where the raster Tier 1 line fallsACCEPTED WITH CONDITIONSMEDIUM for the decomposition and the Tier 1 line · LOW for the first cut's completeness,…6/6
044Tiles are served, because the claim had to be trueACCEPTED WITH CONDITIONSHIGH6/6
045The server keeps a log you can ask questions ofACCEPTED WITH CONDITIONSMEDIUM6/6
046Admission control bounds the queue, not the waitACCEPTED WITH CONDITIONSMEDIUM4/5
047The outbound licence is Elastic License 2.0ACCEPTEDHIGH for the decision · MEDIUM for what it will cost in adoption2/3
048CI does not run the real-data suites, and says so on every runACCEPTEDHIGH for the exclusion · MEDIUM for whether announcing it is enough1/2
049A face refuses in its own vocabulary, even when that vocabulary has no word for itACCEPTEDHIGH for refusing at all · MEDIUM for which exception code
050A release is a tag, and what it publishes is two imagesACCEPTEDHIGH for the registry and the trigger · MEDIUM for the tag scheme
051An appearance is chosen by looking at itACCEPTEDHIGH
052The canonical symbology document is CIMACCEPTED WITH CONDITIONSMEDIUM3/5
053The symbology editor is three columns, and the item page has a headACCEPTEDHIGH
054The symbology document is not boundedACCEPTEDHIGH — MEDIUM on what replaces the bound until 2026-09-05, when condition 1 was measured1/2
055A connection is fields, and this server assembles itACCEPTEDHIGH0/2
056An item is its own thing, and a service is one kind of itACCEPTEDHIGH — MEDIUM on the field layout until 2026-09-05, when condition 1 was answered1/2
057Composing and publishing a serviceACCEPTEDHIGH for the composition and the publish rules · MEDIUM for the faces6/7
058The datastore's schema is edited from the screenACCEPTED WITH CONDITIONSHIGH for the rule and the refusals · HIGH for the dependency list — MEDIUM until…4/4
059Quiescing a data sourceACCEPTED WITH CONDITIONSHIGH for the refusal and the deadline · MEDIUM for the window's length · LOW for the…4/5
060The axis order comes from the authority's register, carried as dataACCEPTED WITH CONDITIONSHIGH for the answer, which is the authority's own · HIGH for the defect, measured on a…3/4
061Metrics are aggregate; per-entity numbers live in the admin APIACCEPTED WITH CONDITIONSMEDIUM2/2
062The query face streams, like every other faceACCEPTEDHIGH — the choice is between two behaviours that were both measured on the same route…
063A field list may differ from the table, in two ways and no moreACCEPTED WITH CONDITIONS~~MEDIUM — the decision is the owner's and the shape is forced by what already exists,…3/3
064Editor tracking, and what `features:edit` meansACCEPTED WITH CONDITIONSMEDIUM3/3

Open questions

Uncertainty is recorded rather than hidden. An empty list here would mean the recording stopped, not that the uncertainty did.

Architecture debt

Temporary compromises, each with the trigger that repays it. A compromise with no trigger is a permanent decision wearing a temporary label.

IDDebtRepay whenState
D-04Multi-tenant resource isolation is not designed.Before multi-tenant deployments are supportedPARKED
D-10ADR-015 is implemented for local passwords only.OIDC before the first deployment with an IdP; /generateToken before any unmodified ArcGIS client is pointed at us The…PARKED
D-107Elevation in the source is dropped and only counted.Whenever 3D storage is decided, or the first user who notices their contours flattenedPARKED
D-108The pipe streams and the importer does not, so the million-feature ceiling has not moved — it has changed owner.The first archive with a layer past a million features, which is an ordinary size for a national road networkPARKED
D-115The Server/Studio surface split is the single cause of four recorded console defects.The next defect whose cause is the splitPARKED
D-144~~Nothing bounds how long one request may hold a database permit.~~ The headline is struck 2026-09-09 rather than reworded, because it was false and the correction had already been written into this row's status cell without reaching its first sentence — half…A way to refuse before the first byte, or a way to end a response that says why.PARKED
D-162A provider that is not PostGIS would be handed PostGIS SQL.When DuckDB is designed as the file-format query engine (Q-81), and not after.PARKED
D-261Every request under /rest/services makes six to eleven short database round trips, and that — not the query, not the response size, not admission control — is what holds the path at about 1,300 requests a second.A deployment whose request rate approaches it, or the day the catalogue and identity reads are held the way ADR-015 §3a…PARKED
D-262The product's own containers are amd64 only, so docker compose up — the first command README.md offers — fails on every arm64 machine.The release workflow publishing both architectures, which GitHub's arm64 runners make free for a public repository; and…PARKED

248 repaid — every one of them kept in the register with what it cost, because a repaid debt is evidence and this page is only the work

Assumptions

Invalidating one triggers a review of every ADR that rests on it. The unvalidated majority is the honest state of the project, not an oversight.

IDAssumptionState
A-001The tile path is CPU-bound enough that language performance materially affects capacityVALIDATED
A-002Single-binary distribution is genuinely valuable for air-gapped installs, not just aesthetically pleasingUNVALIDATED
A-003Most published services are idle most of the time, making shared workers viableDOWNGRADED
A-004Hot-path geometry overhead is material enough to justify our own primitivesVALIDATED
A-005Geometry running in the same runtime meaningfully reduces defect resolution time versus FFIUNVALIDATED
A-006One internal geometry representation can serve both the feature path and the tile path without a second conversionVALIDATED
A-007Crash containment is required in practice, not merely in principle — workers really do die (GDAL on malformed input, plugins, OOM)CONTESTED,
A-008Administrators will not correctly hand-tune per-service worker settings, so defaults must be good and adaptiveVALIDATING
A-011A distinguished central manager process is a robustness and recovery liability, so placement and routing state must be recoverable without oneVALIDATING
A-012The sharing question is really about per-service state size, binding cost and neighbour tolerance — not about "shared versus dedicated"VALIDATING
A-013Our Tier 2 dependencies (GDAL, GEOS, PROJ) are thread safe enough to permit a threaded worker modelVALIDATED
A-014Routing requests to workers already warm for a service materially improves L1 hit rate without wrecking load balanceDOWNGRADED
A-015Per-service warm state is small — connections, schema, symbology, fonts, CRS — making bind/unbind cheapVALIDATED
A-018~~A deliberately boring platform schema can be supported across SQLite and PostgreSQL at acceptable cost~~SUPERSEDED
A-019In-process MVT encoding can meet our latency targetsVALIDATED
A-020Cache seeding absorbs the provider performance gap, so an expensive Oracle tile path is acceptable when paid once rather than per requestUNVALIDATED
A-036RLS delegation and large streaming reads do not collide often — the layers wanting row-level security are not usually the layers wanting bulk exportUNVALIDATED
A-035Data source count stays low relative to service count — many services share one registered databaseUNVALIDATED
A-032COG range-request traffic is view-proportional, so proxying it costs about what serving tiles costsUNVALIDATED
A-033Target clients can render COG directly, so serving one format sufficesUNVALIDATED
A-034Internal ports designed for our own use will be good enough to become a plugin contract later, without a redesignUNVALIDATED
A-030Lease expiry plus checkpoint verification prevents one job running twice across nodesUNVALIDATED
A-031Job rate stays low enough for a database-backed queue — jobs per minute, not messages per secondUNVALIDATED
A-028Administrators can and will declare layer volatility usefully, and keep it roughly accurateUNVALIDATED
A-029Tiles, not feature query responses, are where cache reuse actually isUNVALIDATED
A-026OGC API Features Parts 1+2+3 plus additive extensions can express what §28 requires, without bending the standard's meaningUNVALIDATED
A-027Optimistic concurrency can be made correct against writes we never see, by relying on database-maintained versioning rather than our own event recordUNVALIDATED
A-025A small number of pinned service contexts suffices for real deployments, and administrators will not pin everythingUNVALIDATED
A-024A small residual executor plus explicit refusal is acceptable to real users, given a capability report published in advanceUNVALIDATED
A-022~~The datastore schema can be created on PostGIS, SQL Server Spatial and Oracle Spatial at acceptable cost~~SUPERSEDED
A-023A cheap schema fingerprint can be polled often enough to detect drift on registered sources without loading the source databaseUNVALIDATED
A-021Filter and predicates can be pushed down usefully on all three spatial dialects — narrowed by Q-67: clip and simplify no longer need to be, since only PostGIS serves tiles.VALIDATED
A-017Data sources will frequently be foreign and possibly read-only, so the platform cannot rely on DDL rights in themVALIDATING
A-016GDAL-backed providers can be made optional, so a PostGIS-only deployment ships as one artefactVALIDATED
A-037Allocation rate, not CPU, sets the tile-serving ceiling per workerVALIDATED
A-038GDAL is needed for the import formats we care about at allINVALIDATED
A-039~~Every provider can clip server-side cheaply enough to avoid shipping whole geometries~~SUPERSEDED
A-040All three engines can stream BLOBs out of a query without materialising them in our processUNVALIDATED
A-041A bounded separate connection pool is sufficient protection against slow-client exhaustion during attachment streamingUNVALIDATED
A-042Caps on vertex count, batch size and wall clock are sufficient to make a public general-overlay endpoint safeINVALIDATED
A-043Six geometry engines can be made to agree closely enough that the same query returns the same answer on any providerVALIDATED
A-044Certificate rotation can take effect on the next handshake without recycling the listener or the workerVALIDATED
A-045Soft-fail revocation checking is an acceptable posture for the air-gapped profileUNVALIDATED
A-046An opaque-token session lookup per request is affordable at the concurrency ADR-007 targetsVALIDATED
A-047Every provider supporting RLS delegation can accept a principal name we generate, through administrator-controlled mappingUNVALIDATED
A-048Expand-and-contract is achievable for every migration we will need, including index and constraint changes on large hosted tablesUNVALIDATED
A-049A curated Python wheel set can cover realistic geoprocessing without pip at runtimeUNVALIDATED
A-050Per-worker allocation rate and GC pause can be sampled continuously, cheaply enough to be always-on metricsUNVALIDATED
A-051A supervisor-served admin surface can authenticate without the platform store without becoming a bypassUNVALIDATED
A-010The 100–1,000 service target will not shift upward by an order of magnitude after launchUNVALIDATED
A-057STAsMVT output is acceptable to the ArcGIS and MapLibre clients we care aboutPARTLY
A-058The datastore has spare capacity for tile encoding in a 100–1,000 service deploymentUNVALIDATED
A-059A vertex cap bounds the work for single-pass geometry operationsUNVALIDATED
A-060Using the datastore's PROJ rather than an in-process library costs nothing that mattersPARTLY
A-061An administrator's first act against an unfamiliar server is to open its URL in a browserUNVALIDATED
A-064A security report will arrive at some point after publication, and the cost of having nowhere to send it exceeds the cost of the policyUNVALIDATED
A-065Requiring a DCO rather than a CLA does not deter contribution, and requiring a CLA wouldUNVALIDATED
A-066A stale Public grant discloses nothing that was not already disclosedVALIDATED
A-067Platform-store failures that leave the data sources up are common enough to be worth serving throughUNVALIDATED
A-068Fifteen minutes is longer than a transient outage and shorter than one nobody is handlingUNVALIDATED
A-069One Latin/Greek/Cyrillic typeface serves the deployments this product is forUNVALIDATED,
A-070Substituting an unavailable font beats refusing the requestUNVALIDATED
A-071One style per service is enough to be useful, even though it is not enough to be finishedUNVALIDATED
A-072Refusing a style at write time costs an author less than a blank map costs them at read timeUNVALIDATED
A-073Warm-state fragmentation across workers costs less than the affinity control system would cost in instabilityUNVALIDATED
A-074A QGIS or GDAL client asking a WFS feature type for its default output format gets GML 3.2 — so a WFS offering only GeoJSON would not have connected, and the GML writer is…VALIDATED
A-075ArcGIS Pro's portal connection works against a portal API this project implements, rather than requiring something only Esri's own portal returnsVALIDATED
A-076A CPU rasteriser meets the latency a WMS client expects at typical image sizes on this runtime, without the allocation profile ADR-007 §4.14's worker sizing has no term forVALIDATED
A-077The canonical symbology document carries enough to draw a map a cartographer would accept, rather than enough to describe one.PARTLY
A-052Enforcing the user-type ceiling costs nothing in deployments that never use itUNVALIDATED
A-053A fused deployable remains operable at the top of the stated scale target — 1,000 servicesUNVALIDATED.
A-054Catalogue load and serving load stay similar enough that separate scaling is never forcedUNVALIDATED,
A-055A single-page console with no build step stays maintainableUNVALIDATED.
A-056Operators want start/stop rather than only sharingUNVALIDATED
A-062256 MB uncompressed is above any shapefile a person uploads through a browser and below what troubles the machineUNVALIDATED
A-063Refusing an undeclared encoding costs less than silently corrupting textUNVALIDATED
A-078An operator would rather write MapLibre than a format we invented, because tools exist for the firstUNVALIDATED,
A-079The publisher and the operator are different people often enough for two surfaces to be worth their costUNVALIDATED,
A-080admin:manageServer is the right single gate for Server, so no reader is left without a tool they needUNVALIDATED
A-081The operator console is read on a screen at least 1,180 CSS pixels wideSUPERSEDED
A-082The EPSG register's axis definitions change rarely enough that regenerating from whatever proj.db the PostGIS image ships is soon enoughUNVALIDATED
A-083No deployment needs an axis answer for a CRS code PROJ's own register does not carryUNVALIDATED
A-019In-process MVT encoding meets latency targets — on PostGIS only, which v1 made the whole of it: D-05 closed 2026-08-24 with the scope cut2026-08-12
A-004Hot-path geometry overhead justifies our own primitives2026-08-12
A-001The tile path is CPU-bound enough that language performance materially affects capacity2026-08-12
A-013GDAL, GEOS and PROJ permit a threaded worker model2026-08-12
A-009PostgreSQL/PostGIS is an acceptable hard dependency for the baseline deploymentREINSTATED

What is verified

Counted from [Fact] and [Theory] attributes in the test sources, so this number cannot drift from the suite that produces it.

Host
633
Conformance
485
Core
458
Platform.Postgres
395
Console
172
Api.ArcGis
148
Platform
142
Api.Wfs
113
Architecture
45
Render.Skia
26
Security.Argon2
24
Raster.Tiff
17

Recent work

  1. 90f51d7The published images carry both architectures
  2. c418655The datastore image builds on arm64, because the official one does not
  3. 43bcd32The query path's ceiling has a name: six to eleven database round trips a request
  4. 78ed2d6A debt that is decided and waiting on a trigger is parked, not open
  5. 6955107ADR-046 condition 1 re-measured after D-249: the pipeline is flat, the query path is not
  6. 7cce798features:edit changes your own features, on a layer that records who created them
  7. 1589dbcThe Datastore card sits in the row, says its owners are a list, and says why Read again may repeat
  8. 2200e05D-243 closes: a claimed job holds a lease, and a job whose worker died is taken back
  9. 6136b6cD-237 closes: /admin/health says how large the datastore is and whose layers fill it
  10. d2ce726D-259 closes: the Publish half was D-173's second wave on a fetch, and a red toast now reaches the report
  11. 735947aThe console harness waits out Chrome's verifier rebuild, and presses what it rewrites
  12. 3fcb840An outage's own words reach the operator's log, so /admin/health can stay redacted
  13. f72877eASP.NET Core writes nothing per request unless somebody asks it to
  14. d8a3b09An anonymous request no longer asks the platform store who it is