Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,80 @@
|
||||
# Feature Manifests
|
||||
|
||||
This directory is the machine-readable contract for boilerplate core pruning. It complements `docs/feature-inventory.md`, which remains the human-readable inventory.
|
||||
|
||||
## Manifest-Decision: Pruned Core statt Full-Boilerplate
|
||||
|
||||
Feature Manifest chooses Pruned Core over a permanent Full-Boilerplate retention promise. `core` manifests define what remains in the default boilerplate; `plugin-candidate` manifests are extraction contracts for optional product surfaces; and `remove-candidate` manifests are deletion or review contracts for surfaces that should not become indefinitely supported legacy behavior. These manifests operationalize the inventory decision as planning/runtime guardrails and do not claim that every extraction or deletion has already happened in the current root runtime.
|
||||
|
||||
Architecture Closure closure aligns three canonical sources for future feature work: `docs/architecture-migration-status.md` records the current API, worker, package, pruning, and bundle handoff status; `docs/feature-inventory.md` records the human-readable Core/Plugin/Removal inventory; and `docs/feature-manifests/*.json` records the machine-readable Core/Plugin/Removal contract. Treat the manifest set as the executor-facing source of truth for ownership, dependencies, configuration, provider, worker, UAT, and pruning boundaries. Any future feature addition, migration, extraction, or removal must update the relevant manifest before implementation so code changes satisfy the declared contract instead of relying on stale roadmap assumptions.
|
||||
|
||||
Feature-specific decision reports may live under `docs/feature-decisions/`; canonical examples are the CRM decision report at `docs/feature-decisions/crm-decision-report.md` and the Email Marketing decision report at `docs/feature-decisions/email-marketing-decision-report.md`.
|
||||
|
||||
## Contract
|
||||
|
||||
Each `*.json` manifest follows `schema.json` and uses:
|
||||
|
||||
- `id`: stable feature identifier for cross-references.
|
||||
- `classification`: `core`, `plugin-candidate`, `remove-candidate`, or `mixed`.
|
||||
- `codeAreas`: likely source locations. These are planning hints, not ownership grants.
|
||||
- `dependencies`: other manifest IDs this feature needs.
|
||||
- `consumers`: features or surfaces that rely on this feature.
|
||||
- `integration.ownership`: `tenant-owned`, `admin-managed`, `hybrid`, or `system-only`.
|
||||
- `integration.surfaces`: admin, tenant, public, and API route contracts.
|
||||
- `integration.configuration`: admin settings, tenant settings, environment variables, and secret ownership.
|
||||
- `integration.workers`: queue/job requirements for async, browser-backed, or long-running work.
|
||||
- `integration.externalProviders`: provider dependencies such as billing vendors, AI providers, mail vendors, or browser runtimes.
|
||||
- `integration.permissions`, `auditEvents`, `billingImpacts`, and `uat`: implementation guardrails and operator checks.
|
||||
- `boundaries.mustKeep`: behavior that should remain in the boilerplate.
|
||||
- `boundaries.mayMoveToPlugin`: behavior safe to extract after dependency review.
|
||||
- `boundaries.mustNotTouch`: areas excluded from the current phase or owned by parallel work.
|
||||
- `pruning.action`: current intended action: `keep`, `extract-plugin`, `remove`, or `review`.
|
||||
|
||||
Route entries are source ownership hints unless they explicitly start with a runtime path such as `/v1/...`. Runtime URLs must follow the host split: admin routes live at the admin host root, tenant routes live under `/:tenant` on the app host, and API routes live under `/v1` on the API host. Implementation work must use host-aware helpers rather than reintroducing `/admin/...`, `/app/...`, or `/api/v1/...` links.
|
||||
|
||||
When a manifest change affects UI text, tenant data, or deploy behavior, include the matching verification in the plan: `pnpm run verify:i18n`, tenant-scope negative tests, `pnpm run verify:deploy-workflow`, Docker smoke checks, and production seed artifact verification as applicable.
|
||||
|
||||
## Current Manifests
|
||||
|
||||
- `core.json`: umbrella contract for the boilerplate core.
|
||||
- `product-core.json`: versioned CRM SaaS Product Core blueprint and access-gating contract.
|
||||
- `auth-tenancy.json`: authentication, accounts, tenants, roles, invitations.
|
||||
- `rows-entities-runtime.json`: Prisma/DB, rows, entity runtime foundations.
|
||||
- `billing.json`: Stripe-backed subscription and pricing foundation.
|
||||
- `transactional-mail.json`: password reset, invitation, and system mail.
|
||||
- `admin-app-shell.json`: admin and tenant app shells, dashboards, settings.
|
||||
- `plugin-candidates.json`: feature groups to package or make optional.
|
||||
- `remove-candidates.json`: feature groups to remove or aggressively review.
|
||||
- `ai-chatbot-admin-managed.json`: reference pattern for admin-managed, tenant-consumed, worker/browser-backed AI features.
|
||||
|
||||
## Proprietary Feature Template
|
||||
|
||||
When importing or building a proprietary feature, create or update a manifest before code changes and fill these fields:
|
||||
|
||||
- Feature ownership: `tenant-owned`, `admin-managed`, `hybrid`, or `system-only`.
|
||||
- Admin, tenant, public and API surfaces.
|
||||
- Config ownership: admin settings, tenant settings, environment variables.
|
||||
- Secrets policy: owner, storage, and client exposure rules.
|
||||
- Worker/queue jobs: queue names and job names from `packages/queue`.
|
||||
- Permissions: admin configuration and tenant usage capabilities.
|
||||
- Audit events: configuration, enablement, job lifecycle and failure events.
|
||||
- Billing impact: plan gates, metering, provider cost exposure.
|
||||
- UAT checklist: positive, negative and secret-exposure checks.
|
||||
|
||||
To create a valid scaffold:
|
||||
|
||||
```sh
|
||||
pnpm run manifest:new -- --id my-feature --title "My Feature" --classification plugin-candidate --ownership hybrid
|
||||
```
|
||||
|
||||
The generator writes `docs/feature-manifests/my-feature.json` and refuses to overwrite unless `--force` is passed. Replace scaffold placeholders before implementation.
|
||||
|
||||
## Validation
|
||||
|
||||
Cheap syntax validation:
|
||||
|
||||
```sh
|
||||
find docs/feature-manifests -name '*.json' -print0 | xargs -0 -n1 node -e 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"))'
|
||||
```
|
||||
|
||||
The app test suite validates the manifest structure, required integration fields, dependency references, admin-managed secret ownership, and worker queue/job declarations.
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "admin-app-shell",
|
||||
"title": "Admin and Tenant App Shell",
|
||||
"classification": "core",
|
||||
"status": "proposed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Authenticated tenant shell, admin shell, dashboards, settings layout, and core sidebar structure.",
|
||||
"rationale": [
|
||||
"The boilerplate needs stable authenticated shells even after product-specific modules are removed.",
|
||||
"Admin settings are required for operations, users, pricing configuration, and diagnostics."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/routes/app.$tenant.tsx",
|
||||
"apps/web/app/routes/app.$tenant/dashboard.tsx",
|
||||
"apps/web/app/routes/app.$tenant/settings*",
|
||||
"apps/web/app/routes/admin.tsx",
|
||||
"apps/web/app/routes/admin/dashboard.tsx",
|
||||
"apps/web/app/routes/admin/settings*",
|
||||
"apps/web/app/application/sidebar/AppSidebar.tsx",
|
||||
"apps/web/app/application/sidebar/AdminSidebar.tsx",
|
||||
"apps/web/app/components/layouts/sidebars/*"
|
||||
],
|
||||
"dependencies": [
|
||||
"auth-tenancy",
|
||||
"billing",
|
||||
"rows-entities-runtime"
|
||||
],
|
||||
"consumers": [
|
||||
"core",
|
||||
"plugin-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "hybrid",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin.tsx",
|
||||
"apps/web/app/routes/admin/dashboard.tsx",
|
||||
"apps/web/app/routes/admin/settings*"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant.tsx",
|
||||
"apps/web/app/routes/app.$tenant/dashboard.tsx",
|
||||
"apps/web/app/routes/app.$tenant/settings*"
|
||||
],
|
||||
"publicRoutes": [],
|
||||
"apiRoutes": []
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Admin shell settings and operational defaults"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant shell navigation and tenant settings"
|
||||
],
|
||||
"environment": [],
|
||||
"secrets": []
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Shell features should not introduce background jobs by default."
|
||||
]
|
||||
},
|
||||
"externalProviders": [],
|
||||
"permissions": [
|
||||
"admin.*",
|
||||
"app.settings.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"admin.settings.updated",
|
||||
"tenant.settings.updated"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Shell may expose subscription status but should not own billing logic."
|
||||
],
|
||||
"uat": [
|
||||
"Admin can reach dashboard/settings.",
|
||||
"Tenant can reach dashboard/settings.",
|
||||
"Navigation only shows permitted surfaces."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Tenant app layout",
|
||||
"Admin layout",
|
||||
"Dashboard entry points",
|
||||
"Core settings navigation",
|
||||
"System settings needed for operations"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Sidebar entries for optional product modules",
|
||||
"Admin pages that only manage plugin features",
|
||||
"Demo or playground shell entries"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Route files during manifest Phase 1",
|
||||
"Sidebar and navigation code owned by parallel workers"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"Use plugin/remove manifests to decide which shell entries become optional later."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-05"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "ai-chatbot-admin-managed",
|
||||
"title": "Admin-Managed AI Chatbot",
|
||||
"classification": "plugin-candidate",
|
||||
"status": "proposed",
|
||||
"owner": "feature-integration",
|
||||
"description": "Tenant-facing AI chatbot whose provider credentials, browser/runtime policy, and global availability are managed by admin/platform configuration.",
|
||||
"rationale": [
|
||||
"AI provider keys and browser automation capabilities are sensitive platform resources and should not be stored in tenant-owned settings by default.",
|
||||
"Tenant users can consume the chatbot while admin controls provider choice, budgets, allowed models, browser access, and safety policy.",
|
||||
"Browser-backed AI work is operationally different from normal request/response UI and should be declared as worker-backed before implementation."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/chatbot",
|
||||
"apps/web/app/modules/ai",
|
||||
"apps/web/app/routes/admin/ai/*",
|
||||
"apps/web/app/routes/admin/chat/*",
|
||||
"apps/web/app/routes/app.$tenant/chat/*",
|
||||
"apps/web/app/components/ai-elements/*",
|
||||
"apps/web/app/utils/html-sanitizer.ts",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/ai/upload",
|
||||
"apps/worker",
|
||||
"packages/queue"
|
||||
],
|
||||
"dependencies": [
|
||||
"core",
|
||||
"auth-tenancy",
|
||||
"admin-app-shell",
|
||||
"rows-entities-runtime"
|
||||
],
|
||||
"consumers": [
|
||||
"plugin-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "admin-managed",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin/ai/*",
|
||||
"apps/web/app/routes/admin/chat/*",
|
||||
"admin settings for provider credentials, model policy, browser policy, and usage limits"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant/chat/*",
|
||||
"tenant chatbot launcher or embedded assistant surface"
|
||||
],
|
||||
"publicRoutes": [],
|
||||
"apiRoutes": [
|
||||
"/v1/ai/upload",
|
||||
"streaming chat endpoints",
|
||||
"worker enqueue/status endpoints if browser work is async"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"AI provider API key approved for the deployment region",
|
||||
"Allowed model list",
|
||||
"Default model",
|
||||
"Browser automation enablement",
|
||||
"Browser allowlist/denylist policy",
|
||||
"Per-tenant feature enablement",
|
||||
"Global budget and rate limits"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant-level enable/disable if admin allows it",
|
||||
"Tenant-facing chatbot context and display preferences",
|
||||
"Optional tenant knowledge/context selection without provider secrets"
|
||||
],
|
||||
"environment": [
|
||||
"AI_PROCESSING_REGION",
|
||||
"MISTRAL_API_KEY",
|
||||
"MISTRAL_MODEL",
|
||||
"SCALEWAY_API_KEY",
|
||||
"SCALEWAY_MODEL",
|
||||
"SCALEWAY_BASE_URL",
|
||||
"SCALEWAY_REGION",
|
||||
"ALEPH_ALPHA_API_KEY",
|
||||
"ALEPH_ALPHA_MODEL",
|
||||
"ALEPH_ALPHA_BASE_URL",
|
||||
"AI_GENERATION_MAX_ESTIMATED_INPUT_COST_CENTS",
|
||||
"AI_GENERATION_RATE_LIMIT",
|
||||
"AI_GENERATION_RATE_LIMIT_WINDOW_MS",
|
||||
"BULLMQ_REDIS_URL",
|
||||
"BROWSER_RUNTIME_URL",
|
||||
"BROWSER_RUNTIME_API_KEY",
|
||||
"BROWSER_SESSION_TIMEOUT_MS"
|
||||
],
|
||||
"secrets": [
|
||||
"AI provider API keys are admin/platform-owned.",
|
||||
"Provider keys must never be exposed in tenant routes, tenant JSON loaders, browser bundles, or client-visible config.",
|
||||
"Browser session credentials and proxy credentials are worker/platform-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": true,
|
||||
"queues": [
|
||||
"toolctrl.ai-chatbot",
|
||||
"toolctrl.browser-automation"
|
||||
],
|
||||
"jobs": [
|
||||
"chat-completion",
|
||||
"browser-session-run",
|
||||
"browser-session-cleanup",
|
||||
"usage-flush"
|
||||
],
|
||||
"notes": [
|
||||
"Short text-only chat can stream in request scope, but any browser-backed task must be queue/worker-backed.",
|
||||
"Worker jobs must receive scoped tenant/user/context IDs, not raw secrets from tenant input.",
|
||||
"Browser jobs must enforce admin-owned allowlist policy before navigation."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"deployment-region-ai-provider",
|
||||
"browser-runtime"
|
||||
],
|
||||
"permissions": [
|
||||
"admin.ai.configure",
|
||||
"admin.ai.providers.update",
|
||||
"admin.ai.usage.view",
|
||||
"app.ai.chat.use"
|
||||
],
|
||||
"auditEvents": [
|
||||
"admin.ai.provider_key.updated",
|
||||
"admin.ai.model_policy.updated",
|
||||
"admin.ai.browser_policy.updated",
|
||||
"tenant.ai_chatbot.enabled",
|
||||
"ai_chatbot.browser_job.started",
|
||||
"ai_chatbot.browser_job.completed",
|
||||
"ai_chatbot.browser_job.failed"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"AI token usage may need plan gates or usage metering.",
|
||||
"Browser runtime usage may need separate metering from text-token usage.",
|
||||
"Admin-owned provider keys mean platform billing exposure unless tenant pass-through billing is explicitly designed."
|
||||
],
|
||||
"uat": [
|
||||
"Admin can save provider configuration without exposing the key to tenant responses or client bundles.",
|
||||
"Admin can enable/disable the chatbot for a tenant.",
|
||||
"Tenant can start a chat only when the feature is enabled and permission allows it.",
|
||||
"AI markdown and web-preview surfaces sanitize active HTML and restrict preview iframes to safe HTTP(S) targets.",
|
||||
"Browser-backed chat tasks run through a worker queue and enforce admin allowlist policy.",
|
||||
"Usage and key-policy changes emit audit events."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Provider secrets remain admin/platform-owned.",
|
||||
"Tenant surfaces only receive non-secret feature state and allowed options.",
|
||||
"Browser-backed tasks require worker isolation and navigation policy enforcement."
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"All AI/chatbot/browser automation surfaces",
|
||||
"Provider-specific AI implementation",
|
||||
"Usage metering beyond core billing hooks"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Core auth/session logic except permission checks needed by this plugin.",
|
||||
"Core billing internals except explicit usage-meter hooks."
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "planning",
|
||||
"action": "extract-plugin",
|
||||
"notes": [
|
||||
"Use this as the reference pattern for admin-managed tenant features that need provider secrets or workers.",
|
||||
"Do not implement browser automation in request scope."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/agent-playbooks/admin-managed-features.md",
|
||||
"docs/agent-playbooks/worker-backed-features.md",
|
||||
"docs/agent-playbooks/feature-integration.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "auth-tenancy",
|
||||
"title": "Auth, Accounts, Tenancy, Roles",
|
||||
"classification": "core",
|
||||
"status": "proposed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Authentication, account lifecycle, tenant membership, invitations, and authorization primitives.",
|
||||
"rationale": [
|
||||
"Every SaaS or app installation needs account access, tenant scoping, and permission enforcement.",
|
||||
"Admin and tenant flows depend on the same membership and role foundations.",
|
||||
"Operator admins need silent tenant inspection access without being seeded as tenant members."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/routes/login.tsx",
|
||||
"apps/web/app/routes/register.tsx",
|
||||
"apps/web/app/routes/forgot-password.tsx",
|
||||
"apps/web/app/routes/reset.tsx",
|
||||
"apps/web/app/routes/oauth/github/*",
|
||||
"apps/web/app/services/auth/*",
|
||||
"apps/web/app/routes/verify.$id.tsx",
|
||||
"apps/web/app/routes/invitation.$id.tsx",
|
||||
"apps/web/app/routes/admin/accounts*",
|
||||
"apps/web/app/routes/app.$tenant/settings/*",
|
||||
"apps/web/app/modules/users",
|
||||
"apps/web/app/utils/services/apiService.ts",
|
||||
"apps/web/app/utils/services/authService.ts",
|
||||
"apps/web/app/utils/services/rolesAndPermissionsService.ts",
|
||||
"packages/db/src/permissions-seed.server.ts"
|
||||
],
|
||||
"dependencies": [
|
||||
"transactional-mail"
|
||||
],
|
||||
"consumers": [
|
||||
"admin-app-shell",
|
||||
"billing",
|
||||
"rows-entities-runtime",
|
||||
"plugin-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "system-only",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin/accounts*"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant/settings/*"
|
||||
],
|
||||
"publicRoutes": [
|
||||
"apps/web/app/routes/login.tsx",
|
||||
"apps/web/app/routes/register.tsx",
|
||||
"apps/web/app/routes/forgot-password.tsx",
|
||||
"apps/web/app/routes/reset.tsx",
|
||||
"apps/web/app/routes/verify.$id.tsx",
|
||||
"apps/web/app/routes/invitation.$id.tsx"
|
||||
],
|
||||
"apiRoutes": [
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/auth/login",
|
||||
"/v1/auth/refresh-token",
|
||||
"/v1/auth/logout",
|
||||
"/v1/session/current-user",
|
||||
"/v1/accounts",
|
||||
"/v1/accounts/:id",
|
||||
"/v1/workspaces/:tenantId/auth/providers",
|
||||
"/v1/workspaces/:tenantId/context/revalidate",
|
||||
"/v1/workspaces/:tenantId/invites",
|
||||
"/v1/workspaces/:tenantId/invites/:inviteId/accept",
|
||||
"/v1/workspaces/:tenantId/invites/:inviteId/revoke"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Role and permission policy"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant membership and invitations"
|
||||
],
|
||||
"environment": [
|
||||
"CRYPTO_SECRET",
|
||||
"JWT_SECRET",
|
||||
"SESSION_SECRET",
|
||||
"API_ACCESS_TOKEN",
|
||||
"APP_BASE_DOMAIN",
|
||||
"WEB_APP_HOST",
|
||||
"ADMIN_APP_HOST",
|
||||
"APP_PROTOCOL"
|
||||
],
|
||||
"secrets": [
|
||||
"Session and auth secrets are environment-owned, never tenant-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Auth flows may send email through transactional mail but do not own workers."
|
||||
]
|
||||
},
|
||||
"externalProviders": [],
|
||||
"permissions": [
|
||||
"admin.accounts.*",
|
||||
"admin.users.*",
|
||||
"app.settings.members.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"auth.login",
|
||||
"tenant.member.invited",
|
||||
"tenant.member.updated",
|
||||
"role.permission.updated"
|
||||
],
|
||||
"billingImpacts": [],
|
||||
"uat": [
|
||||
"User can register/login/reset password.",
|
||||
"Login, forgot-password, reset-password, and GitHub OAuth start/callback enforce CSRF or OAuth state and rate-limit policy.",
|
||||
"Service-token validation uses a timing-safe comparison and rejects mismatched tenant-scope overrides.",
|
||||
"Admin can manage accounts/users.",
|
||||
"Tenant membership scoping is enforced for regular users.",
|
||||
"Operator admins can inspect any active tenant space without explicit tenant membership."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Login, registration, password reset, verification, and invitations",
|
||||
"Tenant membership and tenant-aware authorization",
|
||||
"Roles and permissions needed by app and admin surfaces",
|
||||
"Operator-admin tenant inspection bypass at server-side access boundaries only"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Advanced account CRM enrichment",
|
||||
"Optional feature-flagged role management enhancements"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Route implementations during manifest Phase 1",
|
||||
"Sidebar/navigation ownership during parallel work"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"Treat auth-tenancy as a blocking dependency for most other manifests."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "billing",
|
||||
"title": "Billing and Subscriptions",
|
||||
"classification": "core",
|
||||
"status": "proposed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Minimal Stripe-backed subscription, pricing settings, subscription settings, and webhook handling.",
|
||||
"rationale": [
|
||||
"A SaaS boilerplate usually needs billing as a default capability.",
|
||||
"The retained surface should be minimal and separate from marketing pricing presentation."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/routes/subscribe.$tenant*",
|
||||
"apps/web/app/routes/app.$tenant/settings/subscription.tsx",
|
||||
"apps/web/app/routes/admin/settings/pricing*",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/billing/stripe/webhooks",
|
||||
"apps/web/app/application/pricing (admin billing-plan metadata, not public marketing pricing)",
|
||||
"apps/web/app/components/core/pricing (admin billing-plan configuration)",
|
||||
"apps/web/app/components/core/settings/subscription",
|
||||
"stripe"
|
||||
],
|
||||
"dependencies": [
|
||||
"auth-tenancy",
|
||||
"transactional-mail"
|
||||
],
|
||||
"consumers": [
|
||||
"core",
|
||||
"admin-app-shell"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "hybrid",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin/settings/pricing*"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant/settings/subscription.tsx",
|
||||
"apps/web/app/routes/subscribe.$tenant*"
|
||||
],
|
||||
"publicRoutes": [],
|
||||
"apiRoutes": [
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/billing/stripe/webhooks"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Plans, prices, billing policy"
|
||||
],
|
||||
"tenant": [
|
||||
"Subscription selection and billing portal actions"
|
||||
],
|
||||
"environment": [
|
||||
"STRIPE_SK",
|
||||
"STRIPE_WEBHOOK_SIGNING_SECRET",
|
||||
"STRIPE_ACCOUNT_COUNTRY"
|
||||
],
|
||||
"secrets": [
|
||||
"Stripe keys are platform/admin-owned, never tenant-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Webhook handling is request-driven unless later converted to queue processing."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"stripe"
|
||||
],
|
||||
"permissions": [
|
||||
"admin.pricing.*",
|
||||
"app.settings.subscription.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"billing.plan.updated",
|
||||
"billing.subscription.updated",
|
||||
"billing.webhook.received"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Owns subscription and pricing foundation."
|
||||
],
|
||||
"uat": [
|
||||
"Admin can configure pricing.",
|
||||
"Tenant can view/update subscription.",
|
||||
"Stripe webhook updates subscription state."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Stripe subscription and webhook foundation",
|
||||
"Admin pricing configuration",
|
||||
"Tenant subscription settings"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Portal-oriented pricing flows not required by the app shell",
|
||||
"Provider-specific billing extensions beyond Stripe",
|
||||
"Advanced billing analytics beyond subscription state"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Tenant subscription checkout and success routes",
|
||||
"Navigation changes owned by parallel workers"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"Keep billing as core. Public marketing pricing was pruned; admin pricing and tenant subscription flows remain."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-05"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "core",
|
||||
"title": "Minimal Boilerplate Core",
|
||||
"classification": "core",
|
||||
"status": "proposed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Umbrella manifest for features that remain in the default ToolCTRL boilerplate after pruning.",
|
||||
"rationale": [
|
||||
"The boilerplate must support authenticated SaaS operation without marketing, CRM, AI, or support-product assumptions.",
|
||||
"Core should preserve web app and admin shells, auth, tenant membership, database/runtime primitives, transactional email, and minimal billing."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/routes/login.tsx",
|
||||
"apps/web/app/routes/register.tsx",
|
||||
"apps/web/app/routes/app.$tenant*",
|
||||
"apps/web/app/routes/admin*",
|
||||
"apps/web/app/modules/users",
|
||||
"apps/web/app/modules/rows",
|
||||
"packages/db",
|
||||
"packages/create-toolctrl",
|
||||
"server/server.ts",
|
||||
"prisma",
|
||||
"prisma/migrations/20260527035000_04_api_key_log_tenant_scope",
|
||||
"prisma/migrations/20260527035500_04_runtime_query_indexes",
|
||||
"prisma/migrations/20260528090400_outbox_done_retention_index",
|
||||
"apps/web/app/modules/emails",
|
||||
"packages/mail",
|
||||
"scripts/verify-source-health.mjs",
|
||||
"scripts/verify-route-inventory.mjs",
|
||||
"scripts/verify-bundle-size.mjs",
|
||||
"apps/web/app/application/pricing",
|
||||
"apps/web/app/components/core/pricing",
|
||||
"packages/entitlements/src/blueprints.ts",
|
||||
"packages/entitlements/src/product-access.ts"
|
||||
],
|
||||
"dependencies": [
|
||||
"auth-tenancy",
|
||||
"rows-entities-runtime",
|
||||
"billing",
|
||||
"transactional-mail",
|
||||
"admin-app-shell",
|
||||
"product-core"
|
||||
],
|
||||
"consumers": [
|
||||
"plugin-candidates",
|
||||
"remove-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "system-only",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin*"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant*"
|
||||
],
|
||||
"publicRoutes": [
|
||||
"apps/web/app/routes/login.tsx",
|
||||
"apps/web/app/routes/register.tsx"
|
||||
],
|
||||
"apiRoutes": [
|
||||
"apps/api/src/app.ts",
|
||||
"apps/api/src/routes/*.ts",
|
||||
"/healthz",
|
||||
"/readyz",
|
||||
"/v1/health",
|
||||
"/v1/openapi.json",
|
||||
"/v1/docs",
|
||||
"/v1/api-keys/validate",
|
||||
"/v1/usage"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Platform defaults required by retained core features"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant settings required by retained app flows"
|
||||
],
|
||||
"environment": [
|
||||
"DATABASE_URL",
|
||||
"SESSION_SECRET",
|
||||
"APP_BASE_DOMAIN",
|
||||
"WEB_APP_HOST",
|
||||
"ADMIN_APP_HOST",
|
||||
"API_HOST",
|
||||
"APP_PROTOCOL"
|
||||
],
|
||||
"secrets": [
|
||||
"Core platform secrets are deployment-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [
|
||||
"cleanup.run:outbox-done-retention",
|
||||
"cleanup.run:workflow-execution-stalled-reconcile"
|
||||
],
|
||||
"notes": [
|
||||
"Core stays worker-optional locally; staging/production worker-general owns outbox dispatch, DONE-row retention, and conservative repair of queued manual/API workflow executions whose initial outbox dispatch failed."
|
||||
]
|
||||
},
|
||||
"externalProviders": [],
|
||||
"permissions": [
|
||||
"admin.*",
|
||||
"app.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"core.configuration.updated"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Depends on billing manifest for subscription behavior."
|
||||
],
|
||||
"uat": [
|
||||
"A new project can run auth, tenant shell, admin shell, database, mail, billing, and the CRM SaaS Product Core blueprint.",
|
||||
"The local scaffold CLI can generate core, saas, and full workspace profiles without copying secrets or build artifacts.",
|
||||
"The Node web runtime emits baseline security headers, including CSP frame-ancestor/object restrictions and HTTPS HSTS.",
|
||||
"Source-health and route-inventory gates keep the API composition file and web route growth within reviewed budgets."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Authentication and account recovery",
|
||||
"Tenant membership and role/permission enforcement",
|
||||
"Tenant app shell and admin shell",
|
||||
"Prisma-backed persistence foundation",
|
||||
"Transactional email required by auth and tenant operations",
|
||||
"Minimal subscription and pricing foundation"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Product-specific admin dashboards",
|
||||
"Advanced entity builder UI",
|
||||
"Provider-specific adapters beyond the selected defaults"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Application routes and navigation during manifest Phase 1",
|
||||
"Chatbot, affiliate, and marketing code owned by parallel workers"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"This manifest defines the target default surface, not an implementation change.",
|
||||
"Use the child manifests for more precise dependency and boundary checks."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md",
|
||||
"docs/architecture-migration-status.md",
|
||||
"BOILERPLATE-PLAN.md",
|
||||
"prisma/schema/core.prisma",
|
||||
"prisma/schema/outbox.prisma",
|
||||
"prisma/schema/plugins.prisma",
|
||||
"prisma/schema/ai.prisma"
|
||||
],
|
||||
"lastReviewed": "2026-05-29"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "plugin-candidates",
|
||||
"title": "Plugin Candidate Feature Groups",
|
||||
"classification": "plugin-candidate",
|
||||
"status": "review-needed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Feature groups with explicit extraction boundaries for future optional plugins or separately installable modules. Some are currently active in the versioned Product Core CRM SaaS showcase.",
|
||||
"rationale": [
|
||||
"These features are useful in some products but add product opinion, dependencies, or operational complexity beyond the neutral core.",
|
||||
"A feature can be active in product-core today and still remain a plugin-candidate when the intended next boundary is optional extraction rather than deletion.",
|
||||
"Extraction should happen after core dependency boundaries are verified.",
|
||||
"Email Marketing remains a CRM-contact campaign plugin candidate; Email Sequences are documented separately as foundation/admin-preview work with production delivery follow-ups, and marketing-outbound is only a general handler foundation until worker bootstrap is added."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/crm",
|
||||
"apps/web/app/modules/emailMarketing",
|
||||
"apps/web/app/modules/workflowEngine",
|
||||
"apps/web/app/modules/knowledgeBase",
|
||||
"apps/web/app/modules/portals",
|
||||
"apps/web/app/modules/sites",
|
||||
"apps/web/app/modules/analytics",
|
||||
"apps/web/app/modules/metrics",
|
||||
"apps/web/app/modules/events",
|
||||
"apps/web/app/modules/ai",
|
||||
"apps/web/app/modules/chatbot",
|
||||
"apps/web/app/modules/helpDesk",
|
||||
"apps/web/app/modules/onboarding",
|
||||
"apps/web/app/modules/featureFlags",
|
||||
"apps/web/app/modules/widgets",
|
||||
"apps/web/app/modules/notifications",
|
||||
"apps/web/app/modules/storage"
|
||||
],
|
||||
"dependencies": [
|
||||
"core",
|
||||
"auth-tenancy",
|
||||
"rows-entities-runtime",
|
||||
"transactional-mail",
|
||||
"admin-app-shell"
|
||||
],
|
||||
"consumers": [],
|
||||
"components": [
|
||||
{
|
||||
"id": "crm",
|
||||
"title": "CRM",
|
||||
"rationale": "Strong vertical opinion coupled to default company/contact/opportunity entities and email marketing.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/crm",
|
||||
"apps/web/app/routes/admin/crm*",
|
||||
"apps/web/app/routes/app.$tenant/crm*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "email-marketing",
|
||||
"title": "Email Marketing and Newsletter",
|
||||
"rationale": "Email Marketing is an extract-plugin CRM-contact campaign plugin candidate, not Core and not the Email Sequences foundation: campaigns, sender secrets, CRM-contact audience selection, tracking, and consent are product-specific. Current audiences depend on CRM contacts, so extraction must bundle CRM, declare a CRM plugin dependency, or introduce a plugin-owned audience/list abstraction while keeping transactional mail and the separate email-sequences admin-preview foundation separate.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/emailMarketing",
|
||||
"apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts",
|
||||
"apps/web/app/modules/emailMarketing/db/emailSender.ts",
|
||||
"apps/web/app/modules/emailMarketing/db/campaigns.db.server.ts",
|
||||
"apps/web/app/modules/emailMarketing/db/outboundEmails.db.server.ts",
|
||||
"apps/web/app/routes/admin/email-marketing*",
|
||||
"apps/web/app/routes/app.$tenant/email-marketing*",
|
||||
"prisma/schema/email.prisma"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "portals-sites",
|
||||
"title": "Portals and Sites",
|
||||
"rationale": "D007 canonical report: docs/feature-decisions/portals-sites-decision-report.md. Action: extract-plugin. Product-platform tenant/admin surfaces with Stripe portal billing, analytics, domains, and module-owned persistence that should not remain default Boilerplate Core.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/portals",
|
||||
"apps/web/app/modules/sites",
|
||||
"apps/web/app/routes/admin/portals.tsx",
|
||||
"apps/web/app/routes/app.$tenant/__portals/*",
|
||||
"apps/web/app/routes/app.$tenant/sites/*",
|
||||
"prisma/schema/portals.prisma",
|
||||
"prisma/schema/sites.prisma"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "knowledge-base",
|
||||
"title": "Knowledge Base",
|
||||
"rationale": "D009 canonical report: docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md. Action: extract-plugin. Support/docs product surface with public docs visibility and data boundaries, not a minimum SaaS boilerplate requirement.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/knowledgeBase",
|
||||
"apps/web/app/routes/docs/*",
|
||||
"apps/web/app/routes/admin/knowledge-base*",
|
||||
"apps/web/app/routes/help/*",
|
||||
"prisma/schema/knowledgeBase.prisma"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "analytics-metrics",
|
||||
"title": "Analytics and Metrics",
|
||||
"rationale": "Product analytics UI and collection should be opt-in; minimal observability can remain separate.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/analytics",
|
||||
"apps/web/app/modules/metrics",
|
||||
"apps/web/app/routes/admin/analytics*",
|
||||
"apps/web/app/routes/admin/metrics*",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/analytics/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "events-webhooks-audit",
|
||||
"title": "Events, Webhooks, Audit Trails",
|
||||
"rationale": "Domain events may be useful for integrations, while browsers and entity webhooks can be optional.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/events",
|
||||
"apps/web/app/modules/events/services/.server/EventsService.ts",
|
||||
"packages/db/src/outbox.ts",
|
||||
"apps/web/app/routes/admin/events*",
|
||||
"apps/web/app/routes/admin/audit-trails.tsx",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/events/webhooks/attempts/:id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ai-chatbot-prompt-mcp",
|
||||
"title": "AI, Chatbot, Prompt Builder, MCP",
|
||||
"rationale": "Provider cost, security, and product behavior make this a plugin surface.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/ai",
|
||||
"apps/web/app/modules/chatbot",
|
||||
"apps/web/app/modules/promptBuilder",
|
||||
"apps/web/app/modules/mcp",
|
||||
"apps/web/app/routes/admin/ai/*",
|
||||
"apps/web/app/routes/admin/chat/*",
|
||||
"apps/web/app/routes/app.$tenant/chat/*",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/ai/upload"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "help-desk",
|
||||
"title": "Help Desk",
|
||||
"rationale": "Support-product surface that should couple to optional KB and ticket modules.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/helpDesk",
|
||||
"apps/web/app/routes/admin/help-desk*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "onboarding-builder",
|
||||
"title": "Onboarding Builder",
|
||||
"rationale": "Useful for some SaaS products but not part of the minimum boilerplate.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/onboarding",
|
||||
"apps/web/app/routes/admin/onboarding*",
|
||||
"apps/web/app/routes/onboarding.$id.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "feature-flags",
|
||||
"title": "Feature Flags",
|
||||
"rationale": "Advanced admin capability that should be reviewed as plugin or core only if needed by retained flows.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/featureFlags",
|
||||
"apps/web/app/routes/admin/feature-flags*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widgets",
|
||||
"title": "Widgets",
|
||||
"rationale": "Embedded widgets are product-specific and should be optional.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/widgets",
|
||||
"apps/web/app/routes/app.$tenant/__widgets/*",
|
||||
"apps/api/src/app.ts",
|
||||
"packages/db/src/widgets.server.ts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "workflow-engine",
|
||||
"title": "Workflow Engine",
|
||||
"rationale": "D009 canonical report: docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md. Action: extract-plugin. Automation/workflow orchestration is a product-specific capability layered on core events, not a minimum SaaS boilerplate requirement.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/workflowEngine",
|
||||
"apps/web/app/routes/admin/__workflows/*",
|
||||
"apps/web/app/routes/app.$tenant/__workflows/*",
|
||||
"apps/web/app/routes/internal/api/__workflows/*",
|
||||
"prisma/schema/workflows.prisma"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "notifications",
|
||||
"title": "Notifications",
|
||||
"rationale": "Notification admin surfaces are optional; product delivery uses the internal queue and database diagnostics.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/notifications",
|
||||
"apps/web/app/routes/admin/notifications*",
|
||||
"packages/db/src/notifications.server.ts",
|
||||
"apps/worker/src/handlers.ts",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/notifications/stream"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "storage-supabase",
|
||||
"title": "Storage and Supabase",
|
||||
"rationale": "File storage abstraction may be core, while Supabase provider integration should be optional.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/storage",
|
||||
"@supabase/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "hybrid",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin/* optional module routes"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant/* optional module routes"
|
||||
],
|
||||
"publicRoutes": [
|
||||
"optional public plugin routes"
|
||||
],
|
||||
"apiRoutes": [
|
||||
"optional plugin API routes"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Plugin enablement, provider credentials, global policy"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant-level plugin settings when enabled, rendered through the sandboxed PluginSettingsFrame contract with deny-by-default CSP and unexpected-navigation remounts",
|
||||
"Manifest-declared plugin settings UI hosted from ui.settings.entry through PluginSettingsFrame; PluginUserConfig persists only caller-owned per-user settings behind app.plugins.use; lifecycle, grants and tenant-wide configuration remain behind app.plugins.manage.",
|
||||
"Plugin capability-use audit trails remain an account audit surface and require app.settings.auditTrails.view instead of broad app.plugins.use.",
|
||||
"Plugin settings HTML and per-user setting values are size-budgeted server-side; secret-like, reserved, unsafe and over-budget keys/values must be dropped or rejected before reaching iframe state or persistence.",
|
||||
"Plugin settings iframe postMessage rejects and unexpected reloads create client-deduplicated, server-rate-limited plugin.settings_frame.violation audit events with redacted metadata only."
|
||||
],
|
||||
"environment": [
|
||||
"Plugin-specific environment variables only when installed"
|
||||
],
|
||||
"secrets": [
|
||||
"Plugin provider secrets must be admin/platform-owned unless explicitly tenant-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": true,
|
||||
"queues": [
|
||||
"plugin-specific queues when needed",
|
||||
"outbox.workflow",
|
||||
"outbox.plugin-hook",
|
||||
"outbox.plugin-capability-use",
|
||||
"toolctrl.plugin-invoke"
|
||||
],
|
||||
"jobs": [
|
||||
"plugin-specific jobs when needed",
|
||||
"workflow-trigger fanout",
|
||||
"workflow execution request drain",
|
||||
"workflow definition hook fanout",
|
||||
"workflow.executed hook fanout",
|
||||
"plugin-hook fanout",
|
||||
"plugin capability-use audit receiver",
|
||||
"plugin invoke"
|
||||
],
|
||||
"notes": [
|
||||
"Each extracted plugin must declare whether it is worker-backed and which queue group owns its jobs.",
|
||||
"Email-sequence trigger/scheduler/producers are not plugin-candidate jobs in this manifest; they are tracked in docs/features/email-sequences.md as foundation/admin-preview work gated by MARKETING_SEQUENCES_ENABLED and remaining production follow-ups. Signed Mailjet bounce webhook ingestion is API-owned, not a plugin worker job.",
|
||||
"The generic marketing-outbound handler foundation is documented in docs/features/marketing-funnel.md and must not be inferred as bootstrapped plugin delivery until apps/worker/src/index.ts wires a general subscriber.",
|
||||
"Workflow trigger fanout producers should use the shared @toolctrl/queue outbox payload builder instead of hand-writing workflow.trigger.event envelopes.",
|
||||
"Manual workflow runs publish workflow.execution.requested through the transactional outbox before worker-general starts the existing execution via the signed internal bridge or native executor; queued waitForInput continuations can resume natively when that wait boundary is the only native preflight gap.",
|
||||
"Default outbox queue delivery must include receiver-level BullMQ attempts/backoff and deterministic jobId=outbox:<eventId> before marking the Postgres outbox row DONE, so transient subscriber failures are retried by the target queue and dispatcher redelivery does not create duplicate receiver jobs.",
|
||||
"Plugin capability-use batches are produced by worker-owned plugin invocation and drained by worker-general through outbox.plugin-capability-use; receiver inserts must be idempotent with duplicate row ids ignored on outbox redelivery.",
|
||||
"The worker-general runtime must pass through OUTBOX_DISPATCHER_ENABLED plus WORKFLOW_TRIGGER_OUTBOX_* and WORKFLOW_TRIGGER_EXECUTOR_* settings so outbox.workflow is either actively drained or fails startup in required mode; WORKFLOW_TRIGGER_OUTBOX_REQUIRED=true also requires the outbox dispatcher to start.",
|
||||
"WORKFLOW_TRIGGER_EXECUTOR_MODE=native is opt-in and currently supports credential-free safe core event/manual/if/switch/doNothing/log/iterator/variable/alertUser/httpRequest paths plus tenant-scoped rowGet/email blocks and single-boundary waitForInput continuation resumes. Native and hybrid preflight must reject $credentials.*, lookup $credentials, and #with $credentials references before block execution; the worker-native executor must not hydrate stored workflow credentials. Iterator blocks execute the loopNext edge once per array item with context.iterator and then continue through loopEnd. httpRequest uses the shared outbound HTTP guard, email publishes mail.transactional.send through the transactional outbox for worker-mail delivery, while unsupported blocks and arbitrary conditional edges outside if/switch/iterator must persist an errored execution and fail loud instead of falling back to HTTP.",
|
||||
"WorkflowExecution keeps query-shape indexes for tenant-scoped execution lists, workflow execution histories, and status/type filtered admin views while workflow runtime remains a plugin-candidate surface."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"plugin-specific providers"
|
||||
],
|
||||
"permissions": [
|
||||
"admin.plugins.*",
|
||||
"app.plugins.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"plugin.enabled",
|
||||
"plugin.disabled",
|
||||
"plugin.auto_disabled",
|
||||
"plugin.capabilities.revoked",
|
||||
"plugin.configuration.updated",
|
||||
"plugin.invoke.completed",
|
||||
"plugin.invoke.failed",
|
||||
"plugin.invoke.host.log",
|
||||
"plugin.invoke.host.audit"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Plugins may introduce plan gates or usage meters and must declare them before implementation."
|
||||
],
|
||||
"uat": [
|
||||
"Extraction plan defines how the plugin can be disabled without breaking core.",
|
||||
"Extraction plan defines whether plugin surfaces disappear, redirect, or show unavailable states when disabled.",
|
||||
"Plugin dependencies are explicit.",
|
||||
"Capability grants require an explicit capability-review acknowledgement and a manifest/version/scope digest across UI and API entry points.",
|
||||
"Capability grant, revoke, enable, disable, and per-user settings mutations commit atomically with their audit outbox event so plugin authorization and configuration cannot be left half-applied.",
|
||||
"Settings UIs use the sandboxed iframe contract, deny frame navigation, remount after unexpected iframe loads, and persist only non-secret per-user configuration through the reviewed PluginUserConfig path.",
|
||||
"Settings iframe messages use the SDK-exported plugin.settings.* protocol contract, bind request identifiers and keys, and persistent per-user settings writes are rate-limited per tenant/user/plugin.",
|
||||
"Plugin manifests bound and deduplicate allowedHosts, capability names and hook entrypoints so grants and fanout remain reviewable.",
|
||||
"Plugin hook payloads have SDK-level exported types matching the runtime registry; examples use canonical entity/workflow payload fields so plugin authors do not rely on stale ad-hoc shapes. Terminal workflow execution hooks include optional nullable userId for actor-aware plugins while accepting legacy payloads without userId.",
|
||||
"Tenant-scoped service-path workflow definition create/update/delete and builder graph mutations emit metadata only: tenantId, workflowId, userId, status, appliesToAllTenants, update changedFields, and occurredAt; platform-scoped definitions and template-import flows remain out of scope unless routed through the reviewed workflow service path."
|
||||
],
|
||||
"runtime": [
|
||||
"Production install, grant, and enable paths must reject capabilities without reviewed host bindings; currently only log and audit are production-available plugin sandbox host capabilities.",
|
||||
"Worker plugin invocations must enforce current TenantPluginCapabilityGrant rows before loading binaries, consuming rate-limit budget, or entering the sandbox; manifest-declared capabilities alone are not sufficient runtime authorization.",
|
||||
"Plugin invocation verifies stored install metadata (Plugin.wasmSha256 and Plugin.signaturePublicKey) still matches the manifest before signature verification, capability checks, or sandbox entry.",
|
||||
"Plugin signatures cover the canonical manifest payload plus wasmSha256 through the shared @toolctrl/plugin-runtime signing helper so manifest capability, hook, or allowedHost changes require re-signing.",
|
||||
"Plugin invocation must run inside the worker-owned sandbox boundary with memory, host-fn fuel and PLUGIN_MAX_WALL_CLOCK_MS limits; true per-instruction fuel remains a Wasmtime/runtime-upgrade concern."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Core dependencies these plugins rely on",
|
||||
"Data migrations required by retained core features until plugin extraction is implemented"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"All listed component feature groups after dependency review",
|
||||
"Future portal/site-builder plugins may declare affiliate integrations explicitly, but none are active by default."
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Routes, navigation, chatbot code, and historical affiliate migrations unless an owning cleanup task explicitly scopes them"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "extract-plugin",
|
||||
"notes": [
|
||||
"This manifest is a planning bundle. Individual plugin manifests can split out when a candidate is actively extracted.",
|
||||
"Do not remove shared runtime dependencies until consumers are audited."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md",
|
||||
"docs/architecture-migration-status.md",
|
||||
"docs/feature-decisions/crm-decision-report.md",
|
||||
"docs/feature-decisions/email-marketing-decision-report.md",
|
||||
"docs/feature-decisions/portals-sites-decision-report.md",
|
||||
"docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-27"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "product-core",
|
||||
"title": "Product Core CRM SaaS",
|
||||
"classification": "core",
|
||||
"status": "active",
|
||||
"owner": "product-core",
|
||||
"description": "Versioned Product Core contract for the default CRM SaaS showcase and retained product modules.",
|
||||
"rationale": [
|
||||
"The default product is a CRM SaaS rather than an empty minimal shell.",
|
||||
"Product modules are governed by versioned blueprints and entitlement gates instead of undocumented plugin-candidate assumptions.",
|
||||
"Runtime access must resolve through blueprint, plan, custom plan, tenant override, and tenant-owned role permissions."
|
||||
],
|
||||
"codeAreas": [
|
||||
"packages/entitlements/src/blueprints.ts",
|
||||
"packages/entitlements/src/product-access.ts",
|
||||
"packages/entitlements/src/plans.ts",
|
||||
"apps/web/app/modules/crm",
|
||||
"apps/web/app/modules/emailMarketing",
|
||||
"apps/web/app/modules/knowledgeBase",
|
||||
"apps/web/app/modules/helpDesk",
|
||||
"apps/web/app/modules/workflowEngine",
|
||||
"apps/web/app/modules/ai-gen",
|
||||
"apps/web/app/modules/mcp",
|
||||
"apps/web/app/modules/templates/defaultEntityTemplates.ts"
|
||||
],
|
||||
"dependencies": [
|
||||
"core",
|
||||
"auth-tenancy",
|
||||
"billing",
|
||||
"transactional-mail",
|
||||
"rows-entities-runtime"
|
||||
],
|
||||
"consumers": [
|
||||
"admin-app-shell",
|
||||
"billing"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "hybrid",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"Admin Center remains the operator toolbox for Product Core configuration and diagnostics."
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"Tenant CRM, support, KB, marketing, workflow and customization surfaces are enabled only through the CRM SaaS blueprint and entitlement decision."
|
||||
],
|
||||
"publicRoutes": [
|
||||
"Public Product Core surfaces are limited to documented knowledge-base reads and auth/billing entrypoints."
|
||||
],
|
||||
"apiRoutes": [
|
||||
"/v1/usage",
|
||||
"/v1/support/inbound-email",
|
||||
"Product Core API routes must evaluate blueprint and plan gates before tenant data access."
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Blueprint version selection, custom plans, tenant overrides, provider diagnostics and plan catalog."
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant-owned role permissions, tenant toggles where the blueprint allows them, tenant workflow and KB settings."
|
||||
],
|
||||
"environment": [
|
||||
"APP_BASE_DOMAIN",
|
||||
"WEB_APP_HOST",
|
||||
"ADMIN_APP_HOST",
|
||||
"API_HOST",
|
||||
"BULLMQ_REDIS_URL",
|
||||
"CRYPTO_SECRET",
|
||||
"MAILJET_WEBHOOK_SECRET",
|
||||
"STORAGE_PROVIDER",
|
||||
"STORAGE_ENDPOINT",
|
||||
"STORAGE_REGION",
|
||||
"STORAGE_BUCKET",
|
||||
"STORAGE_ACCESS_KEY_ID",
|
||||
"STORAGE_SECRET_ACCESS_KEY",
|
||||
"STORAGE_LOCAL_ROOT"
|
||||
],
|
||||
"secrets": [
|
||||
"Provider credentials remain platform/admin-owned unless a blueprint explicitly declares tenant-owned credentials."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Product Core owns entitlement and blueprint access decisions, while workflow, AI, mail, and plugin runtime queue execution is owned by the corresponding feature manifests and worker groups.",
|
||||
"Workflow and AI jobs stay behind queue/worker boundaries when the corresponding blueprint and plan gates allow access.",
|
||||
"Workflow outbound HTTP blocks must use the shared server-side outbound request guard for URL validation, redirects, timeouts and response-size limits."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"stripe",
|
||||
"deployment-region-ai-provider",
|
||||
"s3-compatible-storage"
|
||||
],
|
||||
"permissions": [
|
||||
"app.dashboard.view",
|
||||
"app.settings.roles.set",
|
||||
"app.settings.apiKeys.create",
|
||||
"admin.pricing.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"product_core.blueprint.enabled",
|
||||
"product_core.tenant_override.updated",
|
||||
"product_core.access.denied"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Starter, Pro and Business plans expose strict CRM SaaS feature limits.",
|
||||
"Custom hidden plans and tenant overrides can change effective entitlement decisions without making the plan public."
|
||||
],
|
||||
"uat": [
|
||||
"Starter can use CRM basics, support tickets and KB reads but cannot use API, email marketing, workflow engine or MCP.",
|
||||
"Pro can use email marketing, imported lists, CRM-based lists, onboarding, API and AI credits but cannot use workflow engine or MCP.",
|
||||
"Business can use workflow engine, advanced builder, high import/export limits and tenant MCP only when the blueprint enables MCP.",
|
||||
"A tenant user without the required tenant-owned role permission is denied even when blueprint and plan allow the feature."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Blueprint definitions are versioned code and manifest contracts.",
|
||||
"Entitlement decisions compose blueprint, plan, tenant override and tenant-owned role checks.",
|
||||
"Admin Center remains operator-owned for configuration and diagnostics."
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Provider-specific implementation adapters can become plugins after Product Core gates remain intact."
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Client/UI code must not read secrets or bypass server/package boundaries.",
|
||||
"Worker-backed features must not run synchronously in UI request handlers."
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "product-core",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"This manifest supersedes the earlier assumption that CRM SaaS product modules are merely plugin candidates for the default product.",
|
||||
"Forms, Surveys and Public Forms are excluded from Product Core and covered by the removal manifest."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"packages/entitlements/src/blueprints.ts",
|
||||
"packages/entitlements/src/product-access.ts",
|
||||
"docs/feature-manifests/core.json",
|
||||
"docs/feature-manifests/remove-candidates.json"
|
||||
],
|
||||
"lastReviewed": "2026-05-27"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "remove-candidates",
|
||||
"title": "Removal Candidate Feature Groups",
|
||||
"classification": "remove-candidate",
|
||||
"status": "review-needed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Feature groups that should be removed from the default boilerplate or aggressively reviewed for deletion.",
|
||||
"rationale": [
|
||||
"These surfaces conflict with the plan to remove marketing/default demo product behavior from the boilerplate.",
|
||||
"Some entries are remnants or provider duplicates that should only survive as explicit adapters."
|
||||
],
|
||||
"codeAreas": [],
|
||||
"dependencies": [
|
||||
"core"
|
||||
],
|
||||
"consumers": [],
|
||||
"components": [
|
||||
{
|
||||
"id": "forms-surveys-public-forms",
|
||||
"title": "Forms, Surveys, and Public Forms",
|
||||
"rationale": "Product Core excludes Forms, Surveys, Public Forms, and form-based Help Desk surveys. They are removal targets for runtime, active schema, navigation, seeds, docs, and gates rather than default CRM SaaS capabilities.",
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/formBuilder",
|
||||
"apps/web/app/modules/surveys",
|
||||
"apps/web/app/routes/__forms/*",
|
||||
"apps/web/app/routes/admin/forms/*",
|
||||
"apps/web/app/routes/app.$tenant/settings/forms/*",
|
||||
"apps/web/app/routes/surveys/*",
|
||||
"apps/web/app/routes/admin/help-desk/surveys/*",
|
||||
"prisma/schema/formBuilder.prisma",
|
||||
"Survey models in prisma/schema/helpDesk.prisma"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marketing-public-site-remnants",
|
||||
"title": "Marketing and Public Site Remnants",
|
||||
"rationale": "D009 canonical report: docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md. Action: remove-from-core. Marketing belongs in a separate project; only minimal legal/auth pages should remain in boilerplate.",
|
||||
"codeAreas": []
|
||||
},
|
||||
{
|
||||
"id": "blog-changelog-remnants",
|
||||
"title": "Blog and Changelog Remnants",
|
||||
"rationale": "D009 canonical report: docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md. Action: remove-from-core. Blog/changelog are marketing/content surfaces and not default boilerplate core.",
|
||||
"codeAreas": []
|
||||
},
|
||||
{
|
||||
"id": "playground-demo-debug",
|
||||
"title": "Playground, Demo, Debug Surfaces",
|
||||
"rationale": "Demo and debug surfaces are not production-ready default boilerplate features.",
|
||||
"codeAreas": []
|
||||
},
|
||||
{
|
||||
"id": "marketing-chatbot",
|
||||
"title": "Marketing Chatbot",
|
||||
"rationale": "D009 canonical report: docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md. Action: remove-from-core. Unauthenticated marketing chatbot conflicts with removing marketing from the default boilerplate.",
|
||||
"codeAreas": []
|
||||
},
|
||||
{
|
||||
"id": "source-product-copy-assets-fake-data",
|
||||
"title": "Source Product Copy, Assets, Fake Data",
|
||||
"rationale": "Whitelabel cleanup should remove source-product-specific copy, fake content, and logos.",
|
||||
"codeAreas": []
|
||||
},
|
||||
{
|
||||
"id": "heavy-visual-editor-dependency-review",
|
||||
"title": "Heavy Visual and Editor Dependency Review",
|
||||
"rationale": "Heavy editor and drag/drop dependencies should be reviewed after their owning feature surfaces are removed or extracted. Do not delete packages that are still imported by core/shared UI or plugin-candidate code.",
|
||||
"codeAreas": [
|
||||
"shared rich-text editor dependency set",
|
||||
"shared drag/drop dependency set",
|
||||
"syntax and math rendering dependency set"
|
||||
]
|
||||
}
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "system-only",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"routes marked for deletion or review"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"tenant routes marked for deletion or review"
|
||||
],
|
||||
"publicRoutes": [
|
||||
"public demo/marketing routes marked for deletion or review"
|
||||
],
|
||||
"apiRoutes": [
|
||||
"API routes marked for deletion or review"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [],
|
||||
"tenant": [],
|
||||
"environment": [
|
||||
"Environment variables tied only to removed features should be removed after dependency audit."
|
||||
],
|
||||
"secrets": [
|
||||
"Secrets tied only to removed features should be removed after dependency audit."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Delete or extract worker jobs only after consumers are audited."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"provider dependencies under removal review"
|
||||
],
|
||||
"permissions": [],
|
||||
"auditEvents": [
|
||||
"removed_feature.audit_retained_if_needed"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"Removal must not delete billing dependencies used by core."
|
||||
],
|
||||
"uat": [
|
||||
"Removing a candidate does not break core auth, tenant shell, admin shell, billing, mail, or rows runtime."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Any dependency still required by core manifests",
|
||||
"Legal/auth public pages if retained by the app shell"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Provider adapters for non-default mail vendors",
|
||||
"Demo fixtures useful for test-only packages"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Historical affiliate migrations and unrelated chatbot, route, and navigation code during this cleanup phase",
|
||||
"Runtime code until the owning worker or implementation phase takes over"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "remove",
|
||||
"notes": [
|
||||
"This manifest captures removal intent; T04 removed the orphan Affiliate icon while preserving historical migration evidence.",
|
||||
"Feature Decision/S06 remove-from-core pruning removed the orphan BlogApi stub; public row sharing at apps/web/app/routes/public/$entity.$id.tsx is now removed from active runtime.",
|
||||
"Feature Manifest/S05 adds pnpm run verify:removed-modules as the runtime scan gate for removed-module regressions.",
|
||||
"Product Core marks Forms, Surveys, Public Forms, and form-based Help Desk surveys as removal targets rather than plugin candidates.",
|
||||
"Fake content helpers and the demo admin entity row route were removed from the active source tree.",
|
||||
"Dependency cleanup must run after feature extraction/removal and live import audits to avoid deleting shared packages prematurely.",
|
||||
"Unused landing-particle runtime code and particle dependencies were removed from active source."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md",
|
||||
"docs/architecture-migration-status.md",
|
||||
"BOILERPLATE-PLAN.md",
|
||||
"docs/feature-decisions/kb-widgets-workflow-marketing-decision-report.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-13"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "rows-entities-runtime",
|
||||
"title": "Rows and Entities Runtime",
|
||||
"classification": "mixed",
|
||||
"status": "review-needed",
|
||||
"owner": "core-pruning",
|
||||
"description": "Prisma, database access, rows, entity data structures, and minimal runtime pieces that other features build on.",
|
||||
"rationale": [
|
||||
"Many features depend on Prisma, entities, and rows as data foundations.",
|
||||
"The runtime foundation is core, while no-code builders and code generation are candidates for extraction."
|
||||
],
|
||||
"codeAreas": [
|
||||
"packages/db",
|
||||
"prisma",
|
||||
"apps/web/app/modules/rows",
|
||||
"apps/web/app/utils/db/**",
|
||||
"apps/web/app/modules/templates/defaultEntityTemplates.ts",
|
||||
"apps/web/app/routes/admin/entities*",
|
||||
"apps/web/app/routes/app.$tenant/settings/entities*",
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/entities/:entity",
|
||||
"/v1/entities/:entity/rows",
|
||||
"/v1/entities/:entity/rows/:rowId",
|
||||
"/v1/relationships",
|
||||
"/v1/relationships/:id",
|
||||
"/v1/exports/:exportJobId/download-url",
|
||||
"packages/exports/src",
|
||||
"apps/web/app/modules/codeGenerator",
|
||||
"apps/web/app/modules/formulas",
|
||||
"apps/web/app/modules/jsonProperties"
|
||||
],
|
||||
"dependencies": [
|
||||
"auth-tenancy"
|
||||
],
|
||||
"consumers": [
|
||||
"core",
|
||||
"billing",
|
||||
"plugin-candidates",
|
||||
"remove-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "system-only",
|
||||
"surfaces": {
|
||||
"adminRoutes": [
|
||||
"apps/web/app/routes/admin/entities*"
|
||||
],
|
||||
"tenantRoutes": [
|
||||
"apps/web/app/routes/app.$tenant/settings/entities*"
|
||||
],
|
||||
"publicRoutes": [],
|
||||
"apiRoutes": [
|
||||
"apps/api/src/app.ts",
|
||||
"/v1/entities/:entity",
|
||||
"/v1/entities/:entity/rows",
|
||||
"/v1/entities/:entity/rows/:rowId",
|
||||
"/v1/relationships",
|
||||
"/v1/relationships/:id",
|
||||
"/v1/exports/:exportJobId/download-url"
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Entity schema and runtime policy"
|
||||
],
|
||||
"tenant": [
|
||||
"Tenant row/entity usage where enabled"
|
||||
],
|
||||
"environment": [
|
||||
"DATABASE_URL"
|
||||
],
|
||||
"secrets": [
|
||||
"Database credentials are deployment-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": false,
|
||||
"queues": [],
|
||||
"jobs": [],
|
||||
"notes": [
|
||||
"Rows runtime should not require workers unless a feature layer adds async processing."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"database"
|
||||
],
|
||||
"permissions": [
|
||||
"admin.entities.*",
|
||||
"app.rows.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"entity.updated",
|
||||
"row.created",
|
||||
"row.updated",
|
||||
"row.deleted"
|
||||
],
|
||||
"billingImpacts": [
|
||||
"May support plan limits or usage meters owned by billing/plugin manifests."
|
||||
],
|
||||
"uat": [
|
||||
"Rows runtime supports retained app data flows.",
|
||||
"CSV and XLSX exports neutralize spreadsheet formula injection prefixes before artifact delivery.",
|
||||
"No-code builder surfaces can be separated from runtime."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Prisma schema and database package foundation",
|
||||
"Rows runtime required by retained app data flows",
|
||||
"Entity data primitives used by core features"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"No-code entity builder UI",
|
||||
"Code generator",
|
||||
"Formula and JSON property authoring surfaces",
|
||||
"Default CRM-oriented entity templates"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Runtime routes or APIs during manifest Phase 1",
|
||||
"Route and navigation edits owned by parallel workers"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "review",
|
||||
"notes": [
|
||||
"Split core runtime from no-code product surfaces before deleting any rows or entity code.",
|
||||
"Audit CRM and email marketing coupling before changing default entity templates."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://toolctrl.local/docs/feature-manifests/schema.json",
|
||||
"title": "ToolCTRL Feature Manifest",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"id",
|
||||
"title",
|
||||
"classification",
|
||||
"status",
|
||||
"owner",
|
||||
"description",
|
||||
"rationale",
|
||||
"codeAreas",
|
||||
"dependencies",
|
||||
"consumers",
|
||||
"integration",
|
||||
"boundaries",
|
||||
"pruning",
|
||||
"validation"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string",
|
||||
"const": "1.0"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"classification": {
|
||||
"type": "string",
|
||||
"enum": ["core", "plugin-candidate", "remove-candidate", "mixed"]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active", "proposed", "review-needed"]
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "Planning owner or broad ownership area, not a code owner assignment."
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"rationale": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"codeAreas": {
|
||||
"type": "array",
|
||||
"description": "Likely source locations. Paths may be globs or package/module names.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"description": "Feature manifest IDs this feature needs to function.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
}
|
||||
},
|
||||
"consumers": {
|
||||
"type": "array",
|
||||
"description": "Feature manifest IDs or feature surfaces that depend on this one.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"description": "Optional list for bundle manifests such as plugin or removal candidates.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "title", "rationale", "codeAreas"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
},
|
||||
"codeAreas": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"ownership",
|
||||
"surfaces",
|
||||
"configuration",
|
||||
"workers",
|
||||
"externalProviders",
|
||||
"permissions",
|
||||
"auditEvents",
|
||||
"billingImpacts",
|
||||
"uat"
|
||||
],
|
||||
"properties": {
|
||||
"ownership": {
|
||||
"type": "string",
|
||||
"enum": ["tenant-owned", "admin-managed", "hybrid", "system-only"]
|
||||
},
|
||||
"surfaces": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["adminRoutes", "tenantRoutes", "publicRoutes", "apiRoutes"],
|
||||
"properties": {
|
||||
"adminRoutes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"tenantRoutes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"publicRoutes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"apiRoutes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["admin", "tenant", "environment", "secrets"],
|
||||
"properties": {
|
||||
"admin": {
|
||||
"type": "array",
|
||||
"description": "Admin-owned settings, provider credentials, policy, and defaults.",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"tenant": {
|
||||
"type": "array",
|
||||
"description": "Tenant-owned settings visible or editable in tenant surfaces.",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"environment": {
|
||||
"type": "array",
|
||||
"description": "Required environment variables or deployment configuration.",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"description": "Secret values and where they are owned. Do not store secret values here.",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"workers": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["required", "queues", "jobs", "notes"],
|
||||
"properties": {
|
||||
"required": { "type": "boolean" },
|
||||
"queues": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"jobs": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalProviders": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][A-Za-z0-9]*(\\.(?:[A-Za-z][A-Za-z0-9]*|\\*))+$"
|
||||
}
|
||||
},
|
||||
"auditEvents": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
|
||||
}
|
||||
},
|
||||
"billingImpacts": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"uat": {
|
||||
"type": "array",
|
||||
"description": "Operator-verifiable acceptance checks for planning and implementation.",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"boundaries": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["mustKeep", "mayMoveToPlugin", "mustNotTouch"],
|
||||
"properties": {
|
||||
"mustKeep": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"mayMoveToPlugin": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"mustNotTouch": {
|
||||
"type": "array",
|
||||
"description": "Areas excluded from the current phase or known to be owned by parallel work.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pruning": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["phase", "action", "notes"],
|
||||
"properties": {
|
||||
"phase": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["keep", "extract-plugin", "remove", "review"]
|
||||
},
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["source", "lastReviewed"],
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"lastReviewed": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"id": "transactional-mail",
|
||||
"title": "Transactional Mail",
|
||||
"classification": "core",
|
||||
"status": "proposed",
|
||||
"owner": "core-pruning",
|
||||
"description": "System email required for authentication, invitations, account notifications, and billing operations.",
|
||||
"rationale": [
|
||||
"Password resets, invitations, and system notifications are core operational requirements.",
|
||||
"Newsletter, inbound, and marketing email should not be bundled with transactional mail."
|
||||
],
|
||||
"codeAreas": [
|
||||
"apps/web/app/modules/emails",
|
||||
"apps/web/app/application/emails",
|
||||
"apps/web/app/utils/services/emailService.ts",
|
||||
"apps/worker/src/jobs/workflow-trigger-native-executor.ts",
|
||||
"apps/worker/src/jobs/transactional-mail-outbox.ts",
|
||||
"apps/worker/src/jobs/transactional-mail-outbox-bootstrap.ts",
|
||||
"apps/worker/src/handlers.ts",
|
||||
"packages/queue",
|
||||
"packages/mail",
|
||||
"@toolctrl/mail"
|
||||
],
|
||||
"dependencies": [],
|
||||
"consumers": [
|
||||
"auth-tenancy",
|
||||
"billing",
|
||||
"workflow-engine",
|
||||
"plugin-candidates"
|
||||
],
|
||||
"integration": {
|
||||
"ownership": "system-only",
|
||||
"surfaces": {
|
||||
"adminRoutes": [],
|
||||
"tenantRoutes": [],
|
||||
"publicRoutes": [],
|
||||
"apiRoutes": []
|
||||
},
|
||||
"configuration": {
|
||||
"admin": [
|
||||
"Transactional mail templates and Mailjet settings"
|
||||
],
|
||||
"tenant": [],
|
||||
"environment": [
|
||||
"MAILJET_API_KEY",
|
||||
"MAILJET_FROM_EMAIL",
|
||||
"MAILJET_FROM_NAME",
|
||||
"MAILJET_WEBHOOK_SECRET",
|
||||
"MAILJET_SECRET_KEY",
|
||||
"TRANSACTIONAL_MAIL_OUTBOX_REQUIRED"
|
||||
],
|
||||
"secrets": [
|
||||
"Mail provider API keys are platform/admin-owned, never tenant-owned."
|
||||
]
|
||||
},
|
||||
"workers": {
|
||||
"required": true,
|
||||
"queues": [
|
||||
"outbox.mail",
|
||||
"toolctrl.email"
|
||||
],
|
||||
"jobs": [
|
||||
"mail.transactional.* outbox bridge",
|
||||
"workflow email block mail.transactional.send producer",
|
||||
"send-transactional-email"
|
||||
],
|
||||
"notes": [
|
||||
"Transactional mail must be drained by an operator-owned permanent Coolify worker-mail resource with WORKER_QUEUE_GROUP=mail in each deployed environment.",
|
||||
"Production worker-mail resources should set TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=true so startup and /readyz fail when the outbox.mail bridge cannot subscribe.",
|
||||
"This manifest records the runtime requirement; it is not live-environment proof that any specific Coolify target currently has worker-mail deployed or healthy.",
|
||||
"Web and API containers either enqueue direct transactional mail jobs or publish mail.* outbox events; they must not be treated as mail consumers. The mail worker must run as a separate worker resource alongside worker-general, worker-import, worker-webhook, and worker-plugin.",
|
||||
"Worker-native workflow email blocks publish mail.transactional.send outbox events; worker-general handles workflow execution while worker-mail remains the only transactional mail consumer.",
|
||||
"Dependent manifests must declare any additional queue use beyond the core toolctrl.email send-transactional-email job."
|
||||
]
|
||||
},
|
||||
"externalProviders": [
|
||||
"mailjet"
|
||||
],
|
||||
"permissions": [
|
||||
"admin.emails.*"
|
||||
],
|
||||
"auditEvents": [
|
||||
"mail.template.updated",
|
||||
"mail.provider.updated"
|
||||
],
|
||||
"billingImpacts": [],
|
||||
"uat": [
|
||||
"Password reset, invitation, and verification emails can be sent through the selected provider."
|
||||
]
|
||||
},
|
||||
"boundaries": {
|
||||
"mustKeep": [
|
||||
"Password reset emails",
|
||||
"Invitation emails",
|
||||
"Verification and system emails required by core flows"
|
||||
],
|
||||
"mayMoveToPlugin": [
|
||||
"Email marketing and newsletter abstractions",
|
||||
"Inbound email handling",
|
||||
"Provider adapters beyond Mailjet"
|
||||
],
|
||||
"mustNotTouch": [
|
||||
"Chatbot or marketing email surfaces owned by parallel workers"
|
||||
]
|
||||
},
|
||||
"pruning": {
|
||||
"phase": "1",
|
||||
"action": "keep",
|
||||
"notes": [
|
||||
"Mailjet is the only transactional runtime provider.",
|
||||
"Removed legacy transactional adapters are not maintained as runtime adapters."
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"source": [
|
||||
"docs/feature-inventory.md",
|
||||
"docs/ops/worker-queues.md",
|
||||
"docs/improvement-plan/OPERATOR-CHECKLIST.md"
|
||||
],
|
||||
"lastReviewed": "2026-05-28"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user