feat(control-lane): a plan never contains a listing — targets (query + count), action-plan/v2 - #5792
Conversation
…gets (query + count), action-plan/v2 SpaceDeletion.PlanSteps replaces StepsOf: every set a step acts on is a ControlLanePlanTarget (an anchored, scoped query with its count) — the subtree uncounted, outside dependents and a network as counts only — the same plan MeshWeaver.Plugins#2426's DeleteSpaceRunner.PlanOf shows. A plan with targets digests as action-plan/v2 exactly as ActionPlanSnapshot.Digest; target-less stays v1. Recycle states its address as path:X (count 1) and no longer lists its network in the notes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Moderate correctness issues remain in malformed-target handling and deletion target/count alignment.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
This PR updates control-lane plans to use structured query/count targets and action-plan/v2 digests while preserving v1 compatibility.
Changes:
- Reworks deletion and recycle plans to avoid member listings.
- Adds target-aware digest encoding and validation.
- Updates tests and architecture documentation.
| File | Summary |
|---|---|
test/MeshWeaver.Graph.Test/ControlLaneTest.cs |
Tests v1/v2 digests and target behavior. |
src/MeshWeaver.Graph/ControlLane/SpaceDeletion.cs |
Generates structured deletion targets. |
src/MeshWeaver.Graph/ControlLane/ControlLaneOperations.cs |
Uses structured plans for deletion and recycle. |
src/MeshWeaver.Graph/ControlLane/ControlLaneEnvelopes.cs |
Adds targets and digest versioning. |
src/MeshWeaver.Documentation/Data/Architecture/ControlLane.md |
Documents target-based digest semantics. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| public static string RootQuery(string space) => $"path:{space}"; | ||
|
|
||
| /// <summary>The space's direct children — a rootless space's content roots. Pure.</summary> | ||
| public static string ChildrenQuery(string space) => $"namespace:{space} scope:children"; |
There was a problem hiding this comment.
Acknowledged, and not changed in this PR on purpose. These queries and labels are byte-for-byte the plan MeshWeaver.Plugins#2426 introduced (DeleteSpaceRunner.PlanOf), and after this PR the in-process runner delegates to this function. Changing the predicate here would therefore change both paths at once. It should be a deliberate design change, not a side effect of the lane alignment. Two things narrow the gap. First, the GitSync step runs before content deletion, so a rootless space's direct _GitSync row is gone before the content roots are deleted. Second, the count comes from the inventory, and the inventory is what executes. Filed to bug triage as a follow-up for the shared plan (rbuergi/Feedback/plan-target-queries-vs-inventory-predicates-20260927).
| $"namespace:{space} scope:descendants nodeType:{AccessAssignmentGuard.AccessAssignmentNodeType}"; | ||
|
|
||
| /// <summary>Every GitSync configuration node in the space. Pure.</summary> | ||
| public static string GitSyncQuery(string space) => $"namespace:{space} scope:descendants nodeType:{GitSyncNodeType}"; |
There was a problem hiding this comment.
Same as the sibling thread. The query is #2426's shared plan shape, and the bound count comes from the inventory predicate that executes (IsGitSync is path-or-type). A path-only _GitSync row counted but not matched by nodeType:GitHubSyncConfig is a real display/test mismatch. Aligning it means giving both targets non-overlapping queries (the exact _GitSync path plus typed entries). That belongs in the shared plan for both paths, and it is filed in the same triage item (rbuergi/Feedback/plan-target-queries-vs-inventory-predicates-20260927).
Test Results (shard 0) 1 files 1 suites 3m 8s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)410 tests 410 ✅ 1m 2s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
Test Results (shard 4)1 678 tests 1 678 ✅ 4m 1s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
Test Results (shard 3)1 006 tests +545 1 006 ✅ +545 6m 6s ⏱️ + 5m 9s Results for commit 18b93f4. ± Comparison against base commit f7dd509. This pull request removes 461 and adds 1006 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results (shard 2) 1 files 1 suites 5m 56s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
Test Results (shard 5) 2 files 2 suites 8m 6s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
Test Results 8 files 8 suites 28m 22s ⏱️ Results for commit 18b93f4. ♻️ This comment has been updated with latest results. |
|
⛔ Merge-queue steward: left dequeued — a job other than a test shard failed — a build or gate failure is never a flake.
Not a catalogued flake. Fix the failure, or — with run URLs, an issue and an assertion-message pattern — add it to |
|
Re-queueing on evidence: the queue build's only red was |
|
⛔ Merge-queue steward: left dequeued — a job other than a test shard failed — a build or gate failure is never a flake.
Not a catalogued flake. Fix the failure, or — with run URLs, an issue and an assertion-message pattern — add it to |
…ward re-queues it, capped Five core PRs (#5789 #5791 #5792 #5793 #5795) sat queue-rejected on 2026-09-27 for one reason: 'MeshWeaver.Plugins did not answer within 42 min (no verdict yet)' — the dependent's legs never got a runner, while every candidate that did run was green. The steward classed that as a gate/build failure ('never a flake') and left them out. - await-dependent-verdict.py: silence exits EXIT_NO_VERDICT=3 (still red, never a pass); every verdict-shaped red stays exit 1. - dotnet-test.yml: the wait step hands exit 3 to its own failing step, 'No verdict in time: the dependent's suites did not report (infrastructure)'. - merge-queue-steward.py: a Dependent-suites job that failed ONLY on that step is starved, not failed — re-queue kind=infra, capped 2 per head sha with the marker comment; beside a build failure or an uncatalogued assertion it still rejects; a verdict red still rejects. Nine new self-test rows (66 ok). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/MeshWeaver.Documentation/Data/Architecture/ControlLane.md # test/MeshWeaver.Graph.Test/ControlLaneTest.cs
…letion: targets for GitSync / teardown (grants, subtree, NodeTypes), counts not listings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

What
This is a follow-up to #5785. It aligns the control lane's plans with MeshWeaver.Plugins#2426, per the rule that a plan never contains a listing.
ControlLanePlanTarget(label, query, count) andControlLanePlanStep.Targets: the same shape as Plugins'PlanTarget/ActionPlanStep.Targets.ControlLanePlan.Digest(): a plan whose steps carry targets digests asaction-plan/v2. This is the exact encoding ofActionPlanSnapshot.Digest: per step, the target count, then each target's query and count, never the label. A plan without targets staysv1byte for byte.SpaceDeletion.PlanStepsreplacesStepsOf. It emits the plan RoutingGrain no-live-subscriber storm: 20,718 errors/3h on memex-cloud (~2/s) — dead portal/circuit and cache/ addresses churned as errors #2426'sDeleteSpaceRunner.PlanOfshows:path:Xwith count 1.path:X scope:subtreewith no count.path:Xwith count 1. Its network is a count plus the digest of the exact address set, and the notes no longer list any addresses.Doc/Architecture/ControlLanedigest section is updated. The "Owner commands" section is untouched, and Secrets: write-only entry, split identities — operator verbs, design doc, CI guard #5790 owns that wording.Verification
dotnet build -c Release -warnaserror, 0 warnings:MeshWeaver.Graph,MeshWeaver.Graph.Test,Memex.Portal.Shared,MeshWeaver.Documentation.Test.ControlLaneTest14/14. The newAPlanWithTargets_IsTheActionPlanV2Encodingpins the v2 preimage and shows that a label is not bound. The DeleteSpace end-to-end test now asserts that no step command names a member (Doomed/Page) and that the subtree target is uncounted.MeshWeaver.Documentation.Test649/649.Cross-repo
Pairs-with: none —
SpaceDeletion.StepsOfis removed, but it merged hours ago in #5785 and nothing outside core references it. Its only consumer is the unmerged Plugins branchfeat/control-instance-lane(Systemorph/MeshWeaver.Plugins#2433), which moves toPlanStepsin the same wave. TheNamedDependentsconstant goes with it for the same reason.Implementers: none — no member is added to an existing interface.
Mirror-sync: none — no i18n key is added or changed.
🤖 Generated with Claude Code