Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
53 changes: 53 additions & 0 deletions bazel/rules/rules_score/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,59 @@ py_binary(
],
)

# AoU chain-forwarding filter for raw TRLC source records (companion to
# aou_forwarding_to_lobster, operating on .trlc sources instead of .lobster
# JSON) -- used by dependable_element to expose forwarded AoUs' TRLC records
# via TrlcProviderInfo.
py_binary(
name = "filter_forwarded_trlc",
srcs = [
"src/aou_forwarding_to_lobster.py",
"src/filter_forwarded_trlc.py",
"src/trlc_record_utils.py",
],
imports = ["src"],
main = "src/filter_forwarded_trlc.py",
visibility = ["//visibility:public"],
deps = [
"@lobster//lobster/common",
requirement("pyyaml"),
],
)

# Retypes a dependable_element's own AoU TRLC records for first-hop external
# exposure (companion to filter_forwarded_trlc.py, which does the same
# retyping for AoUs received from a dependency and chain-forwarded further)
# -- used by dependable_element so a raw ScoreReq.AoU record never leaves the
# TRLC compilation of the assumptions_of_use target that authored it.
py_binary(
name = "expose_own_aou_trlc",
srcs = [
"src/expose_own_aou_trlc.py",
"src/trlc_record_utils.py",
],
imports = ["src"],
main = "src/expose_own_aou_trlc.py",
visibility = ["//visibility:public"],
)

# Deduplicates AoU/ReceivedAoU TRLC records that reach the same consumer via
# more than one path (e.g. a requirements target depending both directly on
# an AoU's original owner and on an intermediate dependable_element that
# chain-forwards that same AoU) -- same package + record name is retained
# deliberately (see filter_forwarded_trlc.py), so without this step such a
# diamond dependency shape would trip TRLC's own duplicate-definition check.
py_binary(
name = "dedupe_aou_trlc",
srcs = [
"src/dedupe_aou_trlc.py",
"src/trlc_record_utils.py",
],
imports = ["src"],
main = "src/dedupe_aou_trlc.py",
visibility = ["//visibility:public"],
)

# HTML merge tool
py_library(
name = "sphinx_html_merge_lib",
Expand Down
2 changes: 1 addition & 1 deletion bazel/rules/rules_score/docs/rule_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ and scope checks at build/test time.
* - Target
- Purpose
* - ``<name>``
- Main target: build runs Sphinx; ``bazel test`` runs the traceability check
- Main target: build runs Sphinx; ``bazel test`` runs the traceability check. Also provides ``TrlcProviderInfo`` (``@trlc//:trlc.bzl``), aggregating this element's own AoU TRLC records (retyped from the original ``AoU`` to ``ScoreReq.ReceivedAoU``, same package + record name, with a fixed generic ``justification`` field injected -- this retyping only applies to what this ``TrlcProviderInfo`` re-exposes; a target depending directly on the original ``assumptions_of_use`` target still gets the true, unmodified ``AoU`` record) with the ``ScoreReq.ReceivedAoU`` records synthesized (retyped from the original ``AoU``/``ReceivedAoU``, same package + record name, with a ``justification`` field injected from ``aou_forwarding.yaml``) for anything it chain-forwards (see ``aou_forwarding`` above) — a downstream ``component_requirements``/``feature_requirements``/``assumed_system_requirements`` target can list this label directly in its own ``deps`` to resolve a ``derived_from`` reference to one of those AoUs, instead of needing direct visibility to the original ``assumptions_of_use`` target. ``deps`` on this provider is always an empty depset: AoU/ReceivedAoU records have no typed cross-reference fields of their own to resolve.
* - ``<name>_doc``
- Internal ``sphinx_module`` target; usable as ``deps`` in other Sphinx builds
* - ``<name>_index``
Expand Down
84 changes: 77 additions & 7 deletions bazel/rules/rules_score/docs/user_guide/assumptions_of_use.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,22 @@ the ``CompReq`` that implements it, alongside any ``FeatReq``/
Two things are required for the reference to resolve:

1. ``import`` the AoU's package, same as any other TRLC cross-reference.
2. List the ``assumptions_of_use`` target that defines (or, for a received/
forwarded AoU, originally defined) the record in the
``component_requirements`` target's ``deps``. This target provides
TrlcProviderInfo, so it can be listed directly -- no intermediate wrapper
is needed.
2. List, in the ``component_requirements`` target's ``deps``, either:

- the ``assumptions_of_use`` target that defines (or, for a received/
forwarded AoU, originally defined) the record, **or**
- the ``dependable_element`` you already depend on that owns or
chain-forwards it. Every ``dependable_element`` also provides
``TrlcProviderInfo``, aggregating its own AoU records (retyped to
``ReceivedAoU`` for this external exposure -- see below) with the
``ReceivedAoU`` records it synthesizes for anything it
chain-forwards via ``aou_forwarding`` (see below) -- so a downstream
requirements target does not need direct visibility to the AoU's
ultimate origin several ``deps`` hops away; it only needs to depend on
the ``dependable_element`` immediately in front of it.

Either way, no intermediate wrapper target is needed -- both kinds of
label already provide ``TrlcProviderInfo`` directly.

.. code-block:: text
:caption: examples/integrator/docs/requirements/component_requirements.trlc
Expand All @@ -145,8 +156,7 @@ Two things are required for the reference to resolve:
testonly = True,
deps = [
":feature_requirements",
"@seooc//docs:sample_aous",
"@some_other_library//:other_library_aous",
"@seooc//:safety_software_seooc_example",
],
)

Expand All @@ -156,6 +166,66 @@ TRLC parser itself at build time, not by a later lobster-report matching step
-- while the resulting lobster item is still tagged and traced exactly as
before, so the coverage report is unaffected.

**Why every externally-exposed AoU is synthesized as ``ReceivedAoU``, not
the raw ``AoU``**
A downstream target can always resolve a ``derived_from`` reference to an
AoU by depending directly on the ``assumptions_of_use`` target that
authored it -- that gets the true, unmodified ``AoU`` record and is the
normal, fully-linked way to consume an AoU, unaffected by anything below.
What must never happen is that raw ``AoU`` record being re-exposed,
verbatim, through a *``dependable_element``'s own* ``TrlcProviderInfo`` --
used by anything that depends on the ``dependable_element`` label instead
of the ``assumptions_of_use`` target directly, precisely so it does not
need to know the AoU's true owner. Doing so verbatim would look, to any
tooling walking the consumer's requirements model, like a second,
independently authored assumption needing its own full
control-measure/safety-analysis linkage, when it is really just a
forwarding/exposure placeholder. Instead, every AoU a ``dependable_element``
re-exposes through its own ``TrlcProviderInfo`` -- whether it is one of the
element's own (first-hop exposure) or one it received from a dependency and
is chain-forwarding further -- is *retyped* to ``ScoreReq.ReceivedAoU`` (a
distinct type, itself extending ``ControlMeasure`` like ``AoU``) with a
mandatory ``justification`` field injected: a fixed, generic notice for the
element's own AoUs (there is no per-AoU forwarding decision to source text
from -- an element's own AoUs are always exposed in full, unconditionally,
unlike chain-forwarding which is gated by ``aou_forwarding.yaml``), or the
``justification`` text carried over from the ``aou_forwarding.yaml`` entry
that authorized the forward. Critically, the retyped record keeps the
**exact same package and record name** as the original -- only its declared
type and the added ``justification`` field change -- so every
``derived_from = [Package.Name@version]`` reference written anywhere in the
chain keeps resolving unchanged, no matter how many hops away from the
original owner it is, or whether the element you depend on is the AoU's
original owner or a forwarder several hops downstream of it.

**Diamond dependencies: automatic deduplication on consumption**
Preserving the AoU's original identity across every forwarding hop (and
across the very first hop of exposure) has one consequence that needs
handling: the same identity can legitimately reach a single TRLC parse via
more than one path -- e.g. a ``component_requirements`` target that lists
both the AoU's original owner and an intermediate ``dependable_element``
that chain-forwards that same AoU directly in its own ``deps`` (a "diamond"
dependency shape). TRLC's own duplicate-definition check keys on
``package + record name`` alone, not on declared type, so without any
further handling this would be rejected as a duplicate definition. To
prevent this, both the point where a ``dependable_element`` collects what it
received from its own ``deps`` and the point where any
``feature_requirements``/``component_requirements``/
``assumed_system_requirements``/``assumptions_of_use`` target merges
``TrlcProviderInfo`` across its own ``deps`` run a deduplication pass (see
``dedupe_aou_trlc.py`` / ``aou_trlc_dedupe.bzl``): whenever the same
``Package.RecordName`` AoU/ReceivedAoU identity is declared more than once
across the merged files, only one declaration is kept and the rest are
dropped before TRLC ever sees them. Since a raw ``AoU`` record can only ever
be legitimately authored once (by its true owner, internally) and never
leaves that owner's own compilation, any identity collision reachable
externally is, by construction, always the same original reached via a
different path -- never two independently-authored, unrelated AoUs that
happen to share a name. This runs automatically -- there is nothing to
configure -- and only ever touches ``AoU``/``ReceivedAoU`` records; a
genuine duplicate definition of any other record type is left alone and
still fails as a real authoring error.

**Example: three-level forwarding chain** (the real working code for this
example lives in ``examples/some_other_library``, ``examples/seooc``, and
``examples/integrator``)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,20 @@ feature_requirements(

component_requirements(
name = "component_requirements",
testonly = True,
srcs = [
"component_requirements.trlc",
],
visibility = ["//visibility:public"],
deps = [
":feature_requirements",
"@seooc//docs:sample_aous",
"@some_other_library//:other_library_aous",
# A single dep on the immediate dependable_element dependency is
# enough to resolve both AoU references below -- it aggregates its
# own AoU (SampleType.SampleAoU) and the AoU it chain-forwards from
# its own dependency (OtherLibrary.TimingConstraint) via
# TrlcProviderInfo. No direct visibility to
# @some_other_library//:other_library_aous (the AoU's ultimate
# origin, several `deps` hops away) is needed.
"@seooc//:safety_software_seooc_example",
],
)
5 changes: 5 additions & 0 deletions bazel/rules/rules_score/lobster/config/lobster_aou.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ conversion-rules:
namespace: req
version-field: version
description-fields: description
- package: ScoreReq
record-type: ReceivedAoU
namespace: req
version-field: version
description-fields: description
81 changes: 81 additions & 0 deletions bazel/rules/rules_score/private/aou_trlc_dedupe.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# *******************************************************************************
# Copyright (c) 2026 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
# *******************************************************************************
"""Shared Starlark helper wiring the ``dedupe_aou_trlc`` tool into a rule action.

An AoU's TRLC identity (package + record name) is deliberately preserved
verbatim by ``filter_forwarded_trlc.py`` when it retypes a chain-forwarded
AoU to ``ScoreReq.ReceivedAoU`` -- this is what lets a ``derived_from``
reference stay valid no matter how many hops of forwarding it has been
through. The unavoidable consequence is that the *same* AoU identity can
legitimately appear in more than one ``.trlc`` file that end up merged into a
single TRLC parse/check -- most commonly in a diamond dependency shape (a
target depends both directly on an AoU's original owner and, transitively,
on an intermediate element that chain-forwards that same AoU). TRLC's own
duplicate-definition check keys on ``(package, name)`` alone, not on
declared type, and rejects this outright.

``dedupe_aou_trlc_files`` runs the ``dedupe_aou_trlc`` tool (see
``src/dedupe_aou_trlc.py``) over a list of files whenever there is more than
one, so any such duplicate AoU/ReceivedAoU identity is collapsed down to a
single declaration before those files are merged for a TRLC
parse/render/check. Used by both ``dependable_element.bzl`` (deduplicating
what it received from its own ``deps`` before chain-forwarding) and
``requirements.bzl`` (deduplicating what a `feature_requirements`/
`component_requirements`/`assumed_system_requirements`/`assumptions_of_use`
target's own ``deps`` expose, which is where the diamond shape most commonly
surfaces for a *downstream* consumer).
"""

def dedupe_aou_trlc_files(ctx, tool, files, output_subdir):
"""Deduplicate AoU/ReceivedAoU TRLC records across a list of files.

Args:
ctx: Rule context (used for ``ctx.actions`` and ``ctx.label``).
tool: ``executable`` File for the ``dedupe_aou_trlc`` tool (an
attribute resolved via ``ctx.executable.<attr_name>``).
files: List of ``File`` to deduplicate. Returned unchanged (no
action is run) if it has fewer than two entries -- a single
file cannot contain a cross-file duplicate.
output_subdir: Subdirectory name (relative to ``ctx.label.name``)
to declare the deduplicated output files under. Callers using
this helper more than once within the same rule implementation
must pass a distinct value each time to avoid output path
collisions.

Returns:
A list of ``File``, order-aligned with ``files``: either ``files``
itself unchanged (fewer than two entries), or a matching list of
freshly declared, deduplicated output files.
"""
if len(files) < 2:
return files

outputs = [
ctx.actions.declare_file("{}/{}/{}_{}".format(ctx.label.name, output_subdir, i, f.basename))
for i, f in enumerate(files)
]

args = ctx.actions.args()
args.add_all("--inputs", files)
args.add_all("--outputs", outputs)

ctx.actions.run(
inputs = files,
outputs = outputs,
executable = tool,
arguments = [args],
progress_message = "Deduplicating AoU TRLC records for %s" % ctx.label.name,
mnemonic = "AoUTrlcDedupe",
)

return outputs
Loading
Loading