@elevasis/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.d.ts +427 -42
  2. package/dist/index.js +589 -47
  3. package/dist/organization-model/index.d.ts +427 -42
  4. package/dist/organization-model/index.js +589 -47
  5. package/package.json +3 -3
  6. package/src/__tests__/template-foundations-compatibility.test.ts +2 -2
  7. package/src/business/acquisition/types.ts +2 -0
  8. package/src/commands/queue/types/task.ts +3 -3
  9. package/src/execution/engine/index.ts +8 -0
  10. package/src/execution/engine/tools/registry.ts +26 -24
  11. package/src/execution/engine/tools/tool-maps.ts +13 -9
  12. package/src/execution/engine/workflow/types.ts +2 -3
  13. package/src/organization-model/README.md +16 -12
  14. package/src/organization-model/__tests__/defaults.test.ts +175 -0
  15. package/src/organization-model/__tests__/domains/customers.test.ts +295 -0
  16. package/src/organization-model/__tests__/domains/goals.test.ts +479 -0
  17. package/src/organization-model/__tests__/domains/identity.test.ts +278 -0
  18. package/src/organization-model/__tests__/domains/navigation.test.ts +212 -0
  19. package/src/organization-model/__tests__/domains/offerings.test.ts +419 -0
  20. package/src/organization-model/__tests__/domains/operations.test.ts +203 -0
  21. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -0
  22. package/src/organization-model/__tests__/domains/roles.test.ts +347 -0
  23. package/src/organization-model/__tests__/domains/statuses.test.ts +243 -0
  24. package/src/organization-model/__tests__/foundation.test.ts +3 -3
  25. package/src/organization-model/__tests__/resolve.test.ts +447 -3
  26. package/src/organization-model/__tests__/schema.test.ts +407 -0
  27. package/src/organization-model/contracts.ts +5 -5
  28. package/src/organization-model/defaults.ts +39 -16
  29. package/src/organization-model/domains/customers.ts +75 -0
  30. package/src/organization-model/domains/goals.ts +80 -0
  31. package/src/organization-model/domains/identity.ts +87 -0
  32. package/src/organization-model/domains/navigation.ts +43 -4
  33. package/src/organization-model/domains/offerings.ts +66 -0
  34. package/src/organization-model/domains/operations.ts +85 -0
  35. package/src/organization-model/domains/{delivery.ts → projects.ts} +6 -6
  36. package/src/organization-model/domains/{lead-gen.ts → prospecting.ts} +5 -5
  37. package/src/organization-model/domains/roles.ts +55 -0
  38. package/src/organization-model/domains/sales.ts +94 -0
  39. package/src/organization-model/domains/shared.ts +30 -1
  40. package/src/organization-model/domains/statuses.ts +130 -0
  41. package/src/organization-model/index.ts +3 -3
  42. package/src/organization-model/organization-graph.mdx +1 -0
  43. package/src/organization-model/organization-model.mdx +84 -19
  44. package/src/organization-model/published.ts +53 -8
  45. package/src/organization-model/schema.ts +67 -7
  46. package/src/organization-model/types.ts +31 -7
  47. package/src/platform/constants/versions.ts +1 -1
  48. package/src/platform/registry/types.ts +1 -1
  49. package/src/projects/api-schemas.ts +1 -0
  50. package/src/reference/_generated/contracts.md +116 -8
  51. package/src/reference/glossary.md +25 -4
  52. package/src/requests/__tests__/api-schemas.test.ts +277 -0
  53. package/src/requests/api-schemas.ts +83 -0
  54. package/src/requests/index.ts +1 -0
  55. package/src/supabase/database.types.ts +88 -0
  56. package/src/organization-model/domains/crm.ts +0 -46
  57. /package/src/business/{delivery → projects}/index.ts +0 -0
  58. /package/src/business/{delivery → projects}/types.ts +0 -0
  59. /package/src/business/{crm → sales}/api-schemas.ts +0 -0
@@ -1,4 +1,4 @@
1
- <!-- Auto-generated on 2026-04-19T04:30:03.546Z by scripts/monorepo/generate-scaffold-contracts.js -->
1
+ <!-- Auto-generated on 2026-04-20T23:27:16.482Z by scripts/monorepo/generate-scaffold-contracts.js -->
2
2
  ---
3
3
  title: Reference Contracts
4
4
  description: Auto-generated TypeScript contracts for SDK consumers. Do not edit manually.
@@ -20,22 +20,22 @@ export type OrganizationModel = z.infer<typeof OrganizationModelSchema>
20
20
  export type OrganizationModelBranding = z.infer<typeof OrganizationModelBrandingSchema>
21
21
  ```
22
22
 
23
- ### `OrganizationModelCrm`
23
+ ### `OrganizationModelSales`
24
24
 
25
25
  ```typescript
26
- export type OrganizationModelCrm = z.infer<typeof OrganizationModelCrmSchema>
26
+ export type OrganizationModelSales = z.infer<typeof OrganizationModelSalesSchema>
27
27
  ```
28
28
 
29
- ### `OrganizationModelLeadGen`
29
+ ### `OrganizationModelProspecting`
30
30
 
31
31
  ```typescript
32
- export type OrganizationModelLeadGen = z.infer<typeof OrganizationModelLeadGenSchema>
32
+ export type OrganizationModelProspecting = z.infer<typeof OrganizationModelProspectingSchema>
33
33
  ```
34
34
 
35
- ### `OrganizationModelDelivery`
35
+ ### `OrganizationModelProjects`
36
36
 
37
37
  ```typescript
38
- export type OrganizationModelDelivery = z.infer<typeof OrganizationModelDeliverySchema>
38
+ export type OrganizationModelProjects = z.infer<typeof OrganizationModelProjectsSchema>
39
39
  ```
40
40
 
41
41
  ### `OrganizationModelFeature`
@@ -62,6 +62,114 @@ export type OrganizationModelSurface = z.infer<typeof SurfaceDefinitionSchema>
62
62
  export type OrganizationModelResourceMapping = z.infer<typeof ResourceMappingSchema>
63
63
  ```
64
64
 
65
+ ### `OrganizationModelTechStackEntry`
66
+
67
+ ```typescript
68
+ export type OrganizationModelTechStackEntry = z.infer<typeof TechStackEntrySchema>
69
+ ```
70
+
71
+ ### `OrganizationModelStatuses`
72
+
73
+ ```typescript
74
+ export type OrganizationModelStatuses = z.infer<typeof StatusesDomainSchema>
75
+ ```
76
+
77
+ ### `OrganizationModelStatusEntry`
78
+
79
+ ```typescript
80
+ export type OrganizationModelStatusEntry = z.infer<typeof StatusEntrySchema>
81
+ ```
82
+
83
+ ### `OrganizationModelStatusSemanticClass`
84
+
85
+ ```typescript
86
+ export type OrganizationModelStatusSemanticClass = z.infer<typeof StatusSemanticClassSchema>
87
+ ```
88
+
89
+ ### `OrganizationModelOperations`
90
+
91
+ ```typescript
92
+ export type OrganizationModelOperations = z.infer<typeof OperationsDomainSchema>
93
+ ```
94
+
95
+ ### `OrganizationModelOperationEntry`
96
+
97
+ ```typescript
98
+ export type OrganizationModelOperationEntry = z.infer<typeof OperationEntrySchema>
99
+ ```
100
+
101
+ ### `OrganizationModelOperationSemanticClass`
102
+
103
+ ```typescript
104
+ export type OrganizationModelOperationSemanticClass = z.infer<typeof OperationSemanticClassSchema>
105
+ ```
106
+
107
+ ### `OrganizationModelCustomers`
108
+
109
+ ```typescript
110
+ export type OrganizationModelCustomers = z.infer<typeof CustomersDomainSchema>
111
+ ```
112
+
113
+ ### `OrganizationModelCustomerSegment`
114
+
115
+ ```typescript
116
+ export type OrganizationModelCustomerSegment = z.infer<typeof CustomerSegmentSchema>
117
+ ```
118
+
119
+ ### `OrganizationModelCustomerFirmographics`
120
+
121
+ ```typescript
122
+ export type OrganizationModelCustomerFirmographics = z.infer<typeof FirmographicsSchema>
123
+ ```
124
+
125
+ ### `OrganizationModelOfferings`
126
+
127
+ ```typescript
128
+ export type OrganizationModelOfferings = z.infer<typeof OfferingsDomainSchema>
129
+ ```
130
+
131
+ ### `OrganizationModelProduct`
132
+
133
+ ```typescript
134
+ export type OrganizationModelProduct = z.infer<typeof ProductSchema>
135
+ ```
136
+
137
+ ### `OrganizationModelPricingModel`
138
+
139
+ ```typescript
140
+ export type OrganizationModelPricingModel = z.infer<typeof PricingModelSchema>
141
+ ```
142
+
143
+ ### `OrganizationModelRoles`
144
+
145
+ ```typescript
146
+ export type OrganizationModelRoles = z.infer<typeof RolesDomainSchema>
147
+ ```
148
+
149
+ ### `OrganizationModelRole`
150
+
151
+ ```typescript
152
+ export type OrganizationModelRole = z.infer<typeof RoleSchema>
153
+ ```
154
+
155
+ ### `OrganizationModelGoals`
156
+
157
+ ```typescript
158
+ export type OrganizationModelGoals = z.infer<typeof GoalsDomainSchema>
159
+ ```
160
+
161
+ ### `OrganizationModelObjective`
162
+
163
+ ```typescript
164
+ export type OrganizationModelObjective = z.infer<typeof ObjectiveSchema>
165
+ ```
166
+
167
+ ### `OrganizationModelKeyResult`
168
+
169
+ ```typescript
170
+ export type OrganizationModelKeyResult = z.infer<typeof KeyResultSchema>
171
+ ```
172
+
65
173
  ### `DeepPartial`
66
174
 
67
175
  ```typescript
@@ -479,7 +587,7 @@ export interface ResourceList {
479
587
 
480
588
  ```typescript
481
589
  /** Webhook provider identifiers */
482
- export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify'
590
+ export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify' | 'test'
483
591
 
484
592
  /** Webhook trigger configuration */
485
593
  ```
@@ -17,12 +17,16 @@ This condensed version covers every ambiguity-prone term a template consumer or
17
17
 
18
18
  **AdminGuard** -- route-level admin wrapper from `@elevasis/ui/features/auth`. Wraps routes restricted to admin members. Must nest inside `ProtectedRoute`. Does not replace `requiresAdmin` on nav entries -- use both when both route access and nav visibility need admin enforcement.
19
19
 
20
+ **`/configure`** -- the recurring organization model editor for external projects. A slash skill (not a command) at `.claude/skills/configure/SKILL.md`. Runs a layered QA flow across `identity`, `customers`, `offerings`, `roles`, `goals`, `techStack`, `features`, and `labels`. Can be invoked without an argument (full layered flow) or with a domain argument (`/configure identity`, `/configure customers`, etc.) for targeted edits. Every write is gated through `resolveOrganizationModel()` + `OrganizationModelSchema.parse()` with a TypeScript type-check fallback. The ambient vibe layer delegates Codify and Toggle intents to `/configure` rather than writing directly. Distinct from `/setup` (first-time bootstrap) and `/org-os manage` (monorepo-only feature gating). `/configure` is available in external projects only.
21
+
20
22
  **Contract** -- the publishable I/O boundary: Zod schemas in `foundations/types/index.ts` for workflow inputs/outputs, or the `FeatureModule` TypeScript shape for shell features. Distinct from "manifest": a contract is the structural definition; a manifest is a specific feature instance conforming to that shape.
21
23
 
22
24
  **DeploymentSpec** -- the complete resource collection for one organization (`workflows`, `agents`, `triggers`, `integrations`, `relationships`, `externalResources`, `humanCheckpoints`). Defined in `@repo/core`. The `operations/src/index.ts` file in each external project exports one `DeploymentSpec`.
23
25
 
24
26
  **Domain** -- removed concept. What was previously a `SemanticDomain` entry in `OrganizationModel.domains` is now expressed directly as a **Feature** in the `features` array. The `domains` field no longer exists on `OrganizationModel`. Semantic grouping (entity IDs, surface IDs, resource IDs, capability IDs) now lives on each `OrganizationModelFeature` object.
25
27
 
28
+ **Domain rename wave (2026-04-20)** -- three legacy top-level domain field names on `OrganizationModel` were renamed to align with user-visible labels: `crm` → `sales`, `leadGen` → `prospecting`, `delivery` → `projects`. Feature ID constants (`CRM_FEATURE_ID`, `LEAD_GEN_FEATURE_ID`) and consumer-facing feature IDs (`'crm'`, `'lead-gen'`) are unchanged. Only the Zod schema field names, domain files, surface IDs, and imports were updated.
29
+
26
30
  **Feature** -- the unified concept replacing the former three-layer system (feature keys, semantic domains, feature modules). Always qualify which layer is in scope:
27
31
 
28
32
  - **Platform capability** -- a product area (Execution Engine, Workflows, Agents, etc.). Not one-to-one with shell features.
@@ -37,25 +41,39 @@ This condensed version covers every ambiguity-prone term a template consumer or
37
41
 
38
42
  **Foundations** -- the adapter layer in `foundations/` (two modules: `config/organization-model.ts` and `types/index.ts`). Source package with no build step. Depends only on `@elevasis/core` (npm) and `zod`. Never import `@repo/core` from foundations -- that would break standalone deployment.
39
43
 
44
+ **Identity domain** -- legal identity, distinct from `branding` (display identity). Lives at `OrganizationModel.identity`. Fields: `mission`, `vision`, `legalName`, `entityType`, `jurisdiction`, `industryCategory`, `geographicFocus`, `timeZone`, `businessHours`. Agents use identity fields for compliance, localization, and context. Edit via `/configure identity`.
45
+
46
+ **Customers domain** -- customer segments with jobs-to-be-done, pains, gains, firmographics, and value propositions. Lives at `OrganizationModel.customers`. Each `CustomerSegment` has `id`, `name`, `description`, `jobsToBeDone`, `pains`, `gains`, `valueProp`, and optional `firmographics`. Agents use segments for outreach targeting and sales automation. Edit via `/configure customers`.
47
+
48
+ **Goals domain** -- organizational goals with period bounds and measurable outcomes. Lives at `OrganizationModel.goals`. User-facing text uses "goals" and "measurable outcomes"; the schema field name `keyResults` is retained for OKR tooling compatibility but is never surfaced to users as "OKR" or "key results". Edit via `/configure goals`.
49
+
40
50
  **Manifest** -- a `FeatureModule` instance that declares what one shell feature contributes at runtime (nav, sidebar, subshell routes, access key, semantic references). The provider registers an array of manifests at startup and validates them against the resolved org model.
41
51
 
42
52
  **MembershipFeatureConfig** -- per-member-per-org feature overrides stored in `org_memberships.config`. The `features` field is now `Record<string, boolean>` -- any feature ID can be overridden per member. Previously hardcoded to five keys (`operations`, `monitoring`, `acquisition`, `delivery`, `seo`); now open to any feature ID in the org model.
43
53
 
44
- **OrganizationModel** -- the top-level semantic contract for an organization. Published from `@elevasis/core/organization-model`. In the template, authored in `foundations/config/organization-model.ts` and exported as `canonicalOrganizationModel` (passed to `ElevasisFeaturesProvider`) and `organizationModel` (enriched shape for app-local use).
54
+ **Offerings domain** -- products and services with pricing model, price, currency, target segment references, and optional delivery-feature references. Lives at `OrganizationModel.offerings`. Each `Product` has `id`, `name`, `description`, `pricingModel` (`one-time | subscription | usage-based | custom`), optional `price` and `currency`, `targetSegmentIds` (cross-ref validated against `customers.segments`), and optional `deliveryFeatureId` (cross-ref validated against `features`). Edit via `/configure offerings`.
45
55
 
46
- **OrganizationModelFeature** -- the TypeScript type (`z.infer<typeof FeatureSchema>`) for a single entry in `OrganizationModel.features`. Replaces the former `OrganizationModelFeatureKey` (closed enum) and `OrganizationModelSemanticDomain`. Each feature has `id`, `label`, `enabled`, and semantic grouping arrays (`entityIds`, `surfaceIds`, `resourceIds`, `capabilityIds`). Exported from `@elevasis/core/organization-model`.
56
+ **Operations domain** -- catalog of stateful runtime entities. Lives at `OrganizationModel.operations`. Each `OperationEntry` has `id`, `label`, `semanticClass` (one of `queue | executions | sessions | notifications | schedules`), optional `featureId`, and optional `supportedStatusSemanticClass` array. Used by the ambient vibe layer to narrate runtime entity state. Default entries cover HITL queue, executions, sessions, notifications, and schedules.
57
+
58
+ **OrganizationModel** -- the top-level semantic contract for an organization. Published from `@elevasis/core/organization-model`. In the template, authored in `foundations/config/organization-model.ts` and exported as `canonicalOrganizationModel` (passed to `ElevasisFeaturesProvider`) and `organizationModel` (enriched shape for app-local use). As of the 2026-04-20 expansion, the model contains 14 top-level domains: `features`, `branding`, `navigation`, `sales` (formerly `crm`), `prospecting` (formerly `leadGen`), `projects` (formerly `delivery`), `identity`, `customers`, `offerings`, `roles`, `goals`, `statuses`, `operations`, and `resourceMappings` (with optional `techStack` per entry). Use `/configure` as the entry point for editing reality domains in external projects.
59
+
60
+ **OrganizationModelFeature** -- the TypeScript type (`z.infer<typeof FeatureSchema>`) for a single entry in `OrganizationModel.features`. Replaces the former `OrganizationModelFeatureKey` (closed enum) and `OrganizationModelSemanticDomain`. Each feature has `id`, `label`, `enabled`, and semantic grouping arrays (`entityIds`, `surfaceIds`, `resourceIds`, `capabilityIds`). Exported from `@elevasis/core/organization-model`. Default feature IDs: `crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`, `submitted-requests`, `seo`. Note: feature IDs `crm` and `lead-gen` are unchanged by the domain rename wave; only the domain field names on `OrganizationModel` were renamed (`crm` → `sales`, `leadGen` → `prospecting`).
47
61
 
48
62
  **Provider / ElevasisFeaturesProvider** -- the runtime that registers manifests, resolves feature access against the org model, dispatches subshell routing via `FeatureShell`, and exposes resolved state through `useElevasisFeatures()`. Mounted in `__root.tsx`. Accepts `features`, optional `organizationModel`, and optional `appShellOverrides`.
49
63
 
50
64
  **Resolved types (ResolvedFeatureModule, ResolvedShellNavItem)** -- provider output. `ResolvedFeatureModule` extends `FeatureModule` with `access` (enabled state and `featureId`) and `semantics` (merged capability and surface IDs). `ResolvedShellNavItem` extends `FeatureNavEntry` with `placement`, `source`, and `featureId`. Both from `@elevasis/ui`.
51
65
 
52
- **Resource** -- an entry in `OrganizationModel.resourceMappings` linking a deployable automation resource into the semantic model. At the registry layer, resources are `WorkflowDefinition` or `AgentDefinition` instances in a `DeploymentSpec`.
66
+ **Resource** -- an entry in `OrganizationModel.resourceMappings` linking a deployable automation resource into the semantic model. At the registry layer, resources are `WorkflowDefinition` or `AgentDefinition` instances in a `DeploymentSpec`. Each resource mapping may carry an optional `techStack` extension with `platform`, `purpose`, `credentialStatus`, and `isSystemOfRecord` fields.
67
+
68
+ **Roles domain** -- role chart with responsibilities, reporting lines, and role holders. Lives at `OrganizationModel.roles`. Each `Role` has `id`, `title`, `responsibilities` (string array), optional `reportsToId` (cross-ref validated within the same collection), and optional `heldBy` (name or email). All field names are plain-language; no EOS jargon (`seats`, `accountabilities`) is used. Edit via `/configure roles`.
69
+
70
+ **Statuses domain** -- flat registry of all status entries across delivery, queue, execution, schedule, and request semantic classes. Lives at `OrganizationModel.statuses`. Each `StatusEntry` has `id`, `label`, `semanticClass` (closed enum), and optional `category`. Labels are the canonical plain-language strings the ambient vibe layer reads when narrating status -- never hardcoded strings. `QueueTaskStatus` in the queue domain routes through this registry.
53
71
 
54
72
  **Settings asymmetry** -- `settings` is a valid feature ID (org-level, present in `OrganizationModel.features` with `enabled: true` by default). `MembershipFeatureConfig.features` is now `Record<string, boolean>`, so `settings` can technically be overridden per member, but the convention is still to gate settings visibility via `requiresAdmin` on the nav entry and `AdminGuard` on routes rather than per-member feature overrides.
55
73
 
56
74
  **Subshell / Sidebar** -- the feature-scoped UI region rendered when the current route matches a manifest's `subshellRoutes`. Each `FeatureModule.sidebar` is a `ComponentType`. Consumers customize by composing the feature's published sidebar primitives (`CrmSidebar`, `CrmSidebarMiddle`, etc.) and assigning their component to `manifest.sidebar`.
57
75
 
58
- **Surface** -- a navigable view in `OrganizationModel.navigation.surfaces`. Identified by a dotted `id` (e.g., `crm.pipeline`). Has `path`, `surfaceType`, optional `featureId` gate (replaces the former `featureKey` field), and cross-reference arrays (`featureIds`, `entityIds`, `resourceIds`, `capabilityIds`). Distinct from "page": a surface is the org-model declaration; a page is the React component rendered at the route.
76
+ **Surface** -- a navigable view in `OrganizationModel.navigation.surfaces`. Identified by a dotted `id` (e.g., `sales.pipeline`). Has `path`, `surfaceType`, optional `featureId` gate (replaces the former `featureKey` field), and cross-reference arrays (`featureIds`, `entityIds`, `resourceIds`, `capabilityIds`). Distinct from "page": a surface is the org-model declaration; a page is the React component rendered at the route.
59
77
 
60
78
  **Topology** -- resource relationships (`triggers`, `uses`, `approval`) declared in `DeploymentSpec.relationships` and `HumanCheckpointDefinition.routesTo`. Used for Command View graph edges.
61
79
 
@@ -66,6 +84,9 @@ This condensed version covers every ambiguity-prone term a template consumer or
66
84
  **`@elevasis/core`** (published npm)
67
85
 
68
86
  - `OrganizationModel`, `OrganizationModelFeature`, `OrganizationModelSurface`, `OrganizationModelResourceMapping`
87
+ - `OrganizationModelIdentity`, `OrganizationModelCustomers`, `OrganizationModelOfferings`, `OrganizationModelRoles`, `OrganizationModelGoals`
88
+ - `OrganizationModelStatuses`, `OrganizationModelOperations`
89
+ - `TechStackEntrySchema`, `OrganizationModelTechStackEntry`
69
90
  - `resolveOrganizationModel`, `defineOrganizationModel`, `DEFAULT_ORGANIZATION_MODEL`
70
91
  - `MembershipFeatureConfig`
71
92
 
@@ -0,0 +1,277 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import {
3
+ CreateRequestInputSchema,
4
+ ListRequestsQuerySchema,
5
+ RequestSeverityEnum,
6
+ RequestCategoryEnum,
7
+ RequestStatusEnum,
8
+ RequestSourceEnum,
9
+ RequestTypeEnum
10
+ } from '../api-schemas'
11
+
12
+ const validInput = {
13
+ type: 'bug' as const,
14
+ title: 'Login button not responding',
15
+ description: 'The login button does nothing when clicked on Safari.',
16
+ severity: 'warning' as const,
17
+ category: 'ui_bug' as const
18
+ }
19
+
20
+ describe('CreateRequestInputSchema', () => {
21
+ it('accepts a minimal valid payload', () => {
22
+ const result = CreateRequestInputSchema.safeParse(validInput)
23
+ expect(result.success).toBe(true)
24
+ })
25
+
26
+ it('accepts a fully-populated payload', () => {
27
+ const result = CreateRequestInputSchema.safeParse({
28
+ ...validInput,
29
+ affected_page: '/projects/abc',
30
+ evidence: { stack: 'TypeError: x is undefined' },
31
+ context: { recent_messages: [], identified_source: 'ui/src/routes/login.tsx' },
32
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
33
+ task_id: '123e4567-e89b-12d3-a456-426614174001'
34
+ })
35
+ expect(result.success).toBe(true)
36
+ })
37
+
38
+ it('accepts null for optional nullish fields', () => {
39
+ const result = CreateRequestInputSchema.safeParse({
40
+ ...validInput,
41
+ affected_page: null,
42
+ evidence: null,
43
+ context: null,
44
+ project_id: null,
45
+ task_id: null
46
+ })
47
+ expect(result.success).toBe(true)
48
+ })
49
+
50
+ it('accepts feature type', () => {
51
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, type: 'feature' })
52
+ expect(result.success).toBe(true)
53
+ })
54
+
55
+ it('accepts question type', () => {
56
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, type: 'question' })
57
+ expect(result.success).toBe(true)
58
+ })
59
+
60
+ it('accepts other type', () => {
61
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, type: 'other' })
62
+ expect(result.success).toBe(true)
63
+ })
64
+
65
+ it('rejects missing type (required, no default)', () => {
66
+ const { type: _type, ...withoutType } = validInput
67
+ const result = CreateRequestInputSchema.safeParse(withoutType)
68
+ expect(result.success).toBe(false)
69
+ })
70
+
71
+ it('rejects invalid type value', () => {
72
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, type: 'enhancement' })
73
+ expect(result.success).toBe(false)
74
+ })
75
+
76
+ it('rejects empty title', () => {
77
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, title: '' })
78
+ expect(result.success).toBe(false)
79
+ })
80
+
81
+ it('rejects empty description', () => {
82
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, description: '' })
83
+ expect(result.success).toBe(false)
84
+ })
85
+
86
+ it('rejects invalid severity', () => {
87
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, severity: 'urgent' })
88
+ expect(result.success).toBe(false)
89
+ })
90
+
91
+ it('rejects invalid category', () => {
92
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, category: 'typo' })
93
+ expect(result.success).toBe(false)
94
+ })
95
+
96
+ it('rejects invalid project_id UUID', () => {
97
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, project_id: 'not-a-uuid' })
98
+ expect(result.success).toBe(false)
99
+ })
100
+
101
+ it('rejects invalid task_id UUID', () => {
102
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, task_id: 'not-a-uuid' })
103
+ expect(result.success).toBe(false)
104
+ })
105
+
106
+ it('rejects client-supplied source (strict mode — mass assignment prevention)', () => {
107
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, source: 'external' })
108
+ expect(result.success).toBe(false)
109
+ })
110
+
111
+ it('rejects client-supplied organization_id (strict mode)', () => {
112
+ const result = CreateRequestInputSchema.safeParse({
113
+ ...validInput,
114
+ organization_id: '123e4567-e89b-12d3-a456-426614174000'
115
+ })
116
+ expect(result.success).toBe(false)
117
+ })
118
+
119
+ it('rejects client-supplied reporter_id (strict mode)', () => {
120
+ const result = CreateRequestInputSchema.safeParse({
121
+ ...validInput,
122
+ reporter_id: '123e4567-e89b-12d3-a456-426614174000'
123
+ })
124
+ expect(result.success).toBe(false)
125
+ })
126
+
127
+ it('rejects client-supplied status (strict mode)', () => {
128
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, status: 'resolved' })
129
+ expect(result.success).toBe(false)
130
+ })
131
+
132
+ it('rejects arbitrary unknown field (strict mode)', () => {
133
+ const result = CreateRequestInputSchema.safeParse({ ...validInput, foo: 'bar' })
134
+ expect(result.success).toBe(false)
135
+ })
136
+ })
137
+
138
+ describe('ListRequestsQuerySchema', () => {
139
+ it('applies default limit of 50', () => {
140
+ const result = ListRequestsQuerySchema.safeParse({})
141
+ expect(result.success).toBe(true)
142
+ if (result.success) {
143
+ expect(result.data.limit).toBe(50)
144
+ }
145
+ })
146
+
147
+ it('coerces string limit to number', () => {
148
+ const result = ListRequestsQuerySchema.safeParse({ limit: '25' })
149
+ expect(result.success).toBe(true)
150
+ if (result.success) {
151
+ expect(result.data.limit).toBe(25)
152
+ }
153
+ })
154
+
155
+ it('rejects limit < 1', () => {
156
+ const result = ListRequestsQuerySchema.safeParse({ limit: '0' })
157
+ expect(result.success).toBe(false)
158
+ })
159
+
160
+ it('rejects limit > 200', () => {
161
+ const result = ListRequestsQuerySchema.safeParse({ limit: '201' })
162
+ expect(result.success).toBe(false)
163
+ })
164
+
165
+ it('rejects non-numeric limit', () => {
166
+ const result = ListRequestsQuerySchema.safeParse({ limit: 'abc' })
167
+ expect(result.success).toBe(false)
168
+ })
169
+
170
+ it('accepts valid status filter', () => {
171
+ const result = ListRequestsQuerySchema.safeParse({ status: 'open' })
172
+ expect(result.success).toBe(true)
173
+ })
174
+
175
+ it('rejects invalid status filter', () => {
176
+ const result = ListRequestsQuerySchema.safeParse({ status: 'archived' })
177
+ expect(result.success).toBe(false)
178
+ })
179
+
180
+ it('accepts valid severity filter', () => {
181
+ const result = ListRequestsQuerySchema.safeParse({ severity: 'critical' })
182
+ expect(result.success).toBe(true)
183
+ })
184
+
185
+ it('rejects invalid severity filter', () => {
186
+ const result = ListRequestsQuerySchema.safeParse({ severity: 'urgent' })
187
+ expect(result.success).toBe(false)
188
+ })
189
+
190
+ it('accepts valid project_id UUID filter', () => {
191
+ const result = ListRequestsQuerySchema.safeParse({
192
+ project_id: '123e4567-e89b-12d3-a456-426614174000'
193
+ })
194
+ expect(result.success).toBe(true)
195
+ })
196
+
197
+ it('rejects invalid project_id UUID', () => {
198
+ const result = ListRequestsQuerySchema.safeParse({ project_id: 'not-a-uuid' })
199
+ expect(result.success).toBe(false)
200
+ })
201
+ })
202
+
203
+ describe('RequestSeverityEnum', () => {
204
+ it('accepts all valid severities', () => {
205
+ const severities = ['critical', 'warning', 'info']
206
+ severities.forEach((s) => {
207
+ expect(RequestSeverityEnum.safeParse(s).success).toBe(true)
208
+ })
209
+ })
210
+
211
+ it('rejects invalid severity', () => {
212
+ expect(RequestSeverityEnum.safeParse('urgent').success).toBe(false)
213
+ })
214
+ })
215
+
216
+ describe('RequestCategoryEnum', () => {
217
+ it('accepts all valid categories (must match DB CHECK constraint)', () => {
218
+ const categories = [
219
+ 'ui_bug',
220
+ 'data_inconsistency',
221
+ 'performance',
222
+ 'error_pattern',
223
+ 'configuration',
224
+ 'feature_request',
225
+ 'api_design',
226
+ 'documentation',
227
+ 'integration_request',
228
+ 'other'
229
+ ]
230
+ categories.forEach((c) => {
231
+ expect(RequestCategoryEnum.safeParse(c).success).toBe(true)
232
+ })
233
+ })
234
+
235
+ it('rejects invalid category', () => {
236
+ expect(RequestCategoryEnum.safeParse('typo').success).toBe(false)
237
+ })
238
+ })
239
+
240
+ describe('RequestStatusEnum', () => {
241
+ it('accepts all valid statuses (must match DB CHECK constraint)', () => {
242
+ const statuses = ['open', 'investigating', 'resolved', 'wont_fix']
243
+ statuses.forEach((s) => {
244
+ expect(RequestStatusEnum.safeParse(s).success).toBe(true)
245
+ })
246
+ })
247
+
248
+ it('rejects invalid status', () => {
249
+ expect(RequestStatusEnum.safeParse('archived').success).toBe(false)
250
+ })
251
+ })
252
+
253
+ describe('RequestSourceEnum', () => {
254
+ it('accepts all valid sources (must match DB CHECK constraint)', () => {
255
+ const sources = ['agent', 'cli', 'api', 'webhook', 'user', 'external']
256
+ sources.forEach((s) => {
257
+ expect(RequestSourceEnum.safeParse(s).success).toBe(true)
258
+ })
259
+ })
260
+
261
+ it('rejects invalid source', () => {
262
+ expect(RequestSourceEnum.safeParse('bot').success).toBe(false)
263
+ })
264
+ })
265
+
266
+ describe('RequestTypeEnum', () => {
267
+ it('accepts all valid types', () => {
268
+ const types = ['bug', 'feature', 'question', 'other']
269
+ types.forEach((t) => {
270
+ expect(RequestTypeEnum.safeParse(t).success).toBe(true)
271
+ })
272
+ })
273
+
274
+ it('rejects invalid type', () => {
275
+ expect(RequestTypeEnum.safeParse('enhancement').success).toBe(false)
276
+ })
277
+ })
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Request Reporting API Schemas
5
+ *
6
+ * Request/response validation for /api/requests and /api/external/requests surfaces.
7
+ * Used by both the API (routes.ts, external-routes.ts) and the CLI.
8
+ *
9
+ * Table mapping:
10
+ * reported_requests -> RequestSchemas
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Enum literals (must match DB CHECK constraints exactly)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export const RequestSeverityEnum = z.enum(['critical', 'warning', 'info'])
18
+ export const RequestCategoryEnum = z.enum([
19
+ 'ui_bug',
20
+ 'data_inconsistency',
21
+ 'performance',
22
+ 'error_pattern',
23
+ 'configuration',
24
+ 'feature_request',
25
+ 'api_design',
26
+ 'documentation',
27
+ 'integration_request',
28
+ 'other'
29
+ ])
30
+ export const RequestStatusEnum = z.enum(['open', 'investigating', 'resolved', 'wont_fix'])
31
+ export const RequestSourceEnum = z.enum(['agent', 'cli', 'api', 'webhook', 'user', 'external'])
32
+ export const RequestTypeEnum = z.enum(['bug', 'feature', 'question', 'other'])
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Input schemas
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Body for POST /api/requests and POST /api/external/requests.
40
+ * `source` and `organization_id` are server-set — never accepted from the client.
41
+ */
42
+ export const CreateRequestInputSchema = z
43
+ .object({
44
+ type: RequestTypeEnum,
45
+ title: z.string().min(1),
46
+ description: z.string().min(1),
47
+ severity: RequestSeverityEnum,
48
+ category: RequestCategoryEnum,
49
+ affected_page: z.string().nullish(),
50
+ evidence: z.record(z.string(), z.unknown()).nullish(),
51
+ context: z.record(z.string(), z.unknown()).nullish(),
52
+ project_id: z.string().uuid().nullish(),
53
+ task_id: z.string().uuid().nullish()
54
+ })
55
+ .strict()
56
+
57
+ export const ListRequestsQuerySchema = z.object({
58
+ status: RequestStatusEnum.optional(),
59
+ severity: RequestSeverityEnum.optional(),
60
+ project_id: z.string().uuid().optional(),
61
+ limit: z.coerce.number().int().min(1).max(200).default(50)
62
+ })
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Bundled export (mirrors ProjectSchemas pattern)
66
+ // ---------------------------------------------------------------------------
67
+
68
+ export const RequestSchemas = {
69
+ CreateRequestInput: CreateRequestInputSchema,
70
+ ListRequestsQuery: ListRequestsQuerySchema
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Inferred types
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export type RequestSeverity = z.infer<typeof RequestSeverityEnum>
78
+ export type RequestCategory = z.infer<typeof RequestCategoryEnum>
79
+ export type RequestStatus = z.infer<typeof RequestStatusEnum>
80
+ export type RequestSource = z.infer<typeof RequestSourceEnum>
81
+ export type RequestType = z.infer<typeof RequestTypeEnum>
82
+ export type CreateRequestInput = z.infer<typeof CreateRequestInputSchema>
83
+ export type ListRequestsQuery = z.infer<typeof ListRequestsQuerySchema>
@@ -0,0 +1 @@
1
+ export * from './api-schemas.js'