gabriel / musehub public
issue-226-i2-root-distance-never-a-hard-error.md markdown
85 lines 3.6 KB
Raw
sha256:7281683f5c41e5d88b6d8811fbdafebd3e01a0c9dcd90975cfcb444ba71e8e81 docs: add local source-of-truth for musehub#225, #226, #227… Sonnet 5 18 hours ago

I2 (root distance) is never a hard error anywhere — is that the right call?

Background

Companion to #225 (dead-code cleanup) and #227 (I3 naming collision), surfaced during the same identity-domain invariant investigation.

muse/plugins/identity/plugin.py's docstring lists three invariants:

I1 Acyclicity     — merge() rejects any new relationship that would introduce a cycle.
I2 Root distance  — derived property; not enforced at merge time.
I3 Quorum soundness — hub-level concern; not enforced at merge time.

I1 not being enforced at merge time is fine — it can't be, a local two-branch merge has no way to check org-authorization signatures or global graph position. But I2 turns out not to be enforced anywhere, not just at merge time:

# musehub/graph/push_validator.py
idx = RootDistanceIndex.build(dag)
for rec in identities:
    h = rec["handle"]
    if idx.distance(h) is None:
        warnings.append(
            f"I2 warning: {h!r} ({rec['type']}) has no path to any human root — "
            f"it is an orphaned node"
        )

This is the only place I2 is checked in the entire push/merge pipeline, and it only ever appends a warning. The push is accepted regardless. An identity graph can contain an arbitrary number of nodes with no path to any human root, indefinitely, with nothing ever blocking it.

The actual question

Is "orphaned node, no path to a human root" always a real problem (should eventually be a hard error, at least in some cases), or is it a legitimate transient state that should stay a soft warning forever? Plausible legitimate cases for a temporarily-orphaned node:

  • An identity mid-construction, about to receive its first spawns or member_of edge in the same push/session.
  • An org or agent identity provisioned ahead of the relationship that will connect it (e.g., bulk-importing identities before wiring relationships in a second pass).

Plausible cases where it's a real integrity problem worth blocking:

  • A node that has been orphaned for a long time (some age/grace-period threshold) with no relationship ever added.
  • A node deliberately detached by a malicious or buggy push, e.g. as part of an attempt to hide a node from ancestry-based authorization checks that key off human_ancestors().

Given IdentityGraphService.human_ancestors() exists and is presumably meant to answer "who is accountable for this node," a permanently orphaned node might silently break whatever consumes that answer elsewhere in the system (issue #225 covers that this method currently has no callers either — worth resolving together).

Scope

  1. Decide, deliberately, whether I2 should ever become a hard error, and under what condition (e.g., "no path to root after a grace period" or "no path to root at the end of a specific action type" vs. "never, soft-warning is correct forever because orphaning is always legitimate transient state").
  2. If a hard-error condition is chosen, implement it in push_validator.py alongside the existing warning path, with a test that constructs both the legitimate-transient case and the should-be-blocked case to confirm the distinction actually works.
  3. If soft-warning-forever is the deliberate final answer, update muse/plugins/identity/plugin.py's docstring and any other "I2 ... not enforced" language to say so explicitly and explain why, rather than reading like an unfinished TODO.

Acceptance criteria

  • A documented, deliberate answer exists for "can an identity node stay permanently orphaned, and if not, what actually stops it" — not left as an open question implied by a docstring caveat.
File History 1 commit
sha256:7281683f5c41e5d88b6d8811fbdafebd3e01a0c9dcd90975cfcb444ba71e8e81 docs: add local source-of-truth for musehub#225, #226, #227… Sonnet 5 18 hours ago