Files
ClawdDotNet/tests/ClawdDotNet.Core.Tests/Cases/CaseRulesTests.cs
T
RichardandClaude Opus 5.5 36fe1ad2aa Eskalation, Konflikt und Beschluss als eigene Vorgaenge (#689, Stufe 1+2)
- Cases/Decisions in der Instanz-DB; CaseDesk fuehrt die Regeln, der Brett-Task traegt
  den Vorgang zum jeweiligen Halter
- Eskalation nur nach oben, eine Ebene je Schritt; die Leitung entscheidet (mit Begruendung,
  bindet ihren Bereich), erledigt selbst oder reicht mit Einschaetzung weiter
- Erst nachsehen: bindende Beschluesse werden vor dem Eskalieren gezeigt; dieselbe Frage
  aus derselben Einheit schliesst sich dem offenen Vorgang an
- Tagesdeckel je Einheit; nach der Frist wird ein Mensch gerufen (Anschluss fuer #644)
- Konflikt: zuerst die Gegenseite, dann Zustellung an die niedrigste gemeinsame Leitung;
  Ebenen dazwischen vermerkt, nicht befragt
- Fremdinhalt-Stufe (#715): Eskalation ohne eigenen Text, kein Konflikt mit Position
- Jede Ebene haengt an (#697), beim Halter gerahmt als Daten
- Escalation ist ein implizites Werkzeug jedes Agenten; Briefing nennt den Weg
- Scanner behaelt einen im Lauf gesetzten Status blockiert/abgebrochen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:46:20 +02:00

104 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ClawdDotNet.Core.Cases;
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Organisation;
using Shouldly;
namespace ClawdDotNet.Core.Tests.Cases;
/// <summary>#689: The rules of escalation, conflict and decision — read off the tree, none configured.</summary>
public sealed class CaseRulesTests
{
/// <summary>
/// purchasing (lead: buy-lead) › team-a (lead: a-lead; buyer-a), team-b (lead: b-lead; buyer-b);
/// devops (lead: ops-lead; dev). No lead above the top units — the top is a human.
/// </summary>
private static OrgChart House() => new(
[
new UnitConfig { Id = "purchasing", Name = "Purchasing", LeaderAgentId = "buy-lead" },
new UnitConfig { Id = "team-a", Name = "Team A", ParentId = "purchasing", LeaderAgentId = "a-lead" },
new UnitConfig { Id = "team-b", Name = "Team B", ParentId = "purchasing", LeaderAgentId = "b-lead" },
new UnitConfig { Id = "devops", Name = "DevOps", LeaderAgentId = "ops-lead" }
],
[
new AgentConfig { AgentId = "buy-lead", UnitId = "purchasing" },
new AgentConfig { AgentId = "a-lead", UnitId = "team-a" },
new AgentConfig { AgentId = "buyer-a", UnitId = "team-a" },
new AgentConfig { AgentId = "b-lead", UnitId = "team-b" },
new AgentConfig { AgentId = "buyer-b", UnitId = "team-b" },
new AgentConfig { AgentId = "ops-lead", UnitId = "devops" },
new AgentConfig { AgentId = "dev", UnitId = "devops" }
]);
[Fact]
public void An_escalation_goes_to_the_own_lead_one_level_at_a_time()
{
var org = House();
CaseRules.NextHolder(org, "buyer-a").ShouldBe("a-lead");
CaseRules.NextHolder(org, "a-lead").ShouldBe("buy-lead");
CaseRules.NextHolder(org, "buy-lead").ShouldBe(CaseRules.Human, "above the top is a human");
CaseRules.NextHolder(null, "anyone").ShouldBe(CaseRules.Human);
}
[Fact]
public void Two_teams_of_one_area_are_decided_by_that_area_s_lead_and_nobody_above_is_bothered()
{
var route = CaseRules.RouteConflict(House(), "a-lead", "b-lead");
route.Decider.ShouldBe("buy-lead");
route.Informed.ShouldBeEmpty();
}
[Fact]
public void Two_areas_go_to_the_top()
{
var route = CaseRules.RouteConflict(House(), "buy-lead", "ops-lead");
route.DecidedByHuman.ShouldBeTrue();
}
[Fact]
public void A_conflict_is_delivered_directly_and_the_levels_between_are_told_not_asked()
{
var route = CaseRules.RouteConflict(House(), "buyer-a", "dev");
route.DecidedByHuman.ShouldBeTrue("purchasing and devops meet only at the top");
route.Informed.ShouldBe(["a-lead", "buy-lead", "ops-lead"], ignoreOrder: true);
}
[Fact]
public void Someone_above_the_other_side_decides_it_that_is_no_conflict()
{
CaseRules.RouteConflict(House(), "a-lead", "buyer-a").Refusal.ShouldNotBeNull().ShouldContain("no conflict");
CaseRules.RouteConflict(House(), "buyer-a", "buy-lead").Refusal.ShouldNotBeNull().ShouldContain("escalate");
}
[Fact]
public void A_decision_binds_the_deciding_lead_s_area_and_everything_below()
{
var org = House();
var decision = new Decision { Id = "d-1", ScopeUnitId = CaseRules.ScopeOf(org, "buy-lead") };
decision.ScopeUnitId.ShouldBe("purchasing");
CaseRules.Binds(org, decision, "buyer-a").ShouldBeTrue();
CaseRules.Binds(org, decision, "dev").ShouldBeFalse();
CaseRules.Binds(org, decision with { ScopeUnitId = CaseRules.ScopeOf(org, CaseRules.Human) }, "dev")
.ShouldBeTrue("a human decides for the whole instance");
}
[Fact]
public void A_decision_is_found_by_its_subject_or_by_telling_words()
{
var decisions = new[]
{
new Decision { Id = "d-1", Subject = "Delivery date field in orders", Question = "Where do we record the delivery date?" },
new Decision { Id = "d-2", Subject = "Weekly report format" }
};
CaseRules.Find(decisions, "delivery date field orders").Select(d => d.Id).ShouldBe(["d-1"]);
CaseRules.Find(decisions, "Order confirmation", "The supplier confirmed a delivery date, where does it go?")
.Select(d => d.Id).ShouldBe(["d-1"]);
CaseRules.Find(decisions, "Holiday plan").ShouldBeEmpty();
}
}