@lucern/contracts 0.1.0-alpha.3 → 0.1.1-alpha.1

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 (145) hide show
  1. package/dist/index.d.ts +2003 -29
  2. package/dist/index.js +5627 -30
  3. package/package.json +15 -58
  4. package/src/agents/v1.ts +8 -0
  5. package/src/api-enums.contract.ts +183 -0
  6. package/{dist/auth-context.contract.js → src/auth-context.contract.ts} +1 -1
  7. package/{dist/auth-session.contract.js → src/auth-session.contract.ts} +1 -1
  8. package/src/auth.contract.ts +162 -0
  9. package/src/beliefs/v1.ts +8 -0
  10. package/src/context-pack.contract.ts +704 -0
  11. package/src/convex-admin.contract.ts +14 -0
  12. package/{dist/events-types.contract.js → src/events-types.contract.ts} +1 -1
  13. package/src/events.contract.ts +376 -0
  14. package/src/evidence/v1.ts +8 -0
  15. package/src/gateway.contract.ts +151 -0
  16. package/src/graph/v1.ts +8 -0
  17. package/src/ids.contract.ts +36 -0
  18. package/src/index.ts +30 -0
  19. package/src/lens-filter.contract.ts +183 -0
  20. package/src/lens-workflow.contract.ts +162 -0
  21. package/src/mcp-tools.contract.ts +3636 -0
  22. package/src/ontologies/v1.ts +8 -0
  23. package/{dist/ontology-matching.contract.js → src/ontology-matching.contract.ts} +1 -1
  24. package/src/prompt.contract.ts +50 -0
  25. package/src/questions/v1.ts +8 -0
  26. package/src/sdk-methods.contract.ts +522 -0
  27. package/src/sdk-tools.contract.ts +1545 -0
  28. package/src/text-matching.contract.ts +347 -0
  29. package/{dist/topic-scope.contract.js → src/topic-scope.contract.ts} +1 -1
  30. package/src/topics/v1.ts +8 -0
  31. package/{dist/v1/agents/v1.d.ts → src/v1/agents/v1.ts} +0 -1
  32. package/{dist/v1/beliefs/v1.d.ts → src/v1/beliefs/v1.ts} +0 -1
  33. package/{dist/v1/evidence/v1.d.ts → src/v1/evidence/v1.ts} +0 -1
  34. package/{dist/v1/graph/v1.d.ts → src/v1/graph/v1.ts} +0 -1
  35. package/src/v1/ontologies/v1.ts +276 -0
  36. package/{dist/v1/questions/v1.d.ts → src/v1/questions/v1.ts} +0 -1
  37. package/src/v1/topics/v1.ts +79 -0
  38. package/{dist/v1/worktrees/v1.d.ts → src/v1/worktrees/v1.ts} +0 -1
  39. package/src/workflow-runtime.contract.ts +440 -0
  40. package/src/worktrees/v1.ts +8 -0
  41. package/tsconfig.json +9 -0
  42. package/README.md +0 -43
  43. package/dist/api-enums.contract.d.ts +0 -59
  44. package/dist/api-enums.contract.d.ts.map +0 -1
  45. package/dist/api-enums.contract.js +0 -148
  46. package/dist/api-enums.contract.js.map +0 -1
  47. package/dist/auth-context.contract.d.ts +0 -9
  48. package/dist/auth-context.contract.d.ts.map +0 -1
  49. package/dist/auth-context.contract.js.map +0 -1
  50. package/dist/auth-session.contract.d.ts +0 -9
  51. package/dist/auth-session.contract.d.ts.map +0 -1
  52. package/dist/auth-session.contract.js.map +0 -1
  53. package/dist/auth.contract.d.ts +0 -89
  54. package/dist/auth.contract.d.ts.map +0 -1
  55. package/dist/auth.contract.js +0 -58
  56. package/dist/auth.contract.js.map +0 -1
  57. package/dist/context-pack.contract.d.ts +0 -495
  58. package/dist/context-pack.contract.d.ts.map +0 -1
  59. package/dist/context-pack.contract.js +0 -170
  60. package/dist/context-pack.contract.js.map +0 -1
  61. package/dist/convex-admin.contract.d.ts +0 -6
  62. package/dist/convex-admin.contract.d.ts.map +0 -1
  63. package/dist/convex-admin.contract.js +0 -2
  64. package/dist/convex-admin.contract.js.map +0 -1
  65. package/dist/events-types.contract.d.ts +0 -9
  66. package/dist/events-types.contract.d.ts.map +0 -1
  67. package/dist/events-types.contract.js.map +0 -1
  68. package/dist/events.contract.d.ts +0 -177
  69. package/dist/events.contract.d.ts.map +0 -1
  70. package/dist/events.contract.js +0 -160
  71. package/dist/events.contract.js.map +0 -1
  72. package/dist/gateway.contract.d.ts +0 -75
  73. package/dist/gateway.contract.d.ts.map +0 -1
  74. package/dist/gateway.contract.js +0 -20
  75. package/dist/gateway.contract.js.map +0 -1
  76. package/dist/ids.contract.d.ts +0 -8
  77. package/dist/ids.contract.d.ts.map +0 -1
  78. package/dist/ids.contract.js +0 -25
  79. package/dist/ids.contract.js.map +0 -1
  80. package/dist/index.d.ts.map +0 -1
  81. package/dist/index.js.map +0 -1
  82. package/dist/lens-filter.contract.d.ts +0 -71
  83. package/dist/lens-filter.contract.d.ts.map +0 -1
  84. package/dist/lens-filter.contract.js +0 -96
  85. package/dist/lens-filter.contract.js.map +0 -1
  86. package/dist/lens-workflow.contract.d.ts +0 -85
  87. package/dist/lens-workflow.contract.d.ts.map +0 -1
  88. package/dist/lens-workflow.contract.js +0 -55
  89. package/dist/lens-workflow.contract.js.map +0 -1
  90. package/dist/mcp-tools.contract.d.ts +0 -152
  91. package/dist/mcp-tools.contract.d.ts.map +0 -1
  92. package/dist/mcp-tools.contract.js +0 -3282
  93. package/dist/mcp-tools.contract.js.map +0 -1
  94. package/dist/ontology-matching.contract.d.ts +0 -9
  95. package/dist/ontology-matching.contract.d.ts.map +0 -1
  96. package/dist/ontology-matching.contract.js.map +0 -1
  97. package/dist/prompt.contract.d.ts +0 -25
  98. package/dist/prompt.contract.d.ts.map +0 -1
  99. package/dist/prompt.contract.js +0 -25
  100. package/dist/prompt.contract.js.map +0 -1
  101. package/dist/sdk-methods.contract.d.ts +0 -356
  102. package/dist/sdk-methods.contract.d.ts.map +0 -1
  103. package/dist/sdk-methods.contract.js +0 -17
  104. package/dist/sdk-methods.contract.js.map +0 -1
  105. package/dist/sdk-tools.contract.d.ts +0 -93
  106. package/dist/sdk-tools.contract.d.ts.map +0 -1
  107. package/dist/sdk-tools.contract.js +0 -1399
  108. package/dist/sdk-tools.contract.js.map +0 -1
  109. package/dist/text-matching.contract.d.ts +0 -54
  110. package/dist/text-matching.contract.d.ts.map +0 -1
  111. package/dist/text-matching.contract.js +0 -278
  112. package/dist/text-matching.contract.js.map +0 -1
  113. package/dist/topic-scope.contract.d.ts +0 -9
  114. package/dist/topic-scope.contract.d.ts.map +0 -1
  115. package/dist/topic-scope.contract.js.map +0 -1
  116. package/dist/v1/agents/v1.d.ts.map +0 -1
  117. package/dist/v1/agents/v1.js +0 -2
  118. package/dist/v1/agents/v1.js.map +0 -1
  119. package/dist/v1/beliefs/v1.d.ts.map +0 -1
  120. package/dist/v1/beliefs/v1.js +0 -2
  121. package/dist/v1/beliefs/v1.js.map +0 -1
  122. package/dist/v1/evidence/v1.d.ts.map +0 -1
  123. package/dist/v1/evidence/v1.js +0 -2
  124. package/dist/v1/evidence/v1.js.map +0 -1
  125. package/dist/v1/graph/v1.d.ts.map +0 -1
  126. package/dist/v1/graph/v1.js +0 -2
  127. package/dist/v1/graph/v1.js.map +0 -1
  128. package/dist/v1/ontologies/v1.d.ts +0 -77
  129. package/dist/v1/ontologies/v1.d.ts.map +0 -1
  130. package/dist/v1/ontologies/v1.js +0 -179
  131. package/dist/v1/ontologies/v1.js.map +0 -1
  132. package/dist/v1/questions/v1.d.ts.map +0 -1
  133. package/dist/v1/questions/v1.js +0 -2
  134. package/dist/v1/questions/v1.js.map +0 -1
  135. package/dist/v1/topics/v1.d.ts +0 -20
  136. package/dist/v1/topics/v1.d.ts.map +0 -1
  137. package/dist/v1/topics/v1.js +0 -62
  138. package/dist/v1/topics/v1.js.map +0 -1
  139. package/dist/v1/worktrees/v1.d.ts.map +0 -1
  140. package/dist/v1/worktrees/v1.js +0 -2
  141. package/dist/v1/worktrees/v1.js.map +0 -1
  142. package/dist/workflow-runtime.contract.d.ts +0 -162
  143. package/dist/workflow-runtime.contract.d.ts.map +0 -1
  144. package/dist/workflow-runtime.contract.js +0 -258
  145. package/dist/workflow-runtime.contract.js.map +0 -1
@@ -0,0 +1,14 @@
1
+ export interface ConvexAdminClient {
2
+ query<T = unknown>(
3
+ functionReference: unknown,
4
+ args: Record<string, unknown>
5
+ ): Promise<T>;
6
+ mutation<T = unknown>(
7
+ functionReference: unknown,
8
+ args: Record<string, unknown>
9
+ ): Promise<T>;
10
+ action<T = unknown>(
11
+ functionReference: unknown,
12
+ args: Record<string, unknown>
13
+ ): Promise<T>;
14
+ }
@@ -5,5 +5,5 @@
5
5
  * Retained here until the Lucern 1.0.0 barrel-sunset cut (D12).
6
6
  * New code should import from "@lucern/contracts" (barrel) or "./events.contract".
7
7
  */
8
+
8
9
  export * from "./events.contract";
9
- //# sourceMappingURL=events-types.contract.js.map
@@ -0,0 +1,376 @@
1
+ /**
2
+ * @lucern/contracts — events (canonical support contract)
3
+ *
4
+ * Consolidated flat support surface for Lucern's domain event stream:
5
+ * - Event type definitions, constants, and shared enums
6
+ * - Event construction + cursor helpers
7
+ *
8
+ * Consolidated from the former split type-definition module
9
+ * and the prior src/events.contract.ts (helpers) in EK-16 T1 PR 3b.
10
+ * Compat shim remains at the old events-types path until Lucern 1.0.0 (D12).
11
+ */
12
+
13
+ // =============================================================================
14
+ // EVENT TYPE DEFINITIONS & CONSTANTS
15
+ // (Formerly the split events type-definition module)
16
+ // =============================================================================
17
+
18
+ export const DOMAIN_EVENT_VERSION = "1.0" as const;
19
+ export const EVENT_RETENTION_DEFAULT_DAYS = 30;
20
+ export const WEBHOOK_MAX_ATTEMPTS = 5;
21
+ export const WEBHOOK_RETRY_DELAYS_MS = [1_000, 5_000, 30_000, 300_000] as const;
22
+
23
+ export const DOMAIN_EVENT_TYPES = [
24
+ "belief.created",
25
+ "belief.forked",
26
+ "belief.confidence_updated",
27
+ "belief.archived",
28
+ "belief.refined",
29
+ "belief.contract_created",
30
+ "belief.contract_evaluated",
31
+ "belief.lineage_queried",
32
+ "evidence.created",
33
+ "evidence.linked",
34
+ "evidence.search_executed",
35
+ "question.created",
36
+ "question.answered",
37
+ "question.refined",
38
+ "question.status_updated",
39
+ "question.archived",
40
+ "edge.created",
41
+ "contradiction.flagged",
42
+ "worktree.created",
43
+ "worktree.activated",
44
+ "worktree.merged",
45
+ "worktree.targets_updated",
46
+ "worktree.metadata_updated",
47
+ "topic.created",
48
+ "topic.updated",
49
+ "topic.archived",
50
+ "task.created",
51
+ "task.completed",
52
+ "task.updated",
53
+ "ontology.bound",
54
+ "context.compiled",
55
+ "identity.key_created",
56
+ "identity.key_rotated",
57
+ "identity.key_revoked",
58
+ "webhook.test",
59
+ ] as const;
60
+
61
+ export type DomainEventType = (typeof DOMAIN_EVENT_TYPES)[number];
62
+
63
+ export type DomainResourceType =
64
+ | "belief"
65
+ | "evidence"
66
+ | "question"
67
+ | "edge"
68
+ | "contradiction"
69
+ | "worktree"
70
+ | "topic"
71
+ | "task"
72
+ | "ontology"
73
+ | "context"
74
+ | "identity"
75
+ | "webhook";
76
+
77
+ export type DomainActorType = "human" | "agent" | "service";
78
+
79
+ export type DomainEventData = Record<string, unknown>;
80
+
81
+ export type DomainEvent = {
82
+ eventId: string;
83
+ type: DomainEventType | string;
84
+ version: typeof DOMAIN_EVENT_VERSION;
85
+ timestamp: number;
86
+ tenantId?: string;
87
+ workspaceId?: string;
88
+ topicId: string;
89
+ resourceId: string;
90
+ resourceType: DomainResourceType | string;
91
+ actorId: string;
92
+ actorType: DomainActorType;
93
+ data: DomainEventData;
94
+ correlationId?: string;
95
+ expiresAt: number;
96
+ };
97
+
98
+ export type CreateDomainEventInput = {
99
+ eventId?: string;
100
+ timestamp?: number;
101
+ tenantId?: string;
102
+ workspaceId?: string;
103
+ topicId: string;
104
+ type: DomainEventType | string;
105
+ resourceId: string;
106
+ resourceType: DomainResourceType | string;
107
+ actorId: string;
108
+ actorType: DomainActorType;
109
+ data?: DomainEventData;
110
+ correlationId?: string;
111
+ retentionDays?: number;
112
+ };
113
+
114
+ export type EventCursor = {
115
+ timestamp: number;
116
+ eventId: string;
117
+ };
118
+
119
+ export type EventListQuery = {
120
+ topicId?: string;
121
+ after?: string;
122
+ types?: string[];
123
+ startTime?: number;
124
+ endTime?: number;
125
+ limit?: number;
126
+ };
127
+
128
+ export type EventListResult = {
129
+ events: DomainEvent[];
130
+ nextCursor: string | null;
131
+ };
132
+
133
+ export type WebhookSecretMode = "configured";
134
+
135
+ export type WebhookRecord = {
136
+ id: string;
137
+ webhookId: string;
138
+ tenantId?: string;
139
+ workspaceId?: string;
140
+ topicId?: string;
141
+ url: string;
142
+ events: string[];
143
+ active: boolean;
144
+ secretConfigured: boolean;
145
+ createdAt: number;
146
+ updatedAt: number;
147
+ createdBy: string;
148
+ updatedBy: string;
149
+ lastDeliveryAt?: number;
150
+ lastSuccessfulDeliveryAt?: number;
151
+ };
152
+
153
+ export type WebhookCreateInput = {
154
+ url: string;
155
+ events: string[];
156
+ secret: string;
157
+ topicId?: string;
158
+ active?: boolean;
159
+ };
160
+
161
+ export type WebhookUpdateInput = {
162
+ url?: string;
163
+ events?: string[];
164
+ secret?: string;
165
+ topicId?: string | null;
166
+ active?: boolean;
167
+ };
168
+
169
+ export type WebhookDeliveryStatus =
170
+ | "pending"
171
+ | "delivering"
172
+ | "succeeded"
173
+ | "failed"
174
+ | "dead_letter";
175
+
176
+ export type WebhookDeliveryRecord = {
177
+ id: string;
178
+ deliveryId: string;
179
+ webhookId: string;
180
+ eventId: string;
181
+ eventType: string;
182
+ topicId: string;
183
+ status: WebhookDeliveryStatus;
184
+ attemptCount: number;
185
+ maxAttempts: number;
186
+ nextAttemptAt?: number;
187
+ lastAttemptAt?: number;
188
+ lastStatusCode?: number;
189
+ lastError?: string;
190
+ deliveredAt?: number;
191
+ createdAt: number;
192
+ updatedAt: number;
193
+ };
194
+
195
+ export type WebhookDeliveryAttemptRecord = {
196
+ id: string;
197
+ attemptId: string;
198
+ deliveryId: string;
199
+ webhookId: string;
200
+ eventId: string;
201
+ eventType: string;
202
+ attemptNumber: number;
203
+ status: "succeeded" | "failed";
204
+ statusCode?: number;
205
+ error?: string;
206
+ responseBody?: string;
207
+ durationMs?: number;
208
+ createdAt: number;
209
+ };
210
+
211
+ export type WebhookDeliveriesResult = {
212
+ deliveries: Array<
213
+ WebhookDeliveryRecord & {
214
+ attempts?: WebhookDeliveryAttemptRecord[];
215
+ }
216
+ >;
217
+ };
218
+
219
+ export type WebhookHealthResult = {
220
+ webhookId: string;
221
+ totalAttempts: number;
222
+ successfulAttempts: number;
223
+ failedAttempts: number;
224
+ successRate: number;
225
+ pendingDeliveries: number;
226
+ deadLetterDeliveries: number;
227
+ lastDeliveryAt?: number;
228
+ lastSuccessfulDeliveryAt?: number;
229
+ };
230
+
231
+ export type WebhookTestResult = {
232
+ webhookId: string;
233
+ deliveryId: string;
234
+ status: WebhookDeliveryStatus;
235
+ attempts: number;
236
+ };
237
+
238
+ export type ReplayEventsInput = EventListQuery & {
239
+ webhookId?: string;
240
+ };
241
+
242
+ export type ReplayEventsResult = EventListResult & {
243
+ replayedDeliveries?: number;
244
+ replayedWebhookId?: string;
245
+ };
246
+
247
+ // =============================================================================
248
+ // EVENT HELPERS
249
+ // (Formerly the body of src/events.contract.ts)
250
+ // =============================================================================
251
+
252
+ function toBase64(value: string): string {
253
+ if (typeof Buffer !== "undefined") {
254
+ return Buffer.from(value, "utf8").toString("base64url");
255
+ }
256
+ return btoa(unescape(encodeURIComponent(value)))
257
+ .replace(/\+/g, "-")
258
+ .replace(/\//g, "_")
259
+ .replace(/=+$/g, "");
260
+ }
261
+
262
+ function fromBase64(value: string): string {
263
+ if (typeof Buffer !== "undefined") {
264
+ return Buffer.from(value, "base64url").toString("utf8");
265
+ }
266
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
267
+ return decodeURIComponent(escape(atob(normalized)));
268
+ }
269
+
270
+ export function createEventId(): string {
271
+ const random = crypto.randomUUID().replace(/-/g, "");
272
+ return `evt_${random}`;
273
+ }
274
+
275
+ export function normalizeRetentionDays(value: number | undefined): number {
276
+ if (!Number.isFinite(value) || typeof value !== "number" || value <= 0) {
277
+ return EVENT_RETENTION_DEFAULT_DAYS;
278
+ }
279
+ return Math.max(1, Math.trunc(value));
280
+ }
281
+
282
+ export function buildDomainEvent(input: CreateDomainEventInput): DomainEvent {
283
+ const timestamp = input.timestamp ?? Date.now();
284
+ const retentionDays = normalizeRetentionDays(input.retentionDays);
285
+ return {
286
+ eventId: input.eventId?.trim() || createEventId(),
287
+ type: input.type.trim(),
288
+ version: DOMAIN_EVENT_VERSION,
289
+ timestamp,
290
+ tenantId: input.tenantId?.trim() || undefined,
291
+ workspaceId: input.workspaceId?.trim() || undefined,
292
+ topicId: input.topicId.trim(),
293
+ resourceId: input.resourceId.trim(),
294
+ resourceType: input.resourceType.trim(),
295
+ actorId: input.actorId.trim(),
296
+ actorType: input.actorType,
297
+ data: input.data ?? {},
298
+ correlationId: input.correlationId?.trim() || undefined,
299
+ expiresAt: timestamp + retentionDays * 24 * 60 * 60 * 1000,
300
+ };
301
+ }
302
+
303
+ export function compareEventCursor(left: EventCursor, right: EventCursor): number {
304
+ if (left.timestamp !== right.timestamp) {
305
+ return left.timestamp - right.timestamp;
306
+ }
307
+ return left.eventId.localeCompare(right.eventId);
308
+ }
309
+
310
+ export function encodeEventCursor(cursor: EventCursor): string {
311
+ return toBase64(JSON.stringify(cursor));
312
+ }
313
+
314
+ export function decodeEventCursor(cursor: string | undefined): EventCursor | null {
315
+ if (!cursor || cursor.trim().length === 0) {
316
+ return null;
317
+ }
318
+ try {
319
+ const parsed = JSON.parse(fromBase64(cursor.trim())) as Partial<EventCursor>;
320
+ if (
321
+ typeof parsed.timestamp !== "number" ||
322
+ !Number.isFinite(parsed.timestamp) ||
323
+ typeof parsed.eventId !== "string" ||
324
+ parsed.eventId.trim().length === 0
325
+ ) {
326
+ return null;
327
+ }
328
+ return {
329
+ timestamp: parsed.timestamp,
330
+ eventId: parsed.eventId.trim(),
331
+ };
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+
337
+ export function isAfterCursor(
338
+ event: Pick<DomainEvent, "timestamp" | "eventId">,
339
+ cursor: EventCursor | null
340
+ ): boolean {
341
+ if (!cursor) {
342
+ return true;
343
+ }
344
+ return compareEventCursor(event, cursor) > 0;
345
+ }
346
+
347
+ export function sortEventsByCursor<T extends Pick<DomainEvent, "timestamp" | "eventId">>(
348
+ events: readonly T[]
349
+ ): T[] {
350
+ return [...events].sort((left, right) => compareEventCursor(left, right));
351
+ }
352
+
353
+ export function inferActorType(args: {
354
+ sessionType?: "agent" | "user";
355
+ authMode?: string;
356
+ principalType?: string;
357
+ }): DomainActorType {
358
+ if (args.sessionType === "agent") {
359
+ return "agent";
360
+ }
361
+ if (
362
+ args.authMode === "service_principal" ||
363
+ args.authMode === "tenant_api_key" ||
364
+ args.principalType === "service"
365
+ ) {
366
+ return "service";
367
+ }
368
+ return "human";
369
+ }
370
+
371
+ export async function emitDomainEvent(
372
+ invokeMutation: (reference: any, args: CreateDomainEventInput) => Promise<unknown>,
373
+ input: CreateDomainEventInput
374
+ ): Promise<unknown> {
375
+ return await invokeMutation("events:recordEvent" as any, input);
376
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Evidence API Contract v1
3
+ *
4
+ * Defines the public surface for evidence operations:
5
+ * create, link, search, context-bound retrieval.
6
+ * Migrates from front-end/lucern/contracts/ during EK-16.
7
+ */
8
+ export {};
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Gateway contract types — shared between Stack's gateway middleware and
3
+ * Lucern's server-core / gateway route handlers.
4
+ *
5
+ * These types describe the authenticated request context that flows from
6
+ * the gateway into Lucern route handlers. The gateway (Stack-side) creates
7
+ * the context; Lucern consumes it read-only.
8
+ *
9
+ * @module @lucern/contracts/src/gateway
10
+ */
11
+
12
+ import type {
13
+ SessionAuthMode,
14
+ SessionDelegationHop,
15
+ SessionPrincipalType,
16
+ } from "./auth-session.contract";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Error codes
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export type PlatformApiErrorCode =
23
+ | "AUTH_REQUIRED"
24
+ | "AUTHENTICATION_REQUIRED"
25
+ | "AUTH_TOKEN_MISSING"
26
+ | "INVALID_REQUEST"
27
+ | "IDEMPOTENCY_KEY_REQUIRED"
28
+ | "FORBIDDEN"
29
+ | "SCOPE_INSUFFICIENT"
30
+ | "ENVIRONMENT_MISMATCH"
31
+ | "KEY_EXPIRED"
32
+ | "KEY_REVOKED"
33
+ | "RATE_LIMIT_EXCEEDED"
34
+ | "NOT_FOUND"
35
+ | "CONFLICT"
36
+ | "UPSTREAM_ERROR"
37
+ | "INTERNAL_ERROR";
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Gateway scope and environment
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export type GatewayScope = {
44
+ tenantId?: string;
45
+ workspaceId?: string;
46
+ };
47
+
48
+ export type GatewayEnvironment = "sandbox" | "production";
49
+
50
+ export type GatewayAuthMode =
51
+ | "interactive_user"
52
+ | "service_principal"
53
+ | "tenant_api_key"
54
+ | "session_token";
55
+
56
+ export type KeyLifecycleStatus =
57
+ | "active"
58
+ | "rotating"
59
+ | "rotated"
60
+ | "expired"
61
+ | "revoked";
62
+
63
+ export type CutoverDomain =
64
+ | "graph"
65
+ | "schema"
66
+ | "identity"
67
+ | "policy"
68
+ | "audit"
69
+ | "admin"
70
+ | "agent"
71
+ | "tool"
72
+ | "prompt"
73
+ | "intelligence";
74
+
75
+ export type CutoverFlagState = "legacy" | "cutover" | "disabled";
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Gateway auth context — the canonical authenticated request shape
79
+ // ---------------------------------------------------------------------------
80
+
81
+ /**
82
+ * Authenticated request context created by the gateway middleware.
83
+ * Lucern route handlers receive this as a read-only parameter.
84
+ *
85
+ * The `convex` field is typed as `unknown` in the contract because Lucern
86
+ * consumers should not use the gateway's Convex client directly — they
87
+ * have their own kernel client. The gateway (Stack-side) narrows this to
88
+ * `ConvexHttpClient` at the construction site.
89
+ */
90
+ export type GatewayAuthContext = {
91
+ userId: string;
92
+ convexToken?: string;
93
+ /** Opaque in contract — narrowed to ConvexHttpClient at the gateway. */
94
+ convex: any; // eslint-disable-line @typescript-eslint/no-explicit-any
95
+ authMode: GatewayAuthMode;
96
+ principalId?: string;
97
+ principalType?: SessionPrincipalType;
98
+ tenantId?: string;
99
+ workspaceId?: string;
100
+ roles?: string[];
101
+ sessionId?: string;
102
+ sessionAuthMode?: SessionAuthMode;
103
+ sessionExpiresAt?: number;
104
+ delegationChain?: SessionDelegationHop[];
105
+ servicePrincipalId?: string;
106
+ servicePrincipalKeyId?: string;
107
+ servicePrincipalTenantId?: string;
108
+ servicePrincipalWorkspaceId?: string;
109
+ requestEnvironment: GatewayEnvironment;
110
+ keyEnvironment?: GatewayEnvironment;
111
+ keyStatus: KeyLifecycleStatus | "unknown";
112
+ grantedScopes: Set<string>;
113
+ cutoverDomain: CutoverDomain;
114
+ cutoverState: CutoverFlagState;
115
+ };
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Gateway response helpers — portable (no Next.js dependency)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ export type GatewayErrorArgs = {
122
+ code: PlatformApiErrorCode;
123
+ message: string;
124
+ status: number;
125
+ correlationId: string;
126
+ policyTraceId?: string;
127
+ invariant?: string;
128
+ suggestion?: string;
129
+ details?: unknown;
130
+ headers?: HeadersInit;
131
+ };
132
+
133
+ export type GatewaySuccessArgs = {
134
+ status?: number;
135
+ correlationId: string;
136
+ policyTraceId?: string;
137
+ idempotentReplay?: boolean;
138
+ };
139
+
140
+ export function requireActorPrincipalId(
141
+ authContext: GatewayAuthContext
142
+ ): string {
143
+ const principalId =
144
+ typeof authContext.principalId === "string"
145
+ ? authContext.principalId.trim()
146
+ : "";
147
+ if (principalId.length > 0) {
148
+ return principalId;
149
+ }
150
+ throw new Error("Access denied: federated principal context required.");
151
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Graph Traversal API Contract v1
3
+ *
4
+ * Defines the public surface for graph operations:
5
+ * traverse, neighborhood, edges, contradictions, structural analysis.
6
+ * Migrates from front-end/lucern/contracts/ during EK-16.
7
+ */
8
+ export {};
@@ -0,0 +1,36 @@
1
+ export type DecodedPrefixedId = {
2
+ prefix: string;
3
+ value: string;
4
+ };
5
+
6
+ const PREFIXED_ID_PATTERN = /^([a-z][a-z0-9]*)_(.+)$/;
7
+
8
+ export function encodePrefixedId(prefix: string, value: string): string {
9
+ const normalizedPrefix = prefix.trim();
10
+ const normalizedValue = value.trim();
11
+
12
+ if (!normalizedPrefix || !normalizedValue) {
13
+ throw new Error("Both prefix and value are required to encode a prefixed ID.");
14
+ }
15
+
16
+ return `${normalizedPrefix}_${normalizedValue}`;
17
+ }
18
+
19
+ export function decodePrefixedId(id: string): DecodedPrefixedId {
20
+ const normalized = id.trim();
21
+ const match = PREFIXED_ID_PATTERN.exec(normalized);
22
+
23
+ if (!match) {
24
+ throw new Error(`Invalid prefixed ID: ${id}`);
25
+ }
26
+
27
+ return {
28
+ prefix: match[1],
29
+ value: match[2],
30
+ };
31
+ }
32
+
33
+ export function hasPrefixedIdPrefix(id: string, prefix: string): boolean {
34
+ const decoded = decodePrefixedId(id);
35
+ return decoded.prefix === prefix.trim();
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Lucern Platform Contracts
3
+ *
4
+ * Machine-readable source of truth for all external API surfaces.
5
+ * Enforcement hierarchy: MCP/API (co-equal) → SDK (derived) → Schema (internal).
6
+ *
7
+ * These contracts are consumed by:
8
+ * - MCP tool registration (learning-loop module)
9
+ * - API route validation (platform gateway)
10
+ * - SDK type generation (@lucern/sdk-* packages)
11
+ * - Contract lint gates (CI)
12
+ */
13
+
14
+ export * from "./api-enums.contract";
15
+ export * from "./auth-session.contract";
16
+ export * from "./auth-context.contract";
17
+ export * from "./convex-admin.contract";
18
+ export * from "./gateway.contract";
19
+ export * from "./context-pack.contract";
20
+ export * from "./events.contract";
21
+ export * from "./events-types.contract";
22
+ export * from "./ids.contract";
23
+ export * as mcpToolsContract from "./mcp-tools.contract";
24
+ export * from "./ontology-matching.contract";
25
+ export * from "./prompt.contract";
26
+ export * from "./sdk-methods.contract";
27
+ export * as sdkToolsContract from "./sdk-tools.contract";
28
+ export * from "./text-matching.contract";
29
+ export * from "./topic-scope.contract";
30
+ export * from "./workflow-runtime.contract";