diff --git a/tools/v1/team/email-ownership-tracker/README.md b/tools/v1/team/email-ownership-tracker/README.md index 4acb8a55..81efb129 100644 --- a/tools/v1/team/email-ownership-tracker/README.md +++ b/tools/v1/team/email-ownership-tracker/README.md @@ -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. diff --git a/tools/v1/team/email-ownership-tracker/docs/review-notes.md b/tools/v1/team/email-ownership-tracker/docs/review-notes.md new file mode 100644 index 00000000..56a41d85 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/docs/review-notes.md @@ -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. diff --git a/tools/v1/team/email-ownership-tracker/docs/test-plan.md b/tools/v1/team/email-ownership-tracker/docs/test-plan.md new file mode 100644 index 00000000..3373e953 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/docs/test-plan.md @@ -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. diff --git a/tools/v1/team/email-ownership-tracker/fixtures/ownership-review-cases.json b/tools/v1/team/email-ownership-tracker/fixtures/ownership-review-cases.json new file mode 100644 index 00000000..c17bba73 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/fixtures/ownership-review-cases.json @@ -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 + } + ] +} diff --git a/tools/v1/team/email-ownership-tracker/specs.md b/tools/v1/team/email-ownership-tracker/specs.md index fa8820cf..0c493b9b 100644 --- a/tools/v1/team/email-ownership-tracker/specs.md +++ b/tools/v1/team/email-ownership-tracker/specs.md @@ -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 diff --git a/tools/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs b/tools/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs new file mode 100644 index 00000000..e8bbe344 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/tests/email-ownership-docs.test.mjs @@ -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")); +});