Files
mxpic_EDA/docs/superpowers/plans/2026-05-31-required-mxpic-router-stack.md
T
xsxx03-art 9b4f43f0b1 update
2026-06-03 10:06:48 +08:00

331 lines
10 KiB
Markdown

# 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.