Maps the feature onto the three existing orchestration/{routing,
assignments,orchestration} scaffold stubs. Key decisions: Assignment
refined as a version-row-per-period model (paired with a separate
append-only AssignmentHistory event log), round-robin concurrency
safety via atomic Redis INCR (reusing existing infra, not a new one),
LEAST_LOADED/SKILL_BASED tie-breaks falling back to that same cursor,
currentLoad read but never mutated by this feature, and the escalation
trigger reusing 005's existing domain-event bus rather than a new
notification path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.9 KiB
Implementation Plan: Orchestration and Assignment
Branch: 007-orchestration-assignment | Date: 2026-09-02 | Spec: spec.md
Input: Feature specification from specs/007-orchestration-assignment/spec.md
Summary
Populate the existing orchestration/routing, orchestration/assignments, and
orchestration/orchestration stub directories (all placeholders today — e.g. the round-robin
strategy stub just returns the first candidate) with the real engine: on a ticket's transition to
HUMAN_ESCALATION (via the existing domain-event bus 005-ai-support first put to use),
routing resolves the applicable hierarchy node and eligible-agent set by calling
006-support-organization's capability-eligibility lookup directly; assignments runs the
resolved (or manually-overridden) pluggable strategy — ROUND_ROBIN (concurrency-safe via
atomic Redis INCR), LEAST_LOADED, SKILL_BASED, MANUAL, DIRECT — and persists both the
current Assignment and its append-only AssignmentHistory; orchestration ties the two
together and moves the ticket to IN_PROGRESS through 003-ticketing's existing state machine.
Technical Context
Language/Version: TypeScript 5.4 / Node.js 20+.
Primary Dependencies: Prisma (new models), Zod, ioredis (already a dependency — atomic
INCR for round-robin, reusing the existing shared client). No new runtime dependency.
Storage: PostgreSQL via Prisma (new Assignment, AssignmentHistory models). Redis for the
round-robin cursor (research.md) — no new infrastructure, reusing src/infrastructure/cache.
Testing: Vitest — unit tests for each pluggable strategy's pure selection logic (given an
eligible set + context, which agent) and for the tie-break composition; integration/concurrency
tests for the full escalation→assignment flow and, specifically, ROUND_ROBIN under genuinely
concurrent requests (Constitution's Testing gate explicitly requires an assignment concurrency
test category — the first time this codebase has a feature that actually needs one, since
003-ticketing's own concurrency guarantee was single-writer-race on ticket status, not a
multi-way selection race).
Target Platform: Same Fastify modular monolith. Populates existing module directories:
src/modules/orchestration/{routing,assignments,orchestration}/.
Project Type: Backend service — single project.
Performance Goals: Round-robin's Redis INCR must stay a single round trip per assignment —
no read-modify-write race window. Not otherwise performance-sensitive.
Constraints: MUST run automatically on human escalation (FR-001); MUST evaluate capability
before availability (FR-003, inherited from 006); MUST be concurrency-safe for ROUND_ROBIN
(FR-006); MUST never lose assignment history (FR-009); MUST reuse 003's ticket state machine, not
invent a new status (FR-014).
Scale/Scope: Three populated modules, five assignment strategies, one manual-assignment admin endpoint, two read endpoints. Explicitly excludes: SLA policy execution, rule-driven escalation, agent-deactivation reaction, workload lifecycle mutation (see spec.md Assumptions).
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle / Section | Check | Result |
|---|---|---|
| I. SaaS Is the Sole Identity & Access Authority | Assignment references Agent/Ticket — both SupportHub's own domain (Principle I's own list names "routing/assignment" as SupportHub's authority). No SaaS identity touched. |
PASS |
| II. Configuration Over Hardcoding | The strategy actually used per ticket is read from the hierarchy node's own assignmentStrategy field (006) — never hardcoded to one strategy; five pluggable implementations selected by that config value. |
PASS |
| III. Layered Architecture With Enforced Module Boundaries | Three modules follow the standard shape (assignments also keeps its pre-existing engine/strategies/rules/calculators extension per doc 05 §8); routing→orchestration/hierarchy (006) and assignments→identity/agents are one-directional, no cycle — orchestration depends on both routing and assignments, neither of which depends back on it. |
PASS |
| IV. AI Recommends, Deterministic Policy Decides | Not applicable in the AI sense — but structurally the same shape: the strategy is deterministic policy, never an LLM call; this feature has no AI involvement at all. | PASS — N/A |
| V. Evidence-Based Verification | Not applicable — no resolution/verification concept in this feature. | PASS — N/A |
| VI. Durable Audit & History | AssignmentHistory is the durable, append-only record doc 07 explicitly requires for "assignment/reassignment" (its own audit list). |
PASS |
| VII. Concurrency-Safe, Durable Job Handling | This is the principle's own named example ("two tickets assigned simultaneously must never double-assign or corrupt round-robin state") — directly implemented via atomic Redis INCR, verified under real concurrent load (research.md, quickstart Scenario 2). |
PASS |
| VIII. Problem and Ticket Are Separate, Related Entities | Assignment references Ticket, not Problem — doesn't touch the distinction. |
PASS — N/A |
| Technology & Platform Constraints | Prisma + Zod + existing ioredis client only, no new dependency. |
PASS |
No violations requiring Complexity Tracking justification.
Post-Design Constitution Re-check
All gates above remain PASS after Phase 1 design. Worth calling out against Principle VII
explicitly: this is the first feature in this codebase since 003-ticketing's optimistic-
concurrency ticket-status guarantee to have a genuine multi-writer race condition as a first-class
requirement (not just a theoretical one) — ROUND_ROBIN's atomic-INCR design and its dedicated
concurrency test (tasks.md) are what make this principle a verified guarantee here, not an
aspiration.
Project Structure
Documentation (this feature)
specs/007-orchestration-assignment/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
└── tasks.md # Phase 2 output (/speckit-tasks — not created here)
Source Code (repository root)
supporthub-api/
├── prisma/
│ └── schema.prisma # MODIFIED — add Assignment, AssignmentHistory
├── src/
│ ├── infrastructure/
│ │ └── cache/ # reused as-is — round-robin's INCR goes through
│ │ the existing redisClient, no new file needed
│ │ beyond the strategy implementation itself
│ └── modules/
│ └── orchestration/
│ ├── hierarchy/ # existing (006) — untouched
│ ├── routing/ # REPLACED stub — resolves node + eligible agents
│ │ └── service/ types/ index.ts (no controller/routes — internal only)
│ ├── assignments/ # REPLACED stub — keeps engine/strategies/rules/
│ │ ├── controller/ routes/ schema/ calculators, adds the standard shape around it
│ │ │ repository/ service/ types/
│ │ │ mapper/ constants/ index.ts
│ │ ├── strategies/ # round-robin, least-loaded, skill-based,
│ │ │ manual/direct (thin — just records the given
│ │ │ agentId)
│ │ ├── engine/ # ties a resolved eligible set + chosen strategy
│ │ │ together into a persisted Assignment
│ │ └── calculators/ # workload/skill-level comparison helpers
│ └── orchestration/ # REPLACED stub — event subscriber + the
│ └── service/ types/ index.ts escalation→assignment→IN_PROGRESS workflow
└── tests/
├── unit/orchestration/ # each strategy's pure selection logic
├── integration/ # full escalation→assignment flow, manual
│ (re)assignment, history
└── concurrency/ # ROUND_ROBIN under real concurrent requests
Structure Decision: Single project. routing and orchestration are internal-only
submodules (no HTTP surface), matching 005's troubleshooting/escalation precedent for
modules whose job is pure orchestration logic invoked by another module's service, not a
caller-facing endpoint of their own.
Complexity Tracking
No constitution violations — table intentionally omitted.