A global admin observes and steers system agent threads

Maintainer, 2026-10-04: "this is no entitlement ⇒ this is access to the bug fix thread", "make a standing rule for global admin for access", "i want to also contribute to thread and be able to steer … same as claude code".

The situation it resolves

The platform's own agents — the pull-request babysitter and fixer, triage, the bug-fix pool, rounds a supervisor relaunches — run their conversations in system and package partitions (for example Hosting/Triage/_Thread/…). No person holds a write grant there, and a platform admin is not a data superuser, so an operator could at best READ such a thread through an entitlement and could never post into it or stop its round. Viewer and Commenter carry neither Update (posting and stopping are writes to the thread node) nor Thread (the permission the delivery gate demands for the cells a round writes).

The rule

ThreadAccessRule (src/MeshWeaver.AI/ThreadAccessRule.cs) is registered for both Thread and ThreadMessage. For everybody it decides exactly what SatelliteAccessRule decided before — a conversation is a satellite of the node it is about. When that says no, and the caller is a global admin (hub.IsGlobalAdmin(userId)), it adds:

Node type A global admin may Never
Thread Read; Create (a delegated sub-thread a steered round opens); Update (post a message, stop or redirect the round); Thread and Comment on the thread Delete
ThreadMessage Read; Create; Update of a cell the admin wrote Delete; Update of a cell somebody else wrote

Scope is the type, not the starter. The widening covers every conversation outside a person's home partition — in practice the system and package partitions the platform's agents run in. No "started by a system identity" predicate is evaluated (maintainer, 2026-10-04: "keep it SIMPLE … Keep ONE exclusion only: threads inside another person's own home partition"); a shared space's ordinary conversations are therefore observable and steerable by a global admin too.

And for both: nothing in a person's home partition (a partition whose root is a User or VUser, matched against UserNodeType.NodeType / VUserNodeType.NodeType — their private conversations; the admin's own home included, where the fold already grants its owner everything). A partition whose root cannot be read counts as private, so the rule fails closed. A cell's author is read from the STORED cell, never from the submitted payload. The node the thread is about, and every other non-thread node, is untouched: the rule governs these two types and nothing else, and GetEffectivePermissions answers for the admin exactly what it answered before.

How the seams ask it

The rule is a core INodeTypeAccessRule for the CRUD operations and an INodeTypePermissionRule for Thread / Comment (core MeshWeaver, Doc/Architecture/AccessControl → the rule table). All three seams consult it through NodeTypeAccessRuleGate, only after the ordinary fold denied: the RLS validator (queries, writes), the [RequiresPermission] delivery gate (submissions, stops, the round's cell creates) and the stream-cache read gate (every node view). So the Threads app's groups, the thread page and an MCP get agree.

Opening versus listing

The rule decides access to a thread reached BY ITS PATH: a link in the Threads app, a notification or a triage item opens it. The Threads app's groups LIST threads with a partitions:all query, and that fan-out spans the partitions the viewer can enumerate. A system partition the admin holds no grant on is not enumerated, so its threads are not listed there, even though each one opens. On the control instance the operator holds a Viewer entitlement on Hosting, so the babysitter, triage and bug-fix groups list as well. ThreadGroupsAccessTest pins this listing boundary.

What steering means

A message the admin posts is the next user turn, and the round that answers it runs as the admin — exactly as it would for any other sender. The agent then acts with the admin's own grants, never with the identity that started the thread; anything the admin may not do, the steered agent may not do either.

Pinned by