Files
mxpic_EDA/docs/superpowers/plans/2026-05-31-required-mxpic-router-stack.md
T

10 KiB

Required mxpic_router Stack Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make mxpic_router plus its Nazca/gdstk/route backend stack mandatory, and route all Build Layout and Build GDS generation through it.

Architecture: Add a focused backend dependency gate module and make server startup fail before Flask launches when the router stack is incomplete. Keep routed_layout_preview.py as the single preview builder and simplify gds_builder.py so the production GDS path delegates to mxpic_router without silent fallback.

Tech Stack: Python Flask backend, sibling mxpic_router, Nazca, gdstk, mxpic_forge route backend, Node static regression tests.


File Structure

  • Create backend/router_dependency.py: one responsibility, validate and expose the required router stack.
  • Modify backend/server.py: import dependency gate, run it before app.run, and always call create_routed_layout_svg for previews.
  • Modify backend/gds_builder.py: make _build_with_mxpic_router the only production builder path.
  • Modify tests/layout-backend-static.test.js: update static assertions to the new required-router contract.
  • Modify GDS_SVG_GENERATION_LOGIC.md: replace the two Build Layout branches with one unified router path.
  • Modify README.md and INTRANET_DEPLOYMENT.md: document simultaneous EDA/router deployment.

Task 1: Static Tests For Required Router Contract

Files:

  • Modify: tests/layout-backend-static.test.js

  • Step 1: Write failing assertions

Replace the old server.py assertions around create_layout_svg_from_gds and create_routed_layout_svg with assertions that:

assert(
  fs.existsSync(path.join(backendDir, 'router_dependency.py')),
  'backend/router_dependency.py should validate required mxpic_router runtime dependencies'
);
assert(
  serverPy.includes('from router_dependency import require_router_stack') &&
    serverPy.includes('require_router_stack()'),
  'server startup should check the full mxpic_router stack before launching Flask'
);
assert(
  !serverPy.includes('create_layout_svg_from_gds'),
  'save-layout route should not use the legacy gdstk-only preview builder'
);
assert(
  serverPy.includes('create_routed_layout_svg') &&
    !serverPy.includes('if layout_has_links(content):'),
  'save-layout route should always use routed preview generation through mxpic_router'
);

Add assertions for router_dependency.py:

const routerDependencyPy = fs.readFileSync(path.join(backendDir, 'router_dependency.py'), 'utf8');
assert(
  routerDependencyPy.includes('def require_router_stack') &&
    routerDependencyPy.includes('importlib.import_module("gdstk")') &&
    routerDependencyPy.includes('importlib.import_module("nazca")') &&
    routerDependencyPy.includes('mxpic_router.builder') &&
    routerDependencyPy.includes('_import_mxpic_forge_route'),
  'router dependency gate should validate mxpic_router, Nazca, gdstk, and route backend imports'
);

Replace the old GDS fallback assertions with:

assert(
  gdsBuilderPy.includes('return _build_with_mxpic_router(') &&
    !gdsBuilderPy.includes('return _build_with_gdstk') &&
    !gdsBuilderPy.includes('return _build_with_nazca'),
  'Build GDS should use mxpic_router as the only production builder'
);
  • Step 2: Run the focused test to verify RED

Run: node tests/layout-backend-static.test.js

Expected: FAIL because router_dependency.py does not exist, server.py still imports create_layout_svg_from_gds, and gds_builder.py still contains fallback returns.

Task 2: Add Router Dependency Gate

Files:

  • Create: backend/router_dependency.py

  • Modify: backend/server.py

  • Step 1: Implement backend/router_dependency.py

Create a small module with:

import importlib
import os
import sys
from dataclasses import dataclass, field
from typing import List


@dataclass
class RouterStackStatus:
    ok: bool
    router_root: str
    checked: List[str] = field(default_factory=list)


def ensure_router_path() -> str:
    router_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "mxpic_router"))
    if os.path.isdir(router_root) and router_root not in sys.path:
        sys.path.insert(0, router_root)
    return router_root


def require_router_stack() -> RouterStackStatus:
    router_root = ensure_router_path()
    checked = []
    missing = []

    for module_name, label in (
        ("mxpic_router", "mxpic_router"),
        ("nazca", "nazca"),
        ("gdstk", "gdstk"),
    ):
        try:
            importlib.import_module(module_name)
            checked.append(label)
        except Exception as exc:
            missing.append(f"{label}: {exc}")

    try:
        router_builder = importlib.import_module("mxpic_router.builder")
        route_factory = getattr(router_builder, "_import_mxpic_forge_route")
        route_factory()
        checked.append("mxpic_forge Route")
    except Exception as exc:
        missing.append(f"mxpic_forge Route: {exc}")

    if missing:
        details = "; ".join(missing)
        raise RuntimeError(
            "Required mxpic_router runtime stack is unavailable. "
            "Deploy mxpic_EDA together with the matched mxpic_router and mxpic_forge checkouts, "
            f"then install nazca and gdstk. Details: {details}"
        )

    return RouterStackStatus(ok=True, router_root=router_root, checked=checked)
  • Step 2: Wire startup check in backend/server.py

Import:

from router_dependency import require_router_stack

Before app.run(...) in the if __name__ == '__main__': block:

    router_status = require_router_stack()
    print(f"Verified mxpic_router stack: {', '.join(router_status.checked)}")
  • Step 3: Run focused test to verify progress

Run: node tests/layout-backend-static.test.js

Expected: still FAIL because preview and GDS builder branches have not been unified yet.

Task 3: Unify Build Layout Preview Path

Files:

  • Modify: backend/server.py

  • Step 1: Remove legacy preview imports

Change imports from:

from layout_preview import create_layout_svg_from_gds
from routed_layout_preview import create_routed_layout_svg, layout_has_links

to:

from routed_layout_preview import create_routed_layout_svg
  • Step 2: Replace preview branch

Replace the if layout_has_links(content): ... else: ... block inside save_layout with one unconditional call:

            create_routed_layout_svg(
                content,
                svg_path,
                pdk_root=current_pdk_root(),
                project_dir=project_root(project),
                technology_manifest_path=technology_manifest_path_for_project(project),
                prefer_full_gds=prefer_full_gds_for_session(session),
            )
  • Step 3: Run focused test

Run: node tests/layout-backend-static.test.js

Expected: still FAIL only on GDS builder fallback assertions.

Task 4: Unify Build GDS Builder Path

Files:

  • Modify: backend/gds_builder.py

  • Step 1: Simplify production builder

Change build_project_gds so after _load_project_cells validation it returns only _build_with_mxpic_router(...):

    cells = _load_project_cells(project_dir)
    if not cells:
        raise ValueError("No saved cell YAML files found for this project")

    return _build_with_mxpic_router(
        project_dir,
        output_path,
        pdk_public_root,
        technology_manifest_path,
        prefer_full_gds,
    )
  • Step 2: Remove unreachable fallback code

Delete _cells_have_links, _cells_have_elements, _build_with_gdstk, _import_public_gds, _build_with_nazca, _build_nazca_element_cells, _build_nazca_element_cell, _element_port_offset, _element_pin_names, _safe_pin_name, _safe_cell_name, _library_cell_by_name, _int, and _number if no remaining code references them.

Remove unused imports such as math and PdkRegistry.

  • Step 3: Run focused test

Run: node tests/layout-backend-static.test.js

Expected: PASS.

Task 5: Update Logic And Release Documentation

Files:

  • Modify: GDS_SVG_GENERATION_LOGIC.md

  • Modify: README.md

  • Modify: INTRANET_DEPLOYMENT.md

  • Step 1: Update logic path markdown

Rewrite Build Layout to describe:

Click Build Layout -> handleBuildLayout -> /api/save-layout -> save_layout
writes .yml -> create_routed_layout_svg -> mxpic_router.build_project_gds
creates Nazca GDS -> gdstk reads temporary GDS and writes .svg

Rewrite Build GDS to describe:

Click Build GDS -> /api/build-gds -> build_gds -> gds_builder.build_project_gds
loads saved .yml files -> _build_with_mxpic_router -> mxpic_router creates
nd.Cell objects and exports .gds
  • Step 2: Update release docs

Add a short note to README.md and INTRANET_DEPLOYMENT.md:

### Required Router Stack

`mxpic_EDA` must be deployed with the matched `mxpic_router` and `mxpic_forge`
checkouts. The backend validates `mxpic_router`, Nazca, gdstk, and the route
backend before launching the Flask server, so release and deploy these
repositories together.
  • Step 3: Run docs/static test

Run: node tests/layout-backend-static.test.js

Expected: PASS.

Task 6: Final Verification

Files:

  • Verify only.

  • Step 1: Run focused backend contract test

Run: node tests/layout-backend-static.test.js

Expected: PASS.

  • Step 2: Run all Node static tests

Run each test script:

node tests\account-pdk-access-static.test.js
node tests\canvas-generation-wiring.test.js
node tests\canvas-helpers.test.js
node tests\canvas-static-route.test.js
node tests\gds-download-static.test.js
node tests\layout-backend-static.test.js
node tests\layout-ui-wiring.test.js
node tests\project-load-static.test.js

Expected: all scripts exit code 0.

  • Step 3: Check git status

Run: git status --short

Expected: modified/added files are limited to the router-stack implementation, the plan, docs, and existing pre-task changes.