Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,29 @@

# Configuration file for the Sphinx documentation builder.

import os
import sys

# Make local extensions importable
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "extensions"))

project = "S-CORE"
project_url = "https://eclipse-score.github.io/score"
version = "0.1"

# Base URL of the fault models guideline page in the process_description build.
# fault_id values in FMEA XML files (e.g. "MF_01_01") are linked as
# {fmea_fault_model_base_url}#fmea_fault_model__mf_01_01
fmea_fault_model_base_url = (
"https://eclipse-score.github.io/process_description/"
"process_areas/safety_analysis/guidance/fault_models_guideline.html"
)

extensions = [
# TODO: remove plantuml here once
# https://github.com/useblocks/sphinx-needs/pull/1508 is merged and docs-as-code
# is updated with new sphinx-needs version
"sphinxcontrib.plantuml",
"score_sphinx_bundle",
"fmea_xml_table",
]
227 changes: 227 additions & 0 deletions docs/extensions/fmea_xml_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
"""
Sphinx extension that provides the ``fmea_xml_table`` directive.

The directive reads a FMEA XML file and:
1. Registers each entry as a hidden ``feat_saf_fmea`` need in sphinx-needs.
2. Renders all entries as a table with the columns defined in FMEA_COLUMNS.

Usage in RST::

.. fmea_xml_table:: path/to/fmea.xml

The path is resolved relative to the document that contains the directive.
"""

import os
import re
import xml.etree.ElementTree as ET
from collections.abc import Sequence

from docutils import nodes
from sphinx.util.nodes import make_refnode
from sphinx.util.docutils import SphinxDirective
from sphinx_needs.api import add_external_need
from sphinx_needs.data import SphinxNeedsData

FMEA_COLUMNS = [
"id",
"violates",
"fault_id",
"failure_effect",
"mitigated_by",
"sufficient",
"status",
"safety_relevant",
"root_cause",
"content",
]

# Fields passed to add_need (must match metamodel's feat_saf_fmea definition).
# safety_relevant and root_cause are now in the metamodel.
_NEED_EXTRA_KWARGS = {"fault_id", "failure_effect", "sufficient", "safety_relevant", "root_cause"}
_NEED_LINK_KWARGS = {"violates", "mitigated_by"}

# fault_id values (e.g. "MF_01_01") map to needs in the process_description build
# using the prefix "fmea_fault_model__" + lower-case value as the anchor.
_FAULT_ID_NEED_PREFIX = "fmea_fault_model__"


class FmeaNeedRef(nodes.Inline, nodes.Element):
"""Placeholder node for need references rendered by fmea_xml_table."""


class FmeaXmlTable(SphinxDirective):
"""Import FMEA data from an XML file and render it as a table."""

has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}

def run(self) -> Sequence[nodes.Node]:
xml_rel_path = self.arguments[0]
xml_abs_path = self.env.relfn2path(xml_rel_path, self.env.docname)[1]

if not os.path.exists(xml_abs_path):
msg = f"fmea_xml_table: file not found: {xml_abs_path}"
return [nodes.system_message(msg, level=3, type="ERROR", line=self.lineno)]

tree = ET.parse(xml_abs_path)
root = tree.getroot()

entries: list[dict[str, str]] = []
result_nodes: list[nodes.Node] = []

for entry_elem in root.findall("entry"):
row = {
field: (entry_elem.findtext(field) or "").strip()
for field in FMEA_COLUMNS
}
entries.append(row)

violates_str = row["violates"]
mitigated_str = row["mitigated_by"]

add_external_need(
app=self.env.app,
need_type="feat_saf_fmea",
title=row["id"],
id=row["id"],
external_url="",
content=row["content"],
status=row["status"],
fault_id=row["fault_id"],
failure_effect=row["failure_effect"],
sufficient=row["sufficient"],
safety_relevant=row["safety_relevant"],
root_cause=row["root_cause"],
violates=violates_str,
mitigated_by=mitigated_str,
)

result_nodes.append(_build_table(entries, self))
return result_nodes


def _append_fault_id_link(paragraph: nodes.paragraph, value: str, base_url: str) -> None:
"""Render a fault_id value as a link to the fault models guideline page."""
full_need_id = f"{_FAULT_ID_NEED_PREFIX}{value.lower()}"
ref = nodes.reference(value, value, refuri=f"{base_url}#{full_need_id}")
paragraph += ref


def _append_need_links(paragraph: nodes.paragraph, value: str, directive: SphinxDirective) -> None:
"""Render a need-link field value as clickable references."""
need_ids = [token.strip() for token in re.split(r"[,|]", value) if token.strip()]
if not need_ids:
paragraph += nodes.Text(value)
return

for index, need_id in enumerate(need_ids):
if index:
paragraph += nodes.Text(", ")

need_ref = FmeaNeedRef("", reftarget=need_id)
need_ref += nodes.Text(need_id)
paragraph += need_ref


def _resolve_fmea_need_refs(app, doctree: nodes.document, fromdocname: str) -> None:
"""Replace placeholder FMEA need refs with final links once all needs are known."""
needs_view = SphinxNeedsData(app.env).get_needs_view()

for node in doctree.findall(FmeaNeedRef):
need_id = str(node.get("reftarget", ""))
target_need = needs_view.get(need_id)

if target_need and target_need.get("docname") and not target_need.get("is_external"):
node.replace_self(
make_refnode(
app.builder,
fromdocname,
target_need["docname"],
need_id,
nodes.Text(need_id),
need_id,
)
)
continue

external_url = target_need.get("external_url") if target_need else None
if external_url:
ref = nodes.reference(need_id, need_id, refuri=external_url)
external_css = target_need.get("external_css") if target_need else None
if external_css:
ref["classes"].append(external_css)
node.replace_self(ref)
continue

node.replace_self(nodes.Text(need_id))


def _build_table(entries: list[dict[str, str]], directive: SphinxDirective) -> nodes.table:
table = nodes.table()
tgroup = nodes.tgroup(cols=len(FMEA_COLUMNS))
table += tgroup

for _ in FMEA_COLUMNS:
tgroup += nodes.colspec(colwidth=1)

# Header
thead = nodes.thead()
tgroup += thead
hrow = nodes.row()
thead += hrow
for col in FMEA_COLUMNS:
cell = nodes.entry()
cell += nodes.paragraph(text=col)
hrow += cell

# Body
tbody = nodes.tbody()
tgroup += tbody
for row in entries:
trow = nodes.row()
tbody += trow
for col in FMEA_COLUMNS:
cell = nodes.entry()
paragraph = nodes.paragraph()
value = row.get(col, "")
if col in _NEED_LINK_KWARGS and value:
_append_need_links(paragraph, value, directive)
elif col == "fault_id" and value:
base_url = directive.env.app.config.fmea_fault_model_base_url
if base_url:
_append_fault_id_link(paragraph, value, base_url)
else:
paragraph += nodes.Text(value)
else:
paragraph += nodes.Text(value)
cell += paragraph
trow += cell

return table


def setup(app):
app.add_config_value("fmea_fault_model_base_url", "", "env")
app.add_directive("fmea_xml_table", FmeaXmlTable)
app.connect("doctree-resolved", _resolve_fmea_need_refs)
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
58 changes: 3 additions & 55 deletions docs/features/persistency/safety_analysis/fmea.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,59 +40,7 @@ Fault models
- EX_01_06: Processing is not complete (infinite loop): Failure initiator not applicable at persistency, so no mitigation is needed. The feature is developed fully deterministic, so no infinite loop is expected caused by persistency.


.. feat_saf_fmea:: Persistency
:violates: feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore
:id: feat_saf_fmea__persistency__message_nreived
:fault_id: MF_01_01
:failure_effect: Message is not received so the feature persistency is not available.
:mitigated_by: aou_req__persistency__error_handling
:sufficient: yes
:status: valid
Failure Mode List
-----------------

User is not able to use the feature. Middleware cant be used. User is not able to use the feature. Middleware cant be used. Loss of execution can only be caused by the application, not by the persistency feature itself.
Failure handling is addressed to the application by the aou_req__persistency__error_handling.

.. feat_saf_fmea:: Persistency
:violates: feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore
:id: feat_saf_fmea__persistency__late_message
:fault_id: MF_01_02
:failure_effect: message received too late.
:mitigated_by: aou_req__persistency__error_handling
:sufficient: yes
:status: valid

Subset of MF_01_01 if the delay is to long.

.. feat_saf_fmea:: Persistency
:violates: feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore
:id: feat_saf_fmea__persistency__corrupted_message
:fault_id: MF_01_05
:failure_effect: message is corrupted so the feature persistency is not available.
:mitigated_by: aou_req__persistency__error_handling
:sufficient: yes
:status: valid

Covered by MF_01_01

.. feat_saf_fmea:: Persistency
:violates: feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore
:id: feat_saf_fmea__persistency__not_sent
:fault_id: MF_01_06
:failure_effect: message is not sent so the feature persistency is not available.
:mitigated_by: aou_req__persistency__error_handling
:sufficient: yes
:status: valid

Covered by MF_01_01 because the violation cause is the same.

.. feat_saf_fmea:: Persistency
:violates: feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore
:id: feat_saf_fmea__persistency__err_handl
:fault_id: EX_01_04
:failure_effect: loss of execution will lead to an unavailability of the persistency feature.
:mitigated_by: aou_req__persistency__error_handling
:sufficient: yes
:status: valid

User is not able to use the feature. Middleware cant be used. Loss of execution can only be caused by the application, not by the persistency feature itself.
Failure handling is addressed to the application by the aou_req__persistency__error_handling.
.. fmea_xml_table:: fmea.xml
69 changes: 69 additions & 0 deletions docs/features/persistency/safety_analysis/fmea.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<fmea>
<entry>
<id>feat_saf_fmea__persistency__message_nreived</id>
<violates>feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore</violates>
<fault_id>MF_01_01</fault_id>
<failure_effect>Message is not received so the feature persistency is not available.</failure_effect>
<mitigated_by>aou_req__persistency__error_handling</mitigated_by>
<sufficient>yes</sufficient>
<status>valid</status>
<safety_relevant>NA</safety_relevant>
<root_cause>NA</root_cause>
<content>User is not able to use the feature. Middleware cant be used. User is not able to use the feature. Middleware cant be used. Loss of execution can only be caused by the application, not by the persistency feature itself.
Failure handling is addressed to the application by the aou_req__persistency__error_handling.</content>
</entry>

<entry>
<id>feat_saf_fmea__persistency__late_message</id>
<violates>feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore</violates>
<fault_id>MF_01_02</fault_id>
<failure_effect>message received too late.</failure_effect>
<mitigated_by>aou_req__persistency__error_handling</mitigated_by>
<sufficient>yes</sufficient>
<status>valid</status>
<safety_relevant>NA</safety_relevant>
<root_cause>NA</root_cause>
<content>Subset of MF_01_01 if the delay is to long.</content>
</entry>

<entry>
<id>feat_saf_fmea__persistency__corrupted_message</id>
<violates>feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore</violates>
<fault_id>MF_01_05</fault_id>
<failure_effect>message is corrupted so the feature persistency is not available.</failure_effect>
<mitigated_by>aou_req__persistency__error_handling</mitigated_by>
<sufficient>yes</sufficient>
<status>valid</status>
<safety_relevant>NA</safety_relevant>
<root_cause>NA</root_cause>
<content>Covered by MF_01_01</content>
</entry>

<entry>
<id>feat_saf_fmea__persistency__not_sent</id>
<violates>feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore</violates>
<fault_id>MF_01_06</fault_id>
<failure_effect>message is not sent so the feature persistency is not available.</failure_effect>
<mitigated_by>aou_req__persistency__error_handling</mitigated_by>
<sufficient>yes</sufficient>
<status>valid</status>
<safety_relevant>NA</safety_relevant>
<root_cause>NA</root_cause>
<content>Covered by MF_01_01 because the violation cause is the same.</content>
</entry>

<entry>
<id>feat_saf_fmea__persistency__err_handl</id>
<violates>feat_arc_dyn__persistency__check_key_default, feat_arc_dyn__persistency__delete_key, feat_arc_dyn__persistency__flush, feat_arc_dyn__persistency__read_key, feat_arc_dyn__persistency__read_from_storage, feat_arc_dyn__persistency__write_key, feat_arc_dyn__persistency__snapshot_restore</violates>
<fault_id>EX_01_04</fault_id>
<failure_effect>loss of execution will lead to an unavailability of the persistency feature.</failure_effect>
<mitigated_by>aou_req__persistency__error_handling</mitigated_by>
<sufficient>yes</sufficient>
<status>valid</status>
<safety_relevant>NA</safety_relevant>
<root_cause>NA</root_cause>
<content>User is not able to use the feature. Middleware cant be used. Loss of execution can only be caused by the application, not by the persistency feature itself.
Failure handling is addressed to the application by the aou_req__persistency__error_handling.</content>
</entry>
</fmea>
Loading