Phase 6 of the roadmap. Populates the identity/teams and orchestration/hierarchy module directories (previously empty or near-empty scaffold stubs) and replaces identity/agents' pre-existing placeholder, which queried a generic User/UserRole model unrelated to this system's real architecture (nothing since 002-saas-integration's CustomerReference has used it). Teams and agents: active/inactive CRUD, never hard deletion; team deactivation never cascades to its agents. Skills: compound-unique upsert on (agentId, skillTag), never duplicates. Availability: a single current record per agent, deliberately last-write-wins rather than optimistic-locked — operational telemetry, not a durable business record. The dynamic hierarchy: a nestable, orderable HierarchyNode tree with every field (scope, assignment strategy reference, entry/exit conditions) stored as opaque admin-set data; cycle detection runs only on reparenting edits (a new node can't form a cycle); every create/edit/activate/deactivate is audited via AuditLog, reusing 002's existing writer pattern. A capability-eligibility read path composes hierarchy scope with caller-supplied skills for the future orchestration/assignment phase (Phase 7) to call, deliberately excluding availability per doc 05's own capability-before-availability ordering. Found and fixed a real bug before it reached tests: the initial availability upsert reset currentLoad to 0 on every update, not just creation. Adds 11 unit tests (cycle detection, capability matching) and 4 integration test files covering all four user stories. Full regression (every pre-existing 002-005 integration test plus all new ones) run against real Postgres/Redis/MinIO: 107 passed, 9 skipped (005's AI-key-gated tests, unrelated), 0 failed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 KiB
description
| description |
|---|
| Task list for 006-support-organization |
Tasks: Support Organization
Input: Design documents from specs/006-support-organization/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/support-org-contract.md, quickstart.md
Tests: Included as first-class tasks — this feature has two genuinely extractable pure functions (cycle detection, capability-matching predicate) alongside the thin-Prisma-query CRUD that dominated 004's test shape.
Organization: Tasks are grouped by user story (US1 = P1 teams/agents, US2 = P1 skills/ availability, US3 = P2 hierarchy tree, US4 = P3 capability-eligibility lookup).
Format: [ID] [P?] [Story] Description
All file paths are relative to supporthub-api/ (repo root).
Phase 1: Setup
- T001 [P] Populate
src/modules/identity/teams/with the standard module shape (controller/,routes/,schema/,repository/,service/,types/,mapper/,constants/,index.ts) — this directory currently has no files - T002 [P] Replace
src/modules/identity/agents/'s existingUser-based stub content (repository/agents.repository.ts,service/agents.service.ts,schema/agents.schema.ts) — keep the module's file layout, replace what each file does - T003 [P] Populate
src/modules/orchestration/hierarchy/with the standard module shape — this directory currently only has an emptyindex.ts
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Schema for all five entities, shared by every user story.
⚠️ CRITICAL: No user-story stage work can begin until this phase is complete.
- T004 Add
Team,Agent,AgentSkill,AgentAvailability,HierarchyNodemodels toprisma/schema.prismaper data-model.md (including@@unique([agentId, skillTag])onAgentSkill,agentId @uniqueonAgentAvailability, theHierarchyTreeself-relation onHierarchyNode, and the(teamId, active)/(parentId, order)/(active)indexes) (depends on T001-T003) - T005 Run
npm run prisma:generateand create the migration (npm run prisma:migrate) for T004 (depends on T004)
Checkpoint: Schema migrated. User stories can now be built.
Phase 3: User Story 1 - An admin builds out teams and agents (Priority: P1) 🎯 MVP
Goal: Team and agent CRUD with active/inactive state, never deletion; team deactivation never cascades.
Independent Test: Quickstart Scenario 1.
Tests for User Story 1
- T006 [US1] Integration test covering Quickstart Scenario 1 (create team/agent, agent
appears in roster, deactivate excludes from active listing without deleting, reactivate
restores it, team deactivation doesn't cascade) against a real Postgres in
tests/integration/support-org-teams-agents.test.ts
Implementation for User Story 1
- T007 [P] [US1] Add
TeamsRepository/TeamsService(create; update; findById with agent roster; findAll) insrc/modules/identity/teams/repository/teams.repository.ts+service/teams.service.ts(depends on T005) - T008 [US1] Add
AgentsRepository/AgentsService(create; update; findById; findAll withactive/teamIdfilters) — replacing the existingUser-based stub — insrc/modules/identity/agents/repository/agents.repository.ts+service/agents.service.ts(depends on T005, T007 for theteamIdFK) - T009 [US1] Add Zod schemas + routes:
POST /admin/teams,PATCH /admin/teams/:teamId,GET /admin/teams/:teamId,GET /admin/teams(gated byfastify.authenticate) inidentity/teams/schema/+routes/, registered fromsrc/api/routes.ts(depends on T007) - T010 [US1] Add Zod schemas + routes:
POST /admin/teams/:teamId/agents,PATCH /admin/agents/:agentId,GET /admin/agents/:agentId,GET /admin/agents(gated byfastify.authenticate) inidentity/agents/schema/+routes/, registered fromsrc/api/routes.ts(depends on T008, T009) - T011 [US1] Run Quickstart Scenario 1 locally and confirm all 5 steps pass
Checkpoint: Teams and agents can be created, updated, and deactivated/reactivated without ever losing history. Usable as the foundation every later story needs.
Phase 4: User Story 2 - An admin manages agent skills and availability (Priority: P1)
Goal: Skill tags (upsert-not-duplicate) and a single current availability record per agent.
Independent Test: Quickstart Scenario 2.
Tests for User Story 2
- T012 [US2] Integration test covering Quickstart Scenario 2 (skill upsert doesn't
duplicate, availability upsert stays single-record, invalid status rejected) against a
real Postgres in
tests/integration/support-org-skills-availability.test.ts
Implementation for User Story 2
- T013 [P] [US2] Add
AgentSkillsRepository/AgentSkillsService(upsert(agentId, skillTag, level);findAllByAgent) insrc/modules/identity/agents/repository/agent-skills.repository.ts+service/agent-skills.service.ts(depends on T005) - T014 [P] [US2] Add
AgentAvailabilityRepository/AgentAvailabilityService(upsert(agentId, status, workingHours, currentLoad?);findByAgent) —statusvalidated against the fixedavailable/busy/away/offlineset at the Zod schema layer (FR-008) — insrc/modules/identity/agents/repository/agent-availability.repository.ts+service/agent-availability.service.ts(depends on T005) - T015 [US2] Add Zod schemas + routes:
PUT /admin/agents/:agentId/skills/:skillTag,GET /admin/agents/:agentId/skills,PUT /admin/agents/:agentId/availability,GET /admin/agents/:agentId/availability(gated byfastify.authenticate) inidentity/agents/schema/+routes/(depends on T013, T014) - T016 [US2] Run Quickstart Scenario 2 locally and confirm all 5 steps pass
Checkpoint: Agents carry real, current skill and availability data — everything a future capability/assignment lookup needs to read.
Phase 5: User Story 3 - An admin configures the dynamic support hierarchy (Priority: P2)
Goal: A nestable, cycle-safe, orderable, audited hierarchy tree — every field stored as opaque data for a future orchestration engine.
Independent Test: Quickstart Scenario 3.
Tests for User Story 3
- T017 [P] [US3] Unit tests for the pure cycle-detection function — a direct self-reference
rejected, a transitive cycle (A's new parent is A's own descendant) rejected, a legitimate
reparent to an unrelated node accepted — in
tests/unit/support-org/cycle-check.test.ts - T018 [US3] Integration test covering Quickstart Scenario 3 (parent/child creation, order
preserved, nonexistent
parentIdrejected, self-reparent rejected, deactivation doesn't cascade) against a real Postgres intests/integration/support-org-hierarchy.test.ts
Implementation for User Story 3
- T019 [P] [US3] Add the pure cycle-detection function
wouldCreateCycle(nodeId, candidateParentId, ancestorChainOf: (id) => string[]) => booleaninsrc/modules/orchestration/hierarchy/service/cycle-check.ts(no dependencies — pure function, takes the ancestor-lookup as an injected function so it's testable without a database) - T020 [US3] Add
HierarchyAuditLogRepository.writeHierarchyAuditEvent(...)(research.md, same shape ascatalog/products'writeIntegrationAuditEvent) insrc/modules/orchestration/hierarchy/repository/hierarchy-audit-log.repository.ts(depends on T005) - T021 [US3] Add
HierarchyRepository(create — validatesparentIdexists,404if not;updateById— validates via T019 before applying aparentIdchange;findById;findChildren(parentId, ordered);findAll(activeOnly?);setActive) +HierarchyService(wraps the repository, calls T020 on every create/edit/activate/ deactivate — FR-017) insrc/modules/orchestration/hierarchy/repository/hierarchy.repository.ts+service/hierarchy.service.ts(depends on T019, T020) - T022 [US3] Add Zod schemas + routes:
POST /admin/hierarchy-nodes,PUT /admin/hierarchy-nodes/:nodeId,PATCH .../activate,PATCH .../deactivate,GET /admin/hierarchy-nodes/:nodeId,GET .../children,GET /admin/hierarchy-nodes(gated byfastify.authenticate) inorchestration/hierarchy/schema/+routes/, registered fromsrc/api/routes.ts(depends on T021) - T023 [US3] Run Quickstart Scenario 3 locally and confirm all 6 steps pass
Checkpoint: The dynamic support hierarchy — this feature's core differentiator — is fully configurable, cycle-safe, and audited.
Phase 6: User Story 4 - A capability-eligibility lookup for a future caller to use (Priority: P3)
Goal: A read-only endpoint composing hierarchy scope with skill matching, deliberately excluding availability.
Independent Test: Quickstart Scenario 4.
Tests for User Story 4
- T024 [P] [US4] Unit tests for the pure capability-matching predicate (agent skill set is a
superset of the required set → eligible; missing one required skill → not eligible; empty
hierarchy scope matches any product/category/priority) in
tests/unit/support-org/capability-match.test.ts - T025 [US4] Integration test covering Quickstart Scenario 4 (skill-only match; availability
never filters; inactive agent excluded; empty result on no match, never an error;
hierarchy-scope skill composition) against a real Postgres in
tests/integration/support-org-capability-lookup.test.ts
Implementation for User Story 4
- T026 [P] [US4] Add the pure capability-matching predicate
isCapabilityEligible(agentSkills: string[], requiredSkills: string[]) => booleaninsrc/modules/orchestration/hierarchy/service/capability-match.ts(no dependencies) - T027 [US4] Add
CapabilityLookupService.findEligibleAgents(skills, productId?, categoryId?, priorityId?): finds activeHierarchyNodes whose scope arrays are empty-or-matching for each given context value, unions theirskillsinto the requested set, queries active agents (active team) via T026's predicate applied to each candidate'sAgentSkilltags — insrc/modules/orchestration/hierarchy/service/capability-lookup.service.ts(depends on T026, and onidentity/agents' repository being queryable — reuses its exported repository rather than duplicating the query) - T028 [US4] Add Zod schema +
GET /support-org/capability-eligibilityroute (nofastify.authenticategate — research.md "Admin endpoint authentication") inorchestration/hierarchy/schema/+routes/, registered fromsrc/api/routes.ts(depends on T027) - T029 [US4] Run Quickstart Scenario 4 locally and confirm all 5 steps pass
Checkpoint: All four user stories work independently and together — a full support organization (teams, agents, skills, availability, hierarchy) exists and is queryable by capability, ready for Phase 7's orchestration engine to build on.
Phase 7: Polish & Cross-Cutting Concerns
- T030 [P] Add a "Support Organization" section to
README.mddescribing the admin CRUD, the cycle-safety/audit guarantees on hierarchy nodes, and the capability-eligibility contract (explicitly noting what it does not do — availability filtering, assignment) - T031 [P] Update
specs/006-support-organization/checklists/requirements.mdNotes with any implementation-time findings - T032 Run
npx tsx scripts/check-architecture.tsandnpm run lint/npm run typecheck - T033 Full regression:
npm run test:unit(scoped totests/unit) to confirm nothing broke elsewhere, then the full integration suite against real Docker-provisioned infra
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies
- Foundational (Phase 2): Depends on Setup — BLOCKS all user stories
- User Story 1 (Phase 3): Depends on Foundational — no dependency on US2-US4
- User Story 2 (Phase 4): Depends on US1 (agents must exist to attach skills/availability to)
- User Story 3 (Phase 5): Depends on Foundational and, loosely, on US1 (
teamIdFK) — no dependency on US2 - User Story 4 (Phase 6): Depends on US2 (skills to match on) and US3 (hierarchy nodes to compose scope from) — genuinely the last story, unlike 004's US3 which only needed US1
- Polish (Phase 7): Depends on all four user stories
Parallel Opportunities
- T001/T002/T003 (independent scaffolding across three separate module directories)
- T007 (Teams) before T008 (Agents) strictly (FK), but both alongside T017/T019 (US3's pure functions, independent of US1/US2 entirely) once T005 exists
- T013/T014 (independent repositories) in parallel
- T024/T026 (pure function + its test) independent of T017/T019
- T030/T031 in Polish
Implementation Strategy
MVP First (User Story 1 Only)
- Setup + Foundational (T001-T005)
- User Story 1 (T006-T011)
- STOP and VALIDATE: Quickstart Scenario 1 passes — teams and agents exist with correct active/inactive semantics. Usable as the foundation for every later phase even before skills, availability, hierarchy, or capability lookup exist.
Incremental Delivery
- Setup + Foundational → schema migrated
- Add User Story 1 → teams and agents exist (MVP)
- Add User Story 2 → agents carry real skill/availability data
- Add User Story 3 → the dynamic support hierarchy is fully configurable and audited
- Add User Story 4 → a real capability-eligibility read path, ready for Phase 7
- Polish → docs and full regression