From aa9529627ead74a9420c17650f157ff7f5df42f2 Mon Sep 17 00:00:00 2001 From: ambujsingh Date: Fri, 3 Jul 2026 13:15:57 +0530 Subject: [PATCH] docs(plantuml): update architecture docs for idmap-based linking Replace all references to plantuml_links.json / standalone linker with the idmap sidecar flow. tooling_chain.puml updated to reflect the new pipeline. --- bazel/rules/rules_score/README.md | 3 +- .../docs/_assets/tooling_chain.puml | 2 +- .../rules_score/docs/tooling_architecture.rst | 20 +- plantuml/sphinx/clickable_plantuml/README.md | 264 ++++++++++++------ 4 files changed, 189 insertions(+), 100 deletions(-) diff --git a/bazel/rules/rules_score/README.md b/bazel/rules/rules_score/README.md index aea4c605..a91439fc 100644 --- a/bazel/rules/rules_score/README.md +++ b/bazel/rules/rules_score/README.md @@ -90,8 +90,9 @@ architectural_design( **`bazel build`** — runs `puml_parser` on every `.puml` file, producing: - a `.fbs.bin` FlatBuffers binary (diagram AST) — consumed by validation/core checks - a `.lobster` traceability file (Interface elements only) — consumed by LOBSTER -- a `plantuml_links.json` — consumed by the `clickable_plantuml` Sphinx extension - a `validation.log` from the `architectural-design` validation profile +- a `.idmap.json` sidecar — consumed by the `clickable_plantuml` Sphinx extension + to resolve cross-diagram links based on element *defines/references* roles Diagrams in `public_api` are classified separately so their lobster items flow through `public_api_lobster_files` for failure-mode traceability. diff --git a/bazel/rules/rules_score/docs/_assets/tooling_chain.puml b/bazel/rules/rules_score/docs/_assets/tooling_chain.puml index 0db400a7..ce5f8b46 100644 --- a/bazel/rules/rules_score/docs/_assets/tooling_chain.puml +++ b/bazel/rules/rules_score/docs/_assets/tooling_chain.puml @@ -42,7 +42,7 @@ rectangle "sphinx_module" <> as sphinx ' ── Tools ───────────────────────────────────────────────────────────────────── rectangle "**TRLC**\ntrlc parser + trlc_rst\n(.trlc/.rsl -> .rst; TRLCRST lib)" <> as trlc rectangle "**rst_to_trlc**\n(.rst -> .trlc)" <> as r2t -rectangle "**PlantUML Parser**\nparser + linker (Rust)\n(.puml -> .fbs.bin + .lobster)" <> as puml +rectangle "**PlantUML Parser**\npuml_cli (Rust)\n(.puml -> .fbs.bin + .lobster + .idmap.json)" <> as puml rectangle "**puml_cli** (FTA mode)\ninline metamodel + extract\n(.puml -> inlined .puml +\nfta_chains.json + root_causes.lobster)" <> as fta rectangle "**fmea_assembler**\nTRLCRST page build\n(.trlc + chains -> fmea.rst)" <> as asm rectangle "**Lobster**\nlobster-trlc / -report /\n-ci-report / gtest_report" <> as lob diff --git a/bazel/rules/rules_score/docs/tooling_architecture.rst b/bazel/rules/rules_score/docs/tooling_architecture.rst index d1eb04df..a9606e69 100644 --- a/bazel/rules/rules_score/docs/tooling_architecture.rst +++ b/bazel/rules/rules_score/docs/tooling_architecture.rst @@ -91,13 +91,14 @@ are rendered under :doc:`tool_reference/index`. - Converts RST requirement directives (``feat_req``, ``comp_req``, …) into ``.trlc`` records so requirements can be authored in either RST or TRLC. * - **PlantUML Parser** - - ``@score_tooling//plantuml/parser:parser`` (Rust) + ``:linker`` + - ``@score_tooling//plantuml/parser:parser`` (Rust) - ``architectural_design``, ``unit_design`` - Parses ``.puml`` diagrams into a FlatBuffers AST (``.fbs.bin``, one - ``root_type`` per diagram kind) and extracts interface ``.lobster`` - items. The **linker** merges the FlatBuffers into ``plantuml_links.json`` - for the ``clickable_plantuml`` Sphinx extension. Rejects syntactically - invalid diagrams with a non-zero exit code. + ``root_type`` per diagram kind), extracts interface ``.lobster`` items, + and emits ``.idmap.json`` sidecars recording the *defines/references* + roles of each element. The ``clickable_plantuml`` Sphinx extension reads + these sidecars to resolve cross-diagram links without a separate linker + step. Rejects syntactically invalid diagrams with a non-zero exit code. * - **puml_cli (FTA mode)** - ``//plantuml/parser/puml_cli`` ``--fta-output-dir`` (Rust; FTA model in the ``puml_fta`` crate) @@ -121,6 +122,15 @@ are rendered under :doc:`tool_reference/index`. parse: an overview table, one section per failure mode (detail + inline fault tree + that chain's control measures), and trailing "Unlinked" sections so nothing is dropped. + * - **safety_analysis_tools** + - ``//bazel/rules/rules_score:safety_analysis_tools`` + (``src/safety_analysis_tools.py``, local) + - ``fmea`` + - Assembles the failure-mode-centric ``fmea.rst`` from ``fta_chains.json`` + plus the FailureMode / ControlMeasure records in one in-process TRLC + parse: an overview table, one section per failure mode (detail + inline + fault tree + that chain's control measures), and trailing "Unlinked" + sections so nothing is dropped. * - **Lobster** - ``@lobster//`` : ``lobster-trlc``, ``lobster-report``, ``lobster-ci-report``, ``lobster-html-report``, ``gtest_report``, diff --git a/plantuml/sphinx/clickable_plantuml/README.md b/plantuml/sphinx/clickable_plantuml/README.md index 9d767037..83d816e7 100644 --- a/plantuml/sphinx/clickable_plantuml/README.md +++ b/plantuml/sphinx/clickable_plantuml/README.md @@ -16,19 +16,22 @@ Sphinx extension that makes PlantUML diagrams clickable by injecting hyperlinks ## Sphinx Integration -The extension hooks into the native Sphinx build lifecycle. URLs are computed by -`app.builder.get_relative_uri()`, which works for any builder and -output directory layout. +The extension hooks into the native Sphinx build lifecycle. URL computation +depends on the configured `plantuml_output_format`: in `svg_obj` mode the +rendered SVG lives in `_images/`, so links are made relative to that directory +(`os.path.relpath(target_uri, imagedir)`); for inline `svg`/`png` the link is +relative to the containing HTML page via +`app.builder.get_relative_uri(from_docname, to_docname)`. ``` Sphinx build lifecycle clickable_plantuml hooks ═══════════════════════════════════ ═══════════════════════════════════════ builder-inited ───► on_builder_inited() - │ (one-time setup) Load all *plantuml_links.json files - │ from srcdir (recursive). - │ Store {puml_basename → alias_map} - │ in app.env. + │ (one-time setup) Load all *.idmap.json files from + │ srcdir (recursive). + │ Build definition index: + │ {alias|id → [definer source paths]}. │ ├─ READ PHASE ────────────────────────────────────────────────────────────── │ for each document: @@ -40,10 +43,9 @@ Sphinx build lifecycle clickable_plantuml hooks │ │ │ doctree-read ───► on_doctree_read() │ (per document) Traverse the parsed doctree. - │ For every plantuml node that has a - │ filename attribute, record - │ {puml_basename → docname} in app.env. - │ Warn on basename collisions. + │ For every plantuml node, record + │ {normalized_source_path → docname} + │ in app.env (path identity, not basename). │ │ env-merge-info ───► on_env_merge_info() │ (parallel builds only) Merge puml→docname maps gathered @@ -55,119 +57,195 @@ Sphinx build lifecycle clickable_plantuml hooks │ post-transform / resolve │ │ │ doctree-resolved ───► on_doctree_resolved() - │ (per document) For each plantuml node, look up the - │ alias_map from app.env. - │ Resolve target .puml → docname, then - │ call app.builder.get_relative_uri() - │ to get the correct relative URL. - │ Append url of is [[url]] - │ directives to node['uml'] before - │ sphinxcontrib-plantuml renders it. + │ (per document) For each plantuml node, load its idmap. + │ For each reference entry, look up the + │ definition index (FQN first, then alias). + │ Apply proximity tiebreak on ambiguity. + │ Build the URL (relative to _images/ in + │ svg_obj mode, else page-relative via + │ get_relative_uri), then append + │ url of is [[url]] directives to + │ node['uml'] before rendering. │ build-finished ``` ## How It Works -1. **Link discovery** (`builder-inited`) – Scans for `*plantuml_links.json` files in the Sphinx source directory. -2. **Diagram location mapping** (`doctree-read`) – As Sphinx reads each document, the extension traverses the parsed doctree to record which `docname` contains which `.puml` diagram (keyed by basename). Basename collisions across documents are reported as warnings. -3. **URL resolution & link injection** (`doctree-resolved`) – For each plantuml node, resolves target `.puml` references to the docname that contains the target diagram, generates a relative URL via `app.builder.get_relative_uri()`, and appends `url of is [[url]]` directives to the PlantUML source before rendering. -4. **Incremental / parallel support** – `env-purge-doc` removes stale entries when a document is re-read; `env-merge-info` merges state from parallel worker processes. +1. **idmap discovery** (`builder-inited`) – Scans for `*.idmap.json` files in + the Sphinx source directory. Each sidecar records *defines* (elements + elaborated in that diagram, i.e. with children/members) and *references* + (leaf mentions and relation endpoints). A global definition index maps + each alias/FQN to the set of diagrams that elaborate it. + +2. **Diagram location mapping** (`doctree-read`) – Records which `docname` + contains which `.puml` diagram, keyed by the canonical workspace-relative + path. A node's identity is recovered by normalising its absolute path + (`srcdir` + the node's `incdir` + `filename`) and deriving exactly one + canonical key using the precomputed workspace offset from `builder-inited`. + Matching is exact canonical-key equality (no basename fallback, no suffix + fallback), so same-basename diagrams in different packages remain distinct. + +3. **URL resolution & link injection** (`doctree-resolved`) – For each + reference in a diagram's idmap, resolves the unique definer via the index. + When multiple diagrams define the same element, a *proximity tiebreak* + selects the definer sharing the longest common path prefix with the source + diagram. On a genuine tie, no link is emitted (safe over wrong). URLs are + built relative to `_images/` in `svg_obj` mode (else page-relative via + `app.builder.get_relative_uri()`) and percent-encoded before injection. + +4. **Incremental / parallel support** – `env-purge-doc` removes stale entries + when a document is re-read; `env-merge-info` merges state from parallel + worker processes. + +## Invariants + +The extension relies on two invariants held by the idmap producer +(`architectural_design()` / `puml_cli`): + +1. **The `--source-name` must be the srcdir-relative workspace path.** + `--source-name` is passed to `puml_cli` and becomes the `source` field in + the idmap. This value is the canonical key used for all matching: a + plantuml node resolves only when its derived canonical key equals the idmap + `source` key exactly (normalized to POSIX form). The value must be stable + across builds and unique — non-unique `source` values raise an + `ExtensionError` rather than silently mislinking. In Bazel: pass + `puml_file.short_path` to satisfy this invariant regardless of how Sphinx + roots `srcdir` or how Bazel symlinks the staged sources. + +2. **PlantUML basenames (file stems) must be unique within a single + `architectural_design` target.** Each `.idmap.json` is written as + `.idmap.json` under the target's output directory, so two + diagrams sharing a stem in one target would collide on output (build + error). Same basenames across *different* targets/packages are fine — + exact canonical-key matching via `--source-name` keeps them independent. + +## Automatic idmap Generation (Bazel) + +`.idmap.json` sidecars are produced by the `architectural_design()` rule. + +The rule passes `--source-name ` and +`--idmap-output-dir` to `puml_cli` for every `.puml` file. The +`--source-name` argument **must be the srcdir-relative workspace path** to +satisfy the canonical-key invariant. Passing `short_path` (the workspace +root-relative path for Bazel sources) ensures this requirement is met. The +resulting idmap `source` field is a stable, unique, workspace-relative path +(e.g. `score/mw/com/proxy_detail.puml`), which becomes the diagram's identity +key throughout the extension. Duplicate `source` values across idmaps raise +an error; duplicate basenames (file stems) within one target also raise an +error. + +### Role detection algorithm + +Given the resolved model of one `.puml` diagram: + +1. **defines** – An element is a *define* when any of the following hold: + - At least one other element lists it as its `parent_id` (component diagrams). + - It has member variables or methods (class diagrams). + - The diagram's `@startuml ` matches its alias or display name + (component and class diagrams). + - It is a `$TopEvent` node — the tree root whose `connection` is `None`, + never used as a relation source (FTA diagrams). +2. **references** – Elements that link away to another diagram: + - Top-level leaf boxes and relation endpoints (component diagrams). + - All participants (sequence diagrams — no defines in sequence). + - `$TransferInGate` nodes whose alias is a TRLC-style FQN + (`Package.Record`) referencing another diagram's top event (FTA diagrams). + - Internal FTA nodes (`$BasicEvent`, `$IntermediateEvent`, `$AndGate`, + `$OrGate`) are omitted — they do not cross-link to other diagrams. + +### Concrete example -## Automatic JSON Generation (Bazel) +```text +' overview.puml — top-level leaves are REFERENCES +@startuml +[Gateway] --> [Proxy] +@enduml +``` -`plantuml_links.json` is generated by the `architectural_design()` rule. +```text +' proxy_detail.puml — Proxy has a child → DEFINE +@startuml +package Proxy { [RequestHandler] } +@enduml +``` -The `architectural_design()` rule invokes `//tools/plantuml/linker:linker` on all -`.fbs.bin` FlatBuffers files produced by the PlantUML parser. See -[Link Mapping Format](#link-mapping-format) for a detailed -description of which links are emitted. +`proxy_detail.idmap.json`: +```json +{ "source": "score/mw/com/proxy_detail.puml", + "defines": [{ "alias": "Proxy", "id": "Proxy" }], + "references": [{ "alias": "RequestHandler", "id": "Proxy.RequestHandler" }] } +``` -### Algorithm +`overview.idmap.json`: +```json +{ "source": "score/overview.puml", + "defines": [], + "references": [{ "alias": "Gateway", "id": "Gateway" }, + { "alias": "Proxy", "id": "Proxy" }] } +``` -Given the set of `.fbs.bin` files for one `architectural_design()` target: +Result: `Proxy` in `overview.puml` links to `proxy_detail.puml`. +`Gateway` has no definer → no link. -1. **Build a top-level index** – For each diagram, collect every component whose - `parent_id` is `None` (i.e. it is not nested inside another component). - The index maps `alias → diagram file`. +## idmap Format -2. **Emit links** – For every component in every diagram, look up its alias in - the top-level index. If a *different* diagram defines that alias as a - top-level component, emit a link entry: +`.idmap.json` files are written by the parser and read by this extension. +They are not intended to be authored manually. - ``` - source_file = diagram that contains the reference - source_id = alias of the component - target_file = diagram that defines it as a top-level component - ``` +```json +{ + "source": "path/to/diagram.puml", + "defines": [ + { "alias": "ComponentName", "id": "fully.qualified.Name" } + ], + "references": [ + { "alias": "OtherComponent", "id": "OtherComponent" } + ] +} +``` -3. **Deduplicate** – Sort and deduplicate so that each `(source_file, source_id)` - pair has exactly one target (first alphabetically). Duplicate `source_id` - entries within the same source diagram are removed because PlantUML's - `url of X is [[…]]` directive supports only one URL per alias. +## End-to-End Clickable Diagram Example -### Concrete Example +This minimal example shows what users should create in docs to get a clickable +diagram: +`docs/arch/overview.puml` ```text -' adas_overview.puml — subsystem context @startuml -component ADAS -component BrakeController -component LaneKeepAssist -ADAS --> BrakeController -ADAS --> LaneKeepAssist +[Gateway] --> [Proxy] @enduml ``` +`docs/arch/proxy_detail.puml` ```text -' brake_controller.puml — component detail @startuml -component BrakeController -interface BrakeDemandIF -interface WheelSpeedIF -BrakeController --> BrakeDemandIF -BrakeController <-- WheelSpeedIF +package Proxy { + [RequestHandler] +} @enduml ``` -Generated links — one in each direction: +`docs/arch/overview.rst` +```rst +Overview +======== -```json -{ - "links": [ - { - "source_file": "adas_overview.puml", - "source_id": "BrakeController", - "target_file": "brake_controller.puml" - }, - { - "source_file": "brake_controller.puml", - "source_id": "BrakeController", - "target_file": "adas_overview.puml" - } - ] -} +.. uml:: overview.puml ``` -Clicking `BrakeController` in the overview navigates to its detail diagram; -clicking it in the detail diagram navigates back to the overview. +`docs/arch/proxy_detail.rst` +```rst +Proxy Detail +============ -`ADAS` and `LaneKeepAssist` appear as top-level only in `adas_overview.puml` and -have no dedicated detail diagram, so **no links** are emitted for them. +.. uml:: proxy_detail.puml +``` -(link-mapping-format)= -## Link Mapping Format +When the idmaps contain: -Place one or more `*plantuml_links.json` filesinside the Sphinx source directory: +- `overview.puml` references `Proxy` +- `proxy_detail.puml` defines `Proxy` -```json -{ - "links": [ - { - "source_file": "my_diagram.puml", - "source_id": "ComponentA", - "target_file": "other_diagram.puml" - } - ] -} -``` +the rendered `Proxy` element in `overview.puml` becomes clickable and opens +the page containing `proxy_detail.puml`.