@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.
- package/README.md +4 -4
- package/hooks/README.md +46 -4
- package/hooks/pre-compact.js +87 -1
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +292 -23
- package/package.json +4 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/init-project.js +69 -375
- package/src/core/AuthManager.js +498 -0
- package/src/core/CrossReferenceEngine.js +624 -0
- package/src/core/DeprecationScheduler.js +183 -0
- package/src/core/LLMPatternDetector.js +218 -0
- package/src/core/RapportOrchestrator.js +186 -0
- package/src/core/RelevanceDetector.js +32 -2
- package/src/core/StandardLifecycle.js +244 -0
- package/src/core/StandardsIngestion.js +341 -28
- package/src/core/parsers/adrParser.js +479 -0
- package/src/core/parsers/cursorRulesParser.js +564 -0
- package/src/core/parsers/eslintParser.js +439 -0
- package/src/handlers/alerts/alertsAcknowledge.js +4 -3
- package/src/handlers/analytics/activitySummaryGet.js +235 -0
- package/src/handlers/analytics/coachingGet.js +361 -0
- package/src/handlers/analytics/developerScoreGet.js +207 -0
- package/src/handlers/collaborators/collaboratorAdd.js +4 -5
- package/src/handlers/collaborators/collaboratorInvite.js +6 -5
- package/src/handlers/collaborators/collaboratorList.js +3 -3
- package/src/handlers/collaborators/collaboratorRemove.js +5 -4
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
- package/src/handlers/correlations/correlationsGet.js +1 -1
- package/src/handlers/correlations/correlationsProjectGet.js +7 -6
- package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
- package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +264 -5
- package/src/handlers/github/githubOAuthCallback.js +14 -2
- package/src/handlers/github/githubOAuthStart.js +1 -1
- package/src/handlers/github/githubPatternsReview.js +1 -1
- package/src/handlers/github/githubReposList.js +1 -1
- package/src/handlers/helpers/auditLogger.js +201 -0
- package/src/handlers/helpers/index.js +19 -1
- package/src/handlers/helpers/lambdaWrapper.js +1 -1
- package/src/handlers/notifications/sendNotification.js +1 -1
- package/src/handlers/projects/projectCreate.js +28 -1
- package/src/handlers/projects/projectDelete.js +3 -3
- package/src/handlers/projects/projectUpdate.js +4 -5
- package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
- package/src/handlers/scheduled/generateAlerts.js +1 -1
- package/src/handlers/standards/catalogGet.js +185 -0
- package/src/handlers/standards/catalogSync.js +120 -0
- package/src/handlers/standards/projectStandardsGet.js +135 -0
- package/src/handlers/standards/projectStandardsPut.js +131 -0
- package/src/handlers/standards/standardsAuditGet.js +65 -0
- package/src/handlers/standards/standardsParseUpload.js +153 -0
- package/src/handlers/standards/standardsRelevantPost.js +213 -0
- package/src/handlers/standards/standardsTransition.js +64 -0
- package/src/handlers/user/userSplashAck.js +91 -0
- package/src/handlers/user/userSplashGet.js +194 -0
- package/src/handlers/users/userProfilePut.js +77 -0
- package/src/index.js +75 -75
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Audit Get Handler
|
|
3
|
+
* Returns the audit trail for a standard's lifecycle transitions
|
|
4
|
+
*
|
|
5
|
+
* GET /api/standards/audit?standard_id=xxx
|
|
6
|
+
* Query params: standard_id (required), cursor (string), limit (number, default 50, max 200)
|
|
7
|
+
* Returns: { audit_trail, total_transitions, current_state }
|
|
8
|
+
* Auth: Cognito JWT required
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { wrapHandler, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
12
|
+
const { StandardLifecycle } = require('./core/StandardLifecycle');
|
|
13
|
+
|
|
14
|
+
const lifecycle = new StandardLifecycle();
|
|
15
|
+
|
|
16
|
+
async function getStandardsAudit({ queryStringParameters, requestContext }) {
|
|
17
|
+
try {
|
|
18
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
19
|
+
|
|
20
|
+
if (!email) {
|
|
21
|
+
return createErrorResponse(401, 'Authentication required');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const id = (queryStringParameters || {}).standard_id;
|
|
25
|
+
|
|
26
|
+
if (!id) {
|
|
27
|
+
return createErrorResponse(400, 'standard_id is required');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Verify the standard exists
|
|
31
|
+
const currentState = await lifecycle.getCurrentState(id);
|
|
32
|
+
|
|
33
|
+
if (!currentState) {
|
|
34
|
+
return createErrorResponse(404, 'Standard not found', { standard_id: id });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get valid transitions from the current state
|
|
38
|
+
const validTransitions = lifecycle.getValidTransitions(currentState);
|
|
39
|
+
|
|
40
|
+
// Get audit history with pagination
|
|
41
|
+
const cursor = queryStringParameters?.cursor || null;
|
|
42
|
+
const limit = queryStringParameters?.limit ? parseInt(queryStringParameters.limit) : 50;
|
|
43
|
+
|
|
44
|
+
const history = await lifecycle.getHistory(id, { cursor, limit });
|
|
45
|
+
|
|
46
|
+
return createSuccessResponse({
|
|
47
|
+
standard_id: id,
|
|
48
|
+
current_state: currentState,
|
|
49
|
+
valid_transitions: validTransitions,
|
|
50
|
+
audit_trail: history.entries,
|
|
51
|
+
total_transitions: history.total_transitions,
|
|
52
|
+
pagination: {
|
|
53
|
+
has_more: history.has_more,
|
|
54
|
+
next_cursor: history.next_cursor,
|
|
55
|
+
limit: limit
|
|
56
|
+
}
|
|
57
|
+
}, 'Audit trail retrieved');
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Standards Audit Get Error:', error);
|
|
61
|
+
return createErrorResponse(500, 'Failed to retrieve audit trail');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
exports.handler = wrapHandler(getStandardsAudit);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Parse Upload Handler
|
|
3
|
+
* Parses uploaded standards files into YAML-compatible format
|
|
4
|
+
*
|
|
5
|
+
* POST /api/standards/parse-upload
|
|
6
|
+
* Body: { project_id, content, format: 'adr'|'eslint'|'cursorrules'|'markdown', filename }
|
|
7
|
+
* Auth: Cognito JWT required
|
|
8
|
+
*
|
|
9
|
+
* Uses the appropriate parser based on the specified format and returns
|
|
10
|
+
* parsed YAML-compatible standards ready for storage or review.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
14
|
+
|
|
15
|
+
const { parseAdr } = require('./core/parsers/adrParser');
|
|
16
|
+
const { parseEslint } = require('./core/parsers/eslintParser');
|
|
17
|
+
const { parseCursorRules } = require('./core/parsers/cursorRulesParser');
|
|
18
|
+
|
|
19
|
+
const SUPPORTED_FORMATS = ['adr', 'eslint', 'cursorrules', 'markdown'];
|
|
20
|
+
|
|
21
|
+
async function parseUploadStandards({ body, requestContext }) {
|
|
22
|
+
try {
|
|
23
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
24
|
+
|
|
25
|
+
if (!email) {
|
|
26
|
+
return createErrorResponse(401, 'Authentication required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { project_id, content, format, filename } = body || {};
|
|
30
|
+
|
|
31
|
+
// Validate required fields
|
|
32
|
+
if (!project_id) {
|
|
33
|
+
return createErrorResponse(400, 'project_id is required');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!content) {
|
|
37
|
+
return createErrorResponse(400, 'content is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!format) {
|
|
41
|
+
return createErrorResponse(400, 'format is required', {
|
|
42
|
+
supported: SUPPORTED_FORMATS
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
47
|
+
return createErrorResponse(400, `Unsupported format: ${format}`, {
|
|
48
|
+
supported: SUPPORTED_FORMATS
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Verify user has access to the project
|
|
53
|
+
const accessResult = await executeQuery(`
|
|
54
|
+
SELECT role FROM rapport.project_collaborators
|
|
55
|
+
WHERE project_id = $1 AND email_address = $2
|
|
56
|
+
`, [project_id, email]);
|
|
57
|
+
|
|
58
|
+
if (accessResult.rowCount === 0) {
|
|
59
|
+
return createErrorResponse(403, 'Access denied to project');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse the content using the appropriate parser
|
|
63
|
+
const parserOptions = {
|
|
64
|
+
filename: filename || `uploaded-${format}`,
|
|
65
|
+
category: undefined // Let parser infer category
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = parseContent(content, format, parserOptions);
|
|
71
|
+
} catch (parseError) {
|
|
72
|
+
return createErrorResponse(422, `Failed to parse ${format} content: ${parseError.message}`, {
|
|
73
|
+
format,
|
|
74
|
+
filename: parserOptions.filename
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Record the upload activity
|
|
79
|
+
await executeQuery(`
|
|
80
|
+
INSERT INTO rapport.activity_log (
|
|
81
|
+
email_address,
|
|
82
|
+
project_id,
|
|
83
|
+
activity_type,
|
|
84
|
+
activity_data,
|
|
85
|
+
created_at
|
|
86
|
+
) VALUES ($1, $2, 'standards_upload', $3, NOW())
|
|
87
|
+
`, [
|
|
88
|
+
email,
|
|
89
|
+
project_id,
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
format,
|
|
92
|
+
filename: filename || null,
|
|
93
|
+
rules_count: parsed.rules ? parsed.rules.length : 0,
|
|
94
|
+
anti_patterns_count: parsed.anti_patterns ? parsed.anti_patterns.length : 0,
|
|
95
|
+
category: parsed.category,
|
|
96
|
+
id: parsed.id
|
|
97
|
+
})
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
return createSuccessResponse({
|
|
101
|
+
project_id,
|
|
102
|
+
format,
|
|
103
|
+
filename: filename || null,
|
|
104
|
+
parsed,
|
|
105
|
+
summary: {
|
|
106
|
+
id: parsed.id,
|
|
107
|
+
category: parsed.category,
|
|
108
|
+
priority: parsed.priority,
|
|
109
|
+
rules_count: parsed.rules ? parsed.rules.length : 0,
|
|
110
|
+
anti_patterns_count: parsed.anti_patterns ? parsed.anti_patterns.length : 0,
|
|
111
|
+
tags: parsed.tags || []
|
|
112
|
+
}
|
|
113
|
+
}, `Successfully parsed ${format} content`);
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Standards Parse Upload Error:', error);
|
|
117
|
+
return createErrorResponse(500, 'Failed to parse uploaded standards');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Route content to the appropriate parser based on format
|
|
123
|
+
*
|
|
124
|
+
* @param {string} content - Raw content to parse
|
|
125
|
+
* @param {string} format - Format identifier
|
|
126
|
+
* @param {Object} options - Parser options
|
|
127
|
+
* @returns {Object} Parsed YAML-compatible standards object
|
|
128
|
+
*/
|
|
129
|
+
function parseContent(content, format, options) {
|
|
130
|
+
switch (format) {
|
|
131
|
+
case 'adr':
|
|
132
|
+
return parseAdr(content, options);
|
|
133
|
+
|
|
134
|
+
case 'eslint':
|
|
135
|
+
return parseEslint(content, options);
|
|
136
|
+
|
|
137
|
+
case 'cursorrules':
|
|
138
|
+
return parseCursorRules(content, options);
|
|
139
|
+
|
|
140
|
+
case 'markdown':
|
|
141
|
+
// Markdown format uses the cursor rules parser since it handles
|
|
142
|
+
// generic markdown with rule sections effectively
|
|
143
|
+
return parseCursorRules(content, {
|
|
144
|
+
...options,
|
|
145
|
+
filename: options.filename || 'standards.md'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
exports.handler = wrapHandler(parseUploadStandards);
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Relevant Post Handler
|
|
3
|
+
*
|
|
4
|
+
* Returns top 10 relevant standards for a project based on detected characteristics.
|
|
5
|
+
* The session-start hook calls this endpoint with locally-detected project characteristics;
|
|
6
|
+
* the handler maps them to categories, queries the database, ranks results, and returns
|
|
7
|
+
* the most relevant standards for injection into the AI coding session.
|
|
8
|
+
*
|
|
9
|
+
* POST /api/standards/relevant
|
|
10
|
+
* Auth: Cognito JWT required
|
|
11
|
+
* Body: { characteristics, projectId?, preferences? }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Category weights for relevance scoring
|
|
18
|
+
*/
|
|
19
|
+
const CATEGORY_WEIGHTS = {
|
|
20
|
+
'serverless-saas-aws': 1.0,
|
|
21
|
+
'multi-agent-orchestration': 1.0,
|
|
22
|
+
'frontend-development': 1.0,
|
|
23
|
+
'database': 0.9,
|
|
24
|
+
'compliance-security': 0.8,
|
|
25
|
+
'cost-optimization': 0.7,
|
|
26
|
+
'real-time-systems': 0.8,
|
|
27
|
+
'testing': 0.6,
|
|
28
|
+
'backend': 0.9,
|
|
29
|
+
'well-architected': 0.7
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Map project characteristics to relevant standard categories
|
|
34
|
+
*/
|
|
35
|
+
function mapCharacteristicsToCategories(characteristics) {
|
|
36
|
+
const categories = new Set();
|
|
37
|
+
|
|
38
|
+
if (characteristics.hasLambda || characteristics.hasSAM) {
|
|
39
|
+
categories.add('serverless-saas-aws');
|
|
40
|
+
categories.add('cost-optimization');
|
|
41
|
+
}
|
|
42
|
+
if (characteristics.hasReact) {
|
|
43
|
+
categories.add('frontend-development');
|
|
44
|
+
}
|
|
45
|
+
if (characteristics.hasDatabase) {
|
|
46
|
+
categories.add('database');
|
|
47
|
+
}
|
|
48
|
+
if (characteristics.hasMultiAgent) {
|
|
49
|
+
categories.add('multi-agent-orchestration');
|
|
50
|
+
}
|
|
51
|
+
if (characteristics.hasAPI) {
|
|
52
|
+
categories.add('backend');
|
|
53
|
+
}
|
|
54
|
+
if (characteristics.hasTests) {
|
|
55
|
+
categories.add('testing');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Always relevant
|
|
59
|
+
categories.add('compliance-security');
|
|
60
|
+
categories.add('well-architected');
|
|
61
|
+
|
|
62
|
+
return Array.from(categories);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Rank standards by relevance score
|
|
67
|
+
*/
|
|
68
|
+
function rankStandards(standards) {
|
|
69
|
+
return standards.map(standard => {
|
|
70
|
+
let score = 0;
|
|
71
|
+
|
|
72
|
+
// Base score from correlation
|
|
73
|
+
score += (standard.correlation || 1.0) * 40;
|
|
74
|
+
|
|
75
|
+
// Maturity score
|
|
76
|
+
const maturityScores = { enforced: 30, validated: 20, recommended: 10, provisional: 5 };
|
|
77
|
+
score += maturityScores[standard.maturity] || 0;
|
|
78
|
+
|
|
79
|
+
// Category weight
|
|
80
|
+
const categoryWeight = CATEGORY_WEIGHTS[standard.category] || 0.5;
|
|
81
|
+
score += categoryWeight * 20;
|
|
82
|
+
|
|
83
|
+
// File applicability bonus
|
|
84
|
+
if (standard.applicable_files && standard.applicable_files.length > 0) {
|
|
85
|
+
score += 5;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cost impact bonus (critical patterns)
|
|
89
|
+
if (standard.cost_impact && standard.cost_impact.severity === 'critical') {
|
|
90
|
+
score += 10;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Anti-patterns bonus (important to know what NOT to do)
|
|
94
|
+
if (standard.anti_patterns) {
|
|
95
|
+
const apCount = Array.isArray(standard.anti_patterns)
|
|
96
|
+
? standard.anti_patterns.length
|
|
97
|
+
: Object.keys(standard.anti_patterns).length;
|
|
98
|
+
if (apCount > 0) score += 5;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...standard,
|
|
103
|
+
relevance_score: Math.round(score * 10) / 10
|
|
104
|
+
};
|
|
105
|
+
}).sort((a, b) => b.relevance_score - a.relevance_score);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Apply user preferences to filter standards
|
|
110
|
+
*/
|
|
111
|
+
function applyPreferences(standards, preferences) {
|
|
112
|
+
if (!preferences) return standards;
|
|
113
|
+
|
|
114
|
+
const enabledCategories = preferences.enabled_categories || {};
|
|
115
|
+
const standardOverrides = preferences.standard_overrides || {};
|
|
116
|
+
|
|
117
|
+
return standards.filter(standard => {
|
|
118
|
+
const category = standard.category;
|
|
119
|
+
const standardPath = `${category}/${standard.element}`;
|
|
120
|
+
|
|
121
|
+
// Individual override takes priority
|
|
122
|
+
if (standardPath in standardOverrides) {
|
|
123
|
+
return standardOverrides[standardPath] === true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Category-level setting
|
|
127
|
+
if (category in enabledCategories) {
|
|
128
|
+
return enabledCategories[category] === true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Default: include
|
|
132
|
+
return true;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function getRelevantStandards({ body, requestContext }) {
|
|
137
|
+
const email = requestContext.authorizer?.claims?.email
|
|
138
|
+
|| requestContext.authorizer?.jwt?.claims?.email;
|
|
139
|
+
|
|
140
|
+
if (!email) {
|
|
141
|
+
return createErrorResponse(401, 'Authentication required');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Parse request body
|
|
145
|
+
let requestBody;
|
|
146
|
+
try {
|
|
147
|
+
requestBody = typeof body === 'string' ? JSON.parse(body) : body;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return createErrorResponse(400, 'Invalid request body');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { characteristics, preferences } = requestBody || {};
|
|
153
|
+
|
|
154
|
+
if (!characteristics || typeof characteristics !== 'object') {
|
|
155
|
+
return createErrorResponse(400, 'characteristics object is required');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Map characteristics to categories
|
|
159
|
+
const categories = mapCharacteristicsToCategories(characteristics);
|
|
160
|
+
|
|
161
|
+
if (categories.length === 0) {
|
|
162
|
+
return createSuccessResponse({
|
|
163
|
+
standards: [],
|
|
164
|
+
categories: [],
|
|
165
|
+
total_matched: 0
|
|
166
|
+
}, 'No relevant categories detected');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Query standards matching categories
|
|
170
|
+
const result = await executeQuery(`
|
|
171
|
+
SELECT
|
|
172
|
+
pattern_id,
|
|
173
|
+
element,
|
|
174
|
+
rule,
|
|
175
|
+
category,
|
|
176
|
+
correlation,
|
|
177
|
+
maturity,
|
|
178
|
+
applicable_files,
|
|
179
|
+
anti_patterns,
|
|
180
|
+
examples,
|
|
181
|
+
cost_impact,
|
|
182
|
+
source
|
|
183
|
+
FROM rapport.standards_patterns
|
|
184
|
+
WHERE category = ANY($1::varchar[])
|
|
185
|
+
AND maturity IN ('enforced', 'validated', 'recommended')
|
|
186
|
+
ORDER BY
|
|
187
|
+
CASE
|
|
188
|
+
WHEN maturity = 'enforced' THEN 1
|
|
189
|
+
WHEN maturity = 'validated' THEN 2
|
|
190
|
+
ELSE 3
|
|
191
|
+
END,
|
|
192
|
+
correlation DESC
|
|
193
|
+
`, [categories]);
|
|
194
|
+
|
|
195
|
+
// Rank by relevance
|
|
196
|
+
let ranked = rankStandards(result.rows);
|
|
197
|
+
|
|
198
|
+
// Apply preferences if provided
|
|
199
|
+
if (preferences) {
|
|
200
|
+
ranked = applyPreferences(ranked, preferences);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Return top 10
|
|
204
|
+
const top = ranked.slice(0, 10);
|
|
205
|
+
|
|
206
|
+
return createSuccessResponse({
|
|
207
|
+
standards: top,
|
|
208
|
+
categories,
|
|
209
|
+
total_matched: result.rows.length
|
|
210
|
+
}, 'Relevant standards retrieved');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
exports.handler = wrapHandler(getRelevantStandards);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Transition Handler
|
|
3
|
+
* Executes lifecycle state transitions on standards
|
|
4
|
+
*
|
|
5
|
+
* POST /api/standards/transition
|
|
6
|
+
* Body: { standard_id, action: 'approve'|'disable'|'deprecate'|'delete'|'enable'|'cancel_deprecation', reason? }
|
|
7
|
+
* Auth: Cognito JWT required
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { wrapHandler, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
11
|
+
const { StandardLifecycle } = require('./core/StandardLifecycle');
|
|
12
|
+
|
|
13
|
+
const lifecycle = new StandardLifecycle();
|
|
14
|
+
|
|
15
|
+
async function transitionStandard({ body, requestContext }) {
|
|
16
|
+
try {
|
|
17
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
18
|
+
|
|
19
|
+
if (!email) {
|
|
20
|
+
return createErrorResponse(401, 'Authentication required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { standard_id: id, action, reason } = body || {};
|
|
24
|
+
|
|
25
|
+
if (!id) {
|
|
26
|
+
return createErrorResponse(400, 'standard_id is required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!action) {
|
|
30
|
+
return createErrorResponse(400, 'action is required', {
|
|
31
|
+
valid_actions: ['propose', 'approve', 'reject', 'disable', 'deprecate', 'delete', 'enable', 'cancel_deprecation']
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Execute the transition
|
|
36
|
+
const result = await lifecycle.transition(id, action, email, reason);
|
|
37
|
+
|
|
38
|
+
return createSuccessResponse({
|
|
39
|
+
standard_id: result.standard_id,
|
|
40
|
+
old_state: result.old_state,
|
|
41
|
+
new_state: result.new_state,
|
|
42
|
+
action: result.action,
|
|
43
|
+
audit_entry: result.audit_entry
|
|
44
|
+
}, `Standard transitioned from '${result.old_state}' to '${result.new_state}'`);
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Standards Transition Error:', error);
|
|
48
|
+
|
|
49
|
+
// Return user-friendly messages for known validation errors
|
|
50
|
+
if (error.message.includes('Invalid action')) {
|
|
51
|
+
return createErrorResponse(400, error.message);
|
|
52
|
+
}
|
|
53
|
+
if (error.message.includes('not found')) {
|
|
54
|
+
return createErrorResponse(404, error.message);
|
|
55
|
+
}
|
|
56
|
+
if (error.message.includes('Cannot perform')) {
|
|
57
|
+
return createErrorResponse(409, error.message);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return createErrorResponse(500, 'Failed to transition standard');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.handler = wrapHandler(transitionStandard);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Splash Acknowledge Handler
|
|
3
|
+
* Records that the user has dismissed the weekly splash screen
|
|
4
|
+
*
|
|
5
|
+
* POST /api/user/splash/acknowledge
|
|
6
|
+
* Auth: Cognito JWT required
|
|
7
|
+
*
|
|
8
|
+
* Body: { week_start: "2026-01-27" }
|
|
9
|
+
* Records acknowledgment so splash does not show again this week
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
13
|
+
|
|
14
|
+
async function acknowledgeUserSplash({ requestContext, body }) {
|
|
15
|
+
const Request_ID = requestContext.requestId;
|
|
16
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
17
|
+
|
|
18
|
+
if (!email) {
|
|
19
|
+
return createErrorResponse(401, 'Authentication required');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const weekStart = body?.week_start;
|
|
23
|
+
if (!weekStart) {
|
|
24
|
+
return createErrorResponse(400, 'week_start is required');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validate week_start format (YYYY-MM-DD)
|
|
28
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
29
|
+
if (!dateRegex.test(weekStart)) {
|
|
30
|
+
return createErrorResponse(400, 'week_start must be in YYYY-MM-DD format');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Attempt to insert acknowledgment into dedicated table
|
|
34
|
+
try {
|
|
35
|
+
await executeQuery(`
|
|
36
|
+
INSERT INTO rapport.user_splash_acknowledgments (email_address, week_start, acknowledged_at)
|
|
37
|
+
VALUES ($1, $2, NOW())
|
|
38
|
+
ON CONFLICT (email_address, week_start) DO NOTHING
|
|
39
|
+
`, [email, weekStart]);
|
|
40
|
+
|
|
41
|
+
return createSuccessResponse(
|
|
42
|
+
{ acknowledged: true, week_start: weekStart },
|
|
43
|
+
'Splash acknowledged',
|
|
44
|
+
{ Request_ID, Timestamp: new Date().toISOString() }
|
|
45
|
+
);
|
|
46
|
+
} catch (tableErr) {
|
|
47
|
+
// If the dedicated table does not exist, fall back to storing
|
|
48
|
+
// acknowledgment as metadata in the patterns table
|
|
49
|
+
console.log('[SplashAck] Dedicated table not available, using fallback:', tableErr.message);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await executeQuery(`
|
|
53
|
+
INSERT INTO rapport.patterns (
|
|
54
|
+
pattern_type,
|
|
55
|
+
pattern_key,
|
|
56
|
+
pattern_value,
|
|
57
|
+
created_by,
|
|
58
|
+
created_at,
|
|
59
|
+
metadata
|
|
60
|
+
)
|
|
61
|
+
VALUES (
|
|
62
|
+
'splash_ack',
|
|
63
|
+
$1,
|
|
64
|
+
$2,
|
|
65
|
+
$3,
|
|
66
|
+
NOW(),
|
|
67
|
+
$4
|
|
68
|
+
)
|
|
69
|
+
ON CONFLICT (pattern_type, pattern_key) WHERE pattern_type = 'splash_ack'
|
|
70
|
+
DO UPDATE SET
|
|
71
|
+
pattern_value = EXCLUDED.pattern_value,
|
|
72
|
+
created_at = NOW()
|
|
73
|
+
`, [
|
|
74
|
+
`splash_ack:${email}:${weekStart}`,
|
|
75
|
+
weekStart,
|
|
76
|
+
email,
|
|
77
|
+
JSON.stringify({ type: 'splash_acknowledgment', week_start: weekStart })
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
return createSuccessResponse(
|
|
81
|
+
{ acknowledged: true, week_start: weekStart, fallback: true },
|
|
82
|
+
'Splash acknowledged (fallback)',
|
|
83
|
+
{ Request_ID, Timestamp: new Date().toISOString() }
|
|
84
|
+
);
|
|
85
|
+
} catch (fallbackErr) {
|
|
86
|
+
return handleError(fallbackErr);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
exports.handler = wrapHandler(acknowledgeUserSplash);
|