Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions tools/v2/team/escalation-tool/README.md
Original file line number Diff line number Diff line change
@@ -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.
44 changes: 44 additions & 0 deletions tools/v2/team/escalation-tool/docs/review-notes.md
Original file line number Diff line number Diff line change
@@ -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.
40 changes: 40 additions & 0 deletions tools/v2/team/escalation-tool/docs/test-plan.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
}
]
}
49 changes: 36 additions & 13 deletions tools/v2/team/escalation-tool/specs.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
112 changes: 112 additions & 0 deletions tools/v2/team/escalation-tool/tests/escalation-docs.test.mjs
Original file line number Diff line number Diff line change
@@ -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"));
});