From e5f004fea4ff4a5a1ca847a5bbb73f7f55eae891 Mon Sep 17 00:00:00 2001 From: Armor00 <2654988228@qq.com> Date: Thu, 9 Jul 2026 21:43:43 +0800 Subject: [PATCH] docs: plan Guanghan topology repairs --- .../2026-07-09-guanghan-topology-repair.md | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-09-guanghan-topology-repair.md diff --git a/docs/superpowers/plans/2026-07-09-guanghan-topology-repair.md b/docs/superpowers/plans/2026-07-09-guanghan-topology-repair.md new file mode 100644 index 0000000..9bfc2c2 --- /dev/null +++ b/docs/superpowers/plans/2026-07-09-guanghan-topology-repair.md @@ -0,0 +1,366 @@ +# Guanghan Temporal Topology Repair 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:** Repair the approved audit findings so the Wiki topology is genuinely database-driven, historically accurate, deployable, navigable, readable, and covered by automated and screenshot verification. + +**Architecture:** The Flask topology service will serve both the local demo page and same-origin JSON endpoints. PostgreSQL remains the source of truth for missions, components, connections, state, and layout; the browser will create SVG nodes and edges entirely from the snapshot response. Layout-only presentation metadata stays in layout tables, while operational facts remain in temporal topology tables. + +**Tech Stack:** PostgreSQL 16, psycopg 3, Flask 3, vanilla JavaScript, SVG, Python unittest, Playwright. + +--- + +## File map + +- `topology_api/config.py`: secure environment loading and service port. +- `topology_api/app.py`: Flask app, health endpoint, and local demo-page serving. +- `topology_api/routes/snapshot.py`: JSON request validation, snapshot and mission-list endpoints. +- `topology_api/queries/snapshot_queries.py`: mission list, snapshot, component provenance, and layout queries. +- `topology_api/migrations/005_layout_presentation.sql`: layout node membership and presentation metadata. +- `topology_api/seed/seed_ghc01_to_ghc05.py`: complete mission anchors, correct temporal facts, component data. +- `topology_api/seed/seed_layout.py`: persisted node groups, concise labels, and radiator decorations. +- `data/wiki/guanghan_topology_demo.html`: same-origin API client, dynamic SVG renderer, time controls, modal, navigation. +- `tests/test_topology_api.py`: real API/database contract coverage. +- `tests/test_guanghan_topology_demo.py`: current source-level page contract. +- `tests/test_guanghan_topology_e2e.py`: browser behavior against the real Flask app and database. + +### Task 1: Secure and unify local serving + +**Files:** +- Modify: `topology_api/config.py` +- Modify: `topology_api/app.py` +- Create: `tests/test_topology_api.py` +- Modify: `data/wiki/guanghan_topology_demo.html` + +- [ ] **Step 1: Write failing tests** + +Add tests asserting: + +```python +def test_app_serves_demo_page_and_health(client): + assert client.get("/health").status_code == 200 + page = client.get("/guanghan_topology_demo.html") + assert page.status_code == 200 + assert "广寒基地拓扑结构" in page.get_data(as_text=True) + +def test_page_uses_same_origin_api(): + html = HTML_PATH.read_text(encoding="utf-8") + assert "http://localhost:5000" not in html + assert "const API_BASE = ''" in html + +def test_config_has_no_default_password(): + source = CONFIG_PATH.read_text(encoding="utf-8") + assert 'os.environ.get("PGPASSWORD",' not in source +``` + +- [ ] **Step 2: Run tests and verify RED** + +Run: + +```text +python -m unittest tests.test_topology_api -v +``` + +Expected failures: no demo route, hardcoded API host, and hardcoded password. + +- [ ] **Step 3: Implement minimal serving and configuration** + +Use `load_dotenv(PROJECT_ROOT / ".env")`, require `PGHOST`, `PGPORT`, `PGUSER`, and `PGPASSWORD`, default only the non-secret topology database name, and default the topology service port to `8080`. + +Serve the existing page: + +```python +@app.get("/guanghan_topology_demo.html") +def topology_demo(): + return send_from_directory(WIKI_DIR, "guanghan_topology_demo.html") +``` + +Use `const API_BASE = ''` so browser requests remain same-origin. + +- [ ] **Step 4: Run tests and verify GREEN** + +Run the Task 1 tests and confirm zero failures. + +- [ ] **Step 5: Commit** + +```text +git commit -m "fix(topology): secure and unify demo serving" +``` + +### Task 2: Validate API requests and expose missions + +**Files:** +- Modify: `topology_api/routes/snapshot.py` +- Modify: `topology_api/queries/snapshot_queries.py` +- Modify: `tests/test_topology_api.py` + +- [ ] **Step 1: Write failing API tests** + +Cover: + +```python +def test_invalid_time_is_json_400(client): + response = client.get("/api/v1/bases/guanghan/snapshot?at=not-a-time") + assert response.status_code == 400 + assert response.json["error"] == "invalid_time" + +def test_time_and_mission_preserve_context(client): + response = client.get( + "/api/v1/bases/guanghan/snapshot" + "?mission=GHC-01&at=2025-01-02T08:00:00%2B08:00" + ) + assert response.status_code == 200 + assert response.json["query"]["requestedMission"] == "GHC-01" + assert response.json["query"]["anchorSource"] == "explicit_time" + +def test_mission_list_is_database_backed(client): + response = client.get("/api/v1/bases/guanghan/missions") + assert response.status_code == 200 + assert "GHC-05" in [row["code"] for row in response.json["missions"]] +``` + +- [ ] **Step 2: Run tests and verify RED** + +Expected failures: invalid time is 500 and the mission endpoint is 404. + +- [ ] **Step 3: Implement validation and mission query** + +Parse ISO 8601 with `datetime.fromisoformat(value.replace("Z", "+00:00"))`, require a timezone, normalize to ISO output, and return JSON 400 on failure. + +Add `fetch_missions(base_code)` returning code, title, occurrence status, and maximum event time ordered chronologically. + +- [ ] **Step 4: Run tests and verify GREEN** + +Run `python -m unittest tests.test_topology_api -v`. + +- [ ] **Step 5: Commit** + +```text +git commit -m "fix(topology): validate time and list missions" +``` + +### Task 3: Correct temporal seed facts + +**Files:** +- Modify: `topology_api/seed/seed_ghc01_to_ghc05.py` +- Modify: `tests/test_topology_api.py` + +- [ ] **Step 1: Write failing mission-anchor and port tests** + +Assert all nine expected terminal instants: + +```python +EXPECTED_LAST_EVENTS = { + "BL-01": "2024-05-17T23:01:00+00:00", + "GHC-01": "2024-06-16T07:23:00+00:00", + "JC-01": "2024-06-17T08:19:00+00:00", + "BL-02": "2024-06-23T05:23:00+00:00", + "GHC-02": "2024-07-15T03:16:00+00:00", + "GHC-03": "2024-08-30T12:45:00+00:00", + "BL-03": "2024-09-23T19:20:00+00:00", + "GHC-04": "2024-11-20T13:57:00+00:00", + "GHC-05": "2025-01-22T10:52:00+00:00", +} +``` + +Also assert Binglun 1/2/3 connect to west/south/east hub ports respectively. + +- [ ] **Step 2: Run and verify RED** + +Expected: five terminal-time mismatches and two Binglun port mismatches. + +- [ ] **Step 3: Add terminal checkpoint events and correct ports** + +Add a non-topology `mission_checkpoint` event at each missing terminal instant. Map Binglun connections: + +```python +hub_ports = { + "binglun_1": "west", + "binglun_2": "south", + "binglun_3": "east", +} +``` + +- [ ] **Step 4: Re-seed and verify GREEN** + +Run: + +```text +python -m topology_api.seed.seed_ghc01_to_ghc05 +python -m topology_api.seed.seed_layout +python -m unittest tests.test_topology_api -v +``` + +- [ ] **Step 5: Commit** + +```text +git commit -m "fix(topology): correct mission anchors and rover ports" +``` + +### Task 4: Persist complete diagram semantics + +**Files:** +- Create: `topology_api/migrations/005_layout_presentation.sql` +- Modify: `topology_api/seed/seed_layout.py` +- Modify: `topology_api/queries/snapshot_queries.py` +- Modify: `tests/test_topology_api.py` + +- [ ] **Step 1: Write failing layout tests** + +Require every layout node to expose: + +```python +{ + "nodeKey": "...", + "memberKeys": ["..."], + "displayMeta": "...", + "displayHint": "...", + "decorations": {"radiator": True} +} +``` + +Assert west and east group membership and exactly four radiator nodes. + +- [ ] **Step 2: Run and verify RED** + +Expected: missing layout fields. + +- [ ] **Step 3: Add schema and query support** + +Add `display_meta text`, `display_hint text`, and `decorations jsonb` to `diagram_nodes`, plus: + +```sql +CREATE TABLE topology.diagram_node_members ( + diagram_node_id uuid NOT NULL REFERENCES topology.diagram_nodes(id) ON DELETE CASCADE, + component_id uuid NOT NULL REFERENCES topology.components(id), + display_order integer NOT NULL DEFAULT 0, + PRIMARY KEY (diagram_node_id, component_id) +); +``` + +Seed direct and grouped members, concise approved labels, and radiator decorations. + +- [ ] **Step 4: Apply migration, seed, and verify GREEN** + +Execute migration transactionally with psycopg, seed layout, then run API tests. + +- [ ] **Step 5: Commit** + +```text +git commit -m "feat(topology): persist complete diagram semantics" +``` + +### Task 5: Render SVG entirely from snapshot data + +**Files:** +- Replace rendering section: `data/wiki/guanghan_topology_demo.html` +- Modify: `tests/test_guanghan_topology_demo.py` +- Modify: `tests/test_guanghan_topology_e2e.py` + +- [ ] **Step 1: Write failing renderer contracts** + +Assert the source no longer contains hardcoded topology nodes or fixed connection inventory, uses `currentData.layout`, renders connections from `currentData.connections`, renders radiators only from visible node decorations, and retains future expansion comments. + +Add E2E assertions: + +- BL-01 has one node, zero connection lines, zero radiators; +- GHC-01 has two nodes and zero connection lines; +- GHC-05 has 14 visual nodes, two green pressurized lines, four radiators; +- no visible SVG text extends outside its owning node rectangle. + +- [ ] **Step 2: Run and verify RED** + +Expected: fixed SVG and historical visibility failures. + +- [ ] **Step 3: Implement dynamic renderer** + +Create SVG elements with `document.createElementNS`, map component keys to persisted layout nodes, aggregate multiple database edges that share visual endpoints and line class, calculate rectangle-edge intersections from node centers, and attach click/keyboard handlers after node creation. + +Use concise persisted `displayMeta`/`displayHint`; truncate text based on node width and add a `