From d99ba0905ae39aa36b454a771e77b48c0d9ee242 Mon Sep 17 00:00:00 2001 From: city in the sky Date: Tue, 7 Jul 2026 20:45:03 +0800 Subject: [PATCH] Add escalation tool test documentation --- tools/v2/team/escalation-tool/README.md | 39 +++++- .../team/escalation-tool/docs/review-notes.md | 44 +++++++ .../v2/team/escalation-tool/docs/test-plan.md | 40 +++++++ .../fixtures/sample-escalation-cases.json | 61 ++++++++++ tools/v2/team/escalation-tool/specs.md | 49 ++++++-- .../tests/escalation-docs.test.mjs | 112 ++++++++++++++++++ 6 files changed, 327 insertions(+), 18 deletions(-) create mode 100644 tools/v2/team/escalation-tool/docs/review-notes.md create mode 100644 tools/v2/team/escalation-tool/docs/test-plan.md create mode 100644 tools/v2/team/escalation-tool/fixtures/sample-escalation-cases.json create mode 100644 tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs diff --git a/tools/v2/team/escalation-tool/README.md b/tools/v2/team/escalation-tool/README.md index 7cb4cc1c..c78810e4 100644 --- a/tools/v2/team/escalation-tool/README.md +++ b/tools/v2/team/escalation-tool/README.md @@ -1,15 +1,44 @@ # Escalation Tool -This folder is the isolated workspace for the Escalation Tool tool. +Escalation Tool is an isolated V2 team workspace for identifying team email +threads that need handoff, manager review, or urgent follow-up. It is not wired +into the main app yet. ## Ownership Boundary All work for this tool must stay inside: -`text -.\tools\v2\team\escalation-tool\ -` +```text +tools/v2/team/escalation-tool/ +``` Do not wire this tool into the main app, routing, inbox architecture, wallet core, Stellar core, database schema, or existing design system unless a future integration issue explicitly allows it. -See specs.md for the issue categories and contributor expectations. +## Reviewer Setup + +This issue adds folder-local documentation, synthetic fixtures, and a +zero-dependency Node contract test. Run from the repository root: + +```bash +node --test tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs +``` + +The test validates that the fixture and documentation describe an isolated, +reviewable escalation workflow. + +## Documentation Map + +- `specs.md` defines the tool scope, status model, and contributor boundary. +- `docs/test-plan.md` lists automated and manual review checks. +- `docs/review-notes.md` summarizes expected reviewer behavior and limitations. +- `fixtures/sample-escalation-cases.json` provides synthetic escalation examples. +- `tests/escalation-docs.test.mjs` validates the local fixture and documentation + contract. + +## Current Limitations + +- No inbox ingestion or production mailbox access is included. +- No notifications, ticket writes, or manager assignment side effects are + performed. +- Main app routing, dashboard placement, auth, wallet, Stellar, and database + integration remain out of scope for this folder-local issue. diff --git a/tools/v2/team/escalation-tool/docs/review-notes.md b/tools/v2/team/escalation-tool/docs/review-notes.md new file mode 100644 index 00000000..e7d93cb0 --- /dev/null +++ b/tools/v2/team/escalation-tool/docs/review-notes.md @@ -0,0 +1,44 @@ +# Escalation Tool Review Notes + +## What This Contribution Covers + +This contribution adds contributor-facing documentation and fixture coverage for +the isolated Escalation Tool workspace. The tool is treated as a self-contained +mini-product and is not connected to the main app. + +Reviewers should expect: + +- a repaired README and specs file for the folder-local workflow. +- a deterministic fixture with representative escalation states. +- a local Node test that validates the fixture and documentation contract. +- clear limitations for future inbox, routing, notification, and persistence + work. + +## What Is Out Of Scope + +This issue does not add: + +- main app routing or dashboard registration. +- inbox reads, mailbox writes, or mail rendering changes. +- notification delivery, ticket creation, or assignment side effects. +- database schema changes or persistence. +- wallet, Stellar, auth, or payment behavior. +- production customer data or live network calls. + +## Reviewer Flow + +1. Run the local Node test from the repository root. +2. Read `fixtures/sample-escalation-cases.json` and confirm all cases are + synthetic. +3. Compare fixture statuses with the rules in `specs.md`. +4. Confirm `docs/test-plan.md` names the current checks and future coverage + gaps. +5. Confirm the PR changes only files inside this tool folder. + +## Known Limitations + +The fixture documents the expected review contract before core scoring logic is +implemented. A future feature issue should add service-level logic and reuse this +status vocabulary instead of inventing a separate escalation model. Main app +integration should remain a separate follow-up after the folder-local model is +accepted. diff --git a/tools/v2/team/escalation-tool/docs/test-plan.md b/tools/v2/team/escalation-tool/docs/test-plan.md new file mode 100644 index 00000000..1314616b --- /dev/null +++ b/tools/v2/team/escalation-tool/docs/test-plan.md @@ -0,0 +1,40 @@ +# Escalation Tool Test Plan + +## Automated Checks + +Run from the repository root: + +```bash +node --test tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs +``` + +The local test checks that: + +- synthetic fixture data uses stable ids and contains no production data. +- each expected escalation status is represented at least once. +- high-risk cases require manager review or urgent escalation. +- cases without an owner are classified as `needs-owner`. +- documentation includes setup, fixture, limitation, and review guidance. + +## Manual Review Checklist + +- Confirm all changed files stay under `tools/v2/team/escalation-tool/`. +- Confirm fixtures are synthetic and do not include real customer, mailbox, or + team data. +- Confirm the documented statuses match the fixture contract in `specs.md`. +- Confirm the review notes make future integration boundaries explicit. +- Confirm no live network calls, secrets, database writes, notifications, wallet + behavior, Stellar behavior, or app routing changes are introduced. + +## Future Code Coverage + +When a core service is added later, extend the local tests to cover: + +- deterministic scoring of escalation signals. +- SLA-sensitive age thresholds. +- owner assignment fallback behavior. +- manager-review and urgent-escalation boundaries. +- empty, loading, error, and success states for future UI work. + +Keep future tests folder-local unless a separate integration issue explicitly +allows app-wide coverage. diff --git a/tools/v2/team/escalation-tool/fixtures/sample-escalation-cases.json b/tools/v2/team/escalation-tool/fixtures/sample-escalation-cases.json new file mode 100644 index 00000000..f6e0f979 --- /dev/null +++ b/tools/v2/team/escalation-tool/fixtures/sample-escalation-cases.json @@ -0,0 +1,61 @@ +{ + "tool": "escalation-tool", + "runContext": { + "now": "2026-07-01T00:00:00.000Z", + "source": "synthetic-folder-local-fixture" + }, + "cases": [ + { + "id": "esc-001", + "threadId": "thread-renewal-sla", + "subject": "Renewal blocker needs same-day decision", + "owner": "Jordan Lee", + "department": "Customer Success", + "ageHours": 7, + "riskLevel": "critical", + "signals": ["sla-risk", "customer-blocked", "decision-needed"], + "recommendedStatus": "urgent-escalation", + "nextAction": "Escalate to the customer success lead and confirm the decision owner.", + "containsProductionData": false + }, + { + "id": "esc-002", + "threadId": "thread-missing-owner", + "subject": "Implementation question without a clear owner", + "owner": "", + "department": "Implementation", + "ageHours": 28, + "riskLevel": "medium", + "signals": ["missing-owner", "stale-thread"], + "recommendedStatus": "needs-owner", + "nextAction": "Assign a named owner before the next customer reply.", + "containsProductionData": false + }, + { + "id": "esc-003", + "threadId": "thread-approval-needed", + "subject": "Discount approval requested by sales", + "owner": "Mira Patel", + "department": "Sales", + "ageHours": 11, + "riskLevel": "high", + "signals": ["approval-needed", "revenue-impact"], + "recommendedStatus": "manager-review", + "nextAction": "Route the thread to the sales manager for approval guidance.", + "containsProductionData": false + }, + { + "id": "esc-004", + "threadId": "thread-status-watch", + "subject": "Routine follow-up remains on track", + "owner": "Noah Kim", + "department": "Support", + "ageHours": 4, + "riskLevel": "low", + "signals": ["recent-response", "owner-present"], + "recommendedStatus": "monitor", + "nextAction": "Monitor the thread and keep the current owner attached.", + "containsProductionData": false + } + ] +} diff --git a/tools/v2/team/escalation-tool/specs.md b/tools/v2/team/escalation-tool/specs.md index da0b862f..353e7d2f 100644 --- a/tools/v2/team/escalation-tool/specs.md +++ b/tools/v2/team/escalation-tool/specs.md @@ -1,36 +1,59 @@ # Escalation Tool -Escalate conversations. +Escalate team conversations that need urgent action, owner reassignment, +manager review, or a documented follow-up path. ## Scope -- Release tier: $(System.Collections.Hashtable.Tier.ToUpperInvariant()) -- Audience: $(System.Collections.Hashtable.Audience) -- Folder ownership: $dir/ +- Release tier: V2 +- Audience: team +- Folder ownership: `tools/v2/team/escalation-tool/` This is a self-contained tooling workspace. Do not wire this tool into the main app, routing, inbox architecture, wallet core, Stellar core, or design system unless a future integration issue explicitly allows it. -Recommended internal structure: +## Recommended Internal Structure - components/ - services/ - hooks/ -- ests/ +- tests/ - docs/ - "@ | Set-Content -Path "tools/v2/team/escalation-tool/README.md" - @" -# Escalation Tool Specs +## Review Model -## Purpose +Source escalation cases should describe synthetic team email context: -Escalate conversations. +- `id`: stable fixture-local identifier. +- `threadId`: synthetic email thread identifier. +- `subject`: synthetic team thread subject. +- `owner`: current accountable team member. +- `department`: team or function responsible for the next step. +- `ageHours`: hours since the item last changed. +- `riskLevel`: `low`, `medium`, `high`, or `critical`. +- `signals`: local reasons for escalation consideration. +- `recommendedStatus`: `monitor`, `needs-owner`, `manager-review`, or + `urgent-escalation`. +- `nextAction`: reviewable action text for the team. +- `containsProductionData`: false for local fixtures. -## Contributor boundary +## Status Rules + +- `monitor`: no immediate handoff is required. +- `needs-owner`: ownership is missing, stale, or ambiguous. +- `manager-review`: a decision, approval, or team lead review is required. +- `urgent-escalation`: a high-risk or SLA-sensitive item needs immediate + follow-up. + +## Contributor Boundary All work for this tool should stay in: -$dir/ +```text +tools/v2/team/escalation-tool/ +``` + +Do not add inbox ingestion, notifications, ticket creation, persistence, routing, +wallet, Stellar, auth, or shared design system changes in this issue. ## Required issue categories diff --git a/tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs b/tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs new file mode 100644 index 00000000..acab0771 --- /dev/null +++ b/tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs @@ -0,0 +1,112 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; + +const currentDir = dirname(fileURLToPath(import.meta.url)); +const toolDir = join(currentDir, ".."); +const fixturePath = join(toolDir, "fixtures", "sample-escalation-cases.json"); +const readmePath = join(toolDir, "README.md"); +const specsPath = join(toolDir, "specs.md"); +const testPlanPath = join(toolDir, "docs", "test-plan.md"); +const reviewNotesPath = join(toolDir, "docs", "review-notes.md"); + +const allowedStatuses = new Set(["monitor", "needs-owner", "manager-review", "urgent-escalation"]); +const allowedRiskLevels = new Set(["low", "medium", "high", "critical"]); +const requiredStatuses = ["monitor", "needs-owner", "manager-review", "urgent-escalation"]; + +async function readJson(path) { + const raw = await readFile(path, "utf8"); + return JSON.parse(raw); +} + +test("sample escalation fixture follows the documented contract", async () => { + const fixture = await readJson(fixturePath); + + assert.equal(fixture.tool, "escalation-tool"); + assert.match(fixture.runContext.now, /^\d{4}-\d{2}-\d{2}T/); + assert.ok(Array.isArray(fixture.cases), "fixture cases must be an array"); + assert.ok(fixture.cases.length >= requiredStatuses.length); + + const seenStatuses = new Set(); + const seenIds = new Set(); + + for (const escalationCase of fixture.cases) { + assert.ok(escalationCase.id, "case needs a stable id"); + assert.equal(seenIds.has(escalationCase.id), false, `${escalationCase.id} is duplicated`); + assert.ok(escalationCase.threadId, `${escalationCase.id} needs a thread id`); + assert.ok(escalationCase.subject, `${escalationCase.id} needs a subject`); + assert.ok(escalationCase.department, `${escalationCase.id} needs a department`); + assert.equal( + escalationCase.containsProductionData, + false, + `${escalationCase.id} must stay synthetic`, + ); + assert.equal(typeof escalationCase.ageHours, "number", `${escalationCase.id} age is numeric`); + assert.ok(escalationCase.ageHours >= 0, `${escalationCase.id} age must be non-negative`); + assert.ok( + allowedRiskLevels.has(escalationCase.riskLevel), + `${escalationCase.id} risk is invalid`, + ); + assert.ok( + allowedStatuses.has(escalationCase.recommendedStatus), + `${escalationCase.id} status is invalid`, + ); + assert.ok(Array.isArray(escalationCase.signals), `${escalationCase.id} signals are listed`); + assert.ok(escalationCase.signals.length > 0, `${escalationCase.id} needs signals`); + assert.ok(escalationCase.nextAction, `${escalationCase.id} needs a next action`); + + if (escalationCase.riskLevel === "critical") { + assert.equal( + escalationCase.recommendedStatus, + "urgent-escalation", + `${escalationCase.id} critical risk must escalate urgently`, + ); + } + + if (!escalationCase.owner) { + assert.equal( + escalationCase.recommendedStatus, + "needs-owner", + `${escalationCase.id} missing owner must be classified as needs-owner`, + ); + } + + if (escalationCase.signals.includes("approval-needed")) { + assert.equal( + escalationCase.recommendedStatus, + "manager-review", + `${escalationCase.id} approval-needed cases require manager review`, + ); + } + + seenStatuses.add(escalationCase.recommendedStatus); + seenIds.add(escalationCase.id); + } + + for (const status of requiredStatuses) { + assert.ok(seenStatuses.has(status), `fixture must include ${status}`); + } +}); + +test("documentation exposes setup, limitations, and review boundaries", async () => { + const [readme, specs, testPlan, reviewNotes] = await Promise.all([ + readFile(readmePath, "utf8"), + readFile(specsPath, "utf8"), + readFile(testPlanPath, "utf8"), + readFile(reviewNotesPath, "utf8"), + ]); + + assert.ok( + readme.includes("node --test tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs"), + ); + assert.ok(readme.includes("tools/v2/team/escalation-tool/")); + assert.ok(specs.includes("urgent-escalation")); + assert.ok(specs.includes("Do not add inbox ingestion")); + assert.ok(testPlan.includes("Manual Review Checklist")); + assert.ok(testPlan.includes("no production data")); + assert.ok(reviewNotes.includes("Out Of Scope")); + assert.ok(reviewNotes.toLowerCase().includes("future")); + assert.ok(reviewNotes.toLowerCase().includes("integration")); +});