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/v1/team/email-ownership-tracker/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
# Email Ownership Tracker

This folder is the isolated workspace for the Email Ownership Tracker tool.
Email Ownership Tracker is an isolated V1 team workspace for reviewing who owns
email threads, when ownership changed, and which handoffs still need team
attention. It is not wired into the main app yet.

## Ownership Boundary

All work for this tool must stay inside:

`text
.\tools\v1\team\email-ownership-tracker\
`
```text
tools/v1/team/email-ownership-tracker/
```

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/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs
```

The test validates the local fixture and documentation contract without using
main app test helpers.

## Documentation Map

- `specs.md` defines the ownership review model and contributor boundary.
- `docs/test-plan.md` lists automated and manual review checks.
- `docs/review-notes.md` summarizes reviewer expectations and limitations.
- `fixtures/ownership-review-cases.json` provides synthetic ownership scenarios.
- `tests/email-ownership-docs.test.mjs` validates the fixture and documentation
contract.

## Current Limitations

- No inbox ingestion, mailbox mutation, team assignment write, notification, or
database persistence is included.
- No app routing, dashboard registration, wallet, Stellar, auth, or shared
design-system integration is included.
- Fixture content is synthetic and should remain folder-local.
44 changes: 44 additions & 0 deletions tools/v1/team/email-ownership-tracker/docs/review-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Email Ownership Tracker Review Notes

## What This Contribution Covers

This contribution adds contributor-facing documentation and fixture coverage for
the isolated Email Ownership Tracker folder. It gives reviewers a local
validation path without connecting to production inbox data, assignment systems,
or the main application.

Reviewers should expect:

- a repaired README and specs file for the folder-local workflow.
- a deterministic synthetic fixture with representative ownership statuses.
- a local Node test that validates fixture and documentation requirements.
- clear limitations for future inbox, notification, assignment, and persistence
work.

## What Is Out Of Scope

This issue does not add:

- inbox reads, mailbox writes, or mail rendering changes.
- owner assignment side effects or notification delivery.
- database schema changes or persistence.
- app routing, dashboard placement, authentication, wallet, Stellar, or shared
design-system changes.
- production sender, recipient, mailbox, customer, or teammate data.

## Reviewer Flow

1. Run the local Node test from the repository root.
2. Inspect `fixtures/ownership-review-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 both current checks and future coverage
gaps.
5. Confirm the PR changes only files inside this tool folder.

## Known Limitations

The fixture documents the review contract before this folder has production
assignment behavior. A future feature issue should implement ownership state
logic inside this folder first; main app integration should remain a separate
follow-up after the isolated model is accepted.
42 changes: 42 additions & 0 deletions tools/v1/team/email-ownership-tracker/docs/test-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Email Ownership Tracker Test Plan

## Automated Checks

Run from the repository root:

```bash
node --test tools/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs
```

The local test checks that:

- synthetic fixture cases use stable identifiers and contain no production data.
- all documented ownership statuses are represented.
- unowned threads are classified as `needs-owner`.
- transfer cases include both current and previous owners.
- stale cases require review and include an age-based signal.
- documentation includes setup, fixture, limitation, and review guidance.

## Manual Review Checklist

- Confirm all changed files stay under `tools/v1/team/email-ownership-tracker/`.
- Confirm fixture content is synthetic and avoids real sender, recipient,
customer, mailbox, or teammate data.
- Confirm the status rules in `specs.md` match the fixture vocabulary.
- Confirm no live network calls, secrets, database writes, assignment writes,
notifications, inbox writes, wallet behavior, Stellar behavior, or app routing
changes are introduced.
- Confirm future integration boundaries are documented instead of implemented.

## Future Code Coverage

When folder-local service or UI code is added, extend coverage for:

- owner assignment and reassignment transitions.
- stale ownership thresholds.
- empty, loading, error, and success states.
- duplicate owner changes and missing-owner warnings.
- audit history ordering and reviewer notes.

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,69 @@
{
"tool": "email-ownership-tracker",
"runContext": {
"now": "2026-07-01T00:00:00.000Z",
"source": "synthetic-folder-local-fixture"
},
"cases": [
{
"id": "ownership-001",
"threadId": "thread-renewal-owner",
"subject": "Renewal follow-up has active owner",
"currentOwner": "Jordan Lee",
"previousOwner": null,
"department": "Customer Success",
"lastChangedAt": "2026-06-30T16:00:00.000Z",
"ageHours": 8,
"signals": ["owner-present", "recent-change"],
"ownershipStatus": "owned",
"reviewRequired": false,
"nextAction": "Keep the current owner attached and monitor the thread.",
"containsProductionData": false
},
{
"id": "ownership-002",
"threadId": "thread-unassigned-implementation",
"subject": "Implementation question needs owner",
"currentOwner": "",
"previousOwner": null,
"department": "Implementation",
"lastChangedAt": "2026-06-29T10:00:00.000Z",
"ageHours": 38,
"signals": ["missing-owner", "customer-waiting"],
"ownershipStatus": "needs-owner",
"reviewRequired": true,
"nextAction": "Assign a named implementation owner before the next reply.",
"containsProductionData": false
},
{
"id": "ownership-003",
"threadId": "thread-transfer-support",
"subject": "Support thread moving to billing owner",
"currentOwner": "Mira Patel",
"previousOwner": "Noah Kim",
"department": "Billing",
"lastChangedAt": "2026-06-30T11:00:00.000Z",
"ageHours": 13,
"signals": ["handoff-requested", "department-change"],
"ownershipStatus": "transfer-pending",
"reviewRequired": true,
"nextAction": "Confirm the handoff and add an internal ownership note.",
"containsProductionData": false
},
{
"id": "ownership-004",
"threadId": "thread-stale-escalation",
"subject": "Escalation has stale owner",
"currentOwner": "Avery Stone",
"previousOwner": null,
"department": "Support",
"lastChangedAt": "2026-06-27T09:00:00.000Z",
"ageHours": 87,
"signals": ["stale-owner", "sla-risk"],
"ownershipStatus": "stale",
"reviewRequired": true,
"nextAction": "Review whether the owner is still accountable or reassign.",
"containsProductionData": false
}
]
}
50 changes: 37 additions & 13 deletions tools/v1/team/email-ownership-tracker/specs.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,60 @@
# Email Ownership Tracker

Track ownership history.
Track ownership history for team email threads and document when threads are
owned, stale, transferred, or missing an accountable owner.

## Scope

- Release tier: $(System.Collections.Hashtable.Tier.ToUpperInvariant())
- Audience: $(System.Collections.Hashtable.Audience)
- Folder ownership: $dir/
- Release tier: V1
- Audience: team
- Folder ownership: `tools/v1/team/email-ownership-tracker/`

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/v1/team/email-ownership-tracker/README.md"
@"

# Email Ownership Tracker Specs
## Fixture Contract

## Purpose
Synthetic ownership cases should include:

Track ownership history.
- `id`: stable fixture-local identifier.
- `threadId`: synthetic email thread identifier.
- `subject`: synthetic thread subject.
- `currentOwner`: assigned team member or an empty string when unowned.
- `previousOwner`: previous team member or null.
- `department`: team responsible for the current next step.
- `lastChangedAt`: ISO timestamp for the latest ownership change.
- `ageHours`: hours since the latest ownership update.
- `signals`: local reasons for the ownership status.
- `ownershipStatus`: `owned`, `needs-owner`, `transfer-pending`, or `stale`.
- `reviewRequired`: true unless ownership is healthy.
- `nextAction`: reviewable guidance for the team.
- `containsProductionData`: false for all local fixtures.

## Contributor boundary
## Status Rules

- `owned`: a named owner is attached and the thread is recent.
- `needs-owner`: no owner is assigned.
- `transfer-pending`: ownership is being handed from one teammate to another.
- `stale`: a named owner exists, but ownership has not changed recently enough.

## Contributor Boundary

All work for this tool should stay in:

$dir/
```text
tools/v1/team/email-ownership-tracker/
```

Do not add inbox writes, mailbox mutations, assignment side effects,
notifications, database persistence, app routes, wallet behavior, Stellar
behavior, auth changes, or shared design system changes in this issue.

## Required issue categories

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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", "ownership-review-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(["owned", "needs-owner", "transfer-pending", "stale"]);
const requiredStatuses = ["owned", "needs-owner", "transfer-pending", "stale"];

async function readJson(path) {
const raw = await readFile(path, "utf8");
return JSON.parse(raw);
}

test("sample ownership fixture follows the documented contract", async () => {
const fixture = await readJson(fixturePath);

assert.equal(fixture.tool, "email-ownership-tracker");
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 ownershipCase of fixture.cases) {
assert.ok(ownershipCase.id, "case needs a stable id");
assert.equal(seenIds.has(ownershipCase.id), false, `${ownershipCase.id} is duplicated`);
assert.ok(ownershipCase.threadId, `${ownershipCase.id} needs a thread id`);
assert.ok(ownershipCase.subject, `${ownershipCase.id} needs a subject`);
assert.ok(ownershipCase.department, `${ownershipCase.id} needs a department`);
assert.match(ownershipCase.lastChangedAt, /^\d{4}-\d{2}-\d{2}T/);
assert.equal(typeof ownershipCase.ageHours, "number", `${ownershipCase.id} age is numeric`);
assert.ok(ownershipCase.ageHours >= 0, `${ownershipCase.id} age must be non-negative`);
assert.equal(
ownershipCase.containsProductionData,
false,
`${ownershipCase.id} must stay synthetic`,
);
assert.ok(
allowedStatuses.has(ownershipCase.ownershipStatus),
`${ownershipCase.id} status is invalid`,
);
assert.equal(typeof ownershipCase.reviewRequired, "boolean");
assert.ok(Array.isArray(ownershipCase.signals), `${ownershipCase.id} signals are listed`);
assert.ok(ownershipCase.signals.length > 0, `${ownershipCase.id} needs signals`);
assert.ok(ownershipCase.nextAction, `${ownershipCase.id} needs a next action`);

if (ownershipCase.ownershipStatus === "owned") {
assert.ok(ownershipCase.currentOwner, `${ownershipCase.id} owned cases need an owner`);
assert.equal(ownershipCase.reviewRequired, false);
}

if (ownershipCase.ownershipStatus === "needs-owner") {
assert.equal(ownershipCase.currentOwner, "", `${ownershipCase.id} should be unowned`);
assert.equal(ownershipCase.reviewRequired, true);
assert.ok(ownershipCase.signals.includes("missing-owner"));
}

if (ownershipCase.ownershipStatus === "transfer-pending") {
assert.ok(ownershipCase.currentOwner, `${ownershipCase.id} needs current owner`);
assert.ok(ownershipCase.previousOwner, `${ownershipCase.id} needs previous owner`);
assert.equal(ownershipCase.reviewRequired, true);
}

if (ownershipCase.ownershipStatus === "stale") {
assert.ok(ownershipCase.currentOwner, `${ownershipCase.id} stale cases still have an owner`);
assert.ok(ownershipCase.signals.includes("stale-owner"));
assert.equal(ownershipCase.reviewRequired, true);
}

seenStatuses.add(ownershipCase.ownershipStatus);
seenIds.add(ownershipCase.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/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs",
),
);
assert.ok(readme.includes("tools/v1/team/email-ownership-tracker/"));
assert.ok(specs.includes("transfer-pending"));
assert.ok(specs.includes("Do not add inbox writes"));
assert.ok(testPlan.includes("Manual Review Checklist"));
assert.ok(testPlan.includes("no live network calls"));
assert.ok(reviewNotes.includes("Out Of Scope"));
assert.ok(reviewNotes.toLowerCase().includes("integration"));
});