Skip to content

ActivationRecycle: recycle a NodeType's live activations on every process - #6054

Merged
rbuergi merged 7 commits into
mainfrom
feat/type-recycle-broadcast
Oct 4, 2026
Merged

rbuergi merged 7 commits into
mainfrom
feat/type-recycle-broadcast

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

What

Type-scoped recycle: dispose every LIVE activation of a NodeType, on every process of the mesh, with an audit. This is the core half of the maintainer's direction (2026-10-04): "system should have rights ⇒ must be called by build queue as system" and "can't we put recycle automatically to all nodes of this type?".

A new build of a NodeType reaches only activations created after it; existing per-node hubs stay on the previous build until disposed (StaleStateUntilRecycle). On the control instance the bug-fix pool kept serving pre-import code for hours because nothing recycled them and a platform admin cannot (no Update on Hosting, by design).

How

  • ActivationRecycle.Request(hub, request) writes Admin/_Recycle/{id} (nodeType: ActivationRecycle) as System. The durable write is the broadcast: every process hears that commit on its IMeshInvalidationFeed (PostgreSQL LISTEN/NOTIFY in production; in-process on a monolith). No cluster-singleton grain.
  • Each process disposes what IT hosts: it walks its mesh hub's HostedHubsCollection (the existing silo-local hosted-hub registry), keeps the per-node hubs whose NodeTypePathHolder names a requested type and that are still running, and posts one DisposeRequest each off the router (NodeOperationIssuingHub()), with the request's reason (sanitised), in paced batches (20 per 500 ms). No index of stored instances is read; cold addresses are never touched; each target quiesces normally.
  • Each process reports {request}/{process} (ActivationRecycleReport: matched, disposed, addresses). Request + reports = the audit (who, when, why, types, per-silo counts).
  • Once: a process handles a request path once; a hub already tearing down is never selected.
  • Refused, nothing written: no reason, nothing named, unknown NodeType, or NodeType itself.

No new rights for anyone: the request is a System write. In MeshWeaver.Plugins the build queue's recycle job issues it (on a NodeType's new build, or enqueued by a platform admin over MCP — enqueueing is a platform action). The direct recycle verb (Update holders) is unchanged.

Tests (executed locally)

  • ActivationRecycleTest (Graph.Test, monolith): two live activations of T and one of U → exactly the two disposed, one report (matched 2 / disposed 2), T reactivates on a FRESH hub; a redelivered request disposes nothing again; a hub already tearing down is not selected; unknown type / blank reason / NodeType refused with nothing written. Falsified: removing the type filter or the per-process dedupe reds two cases.
  • TwoSiloActivationRecycleTest (Orleans.Test): T's 12 activations spread over both silos, all disposed, U spared, two reports whose counts equal what each silo hosted. Falsified: without the shared change feed (silo 2 never hears the commit) it reds.
  • Full MeshWeaver.Graph.Test: 2496/2496. MeshWeaver.Documentation.Test guards: 665/665. Orleans guards (cluster sizing, grain activation sites) green.

The two-silo fixture gains an opt-in shared storage change feed (ShareStorageChanges, the LISTEN/NOTIFY shape) through a new InMemoryStorageAdapter constructor overload; existing fixtures are unchanged.

Docs

Doc/Architecture/StaleStateUntilRecycle → "Recycling a type"; AGENTS.md core-only note under the DisposeRequest section.

Paired: MeshWeaver.Plugins PR (Hosting/Queue recycle job, new-build rule, MCP enqueue_recycle) consumes ActivationRecycle once this reaches a sealed set.

🤖 Generated with Claude Code

…cess

A new build of a NodeType reaches only activations created after it; the
existing per-node hubs keep the build they were born with until something
disposes them. ActivationRecycle is the type-scoped recycle:

- ActivationRecycle.Request writes Admin/_Recycle/{id} as System. The durable
  write IS the broadcast: every process hears it on its IMeshInvalidationFeed
  (LISTEN/NOTIFY in production), no cluster-singleton grain.
- Each process walks its mesh hub's HostedHubsCollection, keeps the per-node
  hubs bound from a requested NodeType (NodeTypePathHolder) that are still
  running, and posts one DisposeRequest each off the router, with the
  request's reason, in paced batches (20 per 500 ms). No index of stored
  instances is read, so cold addresses are never touched.
- Each process writes {request}/{process} (ActivationRecycleReport) as its
  audit; a request path is handled once per process.
- Refused with nothing written: no reason, nothing named, unknown NodeType,
  or the definition type NodeType itself.

Tests: ActivationRecycleTest (monolith: exactly T's two live activations,
fresh reactivation, redelivery, refusals) and TwoSiloActivationRecycleTest
(both silos' activations disposed, one report per silo matching what each
hosted). The two-silo fixture gains an opt-in shared storage change feed
(the LISTEN/NOTIFY shape) via an InMemoryStorageAdapter overload.

Docs: StaleStateUntilRecycle "Recycling a type"; AGENTS.md core-only note.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi enabled auto-merge October 3, 2026 22:49
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  3 files    3 suites   3m 11s ⏱️
561 tests 370 ✅ 191 💤 0 ❌
565 runs  374 ✅ 191 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

    5 files      5 suites   3m 9s ⏱️
1 336 tests 1 336 ✅ 0 💤 0 ❌
1 337 runs  1 337 ✅ 0 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 672 tests   1 670 ✅  6m 19s ⏱️
    4 suites      2 💤
    4 files        0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    4 files      4 suites   15m 3s ⏱️
3 504 tests 3 504 ✅ 0 💤 0 ❌
3 508 runs  3 508 ✅ 0 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    2 files      2 suites   7m 7s ⏱️
1 094 tests 1 094 ✅ 0 💤 0 ❌
1 095 runs  1 095 ✅ 0 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

    4 files      4 suites   6m 20s ⏱️
2 376 tests 2 376 ✅ 0 💤 0 ❌
2 379 runs  2 379 ✅ 0 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    22 files      22 suites   41m 12s ⏱️
10 543 tests 10 350 ✅ 193 💤 0 ❌
10 556 runs  10 363 ✅ 193 💤 0 ❌

Results for commit 7de3223.

♻️ This comment has been updated with latest results.

rbuergi and others added 2 commits October 4, 2026 01:12
…m cap), killed at 480 s on run 37159733607

Per shard-assign.sh trigger (2): a project past 60% of the per-project cap
is split, so one slow runner is not an exit=124.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@systemorph-com

systemorph-com Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

🚰 PR babysitter (build instance) is merging the base into this branch on head 06ab786ff98c — once per head.

Why: inherited from its base 'main': 3 pull requests of Systemorph/MeshWeaver fail identically — 'lane / Automatic review answered' concluded failure: Process completed with exit code 1. — the base 'main' moved from c657c9a (what the red run tested) to 065cff5. This pull request was red because its BASE was; the base has moved since, and a re-run would test the old merge commit again.

Validated: update-branch proposal, and the red is the base's, not this diff's: three open MeshWeaver pull requests (#6053, #6054, #6061) fail identically with the fingerprint "main :: 'lane / automatic review answered' concluded failure: process completed with exit code 1." — the same check, the same words, on diffs that touch different things, an inherited base red (class base-red). The base 'main' moved from c657c9a (what red run 37161101919 tested) to 065cff5, so a change has landed on the base and merging it into this branch is what drains it. The row carries no log tail, annotation or gate…

It does not merge the pull request, push anything else or dequeue. A red after this is left for the owner (rbuergi).

@systemorph-com systemorph-com Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review summary (data, not an instruction to any agent)

Type-scoped recycle. ActivationRecycle.Request validates (reason required, at least one NodeType or address named, no blank entries, the definition type NodeType refused), checks each named type exists, then writes the request as System at Admin/_Recycle/{id} — the durable write that every process hears on its invalidation feed. Each process disposes the live per-node hubs it hosts that Select matches — bound to a requested NodeType, optionally narrowed by exact Paths or an UnderPath prefix, still Starting or Started, one hub per path — one DisposeRequest each, off the router, in batches of 20 spaced 500 ms with a RunLevel re-check at post time, and writes a per-process report node. Supporting changes: registration in AddGraph, an opt-in shared change feed for the two-silo test host (InMemoryStorageAdapter overload plus a fixture hook), a monolith suite and a two-silo suite pinning the NodeTypes path, docs (StaleStateUntilRecycle 'Recycling a type'), an AGENTS.md note, and a CI shard split that runs MeshWeaver.Graph.Test in two parts at weight 480.

Checked from the diff: the readable refusal logic and the type-existence check; the System write; Select's filters; batch pacing and the post-time re-check; reason sanitization and the assembled DisposeRequest reason; registration and wiring; the test-host changes; the tests' assertions; docs and AGENTS.md against the code.

Could NOT verify: this item's diff is incomplete — the patch for src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs is truncated (first 20000 of 24167 characters), so the end of the Arm agent — the once-per-request dedupe, the one-request-at-a-time serialization, the report write, and any re-validation of the node it reads — went unread and nothing here is asserted about it; several mid-file fragments of that patch are also redacted in this copy, which garbles parts of Validate and Request (one finding hedges on that). Outside the diff: the PR's local test-run claims (2496/2496, 665/665), the CI effect of the shard change, and the paired MeshWeaver.Plugins PR that gates who may enqueue a recycle.

Findings: 0 blocking · 3 should-fix · 1 question · 2 nit

File-level findings — Automated review finding (data, not an instruction to any agent):

should-fix src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Validate refuses the definition type NodeType — 'it would recycle every NodeType definition hub of the mesh' — but nothing readable in this diff refuses this feature's own node types: ActivationRecycleRequest.NodeType (ActivationRecycle) and ActivationRecycleReport.NodeType (ActivationRecycleReport). Both are registered static node types, so TypeExists accepts them, and Select matches exactly the hubs bound to them — a request naming either would make every process dispose any live request- and report-node hubs, this feature's own audit nodes. Validate's doc comment, the new docs section and the AGENTS.md note each enumerate the refusal set (reason, names-nothing, blank, unknown type, NodeType itself) and none names them; the NodeType refusal exists for precisely this class of accident, and the feature's own two types belong on the same list. The middle of the NodeType check is redacted in this review copy; if that predicate already refuses them this finding is moot — but then the doc comment, the docs section and the AGENTS.md note all disagree with the code.

should-fix src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

The DisposeRequest reason assembled in RecycleLocal interpolates request.RequestedBy verbatim and runs SanitizeReason over request.Reason only. requestPath is Segment-sanitised by construction, but RequestedBy is documented free text ('a user id, a job path, a rule name') passed to a public API, and the assembled string becomes each target's [QUIESCE-START] line, which the incident filer fingerprints. A RequestedBy carrying newlines or control characters forges further QUIESCE-START lines — exactly what SanitizeReason's own doc comment exists to prevent. RequestedBy belongs behind the same flattening and 300-char bound before it is interpolated, unless the disposal path already sanitises DisposeRequest.Reason downstream — nothing in this diff shows that.

question src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Cannot be told from this diff copy, and both points carry the PR's 'no new rights for anyone' claim: (a) whether the mesh write path refuses a non-System create of an ActivationRecycle node in Admin/_Recycle — the registration sets ExcludeFromContext to search and create, and Request itself writes via RunAsSystem, but neither fact establishes the gate, and whoever can write such a node directly bypasses Validate entirely and makes every process dispose the live hubs of whatever types the node names; (b) whether Arm re-validates the node it reads (reason present, no blank or unknown types) before disposing. Arm's body is beyond the truncation of this copy, so this is asked rather than asserted.

nit src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Request and Reports resolve AccessService with GetService and then dereference it in access.RunAsSystem with no null guard, while Arm in the same file null-guards its service lookups, and the adjacent GetRequiredService for IMeshService at least names the missing service. On a hub whose provider lacks AccessService this fails with a bare NullReferenceException instead of a diagnosable error — GetRequiredService, or a guard that refuses, matches the file's own style.

⚠️ 🚨 The diff is INCOMPLETE: 1 patch(es) truncated and 0 omitted (budget 150000 characters, 20000 per file) — say so in the review summary and do not assert anything about what you could not read.


Internal review of b94c6d6d2b6a708a76ae8c62d51b0672487f8caa — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.

@@ -0,0 +1,187 @@
using System;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix — Automated review finding (data, not an instruction to any agent)

Neither suite exercises the Paths or the UnderPath narrowing of ActivationRecycleRequest, although both are advertised (PR body and the new docs section: 'optionally narrowed to some addresses, or to the activations under one path') and both carry non-obvious logic in Select: Trim of slash runs, case-insensitive comparison, the exact-or-under-slash-prefix rule, and the mode where NodeTypes is empty and any type at a named path matches. Two cases would pin them: a Paths-narrowed recycle sparing a same-typed sibling, and an UnderPath-narrowed one sparing the same type outside the path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 54c1-series commit 'ActivationRecycle: review …': APathsNarrowedRecycle_SparesTheSameTypeSibling (Paths matched slash-trimmed and case-insensitively) and AnUnderPathNarrowedRecycle_SparesTheSameTypeOutsideThePath. The NodeTypes-empty mode no longer exists: Validate now requires at least one NodeType, and Paths/UnderPath only narrow it.

builder.AddUiContributionType();
builder.ConfigureServices(s => s.AddSingleton<UiContributionCatalog>());
// Type-scoped recycle (Admin/_Recycle): every process disposes the live activations it
// HOSTS of the requested NodeTypes — the per-process LiveActivationRegistry is filled by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — Automated review finding (data, not an instruction to any agent)

The comment says 'the per-process LiveActivationRegistry is filled by MeshNodeHubFactory', but the code it introduces walks the existing HostedHubsCollection (RecycleLocal resolves it from the mesh hub), as do the PR body, the AGENTS.md note and the docs section; no LiveActivationRegistry appears anywhere in this diff. A stale name from an earlier iteration — the comment and the code disagree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: the comment now names HostedHubsCollection; LiveActivationRegistry was an earlier iteration and is gone.

@systemorph-com

systemorph-com Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

🚰 PR babysitter (build instance) is merging the base into this branch on head b94c6d6d2b6a — once per head.

Why: inherited from its base 'main': 3 pull requests of Systemorph/MeshWeaver fail identically — 'lane / Automatic review answered' concluded failure: Process completed with exit code 1. — the base 'main' moved from 065cff5 (what the red run tested) to 1144fb5. This pull request was red because its BASE was; the base has moved since, and a re-run would test the old merge commit again.

Validated: The red is the base's, not this diff's: 3 open pull requests of Systemorph/MeshWeaver (#6053, #6054, #6065) fail identically on 'lane / Automatic review answered' concluded failure: Process completed with exit code 1 — the same check, the same words, fingerprint 'main :: lane / automatic review answered concluded failure'. The red runs tested base main at 065cff5; main has since moved to 1144fb5, so the failure was inherited before this branch was ever tested against the current base. Approving the proposed update-branch (merge the moved base into the branch) so the failed runs are r…

It does not merge the pull request, push anything else or dequeue. A red after this is left for the owner (rbuergi).

@systemorph-com systemorph-com Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review summary (data, not an instruction to any agent)

Type-scoped recycle. ActivationRecycle.Request validates (reason required, at least one NodeType or address named, no blank entries, the definition type NodeType refused), checks each named NodeType exists, then writes the request as System at Admin/_Recycle/{id} — the durable write that every process hears on its invalidation feed. Each process's agent disposes the live per-node hubs it hosts that Select matches — bound to a requested NodeType, optionally narrowed by exact Paths or an UnderPath prefix, still Starting or Started, one hub per path — one DisposeRequest each, off the router, in batches of 20 spaced 500 ms with a RunLevel re-check at post time, and writes a per-process report node. Supporting changes: registration in AddGraph, an opt-in shared change feed for the two-silo test host (InMemoryStorageAdapter overload plus a fixture hook), a monolith suite and a two-silo suite, docs (StaleStateUntilRecycle "Recycling a type"), an AGENTS.md note, and a CI shard split running MeshWeaver.Graph.Test in two parts at weight 480. Checked from the diff: the readable refusal logic and the type-existence check; the System write; Select's filters; batch pacing and the post-time re-check; reason sanitization and the assembled DisposeRequest reason; registration and wiring; the test-host changes; the tests' assertions; docs and AGENTS.md against the code. Could NOT verify: this item's diff is incomplete — the patch for src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs is truncated (first 20000 of 24167 characters), so the tail of the Arm agent — the once-per-request dedupe, the one-request-at-a-time serialization, the report write, and any re-validation of the node it reads — went unread and nothing here is asserted about it; several mid-patch fragments of that file and of both test files are also scrubbed in this copy, which garbles parts of Validate, Request and RecycleLocal — the affected findings hedge on exactly those spots. Outside the diff: the PR's local test-run claims (2496/2496, 665/665), the CI effect of the shard change, and the paired MeshWeaver.Plugins PR that gates who may enqueue a recycle. A review of the previous head b94c6d6d raised the same should-fix points (the feature's own node types absent from the refusal set, RequestedBy unsanitized in the DisposeRequest reason, no Paths/UnderPath cases in either suite); each is still present in this head's cumulative diff.

Findings: 0 blocking · 3 should-fix · 2 question · 2 nit

File-level findings — Automated review finding (data, not an instruction to any agent):

should-fix src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Validate refuses a request naming the definition type — the MeshNode.NodeTypePath check answers "'NodeType' is refused: it would recycle every NodeType definition hub" — but as far as this copy shows nothing refuses this feature's own node types, ActivationRecycleRequest.NodeType ("ActivationRecycle") and ActivationRecycleReport.NodeType ("ActivationRecycleReport"). Both are registered node types (AddActivationRecycle registers both), so a request naming either passes Validate, and Select matches exactly the hubs bound to them: every process would dispose its live request- and report-node hubs — this feature's own audit nodes. The NodeType refusal exists for precisely this class of accident, and the refusal sets written in the Validate doc comment, the new docs section and the AGENTS.md note name only reason / names-nothing / blank / unknown type / NodeType. The middle of the NodeType check is scrubbed in this review copy; if that predicate already refuses the two ActivationRecycle types the finding is moot — but then the doc comment, the docs section and the AGENTS.md note all understate the code.

should-fix src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

The DisposeRequest reason assembled in RecycleLocal prefixes "ActivationRecycle '{requestPath}' (requested by {request.RequestedBy …}): " to SanitizeReason(request.Reason) — only request.Reason goes through SanitizeReason, while request.RequestedBy is interpolated verbatim. RequestedBy is documented free text ("a user id, a job path, a rule name") on a public API, and the assembled string is rendered into each target's [QUIESCE-START] line, which the incident filer fingerprints — exactly the log forgery that SanitizeReason's own doc comment ("the same log-forgery rule MeshOperations.SanitizeReason states") exists to prevent; RequestedBy also escapes the 300-character bound. It belongs behind the same flattening and bound before interpolation, unless DisposeRequest.Reason is sanitised somewhere downstream — nothing in this diff shows that.

question src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Cannot be told from this diff copy, and both points carry the PR's "no new rights for anyone" claim: (a) whether the mesh write path refuses a non-System create of an ActivationRecycle node in Admin/_Recycle — the registration sets ExcludeFromContext {search, create} and Request itself writes via RunAsSystem, but neither fact establishes the gate, and the durable write IS the broadcast: whoever can write such a node directly bypasses Validate entirely and makes every process dispose the live hubs of whatever the node names, including NodeType; (b) whether Arm re-validates the node it reads (reason present, no blank or unknown types) before disposing. Arm's body is beyond this copy's truncation, so this is asked rather than asserted.

question src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

The id parameter of Request is documented as "a dedupe key: a second request with the same id is not written again", but the readable body of Request contains no dedupe: it derives nodeId = Segment(id or a truncated default) and calls mesh.CreateNode once. Segment maps every non-alphanumeric character to '-' and collapses empty runs, so distinct ids ("build.42" and "build-42") collapse onto one node path, and a caller-provided id is unbounded in length (only the generated default is cut at 30). Whether a second write to the same path is refused, errors, or succeeds idempotently — which is what the documented dedupe amounts to — depends on mesh.CreateNode's behavior for an existing path, which this diff does not show; the documented behavior is implemented nowhere visible here.

nit src/MeshWeaver.Graph/Configuration/ActivationRecycle.cs

Request and Reports resolve AccessService with GetService and then call access.RunAsSystem with no null guard, while Arm null-guards its service lookups and the adjacent IMeshService is at least resolved with GetRequiredService. On a hub whose provider lacks AccessService this fails with a bare NullReferenceException instead of a diagnosable error — GetRequiredService, or a guard that refuses, matches the file's own style.

⚠️ 🚨 The diff is INCOMPLETE: 1 patch(es) truncated and 0 omitted (budget 150000 characters, 20000 per file) — say so in the review summary and do not assert anything about what you could not read.


Internal review of 4fcd8d5990e79297443735cbca74c50f03642f7d — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.

@@ -0,0 +1,187 @@
using System;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix — Automated review finding (data, not an instruction to any agent)

Neither suite exercises the Paths or the UnderPath narrowing of ActivationRecycleRequest, although both are advertised (PR body and the new docs section: "optionally narrowed to some addresses, or to the activations under one path") and both carry non-obvious logic in Select: slash-trimmed, case-insensitive path sets; the exact-or-under-slash-prefix rule; and the NodeTypes-empty mode, in which a Paths-only request matches ANY bound type at the named addresses — TypeExists runs only over NodeTypes, so a Paths-only request also skips the type check entirely. Two cases would pin them: a Paths-narrowed recycle sparing a same-typed sibling, and an UnderPath-narrowed one sparing the same type outside the path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix as the thread above: both narrowing cases are pinned, and a Paths-only request is refused (a request is always type-scoped, so the type check always runs).

builder.AddUiContributionType();
builder.ConfigureServices(s => s.AddSingleton<UiContributionCatalog>());
// Type-scoped recycle (Admin/_Recycle): every process disposes the live activations it
// HOSTS of the requested NodeTypes — the per-process LiveActivationRegistry is filled by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — Automated review finding (data, not an instruction to any agent)

The added comment says "the per-process LiveActivationRegistry is filled by MeshNodeHubFactory", but no LiveActivationRegistry appears anywhere in this diff: RecycleLocal resolves the existing HostedHubsCollection from the mesh hub, and the PR body, the AGENTS.md note and the docs section all name HostedHubsCollection. A stale name from an earlier iteration — the comment and the code disagree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, same as above.

rbuergi and others added 2 commits October 4, 2026 08:23
…d dedupe, sanitize requester

- Validate: a request is always type-scoped (Paths/UnderPath only narrow);
  NodeType and the feature's own ActivationRecycle/ActivationRecycleReport
  types are refused.
- Each process acts only on a request node created by System and re-validates
  it, so writing Admin/_Recycle directly (a platform admin holds Admin)
  bypasses nothing.
- The request id is a real dedupe key (an existing request is answered, not
  rewritten); a malformed or over-long id is refused instead of collapsed.
- RequestedBy goes through SanitizeReason before it reaches [QUIESCE-START].
- AccessService resolved with GetRequiredService; the agent arms only with one.
- Tests: Paths narrowing spares a same-type sibling, UnderPath spares the type
  outside the subtree, id dedupe + malformed id, a non-System request disposes
  nothing (falsified), own types refused. Docs updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Answers to the file-level findings of the internal review (fixed in ActivationRecycle: review — refuse own types, System-only requests, id dedupe, sanitize requester):

  • should-fix — own node types not refused: fixed. RefusedNodeTypes = NodeType, ActivationRecycle, ActivationRecycleReport; both of the feature's own types are covered by ARefusedRequest_WritesNothing.
  • should-fix — RequestedBy unsanitized: fixed. It goes through SanitizeReason (flattened onto one line and bounded) before it is interpolated into the DisposeRequest reason.
  • question (a) — who may write a request / (b) re-validation: each process now acts only on a request node whose CreatedBy is System, and it re-runs Validate on what it reads. Writing Admin/_Recycle directly therefore disposes nothing. A platform admin holds Admin and could do so; ARequestNotWrittenBySystem_DisposesNothing pins it, and the test was falsified by removing the check.
  • question — the dedupe id: now real. An existing request at the id is answered and nothing is written again. An id that is not letters, digits and -, or is longer than 80 characters, is refused rather than rewritten, so build.42 and build-42 cannot collapse. Pinned by ARequestId_IsADedupeKey_AndAMalformedIdIsRefused.
  • nit — AccessService null: Request and Reports resolve it with GetRequiredService, and the agent arms only when it is present.

Shard 4's red in the previous run was PolymorphicResolverAllocationTest.UnrelatedRegistryEntries_DoNotAllocateAClosurePerCandidate (4,577 bytes vs 1,024, measured under CI load). This diff does not touch MeshWeaver.Messaging.Hub, and the test passes locally 3/3 on this branch. Graph.Test's earlier exit=124 is addressed by running it in two shard parts (406 s solo, 85% of the 8m cap).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant