@equilateral_ai/mindmeld 3.2.0 → 3.3.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 (68) hide show
  1. package/README.md +4 -4
  2. package/hooks/README.md +46 -4
  3. package/hooks/pre-compact.js +87 -1
  4. package/hooks/session-end.js +292 -0
  5. package/hooks/session-start.js +292 -23
  6. package/package.json +4 -2
  7. package/scripts/auth-login.js +53 -0
  8. package/scripts/init-project.js +69 -375
  9. package/src/core/AuthManager.js +498 -0
  10. package/src/core/CrossReferenceEngine.js +624 -0
  11. package/src/core/DeprecationScheduler.js +183 -0
  12. package/src/core/LLMPatternDetector.js +218 -0
  13. package/src/core/RapportOrchestrator.js +186 -0
  14. package/src/core/RelevanceDetector.js +32 -2
  15. package/src/core/StandardLifecycle.js +244 -0
  16. package/src/core/StandardsIngestion.js +341 -28
  17. package/src/core/parsers/adrParser.js +479 -0
  18. package/src/core/parsers/cursorRulesParser.js +564 -0
  19. package/src/core/parsers/eslintParser.js +439 -0
  20. package/src/handlers/alerts/alertsAcknowledge.js +4 -3
  21. package/src/handlers/analytics/activitySummaryGet.js +235 -0
  22. package/src/handlers/analytics/coachingGet.js +361 -0
  23. package/src/handlers/analytics/developerScoreGet.js +207 -0
  24. package/src/handlers/collaborators/collaboratorAdd.js +4 -5
  25. package/src/handlers/collaborators/collaboratorInvite.js +6 -5
  26. package/src/handlers/collaborators/collaboratorList.js +3 -3
  27. package/src/handlers/collaborators/collaboratorRemove.js +5 -4
  28. package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
  29. package/src/handlers/correlations/correlationsGet.js +1 -1
  30. package/src/handlers/correlations/correlationsProjectGet.js +7 -6
  31. package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
  32. package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
  33. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
  34. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
  35. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
  36. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
  37. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
  38. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
  39. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
  40. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
  41. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
  42. package/src/handlers/github/githubConnectionStatus.js +1 -1
  43. package/src/handlers/github/githubDiscoverPatterns.js +264 -5
  44. package/src/handlers/github/githubOAuthCallback.js +14 -2
  45. package/src/handlers/github/githubOAuthStart.js +1 -1
  46. package/src/handlers/github/githubPatternsReview.js +1 -1
  47. package/src/handlers/github/githubReposList.js +1 -1
  48. package/src/handlers/helpers/auditLogger.js +201 -0
  49. package/src/handlers/helpers/index.js +19 -1
  50. package/src/handlers/helpers/lambdaWrapper.js +1 -1
  51. package/src/handlers/notifications/sendNotification.js +1 -1
  52. package/src/handlers/projects/projectCreate.js +28 -1
  53. package/src/handlers/projects/projectDelete.js +3 -3
  54. package/src/handlers/projects/projectUpdate.js +4 -5
  55. package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
  56. package/src/handlers/scheduled/generateAlerts.js +1 -1
  57. package/src/handlers/standards/catalogGet.js +185 -0
  58. package/src/handlers/standards/catalogSync.js +120 -0
  59. package/src/handlers/standards/projectStandardsGet.js +135 -0
  60. package/src/handlers/standards/projectStandardsPut.js +131 -0
  61. package/src/handlers/standards/standardsAuditGet.js +65 -0
  62. package/src/handlers/standards/standardsParseUpload.js +153 -0
  63. package/src/handlers/standards/standardsRelevantPost.js +213 -0
  64. package/src/handlers/standards/standardsTransition.js +64 -0
  65. package/src/handlers/user/userSplashAck.js +91 -0
  66. package/src/handlers/user/userSplashGet.js +194 -0
  67. package/src/handlers/users/userProfilePut.js +77 -0
  68. package/src/index.js +75 -75
@@ -2,8 +2,9 @@
2
2
  * Get Developer Correlations Handler
3
3
  * Returns session-to-commit correlation data for a specific developer
4
4
  *
5
- * GET /api/correlations/developer/{email}
5
+ * GET /api/correlations/developer?email=xxx
6
6
  * Query params:
7
+ * - email (required)
7
8
  * - lookbackDays (optional, default: 30)
8
9
  *
9
10
  * Returns:
@@ -13,7 +14,7 @@
13
14
  */
14
15
 
15
16
  const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
- const { CorrelationAnalyzer } = require('../../core/CorrelationAnalyzer');
17
+ const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
17
18
 
18
19
  exports.handler = wrapHandler(async (event, context) => {
19
20
  // Extract user email from Cognito claims
@@ -22,8 +23,8 @@ exports.handler = wrapHandler(async (event, context) => {
22
23
  return createErrorResponse(401, 'Unauthorized - no email in claims');
23
24
  }
24
25
 
25
- // Get target developer email from path parameters
26
- const targetEmail = decodeURIComponent(event.pathParameters?.email || '');
26
+ // Get target developer email from query parameters
27
+ const targetEmail = decodeURIComponent(event.queryStringParameters?.email || '');
27
28
  if (!targetEmail) {
28
29
  return createErrorResponse(400, 'Developer email is required');
29
30
  }
@@ -121,12 +122,12 @@ async function getSessionHistory(email, lookbackDays, limit) {
121
122
  FROM rapport.session_correlations sc
122
123
  LEFT JOIN rapport.projects p ON sc.project_id = p.project_id
123
124
  WHERE sc.email_address = $1
124
- AND sc.session_started_at > NOW() - INTERVAL '${lookbackDays} days'
125
+ AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
125
126
  ORDER BY sc.session_started_at DESC
126
- LIMIT $2
127
+ LIMIT $3
127
128
  `;
128
129
 
129
- const result = await executeQuery(query, [email, limit]);
130
+ const result = await executeQuery(query, [email, lookbackDays, limit]);
130
131
 
131
132
  return result.rows.map(row => ({
132
133
  sessionId: row.session_id,
@@ -167,12 +168,12 @@ async function getWeeklyTrend(email, lookbackDays) {
167
168
  SUM(sc.session_duration_seconds) / 3600.0 as session_hours
168
169
  FROM rapport.session_correlations sc
169
170
  WHERE sc.email_address = $1
170
- AND sc.session_started_at > NOW() - INTERVAL '${lookbackDays} days'
171
+ AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
171
172
  GROUP BY DATE_TRUNC('week', sc.session_started_at)
172
173
  ORDER BY week DESC
173
174
  `;
174
175
 
175
- const result = await executeQuery(query, [email]);
176
+ const result = await executeQuery(query, [email, lookbackDays]);
176
177
 
177
178
  return result.rows.map(row => ({
178
179
  week: row.week,
@@ -206,14 +207,14 @@ async function getPatternUsage(email, lookbackDays) {
206
207
  JOIN rapport.patterns p ON pu.pattern_id = p.pattern_id
207
208
  LEFT JOIN rapport.session_correlations sc ON pu.session_id = sc.session_id
208
209
  WHERE pu.email_address = $1
209
- AND pu.used_at > NOW() - INTERVAL '${lookbackDays} days'
210
+ AND pu.used_at > NOW() - $2 * INTERVAL '1 day'
210
211
  GROUP BY p.pattern_id, p.intent, p.maturity
211
212
  HAVING COUNT(DISTINCT pu.session_id) >= 2
212
213
  ORDER BY sessions_used DESC
213
214
  LIMIT 10
214
215
  `;
215
216
 
216
- const result = await executeQuery(query, [email]);
217
+ const result = await executeQuery(query, [email, lookbackDays]);
217
218
 
218
219
  return result.rows.map(row => ({
219
220
  patternId: row.pattern_id,
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
- const { CorrelationAnalyzer } = require('../../core/CorrelationAnalyzer');
16
+ const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
17
17
 
18
18
  exports.handler = wrapHandler(async (event, context) => {
19
19
  // Extract user email from Cognito claims
@@ -2,8 +2,9 @@
2
2
  * Get Project Correlations Handler
3
3
  * Returns session-to-commit correlation data for a specific project
4
4
  *
5
- * GET /api/correlations/project/{projectId}
5
+ * GET /api/correlations/project?project_id=xxx
6
6
  * Query params:
7
+ * - project_id (required)
7
8
  * - lookbackDays (optional, default: 30)
8
9
  *
9
10
  * Returns:
@@ -13,7 +14,7 @@
13
14
  */
14
15
 
15
16
  const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
- const { CorrelationAnalyzer } = require('../../core/CorrelationAnalyzer');
17
+ const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
17
18
 
18
19
  exports.handler = wrapHandler(async (event, context) => {
19
20
  // Extract user email from Cognito claims
@@ -22,8 +23,8 @@ exports.handler = wrapHandler(async (event, context) => {
22
23
  return createErrorResponse(401, 'Unauthorized - no email in claims');
23
24
  }
24
25
 
25
- // Get project ID from path parameters
26
- const projectId = event.pathParameters?.projectId;
26
+ // Get project ID from query parameters
27
+ const projectId = event.queryStringParameters?.project_id;
27
28
  if (!projectId) {
28
29
  return createErrorResponse(400, 'Project ID is required');
29
30
  }
@@ -98,12 +99,12 @@ async function getDeveloperBreakdown(projectId, lookbackDays) {
98
99
  FROM rapport.session_correlations sc
99
100
  JOIN "Users" u ON sc.email_address = u."Email_Address"
100
101
  WHERE sc.project_id = $1
101
- AND sc.session_started_at > NOW() - INTERVAL '${lookbackDays} days'
102
+ AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
102
103
  GROUP BY sc.email_address, u."User_Display_Name"
103
104
  ORDER BY total_commits DESC NULLS LAST
104
105
  `;
105
106
 
106
- const result = await executeQuery(query, [projectId]);
107
+ const result = await executeQuery(query, [projectId, lookbackDays]);
107
108
 
108
109
  return result.rows.map(row => ({
109
110
  email: row.email_address,
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Enterprise Audit Log Get Handler
3
+ *
4
+ * GET /api/enterprise/audit?page=&pageSize=&action=&resourceType=&startDate=&endDate=
5
+ * Returns: { Records: AuditLogEntry[] } + meta { Total_Records, Page, Page_Size, Request_ID, Timestamp }
6
+ * Auth: Cognito JWT required, enterprise tier only
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
10
+
11
+ async function getEnterpriseAudit({ queryStringParameters, requestContext }) {
12
+ try {
13
+ const Request_ID = requestContext.requestId;
14
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
15
+ if (!email) {
16
+ return createErrorResponse(401, 'Authentication required');
17
+ }
18
+
19
+ // Look up client_id and verify enterprise tier
20
+ const userResult = await executeQuery(
21
+ `SELECT u.client_id, c.subscription_tier
22
+ FROM rapport.users u
23
+ JOIN rapport.clients c ON c.client_id = u.client_id
24
+ WHERE u.email_address = $1 LIMIT 1`,
25
+ [email]
26
+ );
27
+ if (!userResult.rows.length) {
28
+ return createErrorResponse(403, 'User not found');
29
+ }
30
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
31
+ return createErrorResponse(403, 'Enterprise subscription required');
32
+ }
33
+ const clientId = userResult.rows[0].client_id;
34
+
35
+ const params = queryStringParameters || {};
36
+ const page = Math.max(1, parseInt(params.page) || 1);
37
+ const pageSize = Math.min(100, Math.max(1, parseInt(params.pageSize) || 20));
38
+ const offset = (page - 1) * pageSize;
39
+
40
+ // Build WHERE clause
41
+ const conditions = ['client_id = $1'];
42
+ const values = [clientId];
43
+ let paramIndex = 2;
44
+
45
+ if (params.action) {
46
+ conditions.push(`event_type = $${paramIndex}`);
47
+ values.push(params.action);
48
+ paramIndex++;
49
+ }
50
+
51
+ if (params.resourceType) {
52
+ conditions.push(`entity_type = $${paramIndex}`);
53
+ values.push(params.resourceType);
54
+ paramIndex++;
55
+ }
56
+
57
+ if (params.startDate) {
58
+ conditions.push(`created_at >= $${paramIndex}`);
59
+ values.push(params.startDate);
60
+ paramIndex++;
61
+ }
62
+
63
+ if (params.endDate) {
64
+ conditions.push(`created_at <= $${paramIndex}`);
65
+ values.push(params.endDate);
66
+ paramIndex++;
67
+ }
68
+
69
+ const whereClause = conditions.join(' AND ');
70
+
71
+ // Get total count
72
+ const countResult = await executeQuery(
73
+ `SELECT COUNT(*) as total FROM rapport.unified_audit_log WHERE ${whereClause}`,
74
+ values
75
+ );
76
+ const total = parseInt(countResult.rows[0].total);
77
+
78
+ // Get paginated records, mapping columns to frontend AuditLogEntry type
79
+ const dataResult = await executeQuery(
80
+ `SELECT
81
+ audit_id,
82
+ client_id,
83
+ actor_email,
84
+ event_type as action,
85
+ entity_type as resource_type,
86
+ entity_id as resource_id,
87
+ before_state as old_value,
88
+ after_state as new_value,
89
+ created_at
90
+ FROM rapport.unified_audit_log
91
+ WHERE ${whereClause}
92
+ ORDER BY created_at DESC
93
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
94
+ [...values, pageSize, offset]
95
+ );
96
+
97
+ return createSuccessResponse(
98
+ { Records: dataResult.rows },
99
+ 'Audit log retrieved',
100
+ { Total_Records: total, Page: page, Page_Size: pageSize, Request_ID, Timestamp: new Date().toISOString() }
101
+ );
102
+
103
+ } catch (error) {
104
+ return handleError(error);
105
+ }
106
+ }
107
+
108
+ exports.handler = wrapHandler(getEnterpriseAudit);
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Enterprise Contributors Get Handler
3
+ *
4
+ * GET /api/enterprise/contributors?period=
5
+ * Returns: { Records: [contributor stats] } + meta
6
+ * Auth: Cognito JWT required, enterprise tier only
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
10
+
11
+ async function getEnterpriseContributors({ queryStringParameters, requestContext }) {
12
+ try {
13
+ const Request_ID = requestContext.requestId;
14
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
15
+ if (!email) {
16
+ return createErrorResponse(401, 'Authentication required');
17
+ }
18
+
19
+ const userResult = await executeQuery(
20
+ `SELECT u.client_id, c.subscription_tier
21
+ FROM rapport.users u
22
+ JOIN rapport.clients c ON c.client_id = u.client_id
23
+ WHERE u.email_address = $1 LIMIT 1`,
24
+ [email]
25
+ );
26
+ if (!userResult.rows.length) {
27
+ return createErrorResponse(403, 'User not found');
28
+ }
29
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
30
+ return createErrorResponse(403, 'Enterprise subscription required');
31
+ }
32
+ const clientId = userResult.rows[0].client_id;
33
+
34
+ const params = queryStringParameters || {};
35
+ const period = params.period || '30d';
36
+
37
+ // Calculate date filter
38
+ let intervalDays;
39
+ switch (period) {
40
+ case '7d': intervalDays = 7; break;
41
+ case '90d': intervalDays = 90; break;
42
+ case '30d':
43
+ default: intervalDays = 30; break;
44
+ }
45
+
46
+ // Aggregate contributions across audit trail and knowledge curation
47
+ const result = await executeQuery(
48
+ `WITH audit_activity AS (
49
+ SELECT
50
+ actor_email as contributor_email,
51
+ COUNT(*) FILTER (WHERE event_type = 'DATA_CREATE' AND entity_type = 'STANDARD') as discoveries,
52
+ COUNT(*) FILTER (WHERE event_type = 'DATA_UPDATE' AND entity_type = 'STANDARD') as refinements,
53
+ COUNT(*) FILTER (WHERE event_type = 'DATA_UPDATE' AND entity_type = 'STANDARD'
54
+ AND (after_state->>'status') = 'approved') as verifications,
55
+ COUNT(*) FILTER (WHERE event_type = 'DATA_CREATE' AND entity_type = 'KNOWLEDGE') as curations
56
+ FROM rapport.unified_audit_log
57
+ WHERE client_id = $1
58
+ AND created_at >= NOW() - ($2 || ' days')::interval
59
+ AND actor_email IS NOT NULL
60
+ GROUP BY actor_email
61
+ )
62
+ SELECT
63
+ contributor_email,
64
+ COALESCE(discoveries, 0) as discoveries,
65
+ COALESCE(refinements, 0) as refinements,
66
+ COALESCE(verifications, 0) as verifications,
67
+ COALESCE(curations, 0) as curations,
68
+ COALESCE(discoveries * 3 + refinements * 2 + verifications * 2 + curations * 4, 0) as total_weight
69
+ FROM audit_activity
70
+ ORDER BY total_weight DESC`,
71
+ [clientId, String(intervalDays)]
72
+ );
73
+
74
+ return createSuccessResponse(
75
+ { Records: result.rows },
76
+ 'Contributors retrieved',
77
+ { Total_Records: result.rowCount, Request_ID, Timestamp: new Date().toISOString() }
78
+ );
79
+
80
+ } catch (error) {
81
+ return handleError(error);
82
+ }
83
+ }
84
+
85
+ exports.handler = wrapHandler(getEnterpriseContributors);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Enterprise Knowledge Categories Get Handler
3
+ *
4
+ * GET /api/enterprise/knowledge/categories
5
+ * Returns: { Records: [{ category_id, name, description }] } + meta
6
+ * Auth: Cognito JWT required, enterprise tier only
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
10
+
11
+ async function getEnterpriseKnowledgeCategories({ requestContext }) {
12
+ try {
13
+ const Request_ID = requestContext.requestId;
14
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
15
+ if (!email) {
16
+ return createErrorResponse(401, 'Authentication required');
17
+ }
18
+
19
+ const userResult = await executeQuery(
20
+ `SELECT u.client_id, c.subscription_tier
21
+ FROM rapport.users u
22
+ JOIN rapport.clients c ON c.client_id = u.client_id
23
+ WHERE u.email_address = $1 LIMIT 1`,
24
+ [email]
25
+ );
26
+ if (!userResult.rows.length) {
27
+ return createErrorResponse(403, 'User not found');
28
+ }
29
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
30
+ return createErrorResponse(403, 'Enterprise subscription required');
31
+ }
32
+ const clientId = userResult.rows[0].client_id;
33
+
34
+ const result = await executeQuery(
35
+ `SELECT category_id, name, description
36
+ FROM rapport.knowledge_categories
37
+ WHERE client_id = $1
38
+ ORDER BY name`,
39
+ [clientId]
40
+ );
41
+
42
+ return createSuccessResponse(
43
+ { Records: result.rows },
44
+ 'Categories retrieved',
45
+ { Total_Records: result.rowCount, Request_ID, Timestamp: new Date().toISOString() }
46
+ );
47
+
48
+ } catch (error) {
49
+ return handleError(error);
50
+ }
51
+ }
52
+
53
+ exports.handler = wrapHandler(getEnterpriseKnowledgeCategories);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Enterprise Knowledge Create Handler
3
+ *
4
+ * POST /api/enterprise/knowledge
5
+ * Body: { title, content, category?, tags?, invariant_id? }
6
+ * Returns: { Records: [CuratedKnowledge] } + meta
7
+ * Auth: Cognito JWT required, enterprise tier only
8
+ */
9
+
10
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, logModificationEvent, AuditEventType, EntityType } = require('./helpers');
11
+
12
+ async function createEnterpriseKnowledge({ body, requestContext }) {
13
+ try {
14
+ const Request_ID = requestContext.requestId;
15
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
16
+ if (!email) {
17
+ return createErrorResponse(401, 'Authentication required');
18
+ }
19
+
20
+ const userResult = await executeQuery(
21
+ `SELECT u.client_id, c.subscription_tier
22
+ FROM rapport.users u
23
+ JOIN rapport.clients c ON c.client_id = u.client_id
24
+ WHERE u.email_address = $1 LIMIT 1`,
25
+ [email]
26
+ );
27
+ if (!userResult.rows.length) {
28
+ return createErrorResponse(403, 'User not found');
29
+ }
30
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
31
+ return createErrorResponse(403, 'Enterprise subscription required');
32
+ }
33
+ const clientId = userResult.rows[0].client_id;
34
+
35
+ const requestBody = body || {};
36
+ const { title, content, category, tags, invariant_id } = requestBody;
37
+
38
+ if (!title || !content) {
39
+ return createErrorResponse(400, 'title and content are required');
40
+ }
41
+
42
+ const result = await executeQuery(
43
+ `INSERT INTO rapport.curated_knowledge
44
+ (client_id, invariant_id, title, content, category, tags, curator_email, status)
45
+ VALUES ($1, $2, $3, $4, $5, $6, $7, 'draft')
46
+ RETURNING *`,
47
+ [
48
+ clientId,
49
+ invariant_id || null,
50
+ title,
51
+ content,
52
+ category || null,
53
+ tags || [],
54
+ email,
55
+ ]
56
+ );
57
+
58
+ const created = result.rows[0];
59
+
60
+ await logModificationEvent(AuditEventType.DATA_CREATE, EntityType.KNOWLEDGE, String(created.knowledge_id), {
61
+ requestContext,
62
+ client_id: clientId,
63
+ after_state: { title, category, status: 'draft' },
64
+ });
65
+
66
+ return createSuccessResponse(
67
+ { Records: [created] },
68
+ 'Knowledge item created',
69
+ { Total_Records: 1, Request_ID, Timestamp: new Date().toISOString() }
70
+ );
71
+
72
+ } catch (error) {
73
+ return handleError(error);
74
+ }
75
+ }
76
+
77
+ exports.handler = wrapHandler(createEnterpriseKnowledge);
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Enterprise Knowledge Delete Handler
3
+ *
4
+ * DELETE /api/enterprise/knowledge?knowledge_id=xxx
5
+ * Returns: { Records: [] } + meta
6
+ * Auth: Cognito JWT required, enterprise tier only
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, logModificationEvent, AuditEventType, EntityType } = require('./helpers');
10
+
11
+ async function deleteEnterpriseKnowledge({ queryStringParameters, requestContext }) {
12
+ try {
13
+ const Request_ID = requestContext.requestId;
14
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
15
+ if (!email) {
16
+ return createErrorResponse(401, 'Authentication required');
17
+ }
18
+
19
+ const userResult = await executeQuery(
20
+ `SELECT u.client_id, c.subscription_tier
21
+ FROM rapport.users u
22
+ JOIN rapport.clients c ON c.client_id = u.client_id
23
+ WHERE u.email_address = $1 LIMIT 1`,
24
+ [email]
25
+ );
26
+ if (!userResult.rows.length) {
27
+ return createErrorResponse(403, 'User not found');
28
+ }
29
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
30
+ return createErrorResponse(403, 'Enterprise subscription required');
31
+ }
32
+ const clientId = userResult.rows[0].client_id;
33
+
34
+ const knowledgeId = (queryStringParameters || {}).knowledge_id;
35
+ if (!knowledgeId) {
36
+ return createErrorResponse(400, 'knowledge_id is required');
37
+ }
38
+
39
+ // Fetch current state for audit trail
40
+ const current = await executeQuery(
41
+ 'SELECT * FROM rapport.curated_knowledge WHERE knowledge_id = $1 AND client_id = $2',
42
+ [knowledgeId, clientId]
43
+ );
44
+ if (!current.rows.length) {
45
+ return createErrorResponse(404, 'Knowledge item not found');
46
+ }
47
+ const beforeState = current.rows[0];
48
+
49
+ await executeQuery(
50
+ 'DELETE FROM rapport.curated_knowledge WHERE knowledge_id = $1 AND client_id = $2',
51
+ [knowledgeId, clientId]
52
+ );
53
+
54
+ await logModificationEvent(AuditEventType.DATA_DELETE, EntityType.KNOWLEDGE, String(knowledgeId), {
55
+ requestContext,
56
+ client_id: clientId,
57
+ before_state: { title: beforeState.title, status: beforeState.status },
58
+ });
59
+
60
+ return createSuccessResponse(
61
+ { Records: [] },
62
+ 'Knowledge item deleted',
63
+ { Total_Records: 0, Request_ID, Timestamp: new Date().toISOString() }
64
+ );
65
+
66
+ } catch (error) {
67
+ return handleError(error);
68
+ }
69
+ }
70
+
71
+ exports.handler = wrapHandler(deleteEnterpriseKnowledge);
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Enterprise Knowledge Get Handler
3
+ *
4
+ * GET /api/enterprise/knowledge?page=&pageSize=&status=&category=
5
+ * Returns: { Records: CuratedKnowledge[] } + meta { Total_Records, Page, Page_Size, Request_ID, Timestamp }
6
+ * Auth: Cognito JWT required, enterprise tier only
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
10
+
11
+ async function getEnterpriseKnowledge({ queryStringParameters, requestContext }) {
12
+ try {
13
+ const Request_ID = requestContext.requestId;
14
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
15
+ if (!email) {
16
+ return createErrorResponse(401, 'Authentication required');
17
+ }
18
+
19
+ const userResult = await executeQuery(
20
+ `SELECT u.client_id, c.subscription_tier
21
+ FROM rapport.users u
22
+ JOIN rapport.clients c ON c.client_id = u.client_id
23
+ WHERE u.email_address = $1 LIMIT 1`,
24
+ [email]
25
+ );
26
+ if (!userResult.rows.length) {
27
+ return createErrorResponse(403, 'User not found');
28
+ }
29
+ if (userResult.rows[0].subscription_tier !== 'enterprise') {
30
+ return createErrorResponse(403, 'Enterprise subscription required');
31
+ }
32
+ const clientId = userResult.rows[0].client_id;
33
+
34
+ const params = queryStringParameters || {};
35
+ const page = Math.max(1, parseInt(params.page) || 1);
36
+ const pageSize = Math.min(100, Math.max(1, parseInt(params.pageSize) || 20));
37
+ const offset = (page - 1) * pageSize;
38
+
39
+ const conditions = ['client_id = $1'];
40
+ const values = [clientId];
41
+ let paramIndex = 2;
42
+
43
+ if (params.status) {
44
+ conditions.push(`status = $${paramIndex}`);
45
+ values.push(params.status);
46
+ paramIndex++;
47
+ }
48
+
49
+ if (params.category) {
50
+ conditions.push(`category = $${paramIndex}`);
51
+ values.push(params.category);
52
+ paramIndex++;
53
+ }
54
+
55
+ const whereClause = conditions.join(' AND ');
56
+
57
+ const countResult = await executeQuery(
58
+ `SELECT COUNT(*) as total FROM rapport.curated_knowledge WHERE ${whereClause}`,
59
+ values
60
+ );
61
+ const total = parseInt(countResult.rows[0].total);
62
+
63
+ const dataResult = await executeQuery(
64
+ `SELECT
65
+ knowledge_id, client_id, invariant_id,
66
+ title, content, category, tags,
67
+ curator_email, status,
68
+ created_at, published_at
69
+ FROM rapport.curated_knowledge
70
+ WHERE ${whereClause}
71
+ ORDER BY created_at DESC
72
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
73
+ [...values, pageSize, offset]
74
+ );
75
+
76
+ return createSuccessResponse(
77
+ { Records: dataResult.rows },
78
+ 'Knowledge items retrieved',
79
+ { Total_Records: total, Page: page, Page_Size: pageSize, Request_ID, Timestamp: new Date().toISOString() }
80
+ );
81
+
82
+ } catch (error) {
83
+ return handleError(error);
84
+ }
85
+ }
86
+
87
+ exports.handler = wrapHandler(getEnterpriseKnowledge);