From f2e3808ff43e4b5dfc62242df7a2afc0d174fe67 Mon Sep 17 00:00:00 2001 From: city in the sky Date: Wed, 8 Jul 2026 00:03:16 +0800 Subject: [PATCH] Add email ownership architecture plan --- .../docs/ARCHITECTURE_PLAN.md | 123 ++++++++++++++++ .../docs/DATA_BOUNDARIES.md | 57 ++++++++ .../fixtures/architecture-module-map.json | 94 ++++++++++++ .../tests/architecture-plan.test.mjs | 135 ++++++++++++++++++ 4 files changed, 409 insertions(+) create mode 100644 tools/v1/team/email-ownership-tracker/docs/ARCHITECTURE_PLAN.md create mode 100644 tools/v1/team/email-ownership-tracker/docs/DATA_BOUNDARIES.md create mode 100644 tools/v1/team/email-ownership-tracker/fixtures/architecture-module-map.json create mode 100644 tools/v1/team/email-ownership-tracker/tests/architecture-plan.test.mjs diff --git a/tools/v1/team/email-ownership-tracker/docs/ARCHITECTURE_PLAN.md b/tools/v1/team/email-ownership-tracker/docs/ARCHITECTURE_PLAN.md new file mode 100644 index 00000000..5862e676 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/docs/ARCHITECTURE_PLAN.md @@ -0,0 +1,123 @@ +# Email Ownership Tracker Architecture Plan + +## Folder Boundary + +All code, tests, fixtures, and notes for this tool live under: + +```text +tools/v1/team/email-ownership-tracker/ +``` + +This V1 team tool must stay isolated until a future integration issue explicitly +connects it to the mail product. This architecture issue does not modify the app +shell, dashboard layout, routing, inbox architecture, mail rendering engine, +authentication, wallet core, Stellar core, database schema, notification system, +or shared design system. + +## Module Map + +The intended mini-product is split into folder-local layers: + +| Layer | Planned path | Responsibility | +| ---------- | ------------- | ---------------------------------------------------------------------- | +| Public API | `index.ts` | Re-export stable tool contracts only. | +| Types | `types/` | Define ownership events, owners, teams, history windows, and UI state. | +| Services | `services/` | Own pure ownership-history operations and in-memory adapters. | +| Guards | `guards/` | Validate unsafe input before services, hooks, storage, or rendering. | +| Hooks | `hooks/` | Bridge services to React state without importing app stores. | +| Components | `components/` | Render the isolated tracker surface and accessibility states. | +| Fixtures | `fixtures/` | Provide deterministic local data for tests and review. | +| Tests | `tests/` | Verify contracts, data boundaries, and behavior without app wiring. | +| Docs | `docs/` | Explain ownership, integration constraints, and review checklists. | + +## Data Flow + +The tool should use a one-way ownership flow: + +```text +mail event snapshot + -> guards validate ids, emails, tags, attachments, and timestamps + -> services normalize ownership events and derive current owners + -> hooks expose loading, empty, error, and ready states + -> components render local state without mutating mailbox data +``` + +The current architecture contract does not add persistence, app routes, mailbox +mutation, notification delivery, or live network calls. Future feature issues can +add those adapters only through folder-local boundaries and should document the +new trust assumptions before wiring them. + +## Internal Contracts + +Public API: + +- expose stable type names, service factories, and guard helpers through + `index.ts` only after the related implementation issue lands. +- do not re-export fixtures, tests, or private helper functions. + +Types: + +- use plain serializable objects for ownership events and owner summaries. +- represent timestamps as UTC ISO strings. +- avoid importing main app types from `src/`. + +Services: + +- accept already-guarded inputs and return copied data. +- keep ownership derivation deterministic for the same event order. +- paginate or window histories before expensive derivation. +- do not call network APIs, wallet APIs, database clients, or notification + senders. + +Guards: + +- reject unsafe ids, malformed emails, unsupported actions, invalid timestamps, + oversized histories, unsafe tags, and malformed attachment metadata. +- remain pure and synchronous unless a future issue explicitly adds async + validation. + +Hooks: + +- own only local UI state and invoke folder-local services. +- expose explicit `idle`, `loading`, `ready`, `empty`, and `error` states. +- avoid importing app stores, route state, auth state, wallet state, or inbox + internals. + +Components: + +- render from hook state and props only. +- include accessible names, focus behavior, and status regions for async states. +- do not edit the shared design system or app shell. + +Fixtures and tests: + +- keep data synthetic and deterministic. +- cover malformed input, large histories, empty ownership states, and handoff + notes. +- use local Node or Vitest tests that can run without product integration. + +## Contributor Change Spec + +Future contributors may: + +- add files under this tool folder to complete feature, UI, security, and test + issues. +- add folder-local fixtures, docs, tests, services, hooks, and components. +- update this plan when a future issue changes module ownership. + +Future contributors may not: + +- modify `src/`, app routes, dashboard shell, inbox architecture, auth, wallet, + Stellar, database schema, notification providers, or shared UI primitives from + this issue. +- introduce production data, secrets, paid services, live provider calls, or + mailbox mutations. +- make this tool depend on another isolated tool folder. + +## Review Checklist + +- All changed files stay under `tools/v1/team/email-ownership-tracker/`. +- Architecture docs define components, services, hooks, tests, fixtures, and docs. +- Data ownership and dependency limits are explicit. +- No app integration code is added. +- Tests can verify the architecture contract without running the main app. diff --git a/tools/v1/team/email-ownership-tracker/docs/DATA_BOUNDARIES.md b/tools/v1/team/email-ownership-tracker/docs/DATA_BOUNDARIES.md new file mode 100644 index 00000000..52886e1d --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/docs/DATA_BOUNDARIES.md @@ -0,0 +1,57 @@ +# Email Ownership Tracker Data Boundaries + +## Owned Data + +The isolated tool owns only synthetic or caller-provided data structures: + +- ownership event snapshots. +- current-owner summaries derived from those snapshots. +- local team member references used by the tracker UI. +- attachment metadata, not attachment file bodies. +- local UI state such as loading, empty, error, and ready views. +- deterministic fixtures used by folder-local tests. + +The tool does not own mailbox records, account identities, auth sessions, +wallet accounts, Stellar transactions, database rows, notification jobs, or +provider credentials. + +## Boundary Rules + +Input enters the tool as plain objects. Guards should validate shape, size, and +unsafe characters before any service, hook, component, or future adapter uses the +data. Services should return copied summaries and should not expose internal +mutable collections. + +The tool must not write to an inbox, change message assignment state in the main +mail product, send notifications, persist records, or call a provider from this +architecture issue. Future integration work must document the owner of each +adapter before connecting it. + +## Dependency Rules + +Allowed dependencies: + +- JavaScript and TypeScript standard language features. +- React only inside future `components/` and `hooks/` files. +- Existing project test tools for folder-local tests. +- Existing shared UI primitives only when a future UI issue permits components. + +Forbidden dependencies for this architecture issue: + +- imports from `src/features/`, `src/routes/`, app stores, inbox internals, auth, + wallet, Stellar, database, or notification modules. +- imports from other isolated tool folders. +- new npm packages, paid APIs, cloud services, wallet signatures, or production + credentials. + +## Future Adapter Handoff + +A future integration issue should create an adapter checklist before touching +product data: + +1. identify the source mailbox event and its owner. +2. run guard helpers before transforming the event. +3. limit history windows and team member lists. +4. return copied summaries to UI code. +5. keep writes behind a separately reviewed ownership-write issue. +6. document any new persistence or notification behavior in this folder first. diff --git a/tools/v1/team/email-ownership-tracker/fixtures/architecture-module-map.json b/tools/v1/team/email-ownership-tracker/fixtures/architecture-module-map.json new file mode 100644 index 00000000..7dd4d539 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/fixtures/architecture-module-map.json @@ -0,0 +1,94 @@ +{ + "tool": "Email Ownership Tracker", + "root": "tools/v1/team/email-ownership-tracker/", + "releaseTier": "V1", + "audience": "team", + "modules": [ + { + "name": "public-api", + "path": "index.ts", + "owner": "tool boundary", + "responsibility": "Re-export stable contracts only.", + "allowedImports": ["types", "services", "guards"], + "forbiddenImports": ["src/routes", "src/features", "wallet", "stellar", "database"] + }, + { + "name": "types", + "path": "types/", + "owner": "data model", + "responsibility": "Define serializable ownership event and summary shapes.", + "allowedImports": [], + "forbiddenImports": ["src/", "services/", "components/"] + }, + { + "name": "services", + "path": "services/", + "owner": "ownership logic", + "responsibility": "Derive current owners from guarded history events.", + "allowedImports": ["types", "guards"], + "forbiddenImports": ["src/", "wallet", "stellar", "database", "notification"] + }, + { + "name": "guards", + "path": "guards/", + "owner": "input boundary", + "responsibility": "Reject malformed or hostile ownership inputs.", + "allowedImports": [], + "forbiddenImports": ["src/", "network", "database", "wallet"] + }, + { + "name": "hooks", + "path": "hooks/", + "owner": "ui state bridge", + "responsibility": "Expose local state transitions around folder-local services.", + "allowedImports": ["types", "services", "guards"], + "forbiddenImports": ["src/routes", "src/features", "app stores", "wallet"] + }, + { + "name": "components", + "path": "components/", + "owner": "isolated UI", + "responsibility": "Render tracker states without app-shell integration.", + "allowedImports": ["types", "hooks", "services", "fixtures"], + "forbiddenImports": ["src/routes", "src/features", "mail rendering", "database"] + }, + { + "name": "fixtures", + "path": "fixtures/", + "owner": "review data", + "responsibility": "Provide deterministic synthetic data.", + "allowedImports": [], + "forbiddenImports": ["production data", "network", "secrets"] + }, + { + "name": "tests", + "path": "tests/", + "owner": "contract verification", + "responsibility": "Verify architecture, data, and behavior contracts locally.", + "allowedImports": ["types", "services", "guards", "fixtures", "docs"], + "forbiddenImports": ["src/routes", "wallet", "stellar", "database"] + }, + { + "name": "docs", + "path": "docs/", + "owner": "handoff notes", + "responsibility": "Document architecture, data ownership, and integration limits.", + "allowedImports": [], + "forbiddenImports": ["production credentials", "external provider setup"] + } + ], + "forbiddenChangeAreas": [ + "src/", + "app shell", + "dashboard layout", + "navigation", + "routing", + "inbox architecture", + "mail rendering", + "authentication", + "wallet core", + "Stellar core", + "database schema", + "shared design system" + ] +} diff --git a/tools/v1/team/email-ownership-tracker/tests/architecture-plan.test.mjs b/tools/v1/team/email-ownership-tracker/tests/architecture-plan.test.mjs new file mode 100644 index 00000000..4c09f048 --- /dev/null +++ b/tools/v1/team/email-ownership-tracker/tests/architecture-plan.test.mjs @@ -0,0 +1,135 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { describe, it } from "node:test"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const toolRoot = join(__dirname, ".."); +const docsDir = join(toolRoot, "docs"); +const fixturePath = join(toolRoot, "fixtures", "architecture-module-map.json"); + +const architecturePlan = readFileSync(join(docsDir, "ARCHITECTURE_PLAN.md"), "utf8"); +const dataBoundaries = readFileSync(join(docsDir, "DATA_BOUNDARIES.md"), "utf8"); +const moduleMap = JSON.parse(readFileSync(fixturePath, "utf8")); + +describe("architecture plan", () => { + it("documents the folder-local boundary and forbidden app areas", () => { + assert.match(architecturePlan, /tools\/v1\/team\/email-ownership-tracker\//); + + for (const forbidden of [ + "app shell", + "routing", + "inbox architecture", + "wallet core", + "Stellar core", + "database schema", + "shared design system", + ]) { + assert.ok( + architecturePlan.includes(forbidden), + `expected architecture plan to mention ${forbidden}`, + ); + } + }); + + it("defines the required internal modules for future contributors", () => { + for (const moduleName of [ + "Public API", + "Types", + "Services", + "Guards", + "Hooks", + "Components", + "Fixtures", + "Tests", + "Docs", + ]) { + assert.ok(architecturePlan.includes(moduleName), `missing ${moduleName} module`); + } + }); + + it("contains contributor rules for allowed and forbidden changes", () => { + assert.match(architecturePlan, /Future contributors may:/); + assert.match(architecturePlan, /Future contributors may not:/); + assert.match(architecturePlan, /No app integration code is added/); + }); +}); + +describe("data boundaries", () => { + it("documents owned data without claiming product-owned systems", () => { + for (const owned of [ + "ownership event snapshots", + "current-owner summaries", + "attachment metadata", + "deterministic fixtures", + ]) { + assert.ok(dataBoundaries.includes(owned), `missing owned data: ${owned}`); + } + + for (const notOwned of ["mailbox records", "wallet accounts", "database rows"]) { + assert.ok(dataBoundaries.includes(notOwned), `missing non-owned data: ${notOwned}`); + } + }); + + it("requires future adapters to document product data ownership", () => { + assert.match(dataBoundaries, /Future Adapter Handoff/); + assert.match(dataBoundaries, /identify the source mailbox event and its owner/); + assert.match(dataBoundaries, /keep writes behind a separately reviewed ownership-write issue/); + }); +}); + +describe("module map fixture", () => { + it("is scoped to the issue folder", () => { + assert.equal(moduleMap.tool, "Email Ownership Tracker"); + assert.equal(moduleMap.root, "tools/v1/team/email-ownership-tracker/"); + assert.equal(moduleMap.releaseTier, "V1"); + assert.equal(moduleMap.audience, "team"); + }); + + it("covers every required folder-local architecture layer", () => { + const moduleNames = new Set(moduleMap.modules.map((module) => module.name)); + + for (const expected of [ + "public-api", + "types", + "services", + "guards", + "hooks", + "components", + "fixtures", + "tests", + "docs", + ]) { + assert.ok(moduleNames.has(expected), `missing module map entry: ${expected}`); + } + }); + + it("marks each module with owner, responsibility, and import constraints", () => { + for (const module of moduleMap.modules) { + assert.equal(typeof module.name, "string"); + assert.equal(typeof module.path, "string"); + assert.equal(typeof module.owner, "string"); + assert.equal(typeof module.responsibility, "string"); + assert.ok(Array.isArray(module.allowedImports)); + assert.ok(Array.isArray(module.forbiddenImports)); + assert.ok(module.path.length > 0); + assert.ok(module.owner.length > 0); + assert.ok(module.responsibility.length > 0); + } + }); + + it("keeps sensitive app areas in the forbidden change list", () => { + for (const area of [ + "src/", + "routing", + "inbox architecture", + "wallet core", + "Stellar core", + "database schema", + "shared design system", + ]) { + assert.ok(moduleMap.forbiddenChangeAreas.includes(area), `missing forbidden area: ${area}`); + } + }); +});