SmartInventory → Lepton Infrastructure Cloud — Gaps & Improvement Backlog
Purpose: A forward-looking QA / critique pass on the legacy Lepton SmartInventory / NetworkAccess product (ASP.NET MVC 5 / .NET 4.8 / EF6 / Npgsql / PostgreSQL / jQuery), framed as an improvement backlog for the greenfield Lepton Infrastructure Cloud. Every finding is tied to where the new platform is going: a schema-driven graph core, multi-tenant SaaS, AI-native operations, and a digital thread / event-sourced history.
Subject: references/SmartInventory-3.0/ @ branch develop (product mainline).
Method: Read-only verification + extension. Findings below are evidenced from source (file:line). This document goes DEEPER than §10 of SmartInventory_Legacy_Architecture_Study.md — it substantiates that register against the actual code and adds ~30 issues the study did not call out (master-password backdoor, hardcoded AES key, client-controllable token TTL, disabled TLS validation, in-memory refresh-token store, triple MainContext, _obsolete dead code shipped, plaintext password in RoutingDataSync SQL, client-controllable token expiry, etc.).
Companion docs: SmartInventory_Legacy_Architecture_Study.md (ground truth for “what exists”), Lepton_Infrastructure_Cloud_PRD.md and Lepton_AI_Native_Operations_Vision.md (where we’re going).
Date: 2026-06-16
1. Executive Summary — Top 12 highest-impact concerns
-
An authentication backdoor is committed in source.
SmartInventoryServices/Providers/OAuthProvider.cs:334short-circuits credential validation whenpassword == "MmY4ODk4MDctYWQ0MC00MGQwLWFkNTktNDdkZTM4ZjBjZDdk"(base64 → a fixed GUID), then logs in as any username with an empty password. A second backdoor: a base64 admin master password (ApplicationConfig/ApplicationConfig.cs:13, decodes to@dministr@t0r@123) is passed into every login proc call (DAUser.cs:120). Anyone with read access to this public-on-GitHub-internally repo can authenticate as any user. This is the single most urgent finding. (Must-fix / Critical.) -
A single hardcoded AES key protects all PII and connection strings.
Utility/MiscHelper.cs:338/:359embedEncryptionKey = "MAKV2SPBNI99212"with a fixed PBKDF2 salt copied from a public CodeProject sample (“Ivan Medvedev” bytes). This key encrypts user emails, mobile numbers, and (viaISEncryptedConnection) DB connection strings. One key, in source, for every tenant. (Must-fix / Critical.) -
Plaintext secrets are pervasive and live.
MapFiles/datashare.inc:2carries a live DB password (Lepton@#2022, also inWeb.config:153-154);RoutingDataSync/Program.cs:43concatenates it into a SQL string.Web.configalso exposes an Azure AD client secret (ida:client_secret,:108), ADOID/Seco SSO client id+secret (:76-77), ASP.NET machineKey (validation+decryption keys,:136), Google Maps keys (:29-32,119), an SMB share passwordlepton@123(:82-84), andSI.MobileOps/Web.configships basic-authadmin/secure123(:18-19). The new platform needs a per-tenant secret manager + rotation from day one — and all of these must be rotated now. (Must-fix / Critical.) -
No DI, no unit-of-work, a new DbContext per call — transactions are impossible.
DataAccess/DBHelpers/Repository.cs:13-21constructs a freshnew GenericRepository<T>(new MainContext(...))on every property access, andGenericRepository.ExecuteProcedurewraps each call inusing(context)(disposing it). A single business operation (DACable.SaveCable) spans multiple disposed contexts and separate singletons (DACDBAttribute.Instance,DACable.cs:41) — there is no transaction boundary spanning the proc calls. 56 DA classes carry copy-pasted thread-safe singleton boilerplate that coexists inconsistently with thenewpath. (Anti-pattern-to-avoid / Critical.) -
Business logic is locked in ~963 opaque PostgreSQL functions not in the repo. ~1,210
ExecuteProceduresites in DataAccess alone; 963 distinctfn_*proc names appear as C# string literals; the contract is stringly typed (anonymous-object property names must match proc params via reflection,ProcHelper.cs:26-36) with no compile-time safety. Migration cannot start until these proc bodies are exported and reverse-engineered. This is the largest migration risk. (Migration-risk / Critical.) -
Topology and history are implicit — no graph, no events — so the PRD’s flagship capabilities cannot be built on this base. Connectivity is FK columns (
a_system_id/b_system_id/parent_system_idacross 97 files) resolved by recursive procs (fn_get_fiber_link_path,fn_trace_get_secondary_splitter, …). There is no event store / temporal versioning — no time-travel, no digital thread, no AutoML training signal. Impact/isolation/upstream-downstream traces and “what did the network look like on Jan 15?” (PRD §4.3, Vision §1) are unreachable without re-architecture. (Must-fix / Critical.) -
Multi-tenancy is branch-per-customer, and the generic mainline is already contaminated. Tenant-specific columns
iru_given_airtel,iru_given_jio,fiber_pairs_given_to_airtelare baked into the shared model (Models/Library.cs:6136), the shared write path (DACable.cs:926-987), and the shared upload staging model (Models/TempUpload/TempCDBAttributes.cs:17-27).ApplicationSettings.ClientNameis read at startup and used in reports (MapReport.cs:846). Config-not-code multi-tenancy is the #1 platform requirement (PRD §4.2). (Anti-pattern-to-avoid / Critical.) -
Geometry round-trips as WKT text strings —
ST_GeomFromTextis the single most-called spatial function (≈660 occurrences). 44 model files carry geometry as[NotMapped] string geom; geometry is decoupled intopoint_master/line_master/polygon_masterjoined bysystem_id. C# never handles typed geometry. The new platform should store typedgeometry(…,4326)and use NTS/GeoJSON at the edge (PRD §4.1 data layer). (Anti-pattern-to-avoid / High.) -
Opaque (non-JWT) tokens, ROPC grant,
AllowInsecureHttp=true, wildcard CORS, in-memory refresh tokens. Both APIs use OAuth ROPC with opaque MachineKey bearer tokens,AllowInsecureHttp=true(Startup.cs:21, both services),EnableCorsAttribute("*","*","*")(WebApiConfig.cs), and refresh tokens in a process-staticConcurrentDictionary(OAuthProvider.cs:721) that breaks horizontal scaling and dies on app-pool recycle. IntegrationServices lets the caller set its own token TTL via theExpireTimerequest param (OAuthProvider.cs:73). (Must-fix / High.) -
Reports are built fully in-memory as
DataTables — no streaming, no paging, single-DB.ReportController.cs(12,860 LOC) materializes whole result sets (NPOI Excel/PDF/Shape/KML/DXF) before serializing; there are 75Download*methods including copy-paste twins (...New,...NewIntoExcelNew,...AllNew,DownloadEntityReportNew_obsolete). One PostgreSQL connection string (constr) drives everything; no read-replica/sharding awareness. This does not scale to 500K+-asset Enterprise tenants (PRD §8.1). (Anti-pattern-to-avoid / High.) -
Zero tests, zero observability, polling-based ops. No test project (
test/empty, no xunit/nunit/mstest, no EF Migrations folder); logging is unstructured to DB and file (LogHelper.cs) with no levels/correlation IDs; no health/metrics/tracing (no healthz, Prometheus, AppInsights, Serilog, OpenTelemetry anywhere). Background work is 8+ polling timers across Windows services. Migrations are a manual external “Deployment Manager” with 348 partial, non-authoritative.sqlfiles in-tree. (Must-fix / High.) -
God classes and shipped dead code make the codebase hostile to change (and to AI agents).
LibraryController.cs13,752 LOC;ReportController.cs12,860;DAMisc.cs4,862;Content/js/Main.js~34k. Seven.bak/.bak.js/.bak.cshtmlfiles are committed;_obsoletemethods ship. Three copies ofMainContext.csexist. (Anti-pattern-to-avoid / High.)
2. Findings Backlog
Severity: Critical / High / Med / Low. Classification: Anti-pattern-to-avoid (a design choice the new platform must not repeat), Must-fix (a defect/risk to remediate or guard against), Opportunity (a capability the legacy lacks that the PRD wants).
Security
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| SEC-1 | Master-password backdoor: SmartInventoryServices/Providers/OAuthProvider.cs:334 — else if (password == "MmY4...Y2Q3ZA==") calls ValidateUser(userName, "", "") (empty password) and issues a token as that user. | Universal account takeover for anyone who has read the source (whole company + any leaked checkout). | No hardcoded credentials, ever. OIDC/JWT with per-tenant signing keys; impersonation only via an audited admin flow with its own identity. | Critical | Must-fix |
| SEC-2 | Second backdoor — base64 admin master password QGRtaW5pc3RyQHQwckAxMjM= (ApplicationConfig/ApplicationConfig.cs:13) passed as p_admin_pass into fn_check_user_details on every login (DAUser.cs:120). MobileResourcesKeyPassword similarly hardcoded (:14). | Shared static admin secret in source; logic to honor it lives in an opaque proc. | Remove. Break-glass admin access via short-lived, audited, MFA-gated credentials in the secret manager. | Critical | Must-fix |
| SEC-3 | Single hardcoded AES key + fixed salt MiscHelper.cs:338/359 "MAKV2SPBNI99212"; salt = literal “Ivan Medvedev” bytes (public sample). Encrypts user email/mobile and connection strings (ISEncryptedConnection). | One key in source decrypts all PII across all tenants; no rotation. | Per-tenant keys in a KMS; envelope encryption; rotate. Never derive keys from string literals in code. | Critical | Must-fix |
| SEC-4 | Plaintext secrets across config. DB password in MapFiles/datashare.inc:2 + Web.config:153-154; Azure AD client secret Web.config:108; ADOID/Seco client id+secret :76-77; machineKey :136; Google Maps keys :29-32,119; SMB password lepton@123 :82-84; SI.MobileOps/Web.config:18-19 basic-auth admin/secure123; SmartPlanner svc creds base64 :72-73. ISEncryptedConnection=false (:85) leaves the conn string in cleartext anyway. | Mass credential exposure across every project; lateral movement; the machineKey lets anyone forge/decrypt ViewState & auth cookies. | Per-tenant secret manager/KMS; short-lived roles; rotate every secret; never inline in .inc/.map/.config. | Critical | Must-fix |
| SEC-5 | DB password concatenated into SQL RoutingDataSync/Program.cs:43 (fn_fs_create_routingData('...','"+password+"')). Doubles as injection + secret-in-args. | Secret leaks to PG logs / pg_stat_activity; concatenation is injectable. | Parameterized calls; secret from env/KMS; never pass passwords as proc args. | Critical | Must-fix |
| SEC-6 | AllowInsecureHttp = true in both SmartInventoryServices/App_Start/Startup.cs:21 and IntegrationServices/App_Start/Startup.cs:19. | Tokens issued/accepted over plain HTTP → interceptable bearer tokens. | TLS-only; HSTS; reject non-TLS at the gateway. | High | Must-fix |
| SEC-7 | Wildcard CORS EnableCorsAttribute("*","*","*") (SmartInventoryServices/App_Start/WebApiConfig.cs:20, IntegrationServices/App_Start/WebApiConfig.cs:14) and Access-Control-Allow-Origin: * added in the token response (OAuthProvider.cs:290, :70). | Any origin can call the APIs with credentials. | Per-tenant allow-listed origins at the API gateway. | High | Must-fix |
| SEC-8 | Client-controllable token TTL IntegrationServices/Providers/OAuthProvider.cs:73 — context.Options.AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(ExpireTime) from a request param; default 86400s hardcoded (Startup.cs:18). | A caller can mint an effectively non-expiring token. | Server-set, short TTLs; refresh-token rotation; revocation list. | High | Must-fix |
| SEC-9 | Opaque, non-JWT tokens + in-memory refresh store OAuthProvider.cs:721 (SimpleRefreshTokenProvider uses a static ConcurrentDictionary); IP-binding check is commented out (:751-760). | No stateless validation; tokens lost on recycle; cannot scale horizontally; no claims for fine-grained authz. | JWT/OIDC access tokens with claims; refresh tokens persisted/rotated in a shared store (Redis/DB); revocation. | High | Must-fix |
| SEC-10 | [Authorize] commented out / missing. BOMBOQController.cs:27-28 (//[Authorize] + //[SessionExpire]); the same opt-in is absent on additional controllers (e.g. MultilingualController, several SmartInventoryServices controllers, IntegrationServices/LocationDeltaController). Authz is per-controller opt-in, so dropping the attribute silently exposes the surface. | BOM/BOQ cost reports and other endpoints reachable without auth. | Secure-by-default: deny-by-default authz at the gateway/middleware; no per-controller opt-in. | High | Must-fix |
| SEC-11 | CSRF protection essentially absent — only 2 of ~125 controllers reference ValidateAntiForgeryToken; state-changing POSTs are unprotected. | Cross-site request forgery on authenticated sessions. | SameSite cookies + anti-CSRF tokens, or token-based auth with no ambient cookies. | High | Must-fix |
| SEC-12 | TLS certificate validation disabled OAuthProvider.cs:1004 — ServicePointManager.ServerCertificateValidationCallback += (o,c,ch,er) => true (in the Azure userinfo call). | MITM on outbound SSO/token calls. | Validate certs; pin where appropriate; centralize outbound HTTP with a hardened client. | High | Must-fix |
| SEC-13 | SQL injection via string.Format/concatenation — confirmed live call sites: DataAccess/DA_Fee_tools.cs:144 (...user_id = '{0}', userId), :159/:173 (UPDATE by {0} id), DataAccess/DAUser.cs:757 (managerids = '{0}'), DataAccess/Feasibility/DAFeasibility.cs:481 (layer IN('{0}')) and :515 (WKT 'POINT(" + locPoints + ")'), DataAccess/WFM/DAUserTimeSheet.cs:178 (time string '{2}'). Plus raw cmd.CommandText = query across 31 TempUpload/ DA files and the GetDataTable(sql)/ExecuteSqlCommand(sql) escape hatches. | Injectable on user-, manager-, feasibility-, and timesheet paths (some fed by uploads / map clicks). | Parameterized commands only; typed import pipeline; ban string-built SQL via lint. | Critical | Must-fix |
| SEC-14 | Password hashing unverifiable / likely weak — DAUser.saveChangePassword (:267-275) sets userDetails.password = password with no hashing in C#; all validation delegated to opaque fn_check_user_details. | Cannot confirm passwords are hashed (likely plaintext or weak digest in the proc). | Argon2/bcrypt at the identity layer; never store reversible passwords; delegate to OIDC IdP where possible. | High | Must-fix |
| SEC-15 | throw ex; rethrow destroys stack traces (OAuthProvider.cs:971, :166, GenericRepository.GetDataTable:166); several catch {} swallow auth errors silently (IntegrationServices OAuthProvider.cs:186-189). | Hides security-relevant failures; poor forensics. | Structured exceptions, throw;, centralized error handling + security logging. | Med | Must-fix |
| SEC-16 | License-key crypto is defeated by hardcoded keys. RSAImplementation/Utility/RSAConstants.cs:6-7 hardcodes RSAEncryptionKey="F77F0312…AAAAAAAA" and CryptoEncryptionKey="MAKV2SPBNI99212" (same as SEC-3); the RSA private key is AES-encrypted with this key and a zero IV (RSAOperation.cs:31/:44 — aes.IV = new byte[16]). License delimiters are "!@#"/"$%^" literals. | Anyone with source can decrypt the RSA private key and forge unlimited licenses; expiry enforcement is cosmetic. | Sign licenses server-side with keys in a KMS; verify with a public key shipped to clients; random IVs. | High | Must-fix |
Architecture & layering
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| ARC-1 | New DbContext per property access; no UoW. Repository.cs:13-21 (get { _repo = new GenericRepository<T>(new MainContext(...)); }); using(context) disposes per ExecuteProcedure call (GenericRepository.cs:400-415). | No transaction can span multiple proc/DB calls → partial writes, no atomic multi-entity operations. | DI-managed scoped DbContext/connection + explicit Unit-of-Work / transaction boundaries per request or command. | Critical | Anti-pattern |
| ARC-2 | Two coexisting access patterns. 56 DA classes carry hand-rolled thread-safe singletons (DACable.cs:16-31) and use the per-call repo getter; SaveCable mixes repo.Get with DACDBAttribute.Instance.Get across separate contexts (DACable.cs:40-41). | Inconsistent lifetimes; no clear ownership; impossible to reason about transactions/concurrency. | One data-access pattern, DI-injected, interface-backed, testable. | High | Anti-pattern |
| ARC-3 | No DI container, no interfaces at call sites. Controllers new BL; BL news DA throughout. | Untestable; tight coupling; cannot mock; cannot swap implementations per tenant/vertical. | Constructor injection + interfaces; composition root; per-tenant strategy injection. | High | Anti-pattern |
| ARC-4 | God classes. LibraryController.cs 13,752 LOC; ReportController.cs 12,860; DAMisc.cs 4,862 (instantiated 67×); Content/js/Main.js ~33,985 LOC. | Change is high-risk; AI agents and humans cannot hold these in context; merge conflicts across customer branches. | Bounded modules / vertical slices; small services; feature-sliced frontend. Decompose Misc/Library/Report first. | High | Anti-pattern |
| ARC-5 | Stringly-typed proc contract. ProcHelper.GetInputParamsWithFinalQuery reflects property names into @params (:26-36); decimal→Double, unknown→Varchar coercion (:51-84). | No compile-time safety; silent type coercion bugs; refactors break at runtime. | Typed query layer (e.g. source-generated, or GraphQL/Cypher resolvers) with contract tests. | High | Anti-pattern |
| ARC-6 | Triple MainContext. DataAccess/Context/MainContext.cs, DataAccess/DBContext/MainContext.cs, Utility/DAUtility/DBContext/MainContext.cs (study said two). Plus a second RoutingContext. | Schema mapping drift; ambiguous source of truth. | One schema-as-code model; generated; single context per bounded store. | Med | Anti-pattern |
| ARC-7 | Sync-over-async. MainController.cs:2270/2311/2489/2528 block on .Result for outbound HTTP; Task.Factory.StartNew(...) in OAuth providers. | Thread-pool starvation under load; deadlock risk. | Async all the way; no .Result/.Wait(). | Med | Anti-pattern |
Data model & schema
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| DAT-1 | Table-per-entity sprawl (~5 tables/entity). att_details_<e> + item_template_<e> + temp_du_<e> + audit_att_details_<e> + vw_<e>_map per entity type, driven by the layer_details flag registry. | Adding an entity = creating ~5 tables + a layer_details row + flags + a .map layer — exactly what forces customer branches. | One schema-driven element model: type/subtype + JSON-schema-validated attributes; views/forms/reports auto-generated from the schema (PRD §4.2). | High | Anti-pattern |
| DAT-2 | Hybrid wide-column + EAV. Wide typed att_details_* columns plus an EAV overlay (dynamic_controls → entity_additional_attributes, referenced from Building/Cable/Conduit/Duct/Customer/Competitor models). | Two ways to model “a field”; queries must union both; the EAV side is untyped and unindexed. | One extensibility mechanism: schema-registry-driven typed attributes (JSONB + JSON Schema), indexed, validated. | High | Anti-pattern |
| DAT-3 | layer_details flag registry — the layerDetail model (Models/Layer.cs:211-342) maps 103 columns, ~60 of them is_* booleans (is_template_required, is_dynamic_control_enable, is_split_allowed, is_isp_layer, is_history_enabled, is_barcode_enabled, is_mobile_layer, is_feasibility_layer, …) plus string pointers (audit_table_name, report_view_name, history_view_name, layer_table, layer_template_table, geom_type, map_abbr). This is the de-facto “Equipment Model Builder.” | A boolean-flag God-table encodes behavior; flag combinations are untested and tenant-forked. | Declarative per-type capability config in a schema registry, versioned and tenant-scoped (not 100+ columns on one row). | High | Anti-pattern |
| DAT-4 | Audit via shadow tables audit_att_details_* (one per entity), written DB-side. | Audit is per-table, partial, and not a queryable timeline; no “who/why/when” semantics; no time-travel. | Event-sourced change log (append-only) as the system of record; current state is a projection (PRD §4.4, Vision §1). | High | Opportunity |
| DAT-5 | Geometry decoupled into *_master, shipped as WKT. 44 models carry [NotMapped] string geom; central point_master/line_master/polygon_master joined by system_id; ST_GeomFromText called ≈660× (parse-from-text on write). | Every spatial write re-parses text; geometry and attributes are two round-trips; no typed spatial in the app tier. | Typed geometry(…,4326) on the element; NTS in app; GeoJSON at API edge; collapse the *_master split. | High | Anti-pattern |
| DAT-6 | Hardcoded tenant columns in shared schema. iru_given_airtel/iru_given_jio/fiber_pairs_given_to_airtel in Models/Library.cs:6136-6146, DACable.cs:926-987, Models/TempUpload/TempCDBAttributes.cs:17-27. | Tenant data bled into the generic product; the mainline is not actually generic. | Tenant attributes via schema config, tenant-scoped; zero customer names in shared code/columns. | High | Anti-pattern |
| DAT-7 | Entity overloading / naming conflation. TowerController instantiates VaultMaster to fetch a Tower (Wireless/TowerController.cs:30); misspellings (MaintainenceCharges, DownloadBckup); DA vs DL prefixes; BLJunctionBox with no matching DA (dead-code candidate). | Hidden semantics; orphans; query/report bugs; hostile to schema generation. | Clean, validated naming; no entity reuse; lint orphans; one canonical type per concept. | Med | Anti-pattern |
Topology / graph readiness
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| TOP-1 | Implicit FK topology — a_system_id/b_system_id/parent_system_id across 97 files; splice/port tables (osp_splicing, splice_tray, port_info, fat_connection); traversal in opaque procs (fn_get_fiber_link_path, fn_get_connection_info_path, fn_trace_get_secondary_splitter). | Recursive SQL is O(n log n)+ and brittle; impact/isolation/redundancy traces at 500K+ assets won’t perform; logic is unportable across verticals. | Explicit graph (Apache AGE/Cypher) over the same Postgres; nodes=equipment, edges=cables/fibers/ports; native upstream/downstream/impact/isolation (PRD §4.3). | Critical | Anti-pattern |
| TOP-2 | Routing network rebuilt in bulk. RoutingDataSync/Program.cs:43-71 calls fn_fs_create_routingData(...) + fn_fs_create_routing_nodes() to regenerate the whole routing table; separate RoutingContext/DB. | Topology is stale between batch rebuilds; no incremental updates → wrong feasibility/shortest-path between syncs. | Topology is a live materialized projection of asset-change events; updated incrementally; one graph store. | High | Anti-pattern |
| TOP-3 | No semantic relationships beyond physical FKs (no SERVES_CUSTOMERS, REVENUE_AT_RISK, NEARBY_HAZARDS, FAILURE_PROBABILITY). | The Vision’s “world-model node” (Vision §1) is impossible; agents have nothing to reason over. | Semantic + intelligence + temporal layers on the graph (Vision §1 layers). | High | Opportunity |
Spatial / GIS
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| GIS-1 | WKT string round-tripping (see DAT-5); C# builds/parses POINT(...)/LINESTRING(...) text by raw concatenation (GIS_Convertor/Convertor.cs:768-778), with a lat/lon vs lon/lat ambiguity — :764 builds coords as lat lon while PostGIS 4326 expects lon lat. | Lossy, slow, error-prone; silent coordinate-order bugs; no spatial typing in the app tier. | Typed geometry end-to-end (NTS); GeoJSON/vector tiles (PMTiles + MapLibre, PRD §13); axis order enforced by type. | High | Anti-pattern |
| GIS-2 | Tight MapServer coupling. 19 .map files; ~60+ DB views vw_att_details_<e>_map read with DATA 'sp_geometry FROM vw_… USING srid=4326'; runtime PROCESSING "NATIVE_FILTER=%…Filter%" placeholders (injectable if unvalidated) + plaintext creds in datashare.inc; per-user row filtering via user_permission_area. | A second rendering pipeline tightly bound to DB views and per-tenant filters; symbology in ~270 status-foldered PNGs + symbols.sym; schema changes ripple across 19 files × ~60 views. | Server-side vector tiles from the typed model; style as data (config), not .map files; auth + validated filters at the tile gateway. | Med | Anti-pattern |
| GIS-3 | SRID 4326 hardcoded everywhere; no reprojection strategy surfaced. | Fine for web mercator display, but multi-utility/3D/CityGML (PRD §7.4) needs explicit CRS handling. | First-class CRS metadata per layer; on-the-fly reprojection at the edge. | Low | Opportunity |
Scalability & performance
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| PERF-1 | In-memory DataTable report/export. ReportController.cs (12,860 LOC) + GenericRepository.GetDataTable materialize whole sets before NPOI/PDF/Shape/DXF/KML serialize; no paging/streaming. | Large-tenant exports OOM the web process; blocks request threads. | Streaming/async export jobs (queue + object storage); cursor/keyset paging; pre-aggregated report views. | High | Anti-pattern |
| PERF-2 | N+1 via per-iteration BL/proc calls in report/library loops (BL .Instance + ExecuteProcedure invoked inside foreach). | Multiplies DB round-trips; latency scales with row count. | Set-based queries / batch fetch; graph traversal for connectivity instead of per-row procs. | High | Anti-pattern |
| PERF-3 | No coherent caching strategy + a caching bug. Only [OutputCache(CacheProfile="CacheForOneDay")] on a few controllers (MapLayoutController:32, SplicingController:2301, PrintController:32/94) — but the profile is misconfigured duration="1" (1 second, not a day; Web.config:126). No distributed cache; ReportController:86 actively NoCaches. | Hot reads (layer config, masters, tiles) hit Postgres every request; the one cache that exists effectively does nothing. | Redis for tiles/sessions/config; cache-aside with event-driven invalidation (PRD §13). | Med | Anti-pattern |
| PERF-4 | Single DB, no replica/shard awareness. One constr; Database.CommandTimeout = 18000 (5 hours!) in GenericRepository.cs:22. | A 5-hour command timeout hides runaway queries; single point of contention. | Read replicas for analytics/reports; sane timeouts + cancellation; per-tenant isolation strategy. | Med | Anti-pattern |
Multi-tenancy & config
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| MT-1 | Branch-per-customer (~1,626 branches; Safaricom 1,656 commits behind). Tenant logic = forked controllers/BL + per-tenant .sql. | Unmaintainable; baselines rot; fixes don’t propagate; the dominant cost of the legacy. | Single codebase; tenant config/schema/flags/theming in data; zero customer branches (PRD §4.2, principle 4). | Critical | Anti-pattern |
| MT-2 | Tenant data in shared schema/code (DAT-6) and AppSettings["ClientName"] read at startup (ApplicationSettings.cs:103) used in reports/PDF (MapReport.cs:846, PDFHelper.cs:1394). | Branding/identity compiled in; one tenant per deployment. | Tenant resolved per-request (subdomain/claim); branding/theme/config from tenant store. | High | Anti-pattern |
| MT-3 | Eager, unguarded config reads at startup — ApplicationSettings does ConfigurationManager.AppSettings["X"].ToString().Trim() on dozens of keys; a missing key throws at boot. | Fragile config; per-deployment hand-tuning; no validation. | Typed, validated config with defaults; fail fast with clear errors; per-tenant overrides. | Med | Must-fix |
API design
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| API-1 | Inconsistent/absent versioning. Only wfm/mobile/v1.0 is versioned; api/Library, api/ISP, api/Connection, api/building, … are unversioned. | No contract stability for mobile/external consumers; breaking changes ripple. | Versioned, contract-tested APIs (GraphQL primary + REST integration, PRD §13); deprecation policy. | High | Must-fix |
| API-2 | Untyped JSON passthrough. Many procs return row_to_json deserialized client-side (ProcHelper.ConvertJsonToObject); responses are anonymous shapes. | No schema; consumers couple to proc output; silent breakage. | Typed DTOs / GraphQL schema; OpenAPI; schema registry. | Med | Anti-pattern |
| API-3 | ROPC grant for first- and third-party clients (both OAuth providers). | ROPC is deprecated (OAuth 2.1); ships user passwords to the app. | OIDC auth-code + PKCE for users; client-credentials for machines. | High | Must-fix |
| API-4 | (Positive) IntegrationServices ships Swagger (IntegrationServices/App_Start/Swagger/*). | The one documented API surface — worth preserving. | Keep OpenAPI/Swagger as a first-class deliverable for all APIs. | — | Opportunity |
Offline / mobile
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| MOB-1 | No sync/delta model. No sync/delta/offline/conflict endpoints in SmartInventoryServices/Controllers; mobile calls the same online BL/DA path. | ”Offline-first field ops” (PRD principle 8) is unmet; field work fails without connectivity. | CRDT-based offline sync (Automerge/Yjs, PRD §13); delta endpoints; sync queue. | High | Opportunity |
| MOB-2 | Conflict handling = single optimistic modified_on check (DAUtility.ValidateModifiedDate, used in DACable.SaveCable:44) → rejects, no merge. | Last-writer-loses with a hard reject; bad UX for concurrent field edits. | CRDT merge or explicit conflict resolution UI; event-based reconciliation. | Med | Opportunity |
Observability & ops
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| OPS-1 | No health/metrics/tracing. No healthz/Prometheus/AppInsights/Serilog/OpenTelemetry anywhere; logging is unstructured to DB+file (LogHelper.cs:93/122), no levels/correlation IDs. | Cannot operate a SaaS at scale; no SLOs, no tracing across the service sprawl. | OpenTelemetry traces/metrics/logs; health endpoints; structured logging with correlation IDs; SLO dashboards. | High | Must-fix |
| OPS-2 | Polling-based background work. WFMNotificationService = 8 timers (NotificationService.cs:23-40); ServiceabilityWinService = config-interval Timer FTP folder-watch (BulkServiceabilitySrv.cs:27); RoutingDataSync/UtilizationEmailScheduler consoles. | Latency/cost of polling; Windows-service sprawl; no back-pressure; not cloud-native. | Event-driven (Kafka/NATS, PRD §4.4); k8s jobs/cron; webhooks; idempotent consumers. | Med | Anti-pattern |
| OPS-3 | Manual migrations, 348 partial .sql files. “Deployment Manager” external tool (not in repo); MigrationScripts/*.sql partial/unreliable per AGENTS.md; no EF Migrations folder; no schema-as-code. | Schema drift across tenants; risky deploys; no reproducible environments. | Schema-as-code + automated, idempotent, versioned migrations in CI/CD (PRD §13 GitHub Actions); golden DB per tenant from migrations. | High | Must-fix |
Testing & quality
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| QA-1 | Zero tests. No test project (test/ empty), no xunit/nunit/mstest, no CI test config. | No regression safety; every change is a gamble; blocks strangler-fig migration. | TDD from day one; contract tests for the proc→service migration; CI gates. | Critical | Must-fix |
| QA-2 | Copy-paste variant explosion. 75 Download* methods in ReportController.cs incl. DownloadEntityReportNew, ...NewIntoExcelNew, ...IntoPDFNEW, ...IntoShapeAllNew, and DownloadEntityReportNew_obsolete. | Massive duplication; _obsolete/New/NewNew twins; bug fixes miss copies. | Config-driven export engine (format × scope as parameters), one code path. | High | Anti-pattern |
| QA-3 | .bak files shipped in-tree. ISP.bak.js, ISP.bak.css, and 5 *.bak.cshtml views committed (Views/ISP/*.bak.cshtml, Views/Shared/_ISPLayout.bak.cshtml). | Dead code masquerades as source; confuses humans and AI agents. | No backups in VCS; delete; rely on git history. | Med | Must-fix |
| QA-4 | Dead code / orphans. _obsolete methods; BLJunctionBox with no DA; commented-out blocks (e.g. 240-line commented GrantResourceOwnerCredentials in OAuthProvider.cs:44-282). | Noise; maintenance hazard; misleads agents. | Delete dead code; lint for orphans/unreferenced. | Med | Must-fix |
| QA-5 | Frontend/library sprawl. Main.js ~33,985 LOC; ≥5 jQuery versions, 2 deck.gl versions, DevExtreme. | Unmaintainable JS; huge bundles; no module system. | Single modern stack (React + MapLibre, PRD §13); module bundler; feature slices. | High | Anti-pattern |
AI-readiness
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| AI-1 | No event sourcing / history (audit = shadow tables, DAT-4). | No time-travel, no digital thread, no training data for AutoML failure/demand models (Vision §3, §4). | Event store as system of record; derived projections; feature store from events. | Critical | Opportunity |
| AI-2 | No semantic graph (TOP-1/3). | Agents (Vision §2-3) have no world model to perceive/reason/plan over. | Knowledge graph with semantic/temporal/intelligence layers (Vision §1). | Critical | Opportunity |
| AI-3 | OTDR is metadata-only — only otdr_distance/otdr_length scalars (Models), no .sor trace import anywhere. | No degradation signal → no predictive maintenance / fault-localization (Vision §3.1.1, prediction catalog #1-3). | Ingest OTDR .sor traces + time-series (TimescaleDB, PRD §13); trend per splice point. | High | Opportunity |
| AI-4 | Logic locked in 963 opaque procs. | Agents and AutoML can’t see or reason over business rules; behavior is unportable across verticals. | Explicit, typed domain logic + declarative rules in the schema/semantic layer. | High | Migration-risk |
Migration risk
| ID | What’s wrong today (evidence) | Why it matters | What the new platform should do | Sev | Class |
|---|---|---|---|---|---|
| MIG-1 | ~963 fn_* proc names referenced, bodies not in repo (1,210 call sites in DataAccess; deployed via Deployment Manager). | The actual domain logic is invisible; can’t re-implement without exporting/reverse-engineering live procs. | Treat proc export + behavioral characterization (golden tests) as a dedicated migration workstream before rewriting. | Critical | Migration-risk |
| MIG-2 | No schema-as-code for the keystone tables (point_master/line_master/polygon_master, layer_details DDL absent from repo). | Can’t reproduce the schema; can’t diff tenants; risky strangler-fig cutover. | Reverse-engineer live DDL → schema-as-code; per-tenant schema diff tooling. | High | Migration-risk |
| MIG-3 | Strangler-fig feasibility hampered by god classes (ARC-4), branch sprawl (MT-1), and no tests (QA-1). | The PRD’s mitigation (build new core alongside, migrate module by module — PRD §11 risk #4) needs seams that don’t exist today. | Establish module seams + contract tests at the proc boundary first; migrate vertical by vertical (start fiber). | High | Migration-risk |
3. Prioritized “Do-Not-Carry-Forward” List
In rough priority order — the patterns that must NOT survive into Lepton Infrastructure Cloud:
- Hardcoded credentials & keys of any kind — master-password backdoors (SEC-1, SEC-2), the single AES key (SEC-3), plaintext config/MapServer/SQL secrets (SEC-4, SEC-5). Remediate in the legacy too — these are live risks today.
- Branch-per-customer and any tenant-specific code/columns in shared code (MT-1, MT-2, DAT-6). Multi-tenant config-not-code is the headline requirement.
- New-DbContext-per-call + no UoW + dual singleton/
newpattern (ARC-1, ARC-2). Adopt DI + UoW + transactions from day one. - Stringly-typed proc dispatch as the primary data layer (ARC-5, API-2). Logic belongs in versioned, typed, testable services — not 963 opaque procs.
- Implicit FK topology + bulk-rebuilt routing (TOP-1, TOP-2). Model networks as a graph; topology as a live projection.
- WKT string round-tripping + geometry/attribute split +
.map-file rendering (DAT-5, GIS-1, GIS-2). Typed geometry + vector tiles. - Table-per-entity sprawl + 110-flag
layer_details+ dual EAV/wide-column (DAT-1, DAT-2, DAT-3). One schema-registry-driven element model. - In-memory
DataTablereporting + 75 copy-pasteDownload*methods (PERF-1, QA-2). Streaming, config-driven exports. - Opaque tokens, ROPC, AllowInsecureHttp, wildcard CORS, in-memory refresh store, client-set TTL (SEC-6–SEC-9, API-3). OIDC/JWT, TLS-only, gateway-enforced.
- Polling Windows-service sprawl + manual migrations + no observability (OPS-1, OPS-2, OPS-3). Event-driven, schema-as-code, OpenTelemetry.
- God classes,
.bakfiles,_obsolete/New/NewNewdead code, tripleMainContext, ~34k-lineMain.js(ARC-4, ARC-6, QA-3, QA-4, QA-5). Small modules, no dead code, modern frontend. - Zero tests (QA-1). TDD + contract tests are prerequisites for a safe strangler-fig migration.
4. Things the legacy got RIGHT (worth keeping)
Be fair — several decisions are sound and should carry forward as principles:
- Server-side spatial in PostGIS. All spatial computation (≈5,100
ST_*calls) runs in the database, not the app tier. The PRD’s data layer keeps PostgreSQL+PostGIS as the spatial core — the legacy proved this works at fiber scale. Keep it; just stop the WKT-string marshalling. - PostgreSQL as the single operational DB. The PRD’s “one Postgres with extensions” bet (PostGIS + AGE + TimescaleDB + pgvector) is a natural evolution of the existing all-Postgres footprint — talent and tooling carry over.
- Metadata-driven layer model — the right instinct, wrong implementation.
layer_details+item_template_*+dynamic_controlsis a genuine attempt at schema-driven extensibility (PRD §4.2 calls this out). The new platform should finish this idea (a real schema registry) rather than discard the concept. - Dual rendering (basemap + entity overlay) and per-user spatial row filtering. The split between basemap tiles and an authenticated entity overlay (with
user_permission_areafiltering) is the right shape for multi-tenant map security — re-implement it at a modern tile gateway. - Fine-grained, per-feature module licensing (45 module codes: RDL/FLK/NWT/BMQ/ROW/LMC/…). A working entitlement model that the PRD’s tier/accelerator packaging (PRD §8) can build on — keep the granularity, move it to feature flags.
- Rich, real fiber/FTTx domain depth. Split/merge, splicing GUI, optical link budget, SLD/butterfly diagrams, BOM/BOQ, in-building MDU design, fiber-link circuits — this is the “deep in fiber” accelerator the PRD wants (§5.1). The domain knowledge is an asset; preserve it via characterization tests during migration.
- Documented integration surface + Swagger on IntegrationServices (API-4). A pluggable-connector framework (PRD principle 5) can formalize what’s already bolted in.
- An i18n/multilingual engine and runtime theming (
DynamicTheme) already exist — the white-label/localization needs of a global SaaS are partly solved.
5. Open Questions
- Stored-procedure library — export all ~963
fn_*bodies from a live DB; which encode validation/topology/feasibility logic that must be re-implemented vs. retired? (Blocks MIG-1.) - Live DDL for keystone tables —
point_master/line_master/polygon_master(geometry DDL absent from repo). (Thelayer_detailsflag count is now substantiated at 103 mapped columns inModels/Layer.cs:211-342, correcting the study’s inferred “~110”.) (Blocks MIG-2.) - Password storage — does
fn_check_user_detailshash passwords, and how (SEC-14)? Determines breach exposure and migration path to an IdP. RoutingContext— isrepo_routinga separate physical database, and what is its sync SLA vs. the main DB (TOP-2)?- Master-password / admin-password usage in production — are SEC-1/SEC-2 actually reachable in deployed environments (is the code path live), and have these secrets ever been rotated? (Incident-response question.)
- Customer-branch inventory — which
develop-<customer>branches are live tenants vs. abandoned; what is each fork’s true net delta (informs migration order, MIG-3). - OLT/ODF modeling — genuinely absent or folded under FMS/Rack? Affects the fiber-accelerator schema.
- Deployment Manager — how is schema actually versioned in production; can its scripts be exported as the basis for schema-as-code (OPS-3)?
- EAV vs. wide-column usage in practice — which tenants rely on
entity_additional_attributes, and how much data lives there (informs DAT-2 migration)?
Compiled 2026-06-16 from a read-only verification of references/SmartInventory-3.0@develop. Evidence is cited file:line. This is the improvement backlog input for Lepton Infrastructure Cloud; pair it with the Legacy Architecture Study (what exists) and the PRD + AI-Native Operations Vision (where we’re going).