This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch feat/template-modes-env-config
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 05da4a0a636c3fd3e7bc7c3f00869dedaa7bdb53
Author: Wu Sheng <[email protected]>
AuthorDate: Fri Jun 26 10:14:47 2026 +0800

    fix(infra-3d): remove the silent disk-bundle fallback from the live render 
path
    
    The 3D map was the lone surface that silently served the disk bundle when 
its
    remote couldn't be resolved — in live mode an unreachable ui_template store 
(or
    a missing / invalid remote row) returned the bundled config instead of 
blocking.
    Every other surface (bundle.ts) renders ONLY from remote rows and treats an
    unsynced / disabled / unreachable template as simply absent.
    
    resolveEffectiveConfig now does the same: render only from the remote row — 
a
    real OAP row in live mode, the synthetic bundled row in readonly — and 
return
    null otherwise. The route 503s on null, so the map blocks with its 
connectivity
    state (like layer/overview/alert) instead of masking an outage with a stale
    bundle. The bundle still reaches OAP via boot-seed and is served in readonly
    through the fake-remote row; it is never a live runtime fallback.
    
    Validated live: readonly 200 (bundle via fake-remote), live+reachable 200
    (remote), live+ui_template-unreachable 503 (blocks). 162 BFF + 116 UI tests 
green.
---
 apps/bff/src/http/config/infra-3d.ts | 51 +++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 16 deletions(-)

diff --git a/apps/bff/src/http/config/infra-3d.ts 
b/apps/bff/src/http/config/infra-3d.ts
index 84ac1f4..f6c87e5 100644
--- a/apps/bff/src/http/config/infra-3d.ts
+++ b/apps/bff/src/http/config/infra-3d.ts
@@ -18,12 +18,13 @@
 /**
  * `GET /api/infra-3d/config` — the EFFECTIVE 3D Infrastructure Map config
  * the `/3d/map` view consumes. The config is a first-class template kind
- * (`horizon.infra-3d.config`), so it follows the same bundled → remote
- * policy as every other template: when OAP holds a non-disabled remote
- * row, that wins; otherwise the bundled default is served. Writes do NOT
- * happen here — the admin editor publishes through the generic template
- * sync surface (`POST /api/admin/templates/save`), which validates the
- * 3D-map content before it reaches OAP.
+ * (`horizon.infra-3d.config`) and is read ONLY from the remote row, exactly
+ * like the layer/overview surfaces — never the disk bundle as a runtime
+ * fallback. The bundle reaches OAP via boot-seed; readonly mode renders it
+ * through the synthetic bundled row. When the ui_template store is unreachable
+ * (or the row is absent / invalid) this 503s so the map blocks, rather than
+ * silently serving a stale bundle. Writes do NOT happen here — the admin 
editor
+ * publishes through the generic template sync surface (`POST 
/api/admin/templates/save`).
  */
 
 import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
@@ -38,7 +39,6 @@ import {
   parseEnvelope,
   INFRA3D_CONFIG_KEY,
 } from '../../logic/templates/names.js';
-import { loadBundledInfra3dConfig } from '../../logic/infra-3d/bundled.js';
 import { validateInfra3dConfig } from '../../logic/infra-3d/validate.js';
 import type { Infra3dConfig } from '../../logic/infra-3d/types.js';
 import { logger } from '../../logger.js';
@@ -60,6 +60,16 @@ export function registerInfra3dConfigRoutes(
     { preHandler: auth },
     async (_req: FastifyRequest, reply: FastifyReply) => {
       const cfg = await resolveEffectiveConfig(deps);
+      // Live + ui_template store unreachable → block, exactly like every other
+      // surface (the UI renders its connectivity-empty state). The 3D map used
+      // to be the lone exception that silently served the bundle here.
+      if (!cfg) {
+        return reply.code(503).send({
+          error: 'template_store_unreachable',
+          reason:
+            "OAP's ui_template store is unreachable; in live mode the 3D map 
renders only from OAP (no bundled fallback). It recovers when the store is 
reachable, or run templates.mode=readonly to serve the bundle.",
+        });
+      }
       // The metric fan-out budget is OPERATIONAL (per-deployment, hot-
       // reloaded), so it lives in horizon.yaml — NOT the published template.
       // Inject it server-side so the UI keeps reading `cfg.pipeline.*`; this
@@ -79,18 +89,26 @@ export function registerInfra3dConfigRoutes(
   );
 }
 
-/** Remote-wins resolution, mirroring `pickLayerContent` in the config
- *  bundle. The remote envelope is re-validated defensively — a row
- *  hand-edited on OAP into an invalid shape falls back to bundled rather
- *  than breaking the map. */
-async function resolveEffectiveConfig(deps: Infra3dConfigRouteDeps): 
Promise<Infra3dConfig> {
-  const bundled = loadBundledInfra3dConfig();
+/**
+ * Render ONLY from the remote row — never the disk bundle as a runtime
+ * fallback. This matches the layer/overview surfaces (bundle.ts): the bundle
+ * reaches OAP via boot-seed, then is read back as a remote row. 
`getSyncStatus`
+ * supplies that row — a real OAP row in live mode, the synthetic bundled row 
in
+ * readonly — so readonly renders the bundle through the SAME remote path.
+ *
+ * Returns null (→ the route blocks with 503) when there is no usable remote
+ * row: the store is unreachable, the row is absent / not-yet-seeded / 
disabled,
+ * or the remote envelope is invalid. A live ui_template outage must surface as
+ * a block like every other surface, not a silent stale bundle.
+ */
+async function resolveEffectiveConfig(deps: Infra3dConfigRouteDeps): 
Promise<Infra3dConfig | null> {
   try {
     const sync = await getSyncStatus({
       client: deps.uiTemplateClient(),
       bundled: () => iterateBundledTemplates(),
       logger,
     });
+    if (sync.unreachable) return null;
     const name = formatName('infra-3d', INFRA3D_CONFIG_KEY);
     const row = sync.rows.find((r) => r.name === name);
     if (row && row.status !== 'disabled' && row.effective === 'remote' && 
row.remote) {
@@ -98,11 +116,12 @@ async function resolveEffectiveConfig(deps: 
Infra3dConfigRouteDeps): Promise<Inf
       if (env) {
         const v = validateInfra3dConfig(env.content);
         if (v.ok) return v.value;
-        logger.warn({ issues: v.issues }, 'remote infra-3d config invalid; 
using bundled');
+        logger.warn({ issues: v.issues }, 'remote infra-3d config invalid — 
blocking, not falling back');
       }
     }
+    return null;
   } catch (err) {
-    logger.warn({ err }, 'infra-3d effective resolve failed; using bundled');
+    logger.warn({ err }, 'infra-3d effective resolve failed — blocking, not 
falling back');
+    return null;
   }
-  return bundled;
 }

Reply via email to