Hierarchical Needs with upward_bundles#
Use upward_bundles when a documentation bundle owns Needs at a lower
architectural level and those Needs refer to Needs owned by a higher level.
The declaration is the validation interface of the bundle; it is separate
from the bundles declarations that decide where documentation is mounted.
This How-To uses the SCORE metamodel and the following ownership model:
the module’s
docs()source owns the external-to-the-componentsfeat_reqand the module architecture Need;a component is a
docs_bundleand owns acompand acomp_req;a subcomponent is another
docs_bundleand owns its owncompandcomp_req;both component requirements derive from the module-level feature requirement.
Here, external means external to the component bundle’s ownership. It does not necessarily mean a different Bazel module or repository.
Declare the hierarchy#
The module and its component bundles can be declared in one Bazel package:
load("//:docs.bzl", "docs", "docs_bundle")
docs_bundle(
name = "component",
source_dir = "component",
metamodel = "//src/extensions/score_metamodel:metamodel_yaml",
# The module's own source bundle is generated by docs().
upward_bundles = [":docs_source_bundle"],
)
docs_bundle(
name = "subcomponent",
source_dir = "subcomponent",
metamodel = "//src/extensions/score_metamodel:metamodel_yaml",
# The module is inherited transitively through component.
upward_bundles = [":component"],
)
docs(
source_dir = "module",
project = "Example module",
project_url = "https://example.invalid/module",
metamodel = "//src/extensions/score_metamodel:metamodel_yaml",
bundles = [
{"bundle": ":component", "mount_at": "component"},
{
"bundle": ":subcomponent",
"mount_at": "component/subcomponent",
},
],
)
upward_bundles points from the child to its parent. The parent does not
mount the child through this attribute. Mounting remains a separate,
downward bundles relationship in docs().
The same attribute is available on docs() when the module’s own source
bundle is itself below another documentation bundle:
docs(
source_dir = "docs",
upward_bundles = ["@platform_docs//:docs_bundle"],
bundles = [
{"bundle": ":component", "mount_at": "component"},
],
)
This makes the ancestor export available both to the module’s private source
bundle and to the final composed needs_json build. The generated
:docs_source_bundle remains the upward parent for bundles declared in the
same module. The legacy :_docs_source_bundle label is still accepted, but
new consumers should use the stable public name.
The generated :docs_source_bundle is the module’s own source-level bundle
and is available as the host’s upward hierarchy anchor. This allows
hierarchical bundles to be declared in another Bazel package. Its reusable
Needs export is available as :docs_source_bundle_needs_upward.
The public :docs_bundle target is the composed documentation tree and
must not be used as the upward parent when it already mounts the child; that
would introduce a dependency cycle.
If the module uses a custom conf.py and its feature requirement lives in
module/index.rst, add the module name to required_in_id so the
three-part SCORE requirement ID remains meaningful after the source directory
is stripped:
extensions = ["score_sphinx_bundle"]
version = "0.0.0"
required_in_id = ["module"]
Write the Needs#
The module source can own the feature and module Needs:
.. feat:: Seat heating
:id: feat__seat_heating
:version: 1
:security: NO
:safety: QM
:status: valid
.. feat_req:: Seat heating availability
:id: feat_req__module__seat_heating
:version: 1
:reqtype: Functional
:security: NO
:safety: QM
:status: valid
:valid_from: v1.0
:satisfied_by: feat__seat_heating
The seat heating feature is available to the vehicle user.
.. mod:: Seat heating module
:id: mod__seat_heating_module
:version: 1
:includes: comp__seat_heating_controller
The component bundle can refer to the module-level feat_req while owning
its own requirement and architecture element:
.. comp:: Seat heating controller
:id: comp__seat_heating_controller
:version: 1
:security: NO
:safety: QM
:status: valid
:belongs_to: feat__seat_heating
:consists_of: comp__seat_heating_sensor
.. comp_req:: Controller temperature control
:id: comp_req__component__temperature_control
:version: 1
:reqtype: Functional
:security: NO
:safety: QM
:status: valid
:derived_from: feat_req__module__seat_heating
:satisfied_by: comp__seat_heating_controller
The controller regulates the requested heating level.
The subcomponent follows the same SCORE pattern. Its derived_from link
tests the transitive closure: subcomponent declares only component,
while component declares the module source bundle.
Verify the composed graph#
Build the normal Needs target from the module package:
$ bazel build //path/to/module:needs_json
The resulting graph must contain the module-level feat_req, the component
and subcomponent comp_req Needs, and their resolved derived_from links.
The same fixture should also be rendered with bazel run //path/to/module:docs
to verify the final mounted document paths.
Bundle-local exports#
For every source-bearing docs_bundle the macro also creates
<name>_needs_local and <name>_needs_upward. The first parses only the
bundle’s own sources. The second is the reusable export: it merges the local
Needs with the direct upward_bundles exports, whose files already contain
their own upward closure. This makes the target useful as an explicit input to
another bundle while keeping ownership local.
The public needs_json target still evaluates the complete composed source
tree. It remains the right target for final backlinks, mounted document paths,
global checks, metrics, and rendered documentation.
There is no separate <name>_needs_downward target: downward composition is
owned by the host docs() target and is exposed as its public needs_json
target.
For the separation of composition and Needs ownership, see the architecture concept.