Files
support_backend/specs/006-support-organization/spec.md
T
saqib mirandClaude Sonnet 5 e1983438b9 docs: spec for support organization feature (006)
Phase 6 of the roadmap: Team/Agent/AgentSkill/AgentAvailability models,
dynamic HierarchyNode configuration, and a capability-eligibility read
path for the future orchestration phase to call. Supersedes the
pre-existing identity/agents scaffold stub (which queried a generic
User/UserRole model unrelated to this system's real architecture);
leaves identity/customers and identity/auth untouched as out of scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:49:50 +05:30

18 KiB

Feature Specification: Support Organization

Feature Branch: 006-support-organization

Created: 2026-09-02

Status: Draft

Input: User description: "Phase 6 of docs/10-implementation-roadmap.md: Team/Agent/ AgentSkill/AgentAvailability models, dynamic HierarchyNode configuration, admin hierarchy editor. Per docs/05-orchestration-sla-escalation.md §2-3 and docs/06-database-schema.md 'Domain: Support Hierarchy / Teams / Agents'."

User Scenarios & Testing (mandatory)

User Story 1 - An admin builds out teams and agents (Priority: P1)

An administrator creates a support team, then creates agents and assigns each to a team. An agent can be deactivated (e.g., they leave) without losing their history, and reactivated later.

Why this priority: Every later capability in this feature — skills, availability, hierarchy nodes that reference a team — and every later phase (orchestration, assignment) needs teams and agents to already exist. There is nothing to configure or query without this.

Independent Test: Create a team; create an agent assigned to it; confirm the agent appears under that team; deactivate the agent; confirm it's excluded from an active-agents listing but still resolvable by id.

Acceptance Scenarios:

  1. Given an admin creates a team, When it's saved, Then it exists with active: true and no agents yet.
  2. Given an existing team, When an admin creates an agent assigned to it, Then the agent is retrievable both directly and as part of that team's roster.
  3. Given an active agent, When an admin deactivates them, Then they're excluded from any "active agents" listing but their record (and history — assignments, skills) is not deleted.
  4. Given a deactivated agent, When an admin reactivates them, Then they reappear in active listings.
  5. Given an admin deactivates a team, When it's saved, Then the team's own active agents are unaffected — team deactivation does not cascade into deactivating its agents.

User Story 2 - An admin manages agent skills and availability (Priority: P1)

An administrator tags an agent with capability skills (each with a proficiency level), and an agent's current availability — status, working hours, and current workload — is tracked as a single, current, updatable record per agent.

Why this priority: Skills and availability are what make an agent selectable at all — a future orchestration/assignment phase (Phase 7) is fundamentally "which agents have this skill and are currently available," so this data must be real and current before that phase, or any capability-matching read path in this one (User Story 4), means anything.

Independent Test: Tag an agent with a skill and a proficiency level; confirm it's listed on the agent. Set the agent's availability status to busy; confirm a query for that agent reflects it immediately. Update the same agent's availability again; confirm the prior value is replaced, not duplicated.

Acceptance Scenarios:

  1. Given an agent, When an admin adds a skill tag with a proficiency level, Then the skill appears on the agent's record, and an agent can hold more than one skill tag at once.
  2. Given an agent already has a skill tag, When an admin updates its proficiency level, Then the level changes without creating a duplicate skill entry for the same tag.
  3. Given an agent with no availability record yet, When an admin sets one, Then a single current record exists with a status, working hours, and a current-load counter starting at zero.
  4. Given an agent's existing availability record, When its status is updated, Then the change is immediately reflected — never a second, competing record for the same agent.
  5. Given an availability status, When it's set, Then it MUST be one of a fixed, validated set of values (available, busy, away, offline) — an arbitrary string is rejected.

User Story 3 - An admin configures the dynamic support hierarchy (Priority: P2)

An administrator builds an arbitrary tree of support hierarchy nodes for routing — each node scoped to some combination of product/category/priority, tagged with the skills it covers, pointing at a team, and carrying the assignment-strategy/SLA-policy/escalation-policy references a future orchestration phase will read. Nodes can be nested under a parent, ordered relative to their siblings, and deactivated without being deleted.

Why this priority: This is the product's explicit differentiator (doc 01: "the customer never needs to understand the internal support hierarchy... changing one must never require a code change") — but it's meaningful only once teams exist (User Story 1), so it can't be P1 itself.

Independent Test: Create a root hierarchy node and a child node under it; confirm the child resolves its parent correctly and both are ordered as authored. Deactivate a node; confirm it's excluded from an active-tree listing but still exists. Attempt to create a node whose parentId doesn't exist; confirm it's rejected.

Acceptance Scenarios:

  1. Given an admin creates a hierarchy node with no parentId, When it's saved, Then it exists as a root node.
  2. Given an existing node, When an admin creates another node with parentId set to it, Then the new node is retrievable as a child of the first, and the tree can be read back in full from any root.
  3. Given an admin supplies a parentId that doesn't correspond to any existing node, When the node is created, Then the request is rejected — a hierarchy can never reference a parent that doesn't exist.
  4. Given a node, When an admin sets its productScope/categoryScope/priorityScope/ skills/assignmentStrategy/slaPolicyId/escalationPolicyId/entryConditions/ exitConditions, Then every value is stored and returned exactly as given — none of it is interpreted or enforced by this feature itself (that's the future orchestration engine's job).
  5. Given an active node, When an admin deactivates it, Then it's excluded from a query for the active tree but still exists and its children are unaffected (a deactivated parent does not implicitly deactivate its children).
  6. Given two sibling nodes under the same parent, When an admin sets their order values, Then reading the parent's children back returns them in that exact order.

User Story 4 - A capability-eligibility lookup for a future caller to use (Priority: P3)

Given a required skill (or set of skills) and optionally a product/category/priority context, a lookup returns the active agents who are eligible on capability grounds alone — team membership, skill match, active state. It deliberately does not consider availability, working hours, current load, or perform any assignment decision.

Why this priority: This is the read contract the future orchestration/assignment phase (Phase 7) will call — analogous to how 004-product-knowledge built /knowledge/retrieve for the future AI-support feature to call rather than that feature reaching into 004's internals. It depends on User Stories 1-2 (agents/skills/teams must exist to be queried) and, for product/category/priority-scoped lookups, on User Story 3's hierarchy nodes.

Independent Test: Tag two agents with different skills; query eligibility for one skill; confirm only the matching agent is returned. Deactivate that agent; confirm the query now returns nothing. Query with a skill no agent has; confirm an empty result, never an error.

Acceptance Scenarios:

  1. Given agents with different skill tags, When a capability lookup is run for a specific skill, Then only agents holding that skill are returned.
  2. Given doc 05 §3's explicit ordering ("capability is evaluated before availability"), When the lookup runs, Then its result is never filtered or reordered by availability, working hours, or current load — a busy or offline agent who has the skill is still included; that filtering is explicitly out of scope for this lookup.
  3. Given an inactive agent who otherwise matches, When the lookup runs, Then they are excluded — capability matching never surfaces a deactivated agent.
  4. Given a skill no active agent currently holds, When the lookup runs, Then it returns an empty result, never an error.
  5. Given a lookup scoped to a product/category/priority, When hierarchy nodes exist whose scope matches, Then the matching node(s)' own skills are included as additional eligibility criteria — the lookup composes hierarchy scoping with direct skill matching rather than requiring the caller to pre-resolve which node applies.

Edge Cases

  • What happens if an admin tries to delete a team or agent that has assignment/ticket history (once that history exists in a later phase)? Out of scope for strict enforcement here — this feature does not implement hard deletion of teams, agents, or hierarchy nodes at all, only active/inactive state, consistent with this system's broader convention of never silently losing support-domain history.
  • What happens if a hierarchy node's parentId is later changed to create a cycle (a node becomes its own ancestor)? Rejected — a hierarchy MUST remain a tree, never a cycle.
  • What happens when an agent is reassigned from one team to another? Supported as a normal update to the agent's teamId — the agent's skills and availability record are unaffected by a team change.
  • What happens if two admins race to set the same agent's availability at the same moment? The later write wins (last-write-wins) — availability is frequently-changing operational state, not the kind of durable business record this system's optimistic-concurrency convention (ticket status, knowledge versioning) is built for; see Assumptions.
  • What happens when a capability lookup (User Story 4) is scoped to a product/category/priority that no hierarchy node covers? It falls back to a plain skill-only match across all active agents — an unconfigured scope is "match on skill alone," not "match nothing."

Requirements (mandatory)

Functional Requirements

  • FR-001: The system MUST let an admin create a team, defaulting to active: true.
  • FR-002: The system MUST let an admin create an agent assigned to a team, defaulting to active: true.
  • FR-003: Deactivating an agent MUST exclude them from any active-agent listing without deleting their record or any of their skill/availability data.
  • FR-004: Deactivating a team MUST NOT cascade into deactivating its agents.
  • FR-005: The system MUST let an admin add a skill tag with a proficiency level to an agent, and MUST support an agent holding multiple distinct skill tags at once.
  • FR-006: Updating an existing skill tag's proficiency level for an agent MUST replace the level, never create a duplicate entry for the same agent+tag pair.
  • FR-007: The system MUST let an admin set an agent's availability (status, working hours, current load), maintaining exactly one current record per agent — never more than one.
  • FR-008: An availability status MUST be validated against a fixed set of values (available, busy, away, offline); any other value MUST be rejected.
  • FR-009: The system MUST let an admin create a hierarchy node, optionally under a parent node; creating a node with a parentId that does not reference an existing node MUST be rejected.
  • FR-010: A hierarchy node MUST store name, order, an optional teamId, skills, productScope, categoryScope, priorityScope, assignmentStrategy, optional slaPolicyId/escalationPolicyId, and optional entryConditions/exitConditions, returning every value exactly as given — this feature does not interpret, execute, or validate the semantics of assignmentStrategy/entryConditions/exitConditions beyond storing them.
  • FR-011: The system MUST reject a hierarchy node update that would introduce a cycle (a node set as its own ancestor, directly or transitively).
  • FR-012: Deactivating a hierarchy node MUST exclude it from an active-tree query without deleting it or affecting its children's own active state.
  • FR-013: Sibling hierarchy nodes under the same parent MUST be returned in their authored order when the parent's children are read.
  • FR-014: The system MUST provide a capability-eligibility lookup that, given a required skill (or skills) and an optional product/category/priority context, returns active agents whose team is active and who hold a matching skill — composing a matching hierarchy node's own skills into the criteria when the context resolves to one.
  • FR-015: The capability-eligibility lookup MUST NOT filter or reorder its result by availability status, working hours, or current load (doc 05 §3's explicit capability-before- availability ordering) — that consideration belongs to the future orchestration/assignment phase, not this lookup.
  • FR-016: The capability-eligibility lookup MUST exclude inactive agents and MUST return an empty result, never an error, when nothing matches.
  • FR-017: Every hierarchy node creation, edit, and active-state change MUST be written to the durable audit log (Constitution Principle VI; doc 07 explicitly lists "hierarchy changes" as an audited action).

Key Entities

  • Team: A named grouping of agents, with an active/inactive state independent of its members' own active states.
  • Agent: A support staff member belonging to a team, with an active/inactive state, a set of skill tags, and at most one current availability record.
  • Agent Skill: A capability tag held by an agent, with a proficiency level used by future skill-based assignment logic — not enforced or interpreted by this feature.
  • Agent Availability: An agent's current operational state — status, working hours, and current workload — exactly one record per agent, always representing the latest known value.
  • Hierarchy Node: A configurable, nestable support-routing unit — scoped to product/category/ priority, tagged with skills, pointing at a team and at (not-yet-built) SLA/escalation policy references, with entry/exit conditions and an assignment-strategy reference — all of it stored as data for a future orchestration engine to interpret, never hardcoded logic in this feature.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 100% of deactivated agents are absent from every active-agent listing, while remaining individually retrievable by id.
  • SC-002: An agent's availability is always represented by exactly one record — never zero after being set once, never more than one after any number of updates.
  • SC-003: 100% of hierarchy node creation attempts with a nonexistent parentId are rejected, never silently creating an orphaned or root node instead.
  • SC-004: 100% of hierarchy trees remain acyclic — no update can produce a node that is its own ancestor.
  • SC-005: A capability-eligibility lookup for a skill no active agent holds returns an empty result in 100% of cases, never an error.
  • SC-006: Every hierarchy node change (create, edit, activate/deactivate) produces exactly one corresponding audit log entry.

Assumptions

  • Availability updates use last-write-wins, not optimistic concurrency — unlike ticket status or knowledge versioning (this system's durable-record convention), an agent's availability is frequently-changing operational telemetry, not a business record whose history matters; adding expectedVersion here would add friction without protecting anything meaningful. If a future phase finds contention here a real problem (e.g., automated availability updates racing an agent's own manual one), that's a decision for that phase to revisit, not this one.
  • This feature does not build the orchestration engine, assignment strategies, or SLA/ escalation policy modelsassignmentStrategy, slaPolicyId, and escalationPolicyId on a hierarchy node are stored exactly as given (free-text/reference values) with no validation against a real policy table, because those tables don't exist until Phase 7/8. This mirrors how 004-product-knowledge stored errorCode as free text pending 004's own later ErrorCode model in the same feature — here, the referenced tables are a genuinely later phase, not later in the same feature, so the reference fields stay untyped/unvalidated for now.
  • The capability-eligibility lookup (User Story 4) is a read contract for a future caller, not the orchestration engine itself — it answers "who is capability-eligible," never "who should be assigned." Availability, workload, and strategy selection are explicitly out of scope, per doc 05 §3's own ordering (capability before availability) and doc 05 §8 (orchestration needs its own engine//rules//strategies/ internal structure — not appropriate to build speculatively inside this CRUD-shaped feature).
  • identity/auth remains a stub, unchanged by this feature — every admin endpoint here is gated by the same fastify.authenticate known-limitation stub as every prior feature's admin surface; this feature does not attempt real agent authentication or a login flow (that's Phase 10's Agent/Admin UI territory, or whenever identity/auth itself becomes its own feature).
  • The pre-existing identity/customers/identity/agents scaffold stubs (querying the generic User/UserRole model from the original project scaffold) are superseded, not extended — a real Agent is its own first-class entity per doc 06, unrelated to the generic User model (which nothing in this system's real architecture, starting from 002-saas-integration's CustomerReference, actually uses). identity/customers and its User-based stub are left untouched (out of scope — this feature is about agents/teams/hierarchy, not customers); identity/agents' stub is replaced with a real implementation as part of this feature.