docs(decomposition): enhance docstrings and add spec section for selective subtree recomputation
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 1m0s
CI / quality (pull_request) Successful in 1m12s
CI / typecheck (pull_request) Successful in 1m21s
CI / security (pull_request) Successful in 1m30s
CI / push-validation (pull_request) Successful in 30s
CI / build (pull_request) Successful in 41s
CI / helm (pull_request) Successful in 45s
CI / integration_tests (pull_request) Successful in 3m34s
CI / unit_tests (pull_request) Successful in 4m40s
CI / e2e_tests (pull_request) Successful in 4m10s
CI / docker (pull_request) Successful in 1m32s
CI / coverage (pull_request) Successful in 11m7s
CI / benchmark-regression (push) Has been skipped
CI / status-check (pull_request) Successful in 5s
CI / helm (push) Successful in 32s
CI / build (push) Successful in 59s
CI / lint (push) Successful in 1m7s
CI / quality (push) Successful in 1m24s
CI / typecheck (push) Successful in 1m44s
CI / security (push) Successful in 1m46s
CI / push-validation (push) Successful in 24s
CI / integration_tests (push) Successful in 3m36s
CI / unit_tests (push) Successful in 4m59s
CI / e2e_tests (push) Successful in 5m2s
CI / docker (push) Successful in 1m31s
CI / coverage (push) Successful in 10m47s
CI / status-check (push) Successful in 4s
CI / benchmark-regression (pull_request) Successful in 1h4m59s
CI / benchmark-publish (push) Successful in 1h17m4s

This commit was merged in pull request #10771.
This commit is contained in:
2026-04-24 08:26:59 +00:00
parent 8e25e31218
commit 663a6d2397
3 changed files with 99 additions and 2 deletions
+32
View File
@@ -18647,6 +18647,38 @@ The way child plan results are merged depends on the resource type:
* **Other resources**: Pluggable merge strategies based on resource type
* **Non-mergeable resources**: May require sequential execution only
##### Selective Subtree Recomputation for Decision Correction
When a decision in the decomposition tree is found to be incorrect, the system
supports **selective subtree recomputation**: only the subtree rooted at the
target node is re-evaluated, while sibling branches and ancestor nodes are
preserved unchanged.
This is implemented by `DecompositionService.recompute_subtree()`, which:
1. Accepts a `node_id` identifying the root of the subtree to recompute and
an `existing_result` (`DecompositionResult`) containing the current tree.
2. Performs a breadth-first search over `children_ids` to collect all nodes
in the subtree (the target node and all its descendants).
3. Partitions the nodes into two groups:
- **Recomputed nodes** — the target node and all its descendants.
- **Preserved nodes** — all other nodes (siblings, ancestors, and unrelated
branches).
4. Returns a `DecisionCorrectionResult` with both groups, the configuration
used, and diagnostic metrics (`recomputed_count`, `preserved_count`,
`subtree_size`).
**Acceptance criteria:**
| # | Criterion | Behaviour |
| :- | :-------- | :-------- |
| 1 | Leaf recomputation | Only the target leaf is recomputed; all other nodes preserved |
| 2 | Middle-node recomputation | Target node and all its descendants recomputed; ancestors and siblings preserved |
| 3 | Root recomputation | All nodes recomputed; no nodes preserved |
| 4 | Unknown node | `ValueError` raised when `node_id` is not found in the result |
| 5 | Custom config | Caller-supplied `DecompositionConfig` is attached to the result |
| 6 | Metrics | `recomputed_count`, `preserved_count`, and `subtree_size` always present |
#### The Plan "Decision Tree" and Visualization
!!! adr "Architecture Decision"
@@ -134,7 +134,43 @@ class DecompositionResult:
class DecisionCorrectionResult:
"""Result of selective subtree recomputation for decision correction.
Tracks which nodes were recomputed and which were preserved.
Captures the outcome of :meth:`DecompositionService.recompute_subtree`,
separating the nodes that were re-evaluated from those that were left
intact. This enables callers to audit exactly which parts of the
decomposition tree changed and which were preserved.
Attributes:
target_node_id: The ``node_id`` of the root node whose subtree was
recomputed. All descendants of this node (inclusive) appear in
``recomputed_nodes``; all other nodes appear in
``preserved_nodes``.
recomputed_nodes: Nodes that belong to the recomputed subtree
(the target node and all its descendants, identified via BFS
over ``children_ids``).
preserved_nodes: Nodes outside the recomputed subtree that were
left unchanged. Includes sibling branches and all ancestor
nodes of the target.
config: The :class:`DecompositionConfig` used for this correction
pass. Defaults to :class:`DecompositionConfig` with no
arguments when the caller does not supply one.
metrics: Diagnostic counters produced during recomputation.
Always contains the following keys:
- ``recomputed_count`` -- number of nodes in the recomputed
subtree.
- ``preserved_count`` -- number of nodes outside the subtree.
- ``subtree_size`` -- total nodes visited during the BFS
(equal to ``recomputed_count``).
Usage::
result = svc.recompute_subtree(
node_id="middle_a",
existing_result=decomp_result,
)
print(result.recomputed_node_ids) # ["middle_a", "leaf_a1", ...]
print(result.preserved_node_ids) # ["root", "middle_b", ...]
print(result.metrics["recomputed_count"])
"""
target_node_id: str
@@ -399,7 +399,36 @@ class DecompositionService:
existing_result: DecompositionResult,
config: DecompositionConfig | None = None,
) -> DecisionCorrectionResult:
"""Recompute only the subtree rooted at node_id."""
"""Recompute only the subtree rooted at *node_id*.
Identifies the subtree via breadth-first search over
:attr:`DecompositionNode.children_ids` and partitions the nodes
from *existing_result* into two groups: those inside the subtree
(recomputed) and those outside it (preserved). Sibling branches
and ancestor nodes are always preserved.
Args:
node_id: The ``node_id`` of the root of the subtree to
recompute. Must exist in *existing_result*.
existing_result: The current :class:`DecompositionResult`
whose nodes will be partitioned.
config: Optional :class:`DecompositionConfig` to attach to
the result. When ``None``, a default
:class:`DecompositionConfig` is used.
Returns:
A :class:`DecisionCorrectionResult` containing:
- ``recomputed_nodes`` -- nodes in the subtree rooted at
*node_id* (inclusive).
- ``preserved_nodes`` -- all other nodes from
*existing_result*.
- ``metrics`` -- diagnostic counters (``recomputed_count``,
``preserved_count``, ``subtree_size``).
Raises:
ValueError: If *node_id* is not found in *existing_result*.
"""
cfg = config or DecompositionConfig()
node_map: dict[str, DecompositionNode] = {
n.node_id: n for n in existing_result.nodes