@company-semantics/contracts 0.100.0 → 0.102.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "0.100.0",
3
+ "version": "0.102.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,21 @@
1
+ # api/
2
+
3
+ Shared API route helpers and contracts for backend HTTP endpoints.
4
+
5
+ ## Purpose
6
+
7
+ Provides reusable functions that encode API response shapes shared between the contracts package and backend route implementations. Currently focused on tool discovery response building.
8
+
9
+ ## Invariants
10
+
11
+ - Functions in this domain are pure — no side effects, no database access
12
+ - Response shapes must match the types defined in `src/mcp/` (e.g., `ToolDiscoveryResponse`)
13
+ - Capability graph inclusion is opt-in via query parameter convention (`?include=graph`)
14
+
15
+ ## Public API
16
+
17
+ - `buildToolDiscoveryResponse(tools, includeGraph)` — Constructs a `ToolDiscoveryResponse`, optionally including the capability graph derived from tool metadata
18
+
19
+ ## Dependencies
20
+
21
+ - `src/mcp/` — `MCPToolDescriptor`, `ToolDiscoveryResponse`, `CapabilityGraph` types and `buildCapabilityGraph` function
@@ -0,0 +1,21 @@
1
+ # api/http/
2
+
3
+ HTTP-specific API route contracts and helpers.
4
+
5
+ ## Purpose
6
+
7
+ Contains route-level shared logic for HTTP API endpoints. Organizes helpers by route path under `routes/`. Currently provides the tool discovery response builder used by the backend's `/api/capabilities/tools` endpoint.
8
+
9
+ ## Invariants
10
+
11
+ - Route helpers are pure functions — no HTTP framework coupling (no Fastify, no Express)
12
+ - Helpers encode response shape logic only; authentication, authorization, and request parsing belong in the backend
13
+ - File organization mirrors backend route paths (`routes/ai-chat.ts` → backend's `ai-chat.ts`)
14
+
15
+ ## Public API
16
+
17
+ - `routes/ai-chat.ts` — `buildToolDiscoveryResponse(tools, includeGraph)` for the capabilities/tools endpoint
18
+
19
+ ## Dependencies
20
+
21
+ - `src/mcp/` — Tool descriptor types and capability graph builder
@@ -0,0 +1,11 @@
1
+ import type { ResourceResponse } from '../../../resource-response';
2
+
3
+ /**
4
+ * Wrap resource data with version for cache invalidation.
5
+ * @param data - The resource data
6
+ * @param version - MUST be entity.updatedAt or DB row version. NEVER Date.now().
7
+ * Using request time defeats invalidation — race conditions reappear.
8
+ */
9
+ export function resourceResponse<T>(data: T, version: number): ResourceResponse<T> {
10
+ return { data, version };
11
+ }
@@ -0,0 +1,21 @@
1
+ # auth/
2
+
3
+ ## Purpose
4
+
5
+ Shared types for authentication flows across Company Semantics codebases. Defines the OTP and SSO authentication start protocol.
6
+
7
+ ## Invariants
8
+
9
+ - AuthStartResponse is a discriminated union on `mode` — consumers must handle all three modes
10
+ - OTPErrorCode values are stable strings used by both backend (error generation) and frontend (user messaging)
11
+
12
+ ## Public API
13
+
14
+ **Types:**
15
+ - `OTPErrorCode` — enum: `InvalidCode`, `Expired`, `AlreadyUsed`, `NotFound`
16
+ - `AuthStartMode` — `'otp' | 'sso' | 'hybrid'`
17
+ - `AuthStartResponse` — discriminated union describing auth flow entry point
18
+
19
+ ## Dependencies
20
+
21
+ - None (leaf domain in contracts)
@@ -0,0 +1,22 @@
1
+ # billing/
2
+
3
+ Read-only billing DTO types for the organization billing surface.
4
+
5
+ ## Purpose
6
+
7
+ Defines the shared type vocabulary for organization billing status and plan information. These types are consumed by backend (billing routes) and app (billing UI) to display plan and seat information.
8
+
9
+ ## Invariants
10
+
11
+ - All types are read-only DTOs — no mutation operations in v1
12
+ - `billingOwnerUserId` must equal `org.ownerUserId` — no separate billing owner concept in v1
13
+ - `OrgPlanStatus` is a closed union: `active`, `trialing`, `past_due`, `canceled`
14
+
15
+ ## Public API
16
+
17
+ - `OrgPlanStatus` — Union type for organization plan states
18
+ - `OrgBillingInfo` — Read-only billing information (plan name, status, cadence, seats, billing owner)
19
+
20
+ ## Dependencies
21
+
22
+ - None — leaf domain with no imports from other contracts modules
@@ -0,0 +1,38 @@
1
+ # chat/
2
+
3
+ ## Purpose
4
+
5
+ Shared types for chat persistence, sharing, real-time events, and runtime profile selection.
6
+
7
+ ## Invariants
8
+
9
+ - Chat share snapshot boundary: messages with sequenceNumber > messageCountAtShare are never exposed (SNAPSHOT-1)
10
+ - Domain events for a given entity are emitted in commit order; clients may assume monotonic updatedAt
11
+ - Clients must treat SSE events as idempotent — full payload replacement handles duplicates
12
+ - Invalidation events ensure convergence after SSE disconnects
13
+ - Runtime profile labels are vendor-agnostic (no model names in UI)
14
+ - Default runtime profile is `agentic`
15
+
16
+ ## Public API
17
+
18
+ **Types:**
19
+ - `ChatVisibility` — `'private' | 'public'` share visibility
20
+ - `ChatSummary`, `ChatSummaryExtended` — list view models
21
+ - `ChatShareInfo`, `SharedChatView`, `SharedChatMessage` — sharing types
22
+ - `CreateShareRequest`, `UpdateShareRequest` — share mutation requests
23
+ - `TitleSource`, `ChatListFilters`, `TitleGenerationRequest/Response` — lifecycle types
24
+ - `ChatRuntimeProfile` — `'fast' | 'balanced' | 'agentic'` orchestration strategy
25
+ - `ChatRuntimeProfileInfo` — profile metadata for UI rendering
26
+
27
+ **SSE event types:**
28
+ - `ChatCreatedEvent`, `ChatUpdatedEvent`, `ChatDeletedEvent` — domain events
29
+ - `InvalidateChatEvent`, `InvalidateChatListEvent` — staleness signals
30
+ - `ChatSseEvent` — union of all SSE event types
31
+
32
+ **Runtime values:**
33
+ - `CHAT_RUNTIME_PROFILES` — ordered array of profile info for UI rendering
34
+ - `DEFAULT_CHAT_RUNTIME_PROFILE` — `'agentic'`
35
+
36
+ ## Dependencies
37
+
38
+ - None (leaf domain in contracts)
@@ -0,0 +1,34 @@
1
+ # ci-envelope/
2
+
3
+ Type vocabulary for CI execution envelopes — the governance records that capture AI intent before mutations.
4
+
5
+ ## Purpose
6
+
7
+ Defines the shared type vocabulary for CI execution envelopes. An envelope is an append-only record created before code mutations that captures intent, scope, authority, and outcome. This enables pre-execution gating and post-hoc audit without LLM guesswork.
8
+
9
+ ## Invariants
10
+
11
+ - Types only — no validation logic, no policy mappings (those live in `company-semantics-ci/envelope/`)
12
+ - Envelopes are append-only: status transitions forward only, no field deletions
13
+ - `ENVELOPE_TRANSITIONS` documents the state machine but does not enforce it
14
+ - Terminal states: `blocked` and `finalized` have no outgoing transitions
15
+
16
+ ## Public API
17
+
18
+ **Types:**
19
+ - `CIExecutionEnvelope` — Core envelope record (version, id, actor, session, intent, scope, authority, guardPlan, status, outcome)
20
+ - `CIEnvelopeStatus` — Lifecycle states: `proposed` → `validated` → `executing` → `finalized` (or `blocked`)
21
+ - `ExecutionIntent` — Semantic categories: `feature_extension`, `bug_fix`, `refactor_structural`, `guard_update`, `policy_update`, `schema_change`, `doc_update`, `cleanup`, `experimental`
22
+ - `CIIntentDeclaration` — Intent with description and optional ADR references
23
+ - `CIExecutionScope` — Affected repos, path globs, and change kinds
24
+ - `CIAuthorityGrant` — Permission basis (CLAUDE.md/AI_AUTONOMY.md references)
25
+ - `CIGuardPlan` — Required and advisory guard lists
26
+ - `CIActorInfo`, `CISessionInfo` — Actor and session metadata
27
+ - `CIGuardViolation`, `CIExecutionOutcome` — Post-execution audit data
28
+
29
+ **Constants:**
30
+ - `ENVELOPE_TRANSITIONS` — Valid status transition graph (documentation only)
31
+
32
+ ## Dependencies
33
+
34
+ - None (leaf module — no imports from other contracts domains)
package/src/index.ts CHANGED
@@ -531,3 +531,11 @@ export type {
531
531
  } from './ci-envelope/index'
532
532
 
533
533
  export { ENVELOPE_TRANSITIONS } from './ci-envelope/index'
534
+
535
+ // Resource key types (shared vocabulary for data layer)
536
+ // @see PRD-00321 for design rationale
537
+ export type { ResourceKey, Action } from './resource-keys'
538
+ export { resolveScope, toQueryKey, fromQueryKey, resourceRelationships, matchesResourceKey } from './resource-keys'
539
+
540
+ // Resource response wrapper (typed versioning for cache invalidation)
541
+ export type { ResourceResponse } from './resource-response'
@@ -0,0 +1,23 @@
1
+ # interfaces/
2
+
3
+ Shared logic for MCP tool interface presentation and formatting.
4
+
5
+ ## Purpose
6
+
7
+ Contains pure formatting functions used by MCP tool interfaces. Currently provides help tool enrichment — functions that format workflow summaries and domain groupings from tool descriptors for the `cs_help` tool output.
8
+
9
+ ## Invariants
10
+
11
+ - Functions are pure — no side effects, no service dependencies
12
+ - Input is always `MCPToolDescriptor[]` (the same shape backend tool handlers produce)
13
+ - Output is formatted text, not structured data — these are presentation-layer helpers
14
+
15
+ ## Public API
16
+
17
+ - `mcp/tools/help.ts`:
18
+ - `formatWorkflowSummaries(descriptors)` — Derives workflows via capability graph and formats as text
19
+ - `formatDomainGroupings(descriptors)` — Groups tools by domain field and formats as text
20
+
21
+ ## Dependencies
22
+
23
+ - `src/mcp/` — `MCPToolDescriptor` type and `buildCapabilityGraph` function
@@ -0,0 +1,36 @@
1
+ # mcp/
2
+
3
+ ## Purpose
4
+
5
+ Type vocabulary for MCP (Model Context Protocol) tool discovery, invocation metadata, capability graph derivation, and failure recovery.
6
+
7
+ ## Invariants
8
+
9
+ - Discovery is descriptive (never executes); invocation is authoritative (runtime is executor)
10
+ - Tool discovery output must be runtime-sourced, not streamed char-by-char
11
+ - Intent `mutate` + risk `high` requires `requiresConfirmation: true` — enforced at tool registration
12
+ - ResourceType is a closed union — new types require a contracts release
13
+ - FailureContext recoveryActions are exhaustive — the LLM cannot invent alternatives
14
+ - `cancel` is always implicitly available in failure recovery (not declared in recoveryActions)
15
+ - At most one FailureContext is active at a time (no stacking)
16
+ - Capability graph is a pure derivation from produces/consumes metadata
17
+
18
+ ## Public API
19
+
20
+ **Types:**
21
+ - `MCPToolDescriptor` — complete tool descriptor for discovery and invocation
22
+ - `ToolDomain`, `ToolCategory` (deprecated), `ToolVisibility`, `ToolInvocationMode` — classification enums
23
+ - `ToolEffectClass`, `ToolIntent`, `ToolStability`, `ToolComplexity` — tool metadata types
24
+ - `ToolIntegration` — extensible integration provider tag
25
+ - `ResourceType` — closed union of tool resource flow types
26
+ - `CapabilityGraph`, `CapabilityGraphEdge`, `ToolWorkflow` — graph types
27
+ - `ToolDiscoveryResponse` — HTTP transport for GET /api/capabilities/tools
28
+ - `ToolListMessagePart`, `ToolListDataPart` — chat protocol and wire format parts
29
+ - `FailureContext`, `RecoveryAction` — structured failure recovery types
30
+
31
+ **Runtime values:**
32
+ - `buildCapabilityGraph(tools)` — pure function deriving graph from produces/consumes metadata
33
+
34
+ ## Dependencies
35
+
36
+ - None (leaf domain in contracts; consumed by backend, app)
@@ -0,0 +1,39 @@
1
+ # message-parts/
2
+
3
+ ## Purpose
4
+
5
+ Canonical vocabulary for structured assistant message output. Defines the type system for all rich content parts that flow between backend (producer) and app (consumer) in the chat protocol.
6
+
7
+ ## Invariants
8
+
9
+ - Narrative (text) parts MUST come before surface parts — enforced by PartBuilder state machine
10
+ - SurfacePart is an extensible union — add new surface kinds without protocol changes
11
+ - TextPart is the ONLY part type that may be streamed character-by-character
12
+ - Wire format uses AI SDK's `data-{name}` convention; frontend normalizes to semantic types
13
+ - At most one preview proposal is active at a time
14
+ - Confirmation is deterministic — no natural language inference ("yes" is invalid)
15
+ - Execution results carry `state` resolved from ExecutionState (single authority, no redundant status)
16
+ - Undo creates append-only audit rows — original execution is never mutated
17
+
18
+ ## Public API
19
+
20
+ **Types:**
21
+ - `TextPart`, `SurfacePart`, `AssistantMessagePart` — core part unions
22
+ - `ToolListPart`, `StatusPanelPart`, `ChartPart`, `TablePart` — surface part variants
23
+ - `PreviewData`, `PreviewArtifact`, `PreviewPart` — action preview surfaces
24
+ - `ConfirmationData`, `ConfirmationRiskLevel`, `ConfirmationPart` — confirmation surfaces
25
+ - `ExecutionResultData`, `ExecutionArtifactStatus`, `UndoResultData` — execution results
26
+ - `StreamPhase`, `MessageLifecycleEvent` — message lifecycle events
27
+ - `IntegrationKey`, `IntegrationProvider` — integration identifiers
28
+
29
+ **Runtime values:**
30
+ - `CONFIRMATION_LABELS` — exhaustive map of ExecutionKind to human-readable labels
31
+ - `getConfirmationLabel(kind, fallback)` — label lookup with fallback
32
+ - `WireSurfaceBuilder` — factory for creating wire-format data parts
33
+ - `createPartBuilder()`, `addText()`, `addSurface()`, `buildParts()` — functional part builder
34
+ - `isTextPart()`, `isSurfacePart()` — type guards
35
+
36
+ ## Dependencies
37
+
38
+ - `../execution/kinds` — ExecutionKind type for confirmation labels
39
+ - `../mcp/index` — MCPToolDescriptor for tool list parts
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * This prevents "post-surface commentary" features from sneaking in.
17
17
  *
18
- * @see DECISIONS.md ADR-2026-01-022 for design rationale
18
+ * @see decisions/ADR-CONT-026.md for design rationale
19
19
  */
20
20
 
21
21
  import type { AssistantMessagePart, TextPart, SurfacePart } from './types'
@@ -14,7 +14,7 @@
14
14
  * - At most one action is confirmable at a time
15
15
  * - Mismatched actionId fails closed and re-prompts
16
16
  *
17
- * @see DECISIONS.md for design rationale
17
+ * @see decisions/ADR-CONT-037.md for design rationale
18
18
  */
19
19
 
20
20
  import type { ExecutionKind } from '../execution/kinds'
@@ -24,7 +24,7 @@
24
24
  * INV-6: Undo race condition — 409 on expired window. Server rejects undo
25
25
  * attempts after availableUntil deadline.
26
26
  *
27
- * @see DECISIONS.md for design rationale
27
+ * @see decisions/ADR-CONT-037.md for design rationale
28
28
  */
29
29
 
30
30
  import type { PreviewArtifactKind } from './preview';
@@ -14,7 +14,7 @@
14
14
  * - User messages during preview do not affect mode; only assistant output controls persistence
15
15
  * - At most one preview proposal is active at a time (multiple previews are sequential, not concurrent)
16
16
  *
17
- * @see DECISIONS.md for design rationale
17
+ * @see decisions/ADR-CONT-026.md for design rationale
18
18
  */
19
19
 
20
20
  /**
@@ -10,7 +10,7 @@
10
10
  * - TextPart is the ONLY part type that may be streamed char-by-char
11
11
  * - Once a surface is added, the turn is in "surface phase" permanently
12
12
  *
13
- * @see DECISIONS.md ADR-2026-01-022 for design rationale
13
+ * @see decisions/ADR-CONT-026.md for design rationale
14
14
  */
15
15
 
16
16
  import type { ToolListMessagePart } from '../mcp/index'
@@ -7,7 +7,7 @@
7
7
  * Use these in backend code when writing to UIMessageStream.
8
8
  * Frontend normalizes wire format to semantic types.
9
9
  *
10
- * @see DECISIONS.md ADR-2026-01-022 for design rationale
10
+ * @see decisions/ADR-CONT-026.md for design rationale
11
11
  */
12
12
 
13
13
  import type { MCPToolDescriptor, ToolListDataPart } from '../mcp/index'
@@ -0,0 +1,174 @@
1
+ /**
2
+ * ResourceKey — shared vocabulary for resources across the system.
3
+ * All keys MUST include scope (orgId or userId) to prevent cross-org cache leaks.
4
+ * Naming: camelCase nouns. Collections plural, identities singular.
5
+ */
6
+ export type ResourceKey =
7
+ // Collections (plural) — lists of entities
8
+ | { type: 'members'; orgId: string }
9
+ | { type: 'departments'; orgId: string }
10
+ | { type: 'chats'; orgId: string }
11
+ | { type: 'teams'; orgId: string }
12
+ | { type: 'integrations'; orgId: string }
13
+ | { type: 'invites'; orgId: string }
14
+ | { type: 'auditEvents'; orgId: string }
15
+ // Identities (singular) — single entities
16
+ | { type: 'member'; orgId: string; memberId: string }
17
+ | { type: 'workspace'; orgId: string }
18
+ | { type: 'authSettings'; orgId: string }
19
+ | { type: 'billing'; orgId: string }
20
+ | { type: 'aiUsage'; orgId: string }
21
+ | { type: 'deletionEligibility'; orgId: string }
22
+ | { type: 'transferOwnership'; orgId: string }
23
+ | { type: 'goals'; orgId: string }
24
+ // User-scoped
25
+ | { type: 'dismissedBanners'; userId: string }
26
+ | { type: 'userOrgs'; userId: string }
27
+ | { type: 'sessions'; userId: string };
28
+
29
+ /**
30
+ * Action — structured mutation key used across execution system, audit, permissions.
31
+ */
32
+ export type Action = { resource: string; verb: string };
33
+
34
+ /**
35
+ * Resolve scope for cache partitioning. Handles impersonation.
36
+ */
37
+ export function resolveScope(context: {
38
+ orgId: string;
39
+ impersonatedOrgId?: string;
40
+ }): { orgId: string } {
41
+ return { orgId: context.impersonatedOrgId ?? context.orgId };
42
+ }
43
+
44
+ /** All ResourceKey type literals for exhaustive checking. */
45
+ const ORG_SCOPED_TYPES = [
46
+ 'members', 'departments', 'chats', 'teams', 'integrations', 'invites',
47
+ 'auditEvents', 'workspace', 'authSettings', 'billing', 'aiUsage',
48
+ 'deletionEligibility', 'transferOwnership', 'goals',
49
+ ] as const;
50
+
51
+ const USER_SCOPED_TYPES = ['dismissedBanners', 'userOrgs', 'sessions'] as const;
52
+
53
+ /**
54
+ * Canonical ResourceKey → query key conversion.
55
+ * This is the ONLY function that constructs query keys — no ad-hoc key
56
+ * construction anywhere in the codebase.
57
+ *
58
+ * Mapping is deterministic and stable:
59
+ * { type: 'members', orgId: 'abc' } → ['members', 'abc']
60
+ * { type: 'member', orgId: 'abc', memberId: '123' } → ['member', 'abc', '123']
61
+ */
62
+ export function toQueryKey(key: ResourceKey): readonly string[] {
63
+ switch (key.type) {
64
+ // Identity with extra field
65
+ case 'member':
66
+ return [key.type, key.orgId, key.memberId] as const;
67
+
68
+ // User-scoped
69
+ case 'dismissedBanners':
70
+ case 'userOrgs':
71
+ case 'sessions':
72
+ return [key.type, key.userId] as const;
73
+
74
+ // Org-scoped collections and identities
75
+ case 'members':
76
+ case 'departments':
77
+ case 'chats':
78
+ case 'teams':
79
+ case 'integrations':
80
+ case 'invites':
81
+ case 'auditEvents':
82
+ case 'workspace':
83
+ case 'authSettings':
84
+ case 'billing':
85
+ case 'aiUsage':
86
+ case 'deletionEligibility':
87
+ case 'transferOwnership':
88
+ case 'goals':
89
+ return [key.type, key.orgId] as const;
90
+
91
+ default: {
92
+ const _exhaustive: never = key;
93
+ throw new Error(`Unknown resource type: ${JSON.stringify(_exhaustive)}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Inverse of toQueryKey — reconstructs a ResourceKey from a query key array.
100
+ * Throws if the array cannot be parsed into a valid ResourceKey.
101
+ */
102
+ export function fromQueryKey(queryKey: readonly string[]): ResourceKey {
103
+ const [type, ...rest] = queryKey;
104
+
105
+ if (!type || rest.length === 0) {
106
+ throw new Error(`Invalid query key: expected at least [type, scope], got ${JSON.stringify(queryKey)}`);
107
+ }
108
+
109
+ // Identity with extra field
110
+ if (type === 'member') {
111
+ if (rest.length !== 2) {
112
+ throw new Error(`Invalid query key for 'member': expected [type, orgId, memberId], got ${JSON.stringify(queryKey)}`);
113
+ }
114
+ return { type: 'member', orgId: rest[0], memberId: rest[1] };
115
+ }
116
+
117
+ // User-scoped types
118
+ if ((USER_SCOPED_TYPES as readonly string[]).includes(type)) {
119
+ return { type, userId: rest[0] } as ResourceKey;
120
+ }
121
+
122
+ // Org-scoped types
123
+ if ((ORG_SCOPED_TYPES as readonly string[]).includes(type)) {
124
+ return { type, orgId: rest[0] } as ResourceKey;
125
+ }
126
+
127
+ throw new Error(`Unknown resource type in query key: '${type}'`);
128
+ }
129
+
130
+ /**
131
+ * Bidirectional resource relationships.
132
+ * TanStack does NOT support prefix matching across different key shapes.
133
+ * When invalidating a collection, we must also invalidate related identity entries (and vice versa).
134
+ */
135
+ export const resourceRelationships: Record<string, string[]> = {
136
+ members: ['member'],
137
+ member: ['members'],
138
+ departments: ['department'],
139
+ department: ['departments'],
140
+ chats: ['chat'],
141
+ chat: ['chats'],
142
+ teams: ['team'],
143
+ team: ['teams'],
144
+ };
145
+
146
+ /**
147
+ * Strict predicate for matching resource keys.
148
+ * Compares type + scope fields exactly. No partial matching. No loose comparisons.
149
+ * Used by invalidateResource with TanStack's predicate-based invalidation.
150
+ */
151
+ export function matchesResourceKey(queryKey: readonly unknown[], targetKey: ResourceKey): boolean {
152
+ if (!Array.isArray(queryKey) && !isReadonlyArray(queryKey)) return false;
153
+ const stringKey = queryKey.filter((v): v is string => typeof v === 'string');
154
+ if (stringKey.length !== queryKey.length) return false;
155
+
156
+ let parsed: ResourceKey;
157
+ try {
158
+ parsed = fromQueryKey(stringKey);
159
+ } catch {
160
+ return false;
161
+ }
162
+
163
+ if (parsed.type !== targetKey.type) return false;
164
+
165
+ if ('orgId' in parsed && 'orgId' in targetKey && parsed.orgId !== targetKey.orgId) return false;
166
+ if ('userId' in parsed && 'userId' in targetKey && parsed.userId !== targetKey.userId) return false;
167
+ if ('memberId' in parsed && 'memberId' in targetKey && parsed.memberId !== targetKey.memberId) return false;
168
+
169
+ return true;
170
+ }
171
+
172
+ function isReadonlyArray(value: unknown): value is readonly unknown[] {
173
+ return Array.isArray(value);
174
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Standard response wrapper for resource endpoints.
3
+ * version MUST come from data source (entity.updatedAt), NEVER from request time (Date.now()).
4
+ * Using request time defeats invalidation — race conditions reappear.
5
+ */
6
+ export type ResourceResponse<T> = {
7
+ data: T;
8
+ version: number;
9
+ };
@@ -0,0 +1,34 @@
1
+ # usage/
2
+
3
+ Type vocabulary for AI usage tracking, execution telemetry, and budget governance.
4
+
5
+ ## Purpose
6
+
7
+ Defines shared types for two usage tracking layers: (1) low-level AI usage events (token counts, costs per model/feature) and (2) higher-level runtime execution telemetry (per-request aggregates across tool loops). Also includes budget governance types for org-level spend controls.
8
+
9
+ ## Invariants
10
+
11
+ - All cost fields are `string` (decimal USD with 8 decimal places) — never floating point
12
+ - Types only — no runtime logic, no database access
13
+ - `AiFeature` enum must stay in sync with the backend's `ai_feature` pgEnum
14
+ - `ChatRuntimeProfile` dependency flows from `src/chat/` — usage types reference it, not the reverse
15
+
16
+ ## Public API
17
+
18
+ **AI Usage Types (`types.ts`):**
19
+ - `AiFeature` — Feature categories: `chat`, `chat_title`, `ingest_summarize`, `ingest_embedding`, `goal_extraction`
20
+ - `UsageSummary`, `DailyUsage`, `UsageByModel`, `UsageByFeature`, `UsageByUser` — Aggregation response shapes
21
+ - `UnifiedUsageResponse` and related interfaces — Combined usage dashboard response
22
+
23
+ **Execution Telemetry (`execution-types.ts`):**
24
+ - `RuntimeExecutionTelemetry` — Per-execution record (tokens, cost, duration, profile, success)
25
+ - `ExecutionFailureReason` — Failure categories: `tool_error`, `timeout`, `model_error`, `budget_exceeded`, `client_disconnect`, `unknown`
26
+ - `OrgExecutionSummary`, `DailyExecutionStats`, `ExecutionByUser`, `ExecutionDetail`, `ProfileStats` — Aggregation shapes
27
+
28
+ **Budget Governance (`execution-types.ts`):**
29
+ - `BudgetCheck` — Pre-execution budget gate result
30
+ - `OrgBudgetSettings` — Org budget configuration shape
31
+
32
+ ## Dependencies
33
+
34
+ - `src/chat/` — `ChatRuntimeProfile` type (used by execution telemetry)