# Asset Management — Research & Planning, revised

- **Date:** 2026-10-12
- **Status:** Research, revised against the system as built — supersedes `docs/2026-09-02-asset-management-research.md`
- **Status Date:** 2026-10-12
- **Phases:** TBD (a build plan is written only once the recommendations in Part 5 are chosen)
- **Phases Complete:** 0
- **Notes:** The September research was written against a planner that was six loosely related
  tables in `prospector` with hardcoded types, no relationship model, no history on four of
  its five kinds, and no way to mark a point in time. Between 2026-09-09 and 2026-10-08 that
  changed: migrations 066–116 rebuilt the planner as a module in a new `planner` schema
  (`docs/2026-09-09-planner-module-update.md`), gave it ports, connections and a splicing
  schedule, rebuilt the schematic over it, and added project milestones with an as-built lock
  (`docs/2026-10-02-project-milestones.md`). Both of those plans say in their first paragraph
  that they exist to lay the seams for this module. This document re-reads the September
  research against what now stands, keeps the original item and decision numbering so earlier
  review-by-number still applies, and adds what the builds surfaced. Nothing here is a
  commitment, except where a decision has already been taken in one of the shipped plans, in
  which case it is recorded as taken and the plan is cited.

## How to use this document

- **Part 0** is what changed since September, and what each change means for assets. Read this
  first if you read the original.
- **Parts 1–2** are the market research, carried forward. They are unchanged in substance and
  trimmed where GeoLynx has since answered the point.
- **Part 3** is the inventory of what GeoLynx now has. It is rewritten from scratch, because the
  overlap it describes grew from "a planned network in all but name" to "most of an asset
  register, minus the asset".
- **Part 4** is the positioning argument, revised.
- **Part 5** is the recommendation list: items 1–43 keep their numbers and gain a status, and
  items 44 onward are new.
- **Part 6** is the design decisions: D1–D10 revisited, D11 onward new.
- **Part 7** is the business questions, with the ones the builds answered marked.

The two earlier documents stay where they are. `docs/2026-07-18-asset-management-module.md`
is the requirements-gathering note; `docs/2026-09-02-asset-management-research.md` is the
original of this one. Neither should be used as a pattern source now — the first assumed a
clone of the projects module, and the second assumed the planner was `prospector.network_*`.

---

## Part 0 — What changed since 2026-09-02

Eight builds landed between the original research and this revision. Four bear directly on
assets; the other four changed patterns the module will follow.

| Build | Migrations | What it is | What it means for assets |
|---|---|---|---|
| **Planner module** (`docs/2026-09-09-planner-module-update.md`) | 066–106 | `prospector.network_*` migrated into one `planner.elements` table under a kind → category → type registry, with ownership, containment, routing, ports, connections, a splicing schedule, category-scoped dynamic fields, history on everything and soft delete | The planned-network model the asset register continues is now a real module with the relationship graph, type registry and ownership column that promotion needs. Of the original 43 items, nine are now done in the planner and eleven more become a copy of something the planner has |
| **Planner topology rebuild** (`docs/2026-09-28-planner-topology-rebuild.md`) | 107–110 | The schematic over `planner.elements`: structures a cable ends at appear, no edge is silently dropped, Cytoscape classes come from the registry | A pattern for any diagram over assets; and the fibre trace and capacity SLD it deliberately left out are both backlog items that read as well over assets as over the plan |
| **Project milestones** (`docs/2026-10-02-project-milestones.md`) | 115 | Point-in-time snapshots of a project's plan — Design, Issued for Construction, As Built — with an as-built lock, an approver unlock, and an element-grained A-B compare page | **This is the promotion trigger.** `planner.milestone_elements` of an as-built milestone is the defined source set; `plan_locked_by()` is the one definition of a locked plan and is where the promoted rule goes. D6 is resolved as promotion, and that resolution is recorded in the milestones plan, not here |
| **Wayleave ↔ project integration** (`docs/2026-09-30-wayleave-project-integration.md`, `docs/2026-10-04-wayleave-project-map-layers.md`) | 112–114 | One match view read by both editors' tabs and both editors' maps: a boundary arm and a premise arm, reported separately, filterable from either key | The worked example of a cross-module relation derived spatially and surfaced on the entity people already have open — exactly what item 16 (asset ↔ wayleave) proposes. It also settled the permission, company and enablement axes as three separate decisions, and chose a binary complete/incomplete style for cross-module map layers |
| **Project plan view** (`docs/2026-09-29-project-plan-view.md`) | 111 | A read-only Gantt of delivery dates, with the dashboard's flags on the rows; a `start` role in `dashboard_config` | The fourth dashboard role every module gets; the pattern that a page over another module's records reads **one** flag definition rather than its own |
| **Map project status styles** (`docs/2026-10-07-map-project-status-styles.md`) | 116 | The `?do=map` boundaries coloured by the admin's status style token; each registry entry carries a `map` member | How an asset map layer should take its style — a token the admin already sets, never a status id in code |
| **Map layer protection** | 089, 114 | `map_layers.is_system`; a migration inserting a vector layer must set it | A rule any asset layer migration has to follow (`docs/table-dependency-chains.md`) |
| **Table dependency chains** (`docs/table-dependency-chains.md`) | — | One section per table whose change reaches further than the table | `planner.elements` has seven readers now. Promotion makes an eighth, and the asset table starts its own chain on day one |

Two things the original said that are now simply false, and worth stating so nobody reads
the old document and acts on them:

- **`prospector.network_*` no longer exists as the planner.** The six tables were renamed
  `*_migrated_20260920` as they were migrated (planner decision 16) and the `prospector`
  schema is left standing for a cleanup job. The planned network is `planner.elements`.
- **"Type-scoped dynamic fields" is not the one extension assets require.** It was built,
  in the planner, and then rebuilt at **category** grain when type grain proved wrong
  (planner decision 30, migration 086). The asset module inherits the category-grain answer.

And one thing that is still true and still the point: the task system is designed
(`docs/task_system_plan.md`) and not built. Nothing in `www/fn/` reads a `tasks.*` table.
Items 24–25 and 39 stand exactly as written.

---

## Part 1 — What "asset management" means in the market

*Carried forward from the September research. Market facts unchanged; GeoLynx commentary
revised.*

### 1.1 Three different products share the name

| Category | Core object | The question it answers | Typical products |
|---|---|---|---|
| **EAM / CMMS** — enterprise asset management, computerised maintenance management | A maintainable *thing* with a failure history | "What do we own, what condition is it in, when is it next due maintenance, and what did it cost us?" | IBM Maximo, SAP EAM, Limble, Tractian |
| **ITAM** — IT / equipment asset tracking | A *serialised item* with a custodian | "Who has it, where is it, is it under warranty, is it calibrated?" | Asset Panda, GigaTrak, MapYourTag |
| **Network inventory / OSS-GIS** | A *located, connected* network element | "What exists where, what connects to what, what capacity is spare, and which premises can we serve?" | 3-GIS, IQGeo, VETRO FiberMap, Esri Utility Network, OZMap |

GeoLynx sits in the third category with a useful slice of the first, and that is now more
true than it was in September. The planner holds located, connected, capacity-aware
elements: a-end/b-end on every linear element, port-to-port connections at a named
structure, an ordered route through containers, and a splicing schedule that reports spares.
That is the third category's data model, already built, for the *planned* network.

### 1.2 The universal core — present in all three categories

Table stakes, not differentiators: an asset register with a unique ID per asset plus
make/model/serial, classification, location, ownership, install date, cost, status and
condition; a classification taxonomy with type-specific attributes; a hierarchy; lifecycle
status with transition dates; history and audit; documents and photos; work management;
planned maintenance and inspection regimes; spares; reliability analytics; financials;
compliance reporting (ISO 55000 as the umbrella).

Of these, GeoLynx now has the taxonomy, the hierarchy, the history and audit, the documents
pattern and the status pattern as working code in the planner or in the other modules. What
it has none of is the operational half: condition, inspection, work, spares, financials.

### 1.3 What network and utility inventory adds on top

Outside and inside plant inventory down to strands; connectivity and tracing; capacity and
occupancy — with the recurring industry theme that operators model fibre well and *duct
badly*, and duct space is the constrained resource; serviceability; planned vs as-built, which
vendors call the primary data-quality problem in the sector; third-party infrastructure (PIA);
outage and impact analysis.

**Where GeoLynx now stands against this list is the headline of the revision.** The planner
models duct explicitly — ways, microducts, which way a run occupies, bore diameters, a fit
check, over-capacity reported rather than refused — which is the thing the market says is
usually missing. Planned vs as-built is a built feature: a milestone is the plan at a point
in time, an as-built is a signed statement of what stands, and the compare page shows the
difference element by element with the changed run emphasised. Third-party infrastructure is
modelled as a reference layer our routes pass through, never as our element. What remains
is the step from "signed as-built" to "operated asset".

### 1.4 What GIS specifically contributes — three kinds of association

The Esri Utility Network vocabulary — **containment**, **structural attachment**,
**connectivity** — was recommended in September as vocabulary to steal. **The planner stole
it.** `parent_element_id` with `parent_relation IN ('contained','attached')` is the first
two; `element_connections` is the third; and `planner.category_relations` says which
category may sit inside which, by relation, so a closure goes in a chamber and a chamber
does not go in a closure. An element with no geometry of its own is located through its
parent in one join, exactly as the principle says.

Linear referencing and PAS 128 location confidence remain as described: the first is still
the most expensive thing on the page and still unbuilt; the second is still cheap and still
unbuilt. The planner's `element_routes.container_position` is not linear referencing — it
says which way of a duct a stretch occupies, not where along the duct something happens —
but it is the first position-along-a-run concept in the schema and a route row per stretch
is already the right grain for one.

### 1.5 Field operations

Mobile inspection and survey, asset tagging, custody and checkout, redlining — unchanged
from September, and none of it built. The planner's approach to its own read-only states is
relevant to the redlining item: a user with planner read sees the layers and no tools, and
the backlog records that this leaves every read-only panel unreachable
(`docs/improvement-opportunities.md`, *Planner - Read cannot see the attributes panel*).
Decide that before designing a field-correction flow over assets, because a field user is a
read-only user with one proposal to make.

### 1.6 UK regulatory and commercial drivers

NUAR, PIA, investor scrutiny, land rights, street works — unchanged. One addition from the
builds: the as-built milestone with its immutable payload, captured registry names and
approver unlock is a **due-diligence record** in its own right. It says what was built, when,
in the vocabulary of the day, and who signed it, and a type rename cannot alter it. The
investor and lender case in the original is partly answered before the asset module exists.

**NUAR, verified 2026-10-12.** The Data (Use and Access) Act 2025 (Royal Assent 19 June
2025) puts NUAR on a statutory footing. The duty falls on "undertakers" in the New Roads and
Street Works Act 1991 sense — anyone exercising a statutory right to execute street works,
which includes a right under the Electronic Communications Code, or holding a street works
licence — so **a fibre altnet with Code powers is in scope**, and the Tranche 1 regulations
consultation (6 August – 15 October 2025) proposes no exemption by size; only
national-security sites are excluded. The duty is to record the location and nature of every
item of apparatus, upload it, and keep it current. Proposed **minimum information** per
apparatus: unique asset ID, owner, business and emergency contacts, coordinates with their
reference system, and a type/subtype from a standard list; depth, material, size, install
date and accuracy are desirable, not mandatory. Proposed refresh: **28 days** for apparatus
that could injure on a strike, **quarterly** otherwise. Failing the initial upload is a
criminal offence with a fine; ongoing breaches attract monetary penalties. **Timing has
slipped:** the consultation said a 12-month upload window from "spring 2026 at the earliest"
and enforcement "spring 2027 at the earliest"; as of September 2026 the service is still
described as not yet mandatory, over 400 asset owners contribute voluntarily (Openreach,
CityFibre and Gigaclear among them), and no laid regulations or government response to
Tranche 1 were found. So: not a requirement today, a legal requirement within this module's
lifetime, with a small mandatory field set that items 1, 6 and 9 already cover. The design
input to take now is the **NUAR asset type/subtype list**, which a client's categories will
need to map onto (item 27).

### 1.7 How these implementations fail

Data quality; naming and ID conventions; no physical verification; over-modelling; **two
registers**. The last was called, in September, the trap GeoLynx was closest to walking into.
The planner build and the milestones build were shaped to avoid it: assets will be
**promoted** from the as-built payload, sharing the planner's registries, and a promoted
milestone is permanently locked so the plan cannot be edited out from under the register.
That removes the hand-synchronised-twin failure. It does not remove the brownfield case, which
is now the hardest open problem (item 44).

---

## Part 2 — Fibre-adjacent markets and utilities

*Carried forward. The sector table is unchanged and omitted here; see the September document.*

The argument was that roughly 80% of the model generalises if it is type-driven rather than
fibre-hardcoded: no fibre nouns in table or column names, unit-bearing capacity, generic
connectivity semantics, configurable inspection regimes.

**The planner tested the first three of those and they held.** Kinds are mechanics — `conduit`
and `conductor`, not duct and cable, with an admin-editable label so a water client renames
them to Sleeve and Main with no migration (planner decision 27). Capacity is `capacity` plus
`capacity_unit` on the type, carrying 4 ways, 6 microducts and 144 fibres through one pair,
and the dimensions are `outer_diameter_mm` / `inner_diameter_mm` with the unit in the name
(decision 36). The seed catalogue is fibre because the first client is fibre, and the plan
refused to invent an electrical or water set for a sector with no customer (decision 17).
Port group names are the one fibre-shaped vocabulary left, as a CHECK constraint, and are
flagged for revisiting when a non-fibre sector arrives.

The consequence for assets is that **the sector-neutrality work is done once, in the
registry, and the asset module gets it by referencing that registry.** It must not add a
fibre noun back.

The three UK-wide drivers — NUAR, PAS 128, land rights — still apply to every sector in the
table. The land-rights half now has a worked cross-module pattern (migration 112) to copy.

---

## Part 3 — What GeoLynx now has that overlaps

Rewritten. The September table had nine rows and one of them was "a planned network model".
That row is now most of the register.

### 3.1 The planner module — the planned network as a module

Everything lives in `planner`. The design decisions are in
`docs/2026-09-09-planner-module-update.md` (thirty-nine of them) and are not repeated here;
what follows is the shape, and what each piece means for an asset register.

**The registry — kind → category → type.**

- `element_kinds` — five, fixed in code: `structure`, `equipment`, `conduit`, `conductor`,
  `area`. Kind decides relationship mechanics. Labels are admin-editable; no add, no delete.
- `element_categories` — admin-created. Carries the kind, the `geometry_kind`
  (point / line / polygon), a default status, a default type, and **its own map layer**,
  auto-provisioned into `public.map_layers` and `map_layer_page_config` on save. Duct and
  subduct are one kind and two categories, which is the model's way of saying "same
  mechanics, different thing".
- `element_types` — admin-created within a category. `capacity` / `capacity_unit`,
  `outer_diameter_mm` / `inner_diameter_mm`, `is_network_head` (the schematic root),
  `can_be_cable_end` (whether a cable may terminate here, and whether that is a closed set
  or an open one — migration 105), `routing_weight` (configured, never read — backlog), and
  a **port template** (`element_type_ports`: a 1:8 splitter is input × 1 and output × 8; a
  96F cable is fibre × 96). `vw_type_ways` generates the ways of a conduit type from its
  capacity. A type in use cannot be deleted, only deactivated.
- `category_relations` — which category may sit inside which, by relation (`route`,
  `contained`, `attached`), with a preference used when derive has several candidates.
- `element_statuses` — admin-managed, with a style token, **scoped to categories**
  (absence means all) and a per-category default. Seeded with the fibre vocabulary
  (Planned / Surveyed / Built; Blocked / No Access / Cleared / Resolved for blockages).
- `element_styles` — type-keyed styling served by `planner_styles.php`; status styling was
  built and reverted (084/085) and the decision stands that the planner styles by type.
- `milestone_types` — Design, Issued for Construction, As Built; one flag, `is_as_built`.

**For assets:** this is the taxonomy item 2 asked for, built, admin-managed and
sector-neutral. The settled decision (backlog, 2026-09-09) is that **assets reference the
same categories, types, port templates and styles** rather than carrying a parallel
registry, so a chamber type is one row used by both. Type protection carries over for the
same reason it exists: a promoted asset must resolve its type forever.

**The element.** One table, `planner.elements`, every kind in it (planner decision 1 — a
polymorphic reference cannot carry a foreign key, and a split would be inherited by assets).
Columns: `project_id` (NOT NULL), kind / category / type / status, `element_name`, `geom`
(one untyped column, SRID 27700 by CHECK, geometry type enforced per category by trigger),
`length_m` (trigger-maintained on all three linear kinds), `sld_x` / `sld_y` (schematic
position), `parent_element_id` + `parent_relation` + `parent_position` (housed in what, in
or on it, and which slot or tray), `a_end_element_id` / `b_end_element_id` (where a linear
element physically terminates — equipment or structure), **`ownership`** (`owned` / `leased` /
`third_party` / `shared`), `is_deleted`, audit columns. Everything else — reference,
customer reference, PIANOI reference, address, contact details — is a dynamic field.

**For assets:** D4 (one untyped geometry column), D5 (store the relationship, not the
coordinates) and D8 (tenure as a column) are answered by copying this. `ownership` is the
column promotion filters on: only `owned` promotes, and because PIA is never a planner
element (decision 6), no rented duct can arrive as an asset by any route.

**Relationships — the four the September document wanted, all present.**

| Relation | Where | Shape |
|---|---|---|
| Containment / attachment | `elements.parent_element_id`, `parent_relation`, `parent_position` | One parent per element; chains (splitter in closure in cabinet); position within the parent as a bare integer |
| Routing | `element_routes` | **An ordered list, not a parent pointer** (decision 37): one row per stretch, each naming either a container element **or** an external feature (`external_dataset`, `external_feature_id`, `external_geom` snapshot), with `provenance` (routed / derived / manual), `confidence`, `is_stale`, and `container_position` (which way of the duct). A direct-buried stretch is an absent row, and `element_route_reviews` records that someone decided that |
| Termination | `a_end_element_id` / `b_end_element_id` | Where the cable physically ends. A is the first vertex, B the last |
| Connectivity | `element_ports` + `element_connections` | Ports generated per element from the type template and editable afterwards; a connection is a **pair of ports at a structure** (`at_element_id`), one row per pair, never a range. A fibre spliced at both ends is two rows at two places. A trigger refuses a connection between elements not both present at the location |

`vw_splice_schedule` is derived from the last two: one row per port per location it is
present at, spares falling out as rows with the far side blank. `vw_element_capacity` counts
routes against type capacity (and, per the backlog, wrongly reports a cable's fibres as
permanently spare because it does not count connections — fix before an asset reads it).

**For assets:** item 10 (containment and attachment), item 11 (attachment to third-party
plant with a coordinate snapshot — `external_geom` on a route row is exactly this, for
routing; attachment to a third-party *structure* is not yet modelled, see item 11's status),
item 29 (capacity, generically) and the connectivity anti-goal are all overtaken. Promotion
**copies this graph**; it does not reconstruct it. The routing link is already polymorphic
across our elements and external references, which is the shape brownfield reuse needs
(item 44).

**Field meta.** `planner.element_fields` plus the five EAV value tables and their histories,
scoped to categories through `element_field_categories` (absence means all). Static fields
(name, type, status, the two ends, parent, relation, ownership) are rows in the same meta
table, so the whole attribute form renders from meta through `planner_form.php`. Scoping UIs
exist in Module Management (fields) and Planner Configuration (statuses). A category created
in admin inherits its kind-siblings' fields, statuses and starting type, so it is usable the
moment it exists.

**For assets:** item 3 and item 4 as built. The open question is whether asset fields are
the planner's fields or their own meta (D11).

**History, journal, delete.** Nine history tables with INSERT / UPDATE / DELETE triggers —
`elements`, `element_routes`, `element_ports`, `element_connections` and the five EAV
tables — a journal with `planner_journal_save.php`, and soft delete with no archive UI and
no `planner_approve` pseudo-module. `plannerChangedSinceCount()` reads the nine tables
**forwards** from a milestone's capture time and fingerprints element updates so a schematic
drag does not count.

**Permissions.** A `planner` module (read / write, each alongside the matching `projects`
level), record-level permission derived from the **project**, an `admin_planner` key that is
wired into `routes.php` and `$modePermissions` rather than merely declared, and
`map_get_v2.php`'s `$schemaModuleMap` carrying `'planner' => 'planner'`. Permissions are
uniform by design — edit all, read all, or nothing (decision 29).

**Map.** One view, `planner.vw_elements`, every editor layer a `filter=` on it (decision
24/25); layer draw order in three bands by `layer_category`; snapping from
`map_layer_page_config.snap_to_this` with snap-on-save in the database; A/B end badges on the
selected line; end pickers ordered by distance from their own end; an explode toggle that
fans a containment stack; `is_system` protecting every vector layer from deletion and
repointing. The three GeoServer WMS layers over planner data were **still serving empty** at
the planner plan's last status update (2026-09-29) — their re-point is manual, was deferred
until Release 2 shipped, and is not recorded as done anywhere in the repo.

**Admin.** `?do=admin_planner` with six panes: Kinds, Categories, Relations, Types,
Statuses, Milestone Types. Planner dynamic fields in Module Management. Styles have no UI.

**Endpoints.** `planner_load.php`, `planner_save.php` (the **only** writer of
`planner.elements`), `planner_form.php`, `planner_styles.php`, `planner_contents_load.php`,
`planner_parent_options.php`, `planner_route_derive.php`, `planner_ports_load.php` /
`planner_ports_save.php`, `planner_canvas_load.php` / `planner_connections_save.php`,
`planner_splice_schedule.php`, `network_topo_get.php` / `network_topo_save.php`.

**Not done, and relevant.** The GeoServer re-point; the `prospector` cleanup; the one-click
fix on the canvas's unlinked-cable panel; and a backlog that includes the fibre trace, the
capacity SLD, the duct cross-section fill warning, stale-route discovery, the port top-up
when a type's template changes, the fibre number-to-colour map, and a BOM tab with a
*supply* facet on the type registry. Several of those are asset questions wearing planner
clothes — see Part 5, Group H.

### 3.2 Project milestones — the plan at a point in time, and the trigger

A milestone is a snapshot of a project's plan: `planner.project_milestones` (header, with
the project's own status, boundary and premise count at that moment), `milestone_elements`
(one flat row per element — the real columns of `planner.elements` **plus the registry
names captured beside the ids**, with `fields`, `routes` and `ports` as jsonb),
`milestone_connections`, `milestone_splice_schedule`. `milestone_payload(p_project_id)` is
the one definition of what is captured; `capture_milestone()` inserts from it; the compare
endpoint runs it for a live side. Headers and payloads are immutable by trigger; a non-as-built
milestone can be soft-deleted, an as-built never. Numbers are per project and never reused.

**An as-built locks the plan, not the project.** `plan_locked_by(project_id)` is the single
definition of locked; `requirePlanUnlocked()` calls it from the three planner saves and every
writer of the project boundary. Unlock needs a reason and `projects_approve` write. Relock is
saving another as-built. Project fields, status, dates, attachments and journal keep working
under a lock — a built project still has a lifecycle.

**Compare** is element-grained and server-computed: `added`, `removed`, `moved` (with
Hausdorff distance), `attributes`, `relationships`, with connections diffed on their natural
key and the changed run emphasised on the map. Either side may be live.

**For assets — the seams, as recorded in the milestones plan and the planner plan:**

- `planner.milestone_elements` of an `is_as_built` milestone **is the promotion source**.
  Only rows with `ownership = 'owned'` promote. (The milestones plan's seams section writes
  this as a jsonb path, `attrs->'element'->>'ownership'`; the built payload has `ownership`
  as a column, per decision 3. The column is what to read.)
- **Promotion is idempotent on `element_id`**: a second as-built updates the asset linked
  to that element rather than creating another.
- **`plan_locked_by()` gains the promoted rule** — a promoted milestone cannot be unlocked
  — and nothing else changes; the seven endpoints calling `requirePlanUnlocked()` are
  untouched.
- **`project_milestones` gains `promoted_datetime` / `promoted_user`** in the assets
  migration. `fn_project_milestones_guard()` lists every immutable header column by name, so
  the new columns need a line there too (`docs/table-dependency-chains.md`).
- **`vw_milestone_elements` is the QGIS / GeoServer read** for an as-built drawing.

Three decisions taken there that the asset module inherits rather than re-opens: more than
one as-built per project (phased builds); an as-built that changes nothing is still a save,
because it asserts something different from a plan; and a milestone captures registry names
so a type rename cannot change what an as-built says. The last one is the shape an asset
record should take for the same reason — an asset outlives the registry row's current name.

### 3.3 The cross-module patterns built since September

| Pattern | Where | Rule it settled | Asset item it serves |
|---|---|---|---|
| **One relation, two surfaces** | `wayleave.vw_wayleave_project_matches` (112), read by the wayleave editor's Projects tab and the project editor's Wayleave tab | Two match arms reported separately, never merged into a boolean; `UNION ALL` of the arms with both keys from real columns so the view filters from either side; never a `FULL JOIN` on a `COALESCE`d key; per-row counts that do not sum, so no consumer prints a total | 16 (asset ↔ wayleave), 15 (asset ↔ project) |
| **The same relation on both maps** | `vw_wayleave_project_map`, `vw_project_wayleave_polygons_map`, `vw_project_wayleave_premises_map` (113) | Cross-module layers on an editor style **binary** — complete / incomplete, the line drawn by `dashboard_config`'s `complete_status`, nothing configured meaning everything incomplete | 12 (asset layers on the editors) |
| **Three axes, three decisions** | The project Wayleave tab | Module enablement, permission, and company scope are separate questions; the tab is gated on `projects`, the row **links** per wayleave permission, company scope is applied by each endpoint to its own listed side | Every asset tab on another module's editor |
| **Status token drives the map** | 116, `statusStyleRegistry()`'s `map` member, `buildStatusStyles()` | The admin's token is the one input; a view emits it raw; the client aliases legacy keys and falls to grey dashed for a miss; a Light token strokes in its family's base hue | 5 and 12 (asset lifecycle status on the map) |
| **Materialise, do not resolve live** | `project_premises` (056–059), and the planned premise-count table (`docs/2026-09-29-project-premise-counts.md`, not yet built) | A spatial relationship a list reads is written at change time by the one function that knows what changed | 17 (asset ↔ premises served) |
| **A page over another module reads one flag definition** | `project_plan_load.php` and `dashboard_load.php` both calling the helpers in `global_functions.php` | A project is overdue on both pages for the same reason | 25 (inspection due / overdue surfaced on the dashboard) |
| **Map layers are configured in data** | `public.map_layers`, `is_system` (089), the pre-089 NULL-column trap (116) | A migration inserting a vector layer sets `is_system = true`; match an old row on its `url`; a `vector_style` names a bucket no migration can check | 12 |
| **Delete and restore** | `docs/module-delete-restore-pattern.md`, projects (061–062) | Soft delete, a hierarchy-free rule, a pseudo-module for the approver, two read surfaces that must keep resolving deleted records | 7 |

### 3.4 Everything else in the September inventory, as it stands

| Capability | Status since September |
|---|---|
| Third-party infrastructure (`strategy.openreach_*`) | Unchanged as data. Now reachable from a planner route row via `external_dataset` / `external_feature_id` / `external_geom` — the first place the application records *our thing in their thing* with a snapshot. `project_auto_route.php` computes the ordered PIA span list; capturing it as `provenance = 'routed'` route rows was Phase 5's intent and `planner_route_derive.php` is the derive-by-matching tool |
| Premises and addressing | `project_premises` unchanged; `basedata.abp_main` (065) is the structured address table; the premise-count materialisation is planned, not built |
| Land rights | The coverage model unchanged; the match view (112) added on top |
| Counterparties, pipeline, Land Registry | Unchanged |
| Field meta system | Planner joined `fieldMetaModuleConfig()`; category scoping exists for planner only; the opportunity editor is still the shared-renderer reference and the other three editors still carry private copies |
| Audit, permissions, lifecycle | `projects_approve` now also gates as-built saves and unlocks; `requireNotDeleted()`, `requirePlanUnlocked()` are the two generic refusals a writer calls |
| Work management | **Still designed, not built** |

---

## Part 4 — The GeoLynx flavour, revised

The five September principles, with what the builds did to each.

1. **Every asset is locatable, always.** The planner holds this: own `geom`, or
   `parent_element_id` to something that has one, resolved in a join; `external_geom`
   snapshots a third-party feature a route passes through because that data is refreshed
   in bulk. **Unchanged, and now a copy.**
2. **One hierarchy that a human can explain.** The planner's answer is sharper than the
   September wording: containment is **one parent per element** and routing is **an ordered
   list of containers per stretch**, and they are different relations because they have
   different cardinality (planner decision 37). Depth is not stored as a chain; it composes.
   The naming lesson from project premises — name by where a thing is counted, not by the
   viewer's role — still applies to any lineage view over assets.
3. **Links are first-class, and mostly derived.** Migration 112 is the proof: a wayleave
   affects a project because coverage meets premises or polygon meets boundary, derived,
   on both editors, with no one maintaining it. Asset ↔ wayleave should be the third surface
   over the same kind of view.
4. **The register is the continuation of the plan.** Now mechanised: plan → milestone →
   as-built (signed, locked) → **promote** → operate. The hand-off point is a specific row in
   a specific table, and the lock makes it one-way.
5. **Type-driven, never fibre-driven.** Done in the registry; the asset module's job is not
   to undo it.

A sixth principle the builds added:

6. **A snapshot outlives what it captured, and so does an asset.** `milestone_elements` has
   no foreign key to `planner.elements`, deliberately. An asset record must likewise survive
   its planner element being deleted, its project being archived, and its type being renamed
   — which argues for captured names on the asset as well as ids, exactly as the milestone
   does (D12).

**Anti-goals, revisited.** Not a maintenance scheduling engine — unchanged, the task system
is still unbuilt. Not an accounting fixed-asset register — unchanged. Not telemetry —
unchanged. **Not a circuit/service inventory** needs rewording: the planner now has port-level
connections and a splicing schedule, and the fibre trace is a backlog item that is "now
derivable and was not before". The honest anti-goal is now *not a light-path or service
management product* — the register carries the physical connections the plan carried, and
a trace over them is a fault-investigation tool, not a service inventory.

---

## Part 5 — Recommended features, for review

Items 1–43 keep their September numbers and gain a status. Status vocabulary:

- **Built (planner)** — exists for planned elements; assets get it by sharing the table or
  the registry.
- **Copy** — exists as a pattern the asset module reproduces on its own table.
- **Open** — still to design and build.
- **Changed** — the item stands but its shape changed because of a build.
- **Dropped** — overtaken.

Items 44 onward are new, raised by the builds. Sizes are S/M/L as before.

### Group A — Foundation (the register itself)

1. **Asset register with a single asset entity.** *Changed.* One `assets` table, every kind
   in it, **sharing `planner`'s registries** rather than carrying its own type reference
   scheme. The planner proved the one-table decision (polymorphic references need real
   foreign keys) and the backlog decision of 2026-09-09 settles separate tables for assets:
   they outlive projects, can arrive by import or acquisition with no project, keep changing
   after a milestone locks the plan, and carry lifecycle attributes a planned element has no
   use for. Columns to mirror from `planner.elements`: kind / category / type, name, `geom`,
   `length_m`, parent + relation + position, the two ends, `ownership`. Columns of its own:
   lifecycle status (D13), `origin` (item 46), the promotion link (item 32), install and
   decommission dates, captured registry names (D12). **No `project_id` NOT NULL** — an
   asset's projects are a relation (item 15), not a column. (M)
2. **Asset type taxonomy, admin-managed.** *Built (planner).* The kind → category → type
   registry, with geometry kind on the category, containment rules in `category_relations`,
   capacity and dimensions on the type, and port templates. **Shared, not copied.** The one
   addition assets may need on the registry is per-type lifecycle defaults (an expected life,
   an inspection interval — item 25), which belong beside the physical facet as a third,
   *operational* facet, by the same reasoning that put the supply facet in a BOM tab. (S —
   the registry exists; the facet is new)
3. **Static + dynamic fields via the shared meta path.** *Copy.* `assets.asset_fields` plus
   the five EAV tables and histories, an entry in `fieldMetaModuleConfig()` and
   `dashboardModuleMap()` landed together, `admin_load.php`'s label map in the same change.
   The planner's static-fields-as-meta-rows approach (migration 083) is the one to copy, so
   the whole form renders from meta. (M)
4. **Type-scoped dynamic fields.** *Built (planner), at category grain.* Planner decision 30:
   scoping by type was wrong — 41 types and rising, each ticked individually to say "this
   applies to cables", and a snapshot that no later type inherited. Category grain cannot
   drift because a type belongs to a category. Assets inherit that answer and the scoping UI.
   The open question is D11: whether asset fields **are** the planner's fields. (S if shared;
   M if copied)
5. **Lifecycle status, admin-managed.** *Changed.* The planner's status registry is about the
   plan (Planned / Surveyed / Built) and is scoped to categories with a per-category default.
   An asset's status is about operation (In service / Faulty / Decommissioned / Removed). D13
   recommends a **separate** `assets.asset_statuses` registry of the standard shape, with a
   style token, so the dashboard, the editor badge and the map all read it the way they read
   project status (migration 116's pattern). (S — pattern exists)
6. **Asset identifier scheme.** *Open.* Still the highest-value hour in the project. Two facts
   from the builds sharpen it: the planner moved `element_ref` and `customer_ref` to dynamic
   fields, so a planned element has **no system-generated reference**; and the audit found
   duplicate `customer_*_id` values across projects to be *expected* — a cabinet planned in one
   project is invisible in another and recreated — with the note "the assets module is what
   resolves it". So the asset reference is **minted at promotion** (or import), unique
   estate-wide, and the planner's references are carried as the customer / third-party
   references beside it. Decide the format before the first promotion. (S)
7. **Journal, history and soft delete.** *Copy.* `assets_history` with the standard triggers,
   `asset_journal_save.php`, `is_deleted`, and the delete/restore pattern doc worked through
   for assets. What blocks an asset delete needs deriving: a live promotion link from an
   unlocked plan? a child asset? a connection? (S/M)
8. **Asset list page with the standard filters, plus attachments/photos.** *Copy.* Tabulator,
   the shared file-upload JS and therefore the `#uploadModal` markup, cover image via
   `image_upload.php`'s `entity` key. (M)

### Group B — Spatial and hierarchy

9. **Geometry on the asset — point, line or polygon.** *Copy.* One untyped `geom`, SRID 27700
   by CHECK, geometry type enforced against the category's `geometry_kind` by trigger. (S)
10. **Containment and attachment as explicit relationships.** *Built (planner).* Parent +
    relation + position, with `category_relations` deciding what may hold what. Promotion
    copies the parent link **as an asset-to-asset link**, which requires the parent to have
    been promoted too — ordering inside the promotion, not a new model. (S for the copy; the
    ordering is item 32's)
11. **Attachment to third-party plant, with a coordinate snapshot.** *Changed.* The planner
    has this for **routing** (a subduct through their duct: `external_*` on a route row, with
    `external_geom`), and not for **attachment** (our closure on their pole: `parent_element_id`
    only references our elements). The asset module is where the second case first matters,
    because a closure on an Openreach pole is an asset whether or not it was ever planned
    here. Model it the way the route row does — dataset, feature id, geometry snapshot —
    rather than adding a second kind of parent. The stable-identifier question across
    Openreach refreshes is still unanswered. (M)
12. **Asset map layer(s), registered in `map_layers`.** *Copy, with three rules learned.* On
    `?do=map`, on the project editor and on the wayleave editor, off by default, `is_system`
    set by the migration, a page-config row per page. Style by **lifecycle status token** on
    the module's own map (116) and **binary** on other modules' editors (113), and the planner
    bucket already resolves type styling if type is wanted. A `vector_style` bucket must exist
    in `map_layer_utils.js` or points draw nothing. (M)
13. **Draw and edit asset geometry on the map.** *Copy.* The planner's editor path — meta-driven
    attribute panel, `planner_save.php`'s shape, snapping, A/B badges, the save toast — is the
    pattern. Still the largest UI item, but no longer a new mechanism. An asset editor that
    is a page of its own versus assets edited from the project editor is D14. (L)
14. **Lineage views that read correctly from any level.** *Copy.* The Contents tab (what is
    housed here) and the Routing tab (what am I inside) are the two directions, already built
    for elements. (M)

### Group C — Cross-module links (the GeoLynx differentiator)

15. **Asset ↔ project.** *Changed.* The promotion link gives the building project for free.
    "Which projects have since worked on it" is the brownfield case (item 44): a later
    project's plan references the existing asset, and that reference is the relation. Make
    it a table of its own with a role (built / modified / decommissioned), not a column. (S)
16. **Asset ↔ wayleave, derived spatially.** *Open; pattern now exists.* A
    `wayleave.vw_wayleave_asset_matches` of 112's shape: a polygon arm (asset geometry meets
    a wayleave's drawn polygon) and, for assets that serve premises, a premise arm. Read by a
    Wayleaves tab on the asset and an Assets tab on the wayleave, both maps, binary style.
    The inverse — **our assets on land with no approved wayleave** — is the compliance report
    the September document called a headline, and it is now one `NOT EXISTS` over that view.
    (M — still the strongest candidate for the module's first distinctive feature)
17. **Asset ↔ premises/UPRN served.** *Open.* Materialised, not resolved live, by the
    `project_premises` precedent — and the planned premise-count work is the reminder that
    aggregates over the materialised table need materialising too. Scope the first version to
    the structure a premise's drop terminates at, which the connection model can tell you
    once a drop is a cable with a B end. (L)
18. **Asset ↔ account.** *Open.* Typed relationships: owner, landowner, maintainer, supplier,
    capacity customer. `ownership` says what tenure; this says with whom. (S)
19. **Asset ↔ opportunity: "what have we already got near here".** *Open.*
    `opportunity_process.php` already resolves nearby planner equipment through
    `vw_opportunity_network_points`; a second arm over assets is the same query. (M)
20. **Own assets as a distance-analysis dataset.** *Open.* The tool is dataset-driven; one
    configuration row once the table exists. (S)
21. **Asset ↔ stocklist.** *Open, low priority.* (S)

### Group D — Field operations and data quality

22. **Location confidence per asset (PAS 128 quality level).** *Open.* Still cheap, still the
    best value per hour. Promotion stamps a default by origin — an as-built from a surveyed
    build is QL-B at best; an import is QL-D until someone says otherwise. (S)
23. **Source and evidence per asset.** *Changed.* Half of this is item 46's `origin`; the
    other half is the attachment that proves it. Promotion can carry the as-built milestone
    id as evidence automatically. (S)
24. **Condition observations.** *Open.* A dated, attributed record with a configurable grade
    and photos. Observations, not a maintenance engine. (M)
25. **Inspection regimes.** *Open.* Per type, an inspection kind and interval (the operational
    facet, item 2); the module reports due and overdue through the **dashboard flag
    helpers in `global_functions.php`**, so an overdue inspection is a flag beside an overdue
    project and reads the same on the plan view. The doing of it is a task once the task
    system exists. (M)
26. **QR / barcode asset tagging.** *Open.* Needs item 6 first — a tag is a reference. (S/M)

### Group E — Commercial and regulatory

27. **NUAR-shaped export.** *Open.* Unchanged. Needs 1, 9, 22 and the specification. (M)
28. **Ownership and tenure per asset.** *Built (planner).* `ownership` is the column;
    counterparty and agreement reference are item 18. (S)
29. **Capacity and occupancy, generically.** *Built (planner), deeper than recommended.*
    The September item asked for a shallow total / used / unit. The planner has
    `capacity` + `capacity_unit`, bore diameters, ways, which way a run occupies, ports,
    connections, and a splicing schedule that reports spares. Assets inherit all of it with
    the relationship graph. Two planner backlog items to settle before an asset report reads
    them: `vw_element_capacity` miscounts conductors, and the duct cross-section fill warning
    (warn at 50 / 75 / 90% of bore area, limit in admin) is designed and unbuilt. (S)
30. **Asset register reporting pack.** *Open.* Counts and length by type, status and area;
    build dates; evidence coverage. The as-built milestone already gives the per-project
    half. (S)
31. **Rental/cost tracking against third-party attachments.** *Open.* Route rows through PIA
    spans are the quantity basis, once they are captured. (S)

### Group F — Later, or only if wanted

32. **As-built promotion from the planned network.** *Changed: this is now the foundation,
    not Group F.* D6 is resolved as promotion. The source is `milestone_elements` of an
    as-built; the filter is `ownership = 'owned'`; the key is `element_id`, idempotent; the
    output is an asset per element with a `promoted_from_element_id`, the parent and route
    links rewritten to asset ids, the ports and connections copied, the milestone stamped
    `promoted_*`, and the lock made permanent through `plan_locked_by()`. The questions
    inside it are D12 and D15–D17. (L — and first)
33. **Redline / field-correction approval flow.** *Open.* The wayleave approval model is the
    pattern; the planner's read-only-user gap is the prerequisite (Part 1.5). (M)
34. **Linear referencing on linear assets.** *Open.* Still expensive. The route-row-per-stretch
    grain is where a measure would attach if it is ever wanted. (L)
35. **Offline mobile capture.** *Open.* Still the biggest departure from the architecture. (L)
36. **Movable equipment custody and calibration.** *Open.* (M)
37. **Failure and reliability analytics.** *Open.* (M)
38. **Spares/materials.** *Changed.* The planner backlog now names a **BOM tab** with a
    *supply* facet on the type registry (unit of issue, stock length, pack quantity, waste,
    cost). That is the planning-side half of this item and it wants its own plan doc; the
    asset-side half (spares held against installed assets) stays here, probably never. (M)

### Group G — Recommended *not* to build here

39. **A work-order engine.** Unchanged. The task system is still unbuilt, which makes this
    more tempting and no more right.
40. **Depreciation and financial asset accounting.** Unchanged.
41. **Strand-level connectivity and light-path tracing.** *Reworded.* The register **will**
    carry port-level connections, because the plan does and promotion copies them. What
    stays out is light-path and service management on top. The fibre trace in the backlog
    is a fault-investigation highlight over the connection graph, not a service inventory,
    and belongs to whichever module the graph is being looked at in.
42. **Real-time telemetry / SCADA / OCPP.** Unchanged.
43. **Street works permitting (NRSWA).** Unchanged.

### Group H — New since September

44. **Brownfield reuse: a plan references an existing asset instead of drawing a new one.**
    *Open; the hardest unsolved problem in the module.* A second project that pulls a cable
    through a chamber built by the first must route through **the asset**, not a new planned
    chamber. Milestones do not solve it. Three things it needs: the asset layer visible and
    snappable in the project editor; a planner element able to name an asset as its container
    or parent — the routing link is already polymorphic (`external_dataset = 'assets'` is the
    obvious shape, and it keeps the capacity count honest because the asset's ways are
    consumed by rows naming it); and promotion knowing that an element routed through an
    asset creates no new asset for the container. **Safety net:** flag a same-type planned
    feature within a few metres of an existing asset as a possible duplicate for a human
    decision — never auto-merge, never auto-duplicate. (L)
45. **Promotion review: a dry run before the copy.** *Open.* Promotion is a one-way act that
    locks a milestone permanently. Show what it will do first — how many assets created, how
    many updated, which elements are not `owned` and will be skipped, which parents or
    containers are unpromoted and would break a link — and let the approver decide. The
    compare page's shape (table beside map, flags, emphasis) is the right one. (M)
46. **`origin` on every asset.** *Settled in the backlog, 2026-09-09.* `promoted` / `manual`
    / `imported` / `third_party`, so the register can be audited by where its records came
    from, and so a confidence default (item 22) and a delete rule (item 7) can key on it. (S)
47. **Asset edits after promotion, and a later as-built.** *Open — a decision, not a feature.*
    An asset is edited in operation (a port re-terminated, a status change); the project is
    unlocked, the plan changed, and a second as-built is promoted. Idempotent-on-`element_id`
    says the asset is updated. Which wins — the as-built or the operational edit — and per
    which column? See D17.
48. **A `planner_approve` / `assets_approve` pseudo-module?** *Open.* Promotion is an
    approver act. `projects_approve` already gates the as-built save and the unlock; reusing
    it for promotion keeps one approver role, and the milestones plan chose reuse over a new
    key. Recommend reuse unless a client separates the roles. (S)
49. **Decommission and remove as lifecycle actions, not deletes.** *Open.* A removed asset is
    a status with a date and a reason, journalled, still on the map in a removed style for as
    long as the client wants; soft delete is for records that should never have existed. The
    project archive's "nothing is ever erased" rule, applied to a thing that physically left.
    (S)
50. **Capacity reporting over the register.** *Open.* Spare ways, spare fibres, spare ports
    per structure, across the estate, from the same views the planner uses — once
    `vw_element_capacity` counts connections for conductors. The capacity SLD in the planner
    backlog is the per-project picture of the same numbers. (M)
51. **The unification path, kept open.** *Settled as a constraint.* D6 option (c) — the asset
    register *is* the network model with status distinguishing planned from built — stays
    reachable. Concretely: no column on `assets` that could not exist on `planner.elements`;
    the same registries; the same relationship shapes; no asset-only kind. If the two tables
    converge, the migration is a union, not a translation.

---

## Part 6 — Design decisions

D1–D10 revisited with their September recommendation and what happened to it. D11 onward
are new. **Taken** means decided in a shipped plan; the plan is cited.

**D1 — Schema name.** *Still open. Recommend `assets`.* The `inventory` schema in the
baseline is still empty and still misnamed for this market. Nothing since September bears on
this except that `planner` set the precedent of a schema named for the module.

**D2 — One asset entity or per-type tables.** *Taken, by extension.* The planner settled one
table for the reasons that apply doubly to assets (planner decision 1), and the backlog
settled separate asset tables sharing the planner's registries (2026-09-09). One `assets`
table, every kind in it.

**D3 — How type-specific attributes are handled.** *Taken: category grain.* Planner decision
30. Fields scope to categories; a genuinely different field set is a new category, which also
gets its own map layer.

**D4 — Geometry.** *Taken: copy the planner.* One untyped `geom`, SRID 27700 by CHECK,
geometry kind by trigger from the category.

**D5 — How location inheritance is stored.** *Taken: copy the planner.* The relationship, not
the coordinates; a snapshot only for a third-party feature.

**D6 — Relationship to the planned network.** *Taken: (b) promotion, with (c) reachable.*
Recorded in `docs/2026-10-02-project-milestones.md` and the backlog. The source, filter, key
and lock rule are in Part 3.2.

**D7 — Status model.** *Changed → see D13.*

**D8 — Ownership/tenure.** *Taken: copy the planner.* `ownership` as a column with the four
values; counterparty and agreement as typed account links (item 18).

**D9 — Capacity modelling depth.** *Overtaken.* The planner went deeper than the September
recommendation and assets inherit it. The remaining boundary is service/light-path
management, which stays out.

**D10 — Asset identifier format.** *Still open. Recommend minting at promotion or import.*
Planned elements carry no system reference, and their customer references legitimately
repeat across projects. The asset reference is the first unique estate-wide identifier a
piece of plant gets, so its format is decided before the first promotion, configurable per
deployment, with the planner's references carried beside it.

**D11 — Do asset dynamic fields share the planner's field meta, or get their own?** *New.
Recommend their own meta, with promotion copying values by matching `field_form_id`.* Sharing
would make an admin's planner field appear on every asset and vice versa, and the two forms
want different things — a planned chamber has a PIANOI reference; an installed one has an
install date, a condition grade and an inspection due date. The planner stores a milestone's
field values as jsonb keyed by field id with the form id and label beside it, which is enough
to copy a value into an asset field of the same form id where one exists and to show the
rest read-only as *as planned*. The cost is that `fieldMetaModuleConfig()` gains a sixth
module, which is the normal cost.

**D12 — What an asset captures from the registry at promotion.** *New. Recommend ids plus
names, as the milestone does.* `type_id` resolves forever because a type in use is never
deleted; but a type renamed reads with today's name, and an asset installed in 2026 should
be able to say what it was called when it went in. Capture `type_name`, `category_name` and
`kind_label` on the asset at promotion. This is principle 6.

**D13 — Asset lifecycle status versus planner status.** *New. Recommend a separate
`assets.asset_statuses` registry of the standard admin-managed shape.* The planner's statuses
describe the plan and are scoped per category; an asset's describe operation and are the
same list for a chamber and a cable. Promotion maps the as-built's element status to the
asset's default (*In service*, or whatever the admin sets as the default), and does not try
to carry the planner status across. The asset status carries a style token and is what the
dashboard, the editor badge and the `?do=map` layer read.

**D14 — Where assets are edited.** *New. Recommend an asset editor page of its own, plus
read-only visibility in the project editor.* An asset outlives its project and most of its
edits — condition, status, inspection — have no project. The project editor shows assets as
a layer (item 12) and lets a plan reference them (item 44), but does not edit them. The
planner's meta-driven attribute panel and save endpoint are the shape to copy for the asset
editor's map.

**D15 — What promotion copies versus links.** *New. Recommend: copy the element, copy the
ports and connections, rewrite the parent and route links to asset ids, and link back.* An
asset is a new record with `promoted_from_element_id` and `promoted_milestone_id`; the
element is untouched (it lives in a locked plan and in an immutable payload). Parent and
container references are rewritten to the promoted asset of the referenced element, which
means promotion runs in dependency order — structures, then equipment, then conduit, then
conductor — and refuses an element whose parent is not `owned` with a named reason rather
than silently orphaning it. Connections copy as asset-port pairs. External route rows copy
verbatim.

**D16 — What happens to the plan after promotion.** *Taken, partly.* The milestone's lock
becomes permanent through `plan_locked_by()`. What is **not** settled: whether the project
should be allowed a *new* plan at all after promotion — a second phase inside the same
project, or a second project referencing the first's assets. Recommend the second, because
it is the brownfield case and solving it once solves both.

**D17 — Which record wins when an asset was edited and a later as-built re-promotes it.**
*New. Recommend: the as-built wins for what the plan owns, the asset keeps what the plan
does not know.* Geometry, type, parent, routes, ends, ports and connections come from the
plan, because the as-built is the signed statement of what was built. Lifecycle status,
condition, inspection, origin, the asset reference and every asset-only field are kept.
Attribute values are per field: a field the plan carries is overwritten and the previous
value is in history; a field it does not carry is kept. The promotion review (item 45) shows
every overwrite before it happens.

**D18 — Permissions.** *New. Recommend an `assets` module with read/write, record-level
permission on the asset, and `projects_approve` reused for promotion.* Enablement checked
against `assets`; the project editor's asset layer gated on `projects` enabled plus `assets`
enabled, with the row linking per `assets` permission — the three-axes pattern of the project
Wayleave tab. Company scope applied by each endpoint, fail-closed, as everywhere else, with
the see-but-not-open tier still a cross-cutting backlog item.

**D19 — Promotion is a database function with a PHP caller, not PHP logic.** *New.
Recommend it.* `capture_milestone()` set the pattern: one transaction, one definition, the
PHP endpoint a thin gate. Promotion wants the same — `assets.promote_milestone(p_milestone_id,
p_user)` — so that a QGIS user, a migration or a test can run it and get what the button
gets, and so the dependency ordering and the idempotency live in one place.

---

## Part 7 — Questions only the business can answer

Answered by Dave, 2026-10-12, except where marked.

1. **Who is the first customer for this, and in which sector?** **A fibre altnet, most
   likely.** "Default taxonomy" here means the admin configuration a new instance ships
   with — the kinds, categories, types, port templates and statuses in Planner
   Configuration before anyone edits them — and that seed is already the fibre catalogue
   (planner decision 17). The asset module ships the same seed and needs no second one.
   Closed.
2. **Is NUAR compliance a selling point you want to lead with?** **Not known yet.** What is
   now known (Part 1.6): an altnet with Code powers will be legally obliged to supply its
   buried apparatus, there is no small-operator exemption, the mandatory field set is small
   and items 1, 6 and 9 cover it, and enforcement has slipped past spring 2027. Whether to
   *lead* with it is a positioning choice that can wait; building so that item 27 is cheap
   cannot, and costs nothing beyond mapping categories to the NUAR type list.
3. **Does an asset register already exist to import?** **In general, yes, and in general
   its quality will be poor.** That is a statement about the market, not about one client:
   any operator adopting the module has plant in the ground that was never planned here, held
   in spreadsheets, contractor as-builts or a legacy GIS. So **import is a product capability**
   of the module, alongside promotion, not a one-off cleanup for a first customer. Its
   product shape is what items 6, 22, 45 and 46 already describe — a reference minted on the
   way in, a location confidence that defaults low until someone says otherwise, a dry-run
   review before anything is written, and `origin = 'imported'` so the register can always say
   which records arrived this way. The stocklist importer's lessons (identifier fallthrough,
   foreign numbering that silently matches, feedback written back into the file) are the
   product lessons to carry, not a client's data to design around.
4. ~~Who maintains the register day to day?~~ **Withdrawn as a question** — it is
   client- and process-specific, not knowable in advance. Restated as a requirement: the
   module supports office-maintained and field-maintained registers by configuration, and
   Group D is built when a client needs it, not pre-emptively.
5. ~~Is the pain "we don't know what we own" or "we can't prove what we own"?~~
   **Withdrawn, as 4.** Either or both, per client. Requirement: capture and hierarchy
   (Groups A–B) and evidence, confidence and reporting (items 22, 23, 30) are both in scope;
   neither is deprioritised on the assumption that one pain dominates.
6. ~~Do you rent out capacity or space to anyone?~~ **Withdrawn, as 4.** Requirement:
   tenure (`ownership`), counterparty links (item 18) and capacity (item 29) are built so
   that a tenant who leases out space can record it, without assuming any particular client
   does.
7. ~~How deep does containment actually go in practice?~~ **Moot.** The question only
   existed in September because nothing had been built and the answer would have sized the
   hierarchy machinery. The planner's containment (a splitter in a closure in a cabinet) and
   routing (a cable in a subduct in a duct) have no depth limit, and assets reuse both. There
   is nothing to decide.
8. **Is the task system going to be built?** **Yes. Order: assets first.** The register and
   promotion (Groups A–C, E) need no task system. The only asset items that do are 24–26,
   and those can hold their data — a condition grade, an inspection due date — without an
   engine for a while. Building tasks first gives a work engine whose main objects do not
   exist. The one order to avoid is building inspections *inside* the asset module before
   tasks exist, which is how a second work-order engine gets written (item 39). So: register
   and promotion, then the task system, then Group D on top of it.

New since September:

9. **Does the operation want an asset register before the first as-built is signed?** If
   yes, import is the first feature and promotion the second, and item 44 (brownfield) is
   day-one work rather than a later phase.
10. **Who presses Promote?** The milestones plan kept the as-built save on project write plus
    planner read, and the unlock on `projects_approve`. Promotion is an approver act; whether
    the approver is the same person who signs the as-built decides whether the two should be
    one button.
11. **Can a project have a second plan after promotion, or does new work mean a new
    project?** D16. It decides how much of the brownfield case the project model has to carry.

---

## Suggested sequencing (indicative only, not a plan)

Reordered. September's order started with the register and ended with promotion; the builds
inverted that, because the register's first rows come from promotion and nothing about the
register's shape should be decided before the promotion's shape is.

1. Settle **D10, D11, D13, D15, D17** and Part 7 questions 9–11. Everything in the first
   migration depends on them.
2. **The register and the promotion together** — items 1, 2 (the shared registry, read), 3,
   5, 6, 7, 9, 10, 32, 46, and D19's function — because a table with no rows and a promotion
   with no table are each untestable. Land `promoted_*` on `project_milestones` and the
   promoted rule in `plan_locked_by()` in the same migration, with the lines in
   `fn_project_milestones_guard()` and `docs/table-dependency-chains.md` that go with them.
3. **Visibility** — items 8, 12 (all three maps), 14, 45 (the review), 49.
4. **The first link and the first report** — items 15, 16 and the no-wayleave inverse, 22,
   23, 30.
5. **Brownfield** — item 44, with D16. It needs 2 and 3 to exist first, and it is the piece
   that decides whether the module is a register or a system of record.
6. Everything else by whatever the answers in Part 7 make urgent.

---

## Sources

Internal, written since the September research and read for this revision:

- `docs/2026-09-09-planner-module-update.md` — the planner module; thirty-nine settled
  decisions; *Seams left for assets*
- `docs/2026-09-28-planner-topology-rebuild.md` — the schematic over `planner.elements`
- `docs/2026-10-02-project-milestones.md` — milestones, the as-built lock, compare; *Seams
  left for assets*
- `docs/2026-09-30-wayleave-project-integration.md` and
  `docs/2026-10-04-wayleave-project-map-layers.md` — the cross-module relation pattern
- `docs/2026-09-29-project-plan-view.md` — the `start` role and the shared flag helpers
- `docs/2026-10-07-map-project-status-styles.md` — the status token on a map
- `docs/2026-09-29-project-premise-counts.md` — planned, not built; the materialise rule one
  level up
- `docs/table-dependency-chains.md` — `planner.elements`' seven readers, the milestone
  header's guard, the boundary's lock sites, `map_layers`' `is_system`
- `docs/improvement-opportunities.md`, *Planner / Assets* — the assets item and the planner
  backlog items this document names
- `db/066`–`db/116` headers, read for the shapes stated in Part 3

NUAR, checked 2026-10-12 for Part 1.6 and Part 7 question 2:

- [GOV.UK — National Underground Asset Register Tranche 1 regulations consultation (6 Aug – 15 Oct 2025)](https://www.gov.uk/government/consultations/national-underground-asset-register-tranche-1-regulations/national-underground-asset-register-tranche-1-regulations) — undertaker definition, minimum information, refresh frequencies, upload window
- [legislation.gov.uk — Data (Use and Access) Act 2025, explanatory notes, NUAR provisions](https://www.legislation.gov.uk/ukpga/2025/18/notes/division/8/index.htm) — duties, offences, penalties
- [Construction Management — minimum information specification proposed (7 Aug 2025)](https://constructionmanagement.co.uk/national-underground-asset-register-minimum-information-specification-proposed/)
- [Geospatial Commission blog — towards a comprehensive NUAR (28 Jan 2026)](https://gdsgeospatial.blog.gov.uk/2026/01/28/towards-a-comprehensive-national-underground-asset-register-integrated-into-the-existing-ecosystem/)
- [ISPreview — over 400 asset owners join NUAR (Sep 2026)](https://www.ispreview.co.uk/index.php/2026/09/over-400-asset-owners-join-nuars-uk-map-of-underground-cables-and-pipes.html) — still described as not yet mandatory
- [NUAR FAQs](https://www.nuar.uk/faqs) — asset owner categories include telecommunications

Market and product research, September 2026, carried forward unchanged:

- [3-GIS — telecom asset & inventory management](https://www.3-gis.com/telecom/telecom-asset-inventory-management) and [network management lifecycle](https://www.3-gis.com/en/network-management-lifecycle)
- [VETRO FiberMap — network inventory management](https://vetrofibermap.com/network-inventory-management-software/)
- [IQGeo — Network Manager Telecom](https://www.iqgeo.com/products/network-manager-telecom) and [Inspection & Survey](https://www.iqgeo.com/products/inspection-and-survey)
- [3-GIS — making the most of duct space](https://www.3-gis.com/blog/making-the-most-of-duct-space-challenges-and-opportunities)
- [Esri — Associations (ArcGIS Pro documentation)](https://pro.arcgis.com/en/pro-app/latest/help/data/utility-network/associations.htm) and [connectivity and associations](https://doc.esri.com/en/arcgis-pro/latest/help/data/utility-network/about-connectivity-and-associations.html)
- [SSP Innovations — editing with associations](https://sspinnovations.com/blog/esri-utility-network-editing-associations-connectivity-attachments-containment/)
- [Umbrex — core EAM modules and capabilities](https://umbrex.com/resources/enterprise-asset-management-system-playbook/core-eam-modules-and-capabilities/)
- [TechTarget — what is enterprise asset management](https://www.techtarget.com/searcherp/definition/enterprise-asset-management-EAM)
- [eWorkOrders — CMMS asset management](https://eworkorders.com/asset-management/)
- [Dynaway — pitfalls of a poorly set up asset register](https://www.dynaway.com/blog/asset-register) and [AssetCues — asset tag numbering format](https://www.assetcues.com/blog/asset-tag-numbering-format/)
- [HAUC(UK) — National Underground Asset Register](https://www.hauc-uk.org.uk/news/national-underground-asset-register-nuar) and [Travers Smith — what NUAR means for energy infrastructure](https://www.traverssmith.com/knowledge/knowledge-container/the-new-national-underground-asset-register-what-does-it-mean-for-the-energy-infrastructure-sector/)
- [Geospatial Commission blog — a comprehensive NUAR integrated into the existing ecosystem](https://gdsgeospatial.blog.gov.uk/2026/01/28/towards-a-comprehensive-national-underground-asset-register-integrated-into-the-existing-ecosystem)
- [LSBUD — PAS 128 specification overview](https://lsbud.co.uk/pas128-a-specification-for-underground-utility-detection-verification-and-location/) and [CICES — PAS 128 client specification guide](https://cices.org/media/rbydmvnu/pas128-client-specification-guide-sep-22-final.pdf)
- [IQGeo — how UK altnets can optimize fibre rollout with an integrated PIA strategy](https://www.iqgeo.com/blog/how-uk-altnets-can-optimize-fiber-rollout-with-an-integrated-pia-strategy) and [Coreline Fibre — PIA explained](https://www.corelinefibre.co.uk/blog/pia-explained)
- [Bratby Law — UK altnet consolidation 2026](https://bratby.law/uk-altnet-consolidation-fibre-ma-wave-2026/) and [Infrastructure Investor — buying a distressed altnet](http://theinfrastructureinvestor.com/ma-in-action-distressed.html)
- [Prometheus Group — linear asset management](https://www.prometheusgroup.com/learning-center/linear-asset-management) and [IBM Maximo — linear assets](https://www.ibm.com/docs/en/masv-and-l/maximo-manage/cd?topic=administering-linear-assets)
- [KloudGin — linear vs vertical assets in utility operations](https://kloudgin.com/understanding-the-fundamental-divide-linear-vs-vertical-assets-in-utility-and-public-sector-operations/)
- [IFS — what is utility asset management](https://www.ifs.com/en/glossary/utility-asset-management) and [Bynry — asset management software for water utilities](https://www.bynry.com/blog/asset-management-software-water-utilities)
- [asecaddesign — construction drawings vs as-built drawings in telecom](https://asecaddesign.com/blogs/the-difference-between-construction-drawings-and-as-built-drawings-in-telecom-projects/)
- [AssetCues — tool and equipment barcode tracking](https://www.assetcues.com/blog/tool-tracking-barcode-system/) and [Clappia — asset tagging with barcode and QR labels](https://www.clappia.com/hardware/asset-tagging-qr-barcode-printing)
- [Dhyan — StreetMan smart street assets](https://www.dhyan.com/solutions/smart-street-assets-central-management-software/) and [Sitetracker — EV charging](https://www.sitetracker.com/industries/ev-charging/)
