Files
hermes-agent/tests/tools/test_kanban_provenance.py
Teknium 3b7ff435fd fix(kanban): preserve durable origins for worker-created tasks
Carry the owning task's notification subscriptions independently of dependency
edges, within the creation transaction. Prefer its durable session over worker
and request-local sessions while preserving explicit overrides. Cover worker
CLI create and built-in decomposition, and retain conversation route anchors.
Auto-subscribe no longer upgrades an inherited passive subscription.

Slim adaptation of Christopher-Schulze's session-precedence fix in #85687,
expanded to durable subscription provenance and sibling creation paths.
Related: #85575, #85687

Validation: strict RED/GREEN (7 failing cases before; 7 passing after), then
58 Kanban test files: 383 passed, 2 skipped. Real dispatcher-spawn subprocess
probe covers direct, linked, unlinked, explicit-session, worker CLI, built-in
children and a plain CLI negative control, with recording transport only.

Co-authored-by: Christopher <210261288+Christopher-Schulze@users.noreply.github.com>
2026-09-07 14:16:57 -07:00

62 lines
3.2 KiB
Python

"""Worker provenance is not a dependency edge or a transient runtime session."""
import json
import pytest
@pytest.mark.parametrize("linked,explicit", [(False, None), (True, None), (False, "override")])
def test_worker_create_keeps_durable_origin(tmp_path, monkeypatch, linked, explicit):
from hermes_cli import kanban_db as kb, kanban_db_connect as kbc, kanban_db_notify as kn
from tools import kanban_tools as kt, async_delegation
from gateway.session_context import set_session_vars, clear_session_vars
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
kb.init_db()
with kbc.connect_closing() as conn:
owner = kb.create_task(conn, title="owner", session_id="durable")
kn.add_notify_sub(conn, task_id=owner, platform="discord", chat_id="chat",
user_id="user", notifier_profile="default", delivery_mode="notify",
delivery_metadata={"scope_id": "guild", "parent_chat_id": "forum"})
expected = kn.list_notify_subs(conn, owner)[0]
monkeypatch.setenv("HERMES_KANBAN_TASK", owner)
monkeypatch.setenv("HERMES_SESSION_ID", "ephemeral")
monkeypatch.setattr(async_delegation, "_current_origin_session_id", lambda: "api-origin")
# Even a matching current channel must not upgrade an inherited passive policy.
tokens = set_session_vars(platform="discord", chat_id="chat", profile="default")
try:
result = json.loads(kt._handle_create(dict(title="child", assignee="default",
parents=[owner] if linked else [], session_id=explicit)))
finally:
clear_session_vars(tokens)
assert result["ok"], result
with kbc.connect_closing() as conn:
child = kb.get_task(conn, result["task_id"])
assert child.session_id == (explicit or "durable")
subs = kn.list_notify_subs(conn, child.id)
assert len(subs) == 1
for key in ("platform", "chat_id", "user_id", "delivery_mode", "delivery_metadata", "notifier_profile"):
assert subs[0][key] == expected[key]
assert bool(conn.execute("SELECT 1 FROM task_links WHERE child_id = ?", (child.id,)).fetchone()) == linked
def test_tool_subscription_captures_conversation_anchors(tmp_path, monkeypatch):
from hermes_cli import kanban_db as kb, kanban_db_connect as kbc, kanban_db_notify as kn
from tools import kanban_tools as kt
from gateway.session_context import set_session_vars, clear_session_vars
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.delenv("HERMES_KANBAN_TASK", raising=False)
kb.init_db()
tokens = set_session_vars(platform="discord", chat_id="thread", chat_type="thread",
scope_id="guild", parent_chat_id="forum", profile="default")
try:
result = json.loads(kt._handle_create(dict(title="direct", assignee="default")))
finally:
clear_session_vars(tokens)
assert result["ok"], result
with kbc.connect_closing() as conn:
metadata = kn.list_notify_subs(conn, result["task_id"])[0]["delivery_metadata"]
assert metadata["scope_id"] == "guild"
assert metadata["parent_chat_id"] == "forum"