Skip to content

fix(registry): allow ancestor↔descendant A2A so audit_summary can reach PM - #102

Merged
HongmingWang-Rabbit merged 1 commit into
mainfrom
fix/can-communicate-ancestor-chain
Apr 15, 2026
Merged

fix(registry): allow ancestor↔descendant A2A so audit_summary can reach PM#102
HongmingWang-Rabbit merged 1 commit into
mainfrom
fix/can-communicate-ancestor-chain

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

Discovered via deep workspace inspection during a maintenance cycle: every audit cron in molecule-dev was structurally broken. Security Auditor / UIUX Designer / QA Engineer would correctly try to deliver their hourly `audit_summary` to PM (per #50 + #75), the platform A2A proxy would reject it with 'access denied: workspaces cannot communicate per hierarchy', and the agent would silently fall back to delegating to its direct parent (Dev Lead). PM's category_routing dispatcher was never reached.

Live evidence (just now)

From Security Auditor's memory:
```
Conversation: Delegation results are ready. Review them and take appropriate action:

  • [failed] Delegating to f3531ba4-d5b2-49c6-b312-950413706e45
    Error: access denied: workspaces cannot communicate per hierarchy
    ```
    (`f3531ba4...` is PM's workspace ID. Security Auditor's parent is Dev Lead.)

From Security Auditor's recent activity log:
```
2026-04-15T05:13:07 a2a_send ok Delegating to Backend Engineer
2026-04-15T05:12:25 a2a_send ok Delegating to Dev Lead
2026-04-15T03:51:15 a2a_send ok Delegating to Dev Lead
```
Every fall-back delegation went to Dev Lead instead of PM. PM's inbox shows zero `audit_summary` deliveries this entire session.

Root cause

`registry.CanCommunicate()` only allowed: self → self, siblings, root-level siblings, direct parent ↔ child. A grandchild → grandparent (Security Auditor → Dev Lead → PM) was rejected. The original design wanted strict hierarchy to prevent rogue horizontal A2A — but it also broke the leadership-chain pattern that any audit/escalation flow needs.

Fix

Generalise to ancestor ↔ descendant. Any workspace can talk to any ancestor (any depth) and any descendant (any depth). Direct parent/child remains a fast path that avoids the walk; sibling rules unchanged; cross-subtree A2A still rejected.

Implementation: `isAncestorOf(ancestorID, childID)` walks the parent chain in Go with a `maxAncestorWalk=32` safety cap so a malformed cycle in the workspaces table cannot loop forever. One DB lookup per step. For a typical 3-deep tree this adds 1-2 extra lookups beyond the direct-parent fast path. Could be optimised to a single recursive CTE later if profiling shows it matters.

Tests (13 total, all passing)

Test Behaviour Status
SameWorkspace self ↔ self unchanged
Siblings same parent unchanged
RootSiblings both root unchanged
ParentToChild direct unchanged (fast path)
ChildToParent direct unchanged (fast path)
Allowed_GrandparentToGrandchild NEW — PM → BE
Allowed_GrandchildToGrandparent NEW — Security Auditor → PM (the actual bug)
Allowed_DeepAncestor NEW — 4-level chain
Denied_UnrelatedAncestors NEW — cross-subtree walks terminate denied
Denied_DifferentParents unrelated pairs (extended with walk-lookup mocks) unchanged behaviour
Denied_CousinToRoot extended walk mocks unchanged behaviour
Denied_CallerNotFound DB error fail-secure unchanged
Denied_TargetNotFound DB error fail-secure unchanged

The previous `Denied_Grandchild` test (which asserted the wrong behaviour) was REPLACED.

Local verification

```
ok github.com/Molecule-AI/molecule-monorepo/platform/internal/registry 0.005s (13 tests)
```

Why platform-level

Per the 'platform-wide fixes are mine to ship' rule. Every org template hits the same broken audit-routing chain — fixing it at the platform benefits all users, not just molecule-dev. This unblocks #50 (PM dispatcher prompt) and #75 (category_routing) which both assumed audit_summary delivery worked.

Applied locally

Per the apply-locally rule, I'm rebuilding + force-recreating the platform container in parallel with this PR. Within ~2 min the live system will accept Security Auditor → PM, and the next hourly security audit (next fire 05:17 UTC, +5 min) will deliver its first audit_summary to PM successfully.

Related

🤖 Generated with Claude Code

…ch PM

Found via deep workspace inspection during a maintenance cycle: Security
Auditor's hourly cron correctly tries to delegate_task its audit_summary
to PM, the platform proxy rejects with "access denied: workspaces cannot
communicate per hierarchy", the agent falls back to delegating to its
direct parent (Dev Lead), and PM's category_routing dispatcher (#75) is
never reached.

This breaks the audit-routing contract end-to-end. Every audit cycle was
landing on Dev Lead instead of being fanned out via PM's category_routing
to the right dev role (security → BE+DevOps, ui/ux → FE, etc).

## Root cause
`registry.CanCommunicate()` only allowed:
- self → self
- siblings (same parent)
- root-level siblings
- direct parent → child
- direct child → parent

A grandchild → grandparent (Security Auditor → PM, where parent is Dev
Lead and grandparent is PM) was DENIED. The original design wanted strict
hierarchy to prevent rogue horizontal A2A — but it also broke the
fundamental "child can talk to its leadership chain" pattern that any
audit/escalation flow needs.

## Fix
Generalise to ancestor ↔ descendant. Any workspace can talk to any
ancestor (any depth) and any descendant (any depth). Direct parent/child
remains a fast path that avoids the walk. Sibling rules unchanged.

Cousins still cannot directly communicate (would need to go through their
shared ancestor). Cross-subtree A2A is still rejected.

Implementation: `isAncestorOf(ancestorID, childID)` walks the parent
chain in Go with a maxAncestorWalk=32 safety cap so a malformed cycle in
the workspaces table cannot loop forever. One DB lookup per step. For a
typical 3-deep tree, this adds 1-2 extra lookups vs the old direct-parent
fast path. Could be optimized to a single recursive CTE if profiling
shows it matters; not now.

## Tests
- TestCanCommunicate_Denied_Grandchild → REPLACED with two new tests:
  - TestCanCommunicate_Allowed_GrandparentToGrandchild
  - TestCanCommunicate_Allowed_GrandchildToGrandparent  (the actual bug)
- TestCanCommunicate_Allowed_DeepAncestor — 4-level chain
- TestCanCommunicate_Denied_UnrelatedAncestors — ensures cross-subtree
  walks still terminate denied
- TestCanCommunicate_Denied_DifferentParents — extended with the walk
  lookup mocks so sqlmock doesn't log warnings
- TestCanCommunicate_Denied_CousinToRoot — same

All 13 tests pass clean. The previous direct parent/child / siblings /
self tests are unchanged (fast paths preserved).

## Why platform-level
Per the "platform-wide fixes are mine to ship" rule. Every org template
hits the same broken audit-routing chain — fixing it at the platform
benefits all users, not just molecule-dev. This unblocks #50 (PM
dispatcher prompt) and #75 (category_routing).
@HongmingWang-Rabbit
HongmingWang-Rabbit merged commit edcfd61 into main Apr 15, 2026
7 checks passed
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the fix/can-communicate-ancestor-chain branch April 16, 2026 12:30
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
…-chain

fix(registry): allow ancestor↔descendant A2A so audit_summary can reach PM
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