From b1c6128a68aaae16fd23683b1140fd978d4fe9c0 Mon Sep 17 00:00:00 2001 From: city in the sky Date: Tue, 7 Jul 2026 21:24:51 +0800 Subject: [PATCH] Add mail-to-ticket converter architecture contract --- .../team/mail-to-ticket-converter/README.md | 40 ++++- .../docs/ARCHITECTURE.md | 160 ++++++++++++++++++ .../docs/DATA_OWNERSHIP.md | 77 +++++++++ .../v2/team/mail-to-ticket-converter/specs.md | 63 +++++-- .../tests/architecture-contract.test.mjs | 79 +++++++++ 5 files changed, 401 insertions(+), 18 deletions(-) create mode 100644 tools/v2/team/mail-to-ticket-converter/docs/ARCHITECTURE.md create mode 100644 tools/v2/team/mail-to-ticket-converter/docs/DATA_OWNERSHIP.md create mode 100644 tools/v2/team/mail-to-ticket-converter/tests/architecture-contract.test.mjs diff --git a/tools/v2/team/mail-to-ticket-converter/README.md b/tools/v2/team/mail-to-ticket-converter/README.md index c9d2bcb0..eb7b975b 100644 --- a/tools/v2/team/mail-to-ticket-converter/README.md +++ b/tools/v2/team/mail-to-ticket-converter/README.md @@ -1,15 +1,45 @@ # Mail-to-Ticket Converter -This folder is the isolated workspace for the Mail-to-Ticket Converter tool. +Mail-to-Ticket Converter is a V2 team tool for preparing support-ticket drafts +from email evidence. This issue defines the isolated architecture contract only; +it does not connect the tool to live inboxes, ticketing systems, or the main app. ## Ownership Boundary All work for this tool must stay inside: -`text -.\tools\v2\team\mail-to-ticket-converter\ -` +```text +tools/v2/team/mail-to-ticket-converter/ +``` 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. +## Architecture Map + +- `specs.md` defines release tier, contributor boundaries, and module rules. +- `docs/ARCHITECTURE.md` defines components, services, hooks, tests, docs, and + dependency direction. +- `docs/DATA_OWNERSHIP.md` documents draft ticket ownership, prohibited + production ownership, and future adapter constraints. +- `tests/architecture-contract.test.mjs` validates that the architecture + contract stays explicit and folder-local. + +## Local Validation + +Run from the repository root: + +```bash +node --test tools/v2/team/mail-to-ticket-converter/tests/architecture-contract.test.mjs +``` + +The test is zero-dependency and does not import the main app. + +## Current Limitations + +- No inbox ingestion, mailbox mutation, ticket creation, ticket update, + notification, database persistence, or external ticketing provider call is + implemented. +- No app shell, routing, dashboard, auth, wallet, Stellar, or shared + design-system integration is included. +- Future implementation work should build local services and fixtures first; + app-level wiring belongs in a separate integration issue. diff --git a/tools/v2/team/mail-to-ticket-converter/docs/ARCHITECTURE.md b/tools/v2/team/mail-to-ticket-converter/docs/ARCHITECTURE.md new file mode 100644 index 00000000..66c7b9d7 --- /dev/null +++ b/tools/v2/team/mail-to-ticket-converter/docs/ARCHITECTURE.md @@ -0,0 +1,160 @@ +# Mail-to-Ticket Converter Architecture Contract + +## Purpose + +Mail-to-Ticket Converter is a self-contained V2 team mini-product. It prepares +reviewable ticket drafts from email evidence so a team can triage support work +without wiring this folder into the live inbox or ticketing provider during the +architecture phase. + +## Ownership Boundary + +All implementation, fixtures, tests, and docs for this tool must remain inside: + +```text +tools/v2/team/mail-to-ticket-converter/ +``` + +No file outside this folder is required for this architecture issue. + +## Module Boundaries + +### `components/` + +Responsibility: render the local ticket conversion review surface. + +Planned components: + +- `MailToTicketConverterShell`: local root component that receives sanitized + email evidence or fixture data and coordinates the conversion flow. +- `TicketDraftPreview`: displays extracted title, description, priority, + requester, assignee hint, and tags. +- `EvidenceSummaryPanel`: shows summarized source evidence without exposing + production email bodies. +- `DuplicateTicketNotice`: presents possible duplicate ticket warnings. +- `TicketReviewActions`: presents reviewer actions such as accept draft, defer, + reject, or mark-needs-more-evidence. + +Rules: + +- Components receive data from hooks or props. +- Components do not read the inbox, database, wallet, Stellar, auth, or + ticketing provider state. +- Components do not create or update production tickets. + +### `services/` + +Responsibility: hold pure business logic and deterministic helpers. + +Planned services: + +- `emailEvidenceNormalizer`: converts caller-provided evidence into a local, + sanitized input shape. +- `ticketDraftBuilder`: prepares ticket title, description, priority, tags, and + requester metadata. +- `ticketConfidenceScorer`: computes confidence and review requirements. +- `duplicateTicketDetector`: flags local duplicate candidates from injected + ticket summaries or fixtures. +- `draftReviewStateService`: models local reviewer states and transition rules. + +Rules: + +- Services do not import React. +- Services do not make live network calls. +- Services use injected data or local fixtures instead of fetching from the + main app or external ticketing systems. + +### `hooks/` + +Responsibility: bridge component state to local services. + +Planned hooks: + +- `useMailToTicketDrafts`: manages draft loading, normalization, scoring, and + review state. +- `useTicketDraftFilters`: owns local filters for priority, status, requester, + team, and confidence. +- `useTicketReviewActions`: exposes local reviewer actions without writing to + production ticketing systems. + +Rules: + +- Hooks call services instead of duplicating business logic. +- Hooks do not write to global stores or app contexts. +- Hooks do not register routes, navigation items, inbox actions, or ticketing + provider webhooks. + +### `tests/` + +Responsibility: provide local confidence without requiring the main app. + +Expected coverage: + +- Architecture and documentation contract tests. +- Service tests for evidence normalization, draft building, confidence scoring, + duplicate detection, and review transitions. +- Fixture tests proving all sample data is synthetic. +- Hook and component tests after implementation exists. + +Rules: + +- Tests must stay folder-local. +- Tests may use local fixtures and standard test utilities already present in + the repository. +- Tests must not require a live inbox, production database, wallet, Stellar + account, or external ticketing provider. + +### `docs/` + +Responsibility: explain the tool contract for future contributors. + +Expected contents: + +- Architecture and dependency boundaries. +- Data ownership and privacy rules. +- Review notes and test plans for future implementation issues. +- Future integration notes that describe, but do not implement, app wiring. + +## Dependency Graph + +```text +components/ -> hooks/ -> services/ +tests/ -> docs/, fixtures/, components/, hooks/, services/ +docs/ -> descriptive only +``` + +Components should not import services directly once hooks exist. Services should +not import components or hooks. Docs should not be imported at runtime. + +## Integration Constraints + +This architecture issue must not: + +- Modify the main app shell, dashboard, routing, navigation, or shared layout. +- Connect to the existing inbox architecture or mail rendering engine. +- Create, update, sync, or delete tickets in any external provider. +- Add database schema, persistence, or production audit writes. +- Add authentication, wallet, Stellar, or payment behavior. +- Add notifications, webhooks, ticketing sync, or external provider calls. +- Change the shared design system. + +Future integration should pass sanitized evidence and callbacks into the +folder-local shell through a thin adapter. The tool should remain the source of +truth for conversion logic, while the main app owns routing, authorization, +live mail access, persistence, ticket publishing, and provider credentials. + +## Contributor Rules + +May change inside this folder: + +- Local components, services, hooks, tests, fixtures, and docs. +- Internal types and local data contracts. +- Architecture docs when module boundaries evolve. + +Must not change in this issue: + +- Files outside `tools/v2/team/mail-to-ticket-converter/`. +- Main app shell, routing, inbox, wallet, Stellar, auth, database, ticketing + provider, or shared design-system code. +- Production data access, production ticket creation, external provider calls, + or live network integrations. diff --git a/tools/v2/team/mail-to-ticket-converter/docs/DATA_OWNERSHIP.md b/tools/v2/team/mail-to-ticket-converter/docs/DATA_OWNERSHIP.md new file mode 100644 index 00000000..6ba82d48 --- /dev/null +++ b/tools/v2/team/mail-to-ticket-converter/docs/DATA_OWNERSHIP.md @@ -0,0 +1,77 @@ +# Mail-to-Ticket Converter Data Ownership + +## Local Data Model + +This tool owns only folder-local, review-oriented data shapes: + +- `EmailEvidence`: sanitized evidence summary provided by fixtures or a future + adapter. +- `TicketDraft`: local title, description, priority, requester, tags, and + assignee hint. +- `TicketConfidence`: confidence score, missing-field warnings, and review + requirements. +- `DuplicateTicketHint`: local warning that a draft may match an existing + ticket summary. +- `TicketReviewState`: local reviewer status such as `new`, `accepted`, + `deferred`, `rejected`, or `needs-more-evidence`. +- `TicketDraftFilterState`: local filters for team, status, priority, and + confidence. + +These models are internal contracts. They do not imply database ownership, +mailbox ownership, or production ticketing rights. + +## Data This Tool Does Not Own + +The tool must not own or mutate: + +- Live inbox threads, rendered email bodies, attachments, or mailbox metadata. +- User, teammate, department, auth, role, or permission records. +- Wallet, Stellar, payment, or blockchain state. +- Main application database rows or migrations. +- Production ticketing provider records, ticket ids, comments, labels, status + changes, or audit trails. +- Notification, CRM, analytics, or webhook provider records. + +## Source Data Rules + +Before integration exists, data should come from local fixtures or inputs +provided by the caller. Fixtures must be synthetic and must not include real +sender, recipient, customer, mailbox, teammate, account, organization, ticket, +or provider data. + +After a future integration issue is approved, the main app should prepare and +sanitize evidence before passing it into this tool. This folder should not fetch +or scrape the inbox directly and should not call ticketing providers directly. + +## Storage Rules + +- Architecture-only work does not persist data. +- Future local prototypes may keep state in memory or local fixtures. +- Production persistence must be handled by a separate integration issue. +- Ticket creation or updates must be a separate issue with explicit review, + authorization, idempotency, validation, and rollback behavior. + +## Dependency Ownership + +- `components/` own rendering and local user intent. +- `hooks/` own local state orchestration. +- `services/` own evidence normalization, draft building, scoring, duplicate + detection, and transition logic. +- `tests/` own local verification. +- `docs/` own contributor guidance and architecture constraints. + +No layer owns main-app data access, production persistence, ticket provider +credentials, wallet behavior, or Stellar behavior. + +## Future Adapter Contract + +A future integration should provide this tool with: + +- sanitized email evidence summaries. +- optional ticketing taxonomy metadata. +- reviewer identity already authorized by the main app. +- duplicate ticket summaries that are safe to display. +- callbacks for previewing, creating, or updating tickets. + +That adapter should live outside this folder and should be reviewed in its own +issue. This architecture issue only defines the local contract. diff --git a/tools/v2/team/mail-to-ticket-converter/specs.md b/tools/v2/team/mail-to-ticket-converter/specs.md index 6b6dff0c..70b8977d 100644 --- a/tools/v2/team/mail-to-ticket-converter/specs.md +++ b/tools/v2/team/mail-to-ticket-converter/specs.md @@ -1,36 +1,64 @@ # Mail-to-Ticket Converter -Convert mail into tickets. +Convert email evidence into reviewable ticket drafts while keeping live inbox +and ticketing integration out of this architecture-only issue. ## 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/mail-to-ticket-converter/` 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/ - docs/ - "@ | Set-Content -Path "tools/v2/team/mail-to-ticket-converter/README.md" - @" +- tests/ -# Mail-to-Ticket Converter Specs +## Module Boundary Summary -## Purpose +- `components/`: UI for reviewing draft tickets, extracted fields, evidence, + duplicate warnings, and reviewer actions. +- `services/`: pure logic for normalizing email evidence, extracting ticket + fields, scoring confidence, detecting duplicates, and modeling draft states. +- `hooks/`: local React state adapters that connect components to services + without reaching into app-wide stores. +- `tests/`: folder-local contract, fixture, service, hook, and component tests. +- `docs/`: architecture notes, data ownership rules, review plans, and future + integration notes. -Convert mail into tickets. +## Data Ownership -## Contributor boundary +This tool may own local ticket drafts, extracted fields, evidence summaries, +confidence signals, duplicate hints, review notes, and synthetic fixtures inside +this folder. It must not own or mutate live inbox threads, rendered mail, +attachments, ticketing provider records, user profiles, wallet state, Stellar +data, database rows, or production audit trails. + +## Dependency Rules + +- Components may depend on hooks and local types. +- Hooks may depend on services and local types. +- Services may depend on local fixtures, local types, and standard JavaScript + utilities. +- Tests may import local modules and read local docs or fixtures. +- Docs must remain descriptive and must not require runtime integration. + +Components should not call services directly once hooks exist. Services should +not import React, app shell modules, route modules, auth, wallet, Stellar, inbox, +mail rendering, database, or ticketing provider code. + +## Contributor Boundary All work for this tool should stay in: -$dir/ +```text +tools/v2/team/mail-to-ticket-converter/ +``` ## Required issue categories @@ -39,3 +67,12 @@ $dir/ - UI and accessibility - Security and performance - Testing and documentation + +## Future Integration Constraints + +Future app integration must be a separate issue. That follow-up should pass +sanitized email evidence into this tool through an adapter instead of letting +this folder read the inbox directly. It should also keep ticket creation, +ticket updates, provider authentication, database persistence, notifications, +routing, and auth checks outside this architecture-only contribution until +explicitly approved. diff --git a/tools/v2/team/mail-to-ticket-converter/tests/architecture-contract.test.mjs b/tools/v2/team/mail-to-ticket-converter/tests/architecture-contract.test.mjs new file mode 100644 index 00000000..23607b2c --- /dev/null +++ b/tools/v2/team/mail-to-ticket-converter/tests/architecture-contract.test.mjs @@ -0,0 +1,79 @@ +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 docs = { + readme: join(toolDir, "README.md"), + specs: join(toolDir, "specs.md"), + architecture: join(toolDir, "docs", "ARCHITECTURE.md"), + dataOwnership: join(toolDir, "docs", "DATA_OWNERSHIP.md"), +}; + +async function readDoc(path) { + return readFile(path, "utf8"); +} + +test("architecture docs define folder-local module boundaries", async () => { + const [readme, specs, architecture] = await Promise.all([ + readDoc(docs.readme), + readDoc(docs.specs), + readDoc(docs.architecture), + ]); + + const combined = `${readme}\n${specs}\n${architecture}`; + + assert.ok(combined.includes("tools/v2/team/mail-to-ticket-converter/")); + assert.ok(readme.includes("docs/ARCHITECTURE.md")); + assert.ok(readme.includes("docs/DATA_OWNERSHIP.md")); + + for (const boundary of ["components/", "services/", "hooks/", "tests/", "docs/"]) { + assert.ok(combined.includes(boundary), `${boundary} boundary must be documented`); + } + + assert.ok(architecture.includes("components/ -> hooks/ -> services/")); + assert.ok(specs.includes("Components should not call services directly")); +}); + +test("data ownership and integration constraints are explicit", async () => { + const [specs, architecture, dataOwnership] = await Promise.all([ + readDoc(docs.specs), + readDoc(docs.architecture), + readDoc(docs.dataOwnership), + ]); + + assert.ok(specs.includes("Release tier: V2")); + assert.ok(specs.includes("Audience: team")); + assert.ok(dataOwnership.includes("Fixtures must be synthetic")); + assert.ok(dataOwnership.includes("must not own or mutate")); + assert.ok(dataOwnership.includes("ticketing provider records")); + + for (const forbidden of [ + "main app shell", + "routing", + "inbox architecture", + "wallet", + "Stellar", + "database", + "ticketing provider", + "design system", + ]) { + assert.ok( + architecture.toLowerCase().includes(forbidden.toLowerCase()), + `${forbidden} constraint must be documented`, + ); + } +}); + +test("template placeholders are removed from contributor-facing docs", async () => { + const [readme, specs] = await Promise.all([readDoc(docs.readme), readDoc(docs.specs)]); + const combined = `${readme}\n${specs}`; + + for (const residue of ["$dir", "Set-Content", "System.Collections", '@"']) { + assert.equal(combined.includes(residue), false, `${residue} template residue remains`); + } +});