# 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: ```javascript 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`: ```javascript 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: ```javascript 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: ```python 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: ```python from router_dependency import require_router_stack ``` Before `app.run(...)` in the `if __name__ == '__main__':` block: ```python 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: ```python from layout_preview import create_layout_svg_from_gds from routed_layout_preview import create_routed_layout_svg, layout_has_links ``` to: ```python 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: ```python 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(...)`: ```python 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: ```text 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: ```text 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`: ```markdown ### 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: ```powershell 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.