@cdklabs/cdk-appmod-catalog-blueprints 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.jsii +8644 -0
  2. package/LICENSE +202 -0
  3. package/README.md +212 -0
  4. package/lib/document-processing/agentic-document-processing.d.ts +16 -0
  5. package/lib/document-processing/agentic-document-processing.js +90 -0
  6. package/lib/document-processing/base-document-processing.d.ts +189 -0
  7. package/lib/document-processing/base-document-processing.js +509 -0
  8. package/lib/document-processing/bedrock-document-processing.d.ts +167 -0
  9. package/lib/document-processing/bedrock-document-processing.js +297 -0
  10. package/lib/document-processing/index.d.ts +3 -0
  11. package/lib/document-processing/index.js +20 -0
  12. package/lib/document-processing/resources/default-bedrock-invoke/index.py +63 -0
  13. package/lib/document-processing/resources/default-bedrock-invoke/requirements.txt +4 -0
  14. package/lib/document-processing/resources/default-doc-retrieval-lambda/index.mjs +92 -0
  15. package/lib/document-processing/resources/default-doc-retrieval-lambda/package.json +10 -0
  16. package/lib/document-processing/resources/default-error-handler/index.js +46 -0
  17. package/lib/document-processing/resources/default-error-handler/package.json +4 -0
  18. package/lib/document-processing/resources/default-image-processor/classifier.mjs +665 -0
  19. package/lib/document-processing/resources/default-image-processor/extractors.mjs +465 -0
  20. package/lib/document-processing/resources/default-image-processor/index.mjs +143 -0
  21. package/lib/document-processing/resources/default-image-processor/package-lock.json +12 -0
  22. package/lib/document-processing/resources/default-image-processor/package.json +4 -0
  23. package/lib/document-processing/resources/default-image-validator/index.mjs +76 -0
  24. package/lib/document-processing/resources/default-image-validator/package-lock.json +154 -0
  25. package/lib/document-processing/resources/default-image-validator/package.json +7 -0
  26. package/lib/document-processing/resources/default-pdf-processor/index.js +46 -0
  27. package/lib/document-processing/resources/default-pdf-validator/index.js +36 -0
  28. package/lib/document-processing/resources/default-sqs-consumer/index.py +111 -0
  29. package/lib/document-processing/resources/default-sqs-consumer/requirements.txt +4 -0
  30. package/lib/document-processing/resources/default-sqs-consumer/sample_payload.json +20 -0
  31. package/lib/document-processing/resources/default-sqs-consumer/sample_payload_multi.json +24 -0
  32. package/lib/document-processing/resources/default-strands-agent/index.py +111 -0
  33. package/lib/document-processing/resources/default-strands-agent/requirements.txt +6 -0
  34. package/lib/document-processing/tests/agentic-document-processing-nag.test.d.ts +1 -0
  35. package/lib/document-processing/tests/agentic-document-processing-nag.test.js +107 -0
  36. package/lib/document-processing/tests/agentic-document-processing.test.d.ts +1 -0
  37. package/lib/document-processing/tests/agentic-document-processing.test.js +125 -0
  38. package/lib/document-processing/tests/bedrock-document-processing-nag.test.d.ts +1 -0
  39. package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +101 -0
  40. package/lib/document-processing/tests/bedrock-document-processing.test.d.ts +1 -0
  41. package/lib/document-processing/tests/bedrock-document-processing.test.js +79 -0
  42. package/lib/framework/custom-resource/default-runtimes.d.ts +21 -0
  43. package/lib/framework/custom-resource/default-runtimes.js +34 -0
  44. package/lib/framework/custom-resource/index.d.ts +1 -0
  45. package/lib/framework/custom-resource/index.js +18 -0
  46. package/lib/framework/foundation/access-log.d.ts +69 -0
  47. package/lib/framework/foundation/access-log.js +121 -0
  48. package/lib/framework/foundation/eventbridge-broker.d.ts +18 -0
  49. package/lib/framework/foundation/eventbridge-broker.js +42 -0
  50. package/lib/framework/foundation/index.d.ts +3 -0
  51. package/lib/framework/foundation/index.js +20 -0
  52. package/lib/framework/foundation/network.d.ts +19 -0
  53. package/lib/framework/foundation/network.js +83 -0
  54. package/lib/framework/index.d.ts +2 -0
  55. package/lib/framework/index.js +19 -0
  56. package/lib/framework/quickstart/base-quickstart.d.ts +30 -0
  57. package/lib/framework/quickstart/base-quickstart.js +30 -0
  58. package/lib/index.d.ts +4 -0
  59. package/lib/index.js +21 -0
  60. package/lib/tsconfig.tsbuildinfo +1 -0
  61. package/lib/utilities/cdk-nag-config.d.ts +42 -0
  62. package/lib/utilities/cdk-nag-config.js +194 -0
  63. package/lib/utilities/data-loader-lambda/index.py +282 -0
  64. package/lib/utilities/data-loader-lambda/requirements.txt +3 -0
  65. package/lib/utilities/data-loader.d.ts +173 -0
  66. package/lib/utilities/data-loader.js +447 -0
  67. package/lib/utilities/index.d.ts +3 -0
  68. package/lib/utilities/index.js +20 -0
  69. package/lib/utilities/lambda-iam-utils.d.ts +145 -0
  70. package/lib/utilities/lambda-iam-utils.js +235 -0
  71. package/lib/utilities/lambda_layers/data-masking/layer-construct.d.ts +42 -0
  72. package/lib/utilities/lambda_layers/data-masking/layer-construct.js +53 -0
  73. package/lib/utilities/lambda_layers/data-masking/layer-construct.ts +88 -0
  74. package/lib/utilities/observability/bedrock-observability.d.ts +18 -0
  75. package/lib/utilities/observability/bedrock-observability.js +131 -0
  76. package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.d.ts +6 -0
  77. package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +22 -0
  78. package/lib/utilities/observability/index.d.ts +6 -0
  79. package/lib/utilities/observability/index.js +25 -0
  80. package/lib/utilities/observability/lambda-observability-property-injector.d.ts +8 -0
  81. package/lib/utilities/observability/lambda-observability-property-injector.js +43 -0
  82. package/lib/utilities/observability/log-group-data-protection-props.d.ts +19 -0
  83. package/lib/utilities/observability/log-group-data-protection-props.js +5 -0
  84. package/lib/utilities/observability/observability.d.ts +83 -0
  85. package/lib/utilities/observability/observability.js +278 -0
  86. package/lib/utilities/observability/observable.d.ts +32 -0
  87. package/lib/utilities/observability/observable.js +3 -0
  88. package/lib/utilities/observability/powertools-config.d.ts +3 -0
  89. package/lib/utilities/observability/powertools-config.js +25 -0
  90. package/lib/utilities/observability/resources/bedrock-manage-logging-configuration/index.py +27 -0
  91. package/lib/utilities/observability/state-machine-observability-property-injector.d.ts +8 -0
  92. package/lib/utilities/observability/state-machine-observability-property-injector.js +49 -0
  93. package/lib/utilities/tests/data-loader-nag.test.d.ts +1 -0
  94. package/lib/utilities/tests/data-loader-nag.test.js +432 -0
  95. package/lib/utilities/tests/data-loader.test.d.ts +1 -0
  96. package/lib/utilities/tests/data-loader.test.js +284 -0
  97. package/lib/webapp/frontend-construct.d.ts +136 -0
  98. package/lib/webapp/frontend-construct.js +253 -0
  99. package/lib/webapp/index.d.ts +1 -0
  100. package/lib/webapp/index.js +18 -0
  101. package/lib/webapp/tests/frontend-construct-nag.test.d.ts +1 -0
  102. package/lib/webapp/tests/frontend-construct-nag.test.js +266 -0
  103. package/lib/webapp/tests/frontend-construct.test.d.ts +1 -0
  104. package/lib/webapp/tests/frontend-construct.test.js +385 -0
  105. package/package.json +183 -0
@@ -0,0 +1,154 @@
1
+ {
2
+ "name": "default-image-validator",
3
+ "version": "0.0.1",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "default-image-validator",
9
+ "version": "0.0.1",
10
+ "dependencies": {
11
+ "file-type": "^20.5.0"
12
+ }
13
+ },
14
+ "node_modules/@tokenizer/inflate": {
15
+ "version": "0.2.7",
16
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
17
+ "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
18
+ "dependencies": {
19
+ "debug": "^4.4.0",
20
+ "fflate": "^0.8.2",
21
+ "token-types": "^6.0.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "funding": {
27
+ "type": "github",
28
+ "url": "https://github.com/sponsors/Borewit"
29
+ }
30
+ },
31
+ "node_modules/@tokenizer/token": {
32
+ "version": "0.3.0",
33
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
34
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
35
+ },
36
+ "node_modules/debug": {
37
+ "version": "4.4.0",
38
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
39
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
40
+ "dependencies": {
41
+ "ms": "^2.1.3"
42
+ },
43
+ "engines": {
44
+ "node": ">=6.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "supports-color": {
48
+ "optional": true
49
+ }
50
+ }
51
+ },
52
+ "node_modules/fflate": {
53
+ "version": "0.8.2",
54
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
55
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
56
+ },
57
+ "node_modules/file-type": {
58
+ "version": "20.5.0",
59
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz",
60
+ "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==",
61
+ "dependencies": {
62
+ "@tokenizer/inflate": "^0.2.6",
63
+ "strtok3": "^10.2.0",
64
+ "token-types": "^6.0.0",
65
+ "uint8array-extras": "^1.4.0"
66
+ },
67
+ "engines": {
68
+ "node": ">=18"
69
+ },
70
+ "funding": {
71
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
72
+ }
73
+ },
74
+ "node_modules/ieee754": {
75
+ "version": "1.2.1",
76
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
77
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
78
+ "funding": [
79
+ {
80
+ "type": "github",
81
+ "url": "https://github.com/sponsors/feross"
82
+ },
83
+ {
84
+ "type": "patreon",
85
+ "url": "https://www.patreon.com/feross"
86
+ },
87
+ {
88
+ "type": "consulting",
89
+ "url": "https://feross.org/support"
90
+ }
91
+ ]
92
+ },
93
+ "node_modules/ms": {
94
+ "version": "2.1.3",
95
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
96
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
97
+ },
98
+ "node_modules/peek-readable": {
99
+ "version": "7.0.0",
100
+ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz",
101
+ "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==",
102
+ "engines": {
103
+ "node": ">=18"
104
+ },
105
+ "funding": {
106
+ "type": "github",
107
+ "url": "https://github.com/sponsors/Borewit"
108
+ }
109
+ },
110
+ "node_modules/strtok3": {
111
+ "version": "10.2.2",
112
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz",
113
+ "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==",
114
+ "dependencies": {
115
+ "@tokenizer/token": "^0.3.0",
116
+ "peek-readable": "^7.0.0"
117
+ },
118
+ "engines": {
119
+ "node": ">=18"
120
+ },
121
+ "funding": {
122
+ "type": "github",
123
+ "url": "https://github.com/sponsors/Borewit"
124
+ }
125
+ },
126
+ "node_modules/token-types": {
127
+ "version": "6.0.0",
128
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
129
+ "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
130
+ "dependencies": {
131
+ "@tokenizer/token": "^0.3.0",
132
+ "ieee754": "^1.2.1"
133
+ },
134
+ "engines": {
135
+ "node": ">=14.16"
136
+ },
137
+ "funding": {
138
+ "type": "github",
139
+ "url": "https://github.com/sponsors/Borewit"
140
+ }
141
+ },
142
+ "node_modules/uint8array-extras": {
143
+ "version": "1.4.0",
144
+ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
145
+ "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
146
+ "engines": {
147
+ "node": ">=18"
148
+ },
149
+ "funding": {
150
+ "url": "https://github.com/sponsors/sindresorhus"
151
+ }
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@document-processing/default-image-validator",
3
+ "version": "0.0.1",
4
+ "dependencies": {
5
+ "file-type": "^20.5.0"
6
+ }
7
+ }
@@ -0,0 +1,46 @@
1
+ exports.handler = async (event) => {
2
+ console.log('Received event:', JSON.stringify(event, null, 2));
3
+
4
+ try {
5
+ // Extract document info from event
6
+ const { documentId, bucket, key, status } = event;
7
+
8
+ // Check if document was valid
9
+ if (status !== 'VALID') {
10
+ throw new Error('Cannot process invalid document');
11
+ }
12
+
13
+ // Simulate processing
14
+ console.log(`Processing document ${documentId} from ${bucket}/${key}`);
15
+
16
+ // In a real implementation, this would:
17
+ // 1. Download the PDF from S3
18
+ // 2. Process it (OCR, text extraction, etc)
19
+ // 3. Apply data masking
20
+ // 4. Upload processed result back to S3
21
+
22
+ return {
23
+ statusCode: 200,
24
+ documentId,
25
+ bucket,
26
+ key,
27
+ status: 'PROCESSED',
28
+ outputKey: `processed/${documentId}.pdf`,
29
+ metadata: {
30
+ pageCount: 5, // Example metadata
31
+ processedAt: new Date().toISOString(),
32
+ contentType: 'application/pdf'
33
+ },
34
+ timestamp: new Date().toISOString()
35
+ };
36
+ } catch (error) {
37
+ console.error('Processing error:', error);
38
+ return {
39
+ statusCode: 500,
40
+ documentId: event.documentId,
41
+ error: error.message,
42
+ status: 'PROCESSING_FAILED',
43
+ timestamp: new Date().toISOString()
44
+ };
45
+ }
46
+ };
@@ -0,0 +1,36 @@
1
+ exports.handler = async (event) => {
2
+ console.log('Received event:', JSON.stringify(event, null, 2));
3
+
4
+ try {
5
+ // Extract document info from event
6
+ const { documentId, bucket, key } = event;
7
+
8
+ // Basic validation
9
+ if (!documentId || !bucket || !key) {
10
+ throw new Error('Missing required fields: documentId, bucket, or key');
11
+ }
12
+
13
+ // Check file extension
14
+ if (!key.toLowerCase().endsWith('.pdf')) {
15
+ throw new Error('Invalid file format. Only PDF files are supported.');
16
+ }
17
+
18
+ return {
19
+ statusCode: 200,
20
+ documentId,
21
+ bucket,
22
+ key,
23
+ status: 'VALID',
24
+ timestamp: new Date().toISOString()
25
+ };
26
+ } catch (error) {
27
+ console.error('Validation error:', error);
28
+ return {
29
+ statusCode: 400,
30
+ documentId: event.documentId,
31
+ error: error.message,
32
+ status: 'INVALID',
33
+ timestamp: new Date().toISOString()
34
+ };
35
+ }
36
+ };
@@ -0,0 +1,111 @@
1
+ import json
2
+ import os
3
+ import boto3
4
+ from urllib.parse import unquote_plus
5
+ import time
6
+ import re
7
+ from aws_lambda_powertools import Metrics, Tracer
8
+ from aws_lambda_powertools.metrics import MetricUnit
9
+
10
+ sfn_client = boto3.client('stepfunctions')
11
+ metrics = Metrics()
12
+ tracer = Tracer()
13
+
14
+ @metrics.log_metrics
15
+ @tracer.capture_lambda_handler
16
+ def handler(event, context):
17
+ tracer.put_annotation(key="invoke_type", value="sqsconsumer")
18
+ metrics.add_dimension(name="invoke_type", value="sqsconsumer")
19
+ print(f'SQS Consumer: Received event: {json.dumps(event, indent=2)}')
20
+
21
+ results = []
22
+
23
+ for record in event['Records']:
24
+ try:
25
+ print(f'Processing SQS record: {record["messageId"]}')
26
+
27
+ # Parse S3 event from SQS message body
28
+ s3_event = json.loads(record['body'])
29
+ print(f'Parsed S3 event: {json.dumps(s3_event, indent=2)}')
30
+
31
+ # Skip S3 test events
32
+ if "Event" in s3_event and s3_event["Event"] == 's3:TestEvent':
33
+ print(f'Skipping S3 test event: {s3_event["Event"]}')
34
+ continue
35
+
36
+ if "Records" in s3_event:
37
+ # Process each S3 record in the event
38
+ for s3_record in s3_event['Records']:
39
+ event_name = s3_record['eventName']
40
+
41
+ bucket = s3_record['s3']['bucket']['name']
42
+ key = unquote_plus(s3_record['s3']['object']['key'])
43
+
44
+ # Skip folder creation events (keys ending with '/')
45
+ if key.endswith('/'):
46
+ print(f'Skipping folder creation event: s3://{bucket}/{key}')
47
+ continue
48
+
49
+ event_time = s3_record['eventTime']
50
+
51
+ print(f'Processing S3 object: s3://{bucket}/{key}')
52
+
53
+ # Generate unique document ID from S3 key and timestamp
54
+ timestamp = int(time.time() * 1000)
55
+ document_id = (key
56
+ .replace('raw/', '', 1) # Remove raw/ prefix
57
+ .rsplit('.', 1)[0] # Remove file extension
58
+ )
59
+ document_id = re.sub(r'[^a-zA-Z0-9-]', '-', document_id) + '-' + str(timestamp)
60
+
61
+ # Extract filename from key
62
+ filename = key.replace('raw/', '', 1) # Remove raw/ prefix
63
+
64
+ # Prepare Step Functions execution input
65
+ step_function_input = {
66
+ 'documentId': document_id,
67
+ 'bucket': bucket,
68
+ 'key': key,
69
+ 'filename': filename,
70
+ 'eventTime': event_time,
71
+ 'eventName': event_name,
72
+ 'source': 'sqs-consumer'
73
+ }
74
+
75
+ print(f'Starting Step Functions execution with input: {json.dumps(step_function_input, indent=2)}')
76
+
77
+ # Start Step Functions execution
78
+ execution_name = f'{document_id}-execution'[:80] # AWS limit
79
+
80
+ response = sfn_client.start_execution(
81
+ stateMachineArn=os.environ['STATE_MACHINE_ARN'],
82
+ input=json.dumps(step_function_input),
83
+ name=execution_name
84
+ )
85
+
86
+ print(f'Step Functions execution started: {response["executionArn"]}')
87
+
88
+ results.append({
89
+ 'documentId': document_id,
90
+ 'executionArn': response['executionArn'],
91
+ 's3Location': f's3://{bucket}/{key}'
92
+ })
93
+ else:
94
+ print(f"Skipping")
95
+
96
+ except Exception as error:
97
+ metrics.add_metric(name="FailedToProcessed", unit=MetricUnit.Count, value=1)
98
+ print(f'Error processing SQS record: {record["messageId"]} {str(error)}')
99
+
100
+ # Re-raise error to trigger SQS retry mechanism
101
+ # After max retries, message will go to Dead Letter Queue
102
+ raise Exception(f'Failed to process SQS record {record["messageId"]}: {str(error)}')
103
+
104
+ metrics.add_metric(name="SuccessfullyProcessed", unit=MetricUnit.Count, value=len(results))
105
+ print(f'Successfully processed {len(results)} documents: {results}')
106
+
107
+ return {
108
+ 'statusCode': 200,
109
+ 'processedCount': len(results),
110
+ 'results': results
111
+ }
@@ -0,0 +1,4 @@
1
+ boto3>=1.26.0
2
+ aws-lambda-powertools
3
+ urllib3>=1.26.0,<2.0.0
4
+ aws-xray-sdk
@@ -0,0 +1,20 @@
1
+ {
2
+ "Records": [
3
+ {
4
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
5
+ "receiptHandle": "MessageReceiptHandle",
6
+ "body": "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"us-east-1\",\"eventTime\":\"2024-01-15T10:30:00.000Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"EXAMPLE\"},\"requestParameters\":{\"sourceIPAddress\":\"127.0.0.1\"},\"responseElements\":{\"x-amz-request-id\":\"EXAMPLE123456789\",\"x-amz-id-2\":\"EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"testConfigRule\",\"bucket\":{\"name\":\"my-document-bucket\",\"ownerIdentity\":{\"principalId\":\"EXAMPLE\"},\"arn\":\"arn:aws:s3:::my-document-bucket\"},\"object\":{\"key\":\"raw/invoice-2024-001.pdf\",\"size\":1024,\"eTag\":\"0123456789abcdef0123456789abcdef\",\"sequencer\":\"0A1B2C3D4E5F678901\"}}}]}",
7
+ "attributes": {
8
+ "ApproximateReceiveCount": "1",
9
+ "SentTimestamp": "1705315800000",
10
+ "SenderId": "AIDACKCEVSQ6C2EXAMPLE",
11
+ "ApproximateFirstReceiveTimestamp": "1705315800000"
12
+ },
13
+ "messageAttributes": {},
14
+ "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
15
+ "eventSource": "aws:sqs",
16
+ "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:my-queue",
17
+ "awsRegion": "us-east-1"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "Records": [
3
+ {
4
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
5
+ "receiptHandle": "MessageReceiptHandle1",
6
+ "body": "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"us-east-1\",\"eventTime\":\"2024-01-15T10:30:00.000Z\",\"eventName\":\"ObjectCreated:Put\",\"s3\":{\"s3SchemaVersion\":\"1.0\",\"bucket\":{\"name\":\"my-document-bucket\"},\"object\":{\"key\":\"raw/receipt-scan.jpg\",\"size\":2048}}}]}",
7
+ "attributes": {
8
+ "ApproximateReceiveCount": "1"
9
+ },
10
+ "eventSource": "aws:sqs",
11
+ "awsRegion": "us-east-1"
12
+ },
13
+ {
14
+ "messageId": "29dd0b57-b21e-4ac1-bd88-01bbb068cb79",
15
+ "receiptHandle": "MessageReceiptHandle2",
16
+ "body": "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"us-east-1\",\"eventTime\":\"2024-01-15T10:31:00.000Z\",\"eventName\":\"ObjectCreated:Put\",\"s3\":{\"s3SchemaVersion\":\"1.0\",\"bucket\":{\"name\":\"my-document-bucket\"},\"object\":{\"key\":\"raw/contract%20document.pdf\",\"size\":4096}}}]}",
17
+ "attributes": {
18
+ "ApproximateReceiveCount": "1"
19
+ },
20
+ "eventSource": "aws:sqs",
21
+ "awsRegion": "us-east-1"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,111 @@
1
+ import boto3
2
+ import os
3
+ import json
4
+ from strands import Agent, tool
5
+ from strands_tools import file_read
6
+ from aws_lambda_powertools import Metrics, Tracer
7
+ from aws_lambda_powertools.metrics import MetricUnit
8
+
9
+ s3 = boto3.client('s3')
10
+ metrics = Metrics()
11
+ tracer = Tracer()
12
+
13
+ @tracer.capture_method
14
+ def extract_json_from_text(text):
15
+ """Extract JSON object from text body enclosed in ```json blocks or raw JSON."""
16
+ import re
17
+
18
+ # First try to find JSON in ```json blocks
19
+ match = re.search(r'```json\s*({.*?})\s*```', text, re.DOTALL)
20
+ if match:
21
+ try:
22
+ return json.loads(match.group(1))
23
+ except json.JSONDecodeError:
24
+ pass
25
+
26
+ # If no ```json blocks, look for raw JSON objects in the text
27
+ json_match = re.search(r'({[^{}]*(?:{[^{}]*}[^{}]*)*})', text, re.DOTALL)
28
+ if json_match:
29
+ try:
30
+ return json.loads(json_match.group(1))
31
+ except json.JSONDecodeError:
32
+ pass
33
+
34
+ return None
35
+
36
+
37
+ @tracer.capture_method
38
+ def download_tools():
39
+ tools = json.loads(os.environ['TOOLS_CONFIG'])
40
+ tools_arr = []
41
+ for tool in tools:
42
+ bucket, key, filename = parse_s3_path(tool)
43
+ local_path = f"/tmp/{filename}"
44
+ s3.download_file(bucket, key, local_path)
45
+ tools_arr.append(local_path)
46
+
47
+ return tools_arr
48
+
49
+ @tracer.capture_method
50
+ def parse_s3_path(s3_path):
51
+ # Remove s3:// prefix if present
52
+ path = s3_path.replace('s3://', '')
53
+
54
+ # Split into parts
55
+ parts = path.split('/', 1)
56
+ bucket = parts[0]
57
+
58
+ if len(parts) == 1:
59
+ return bucket, '', ''
60
+
61
+ # Key is the prefix (includes filename)
62
+ prefix = parts[1]
63
+ filename = prefix.split('/')[-1]
64
+
65
+ return bucket, prefix, filename
66
+
67
+ @tracer.capture_method
68
+ def download_attached_document(event):
69
+ bucket = event['bucket']
70
+ key = event['key']
71
+
72
+ # Download file to /tmp
73
+ local_path = f"/tmp/{key.split('/')[-1]}"
74
+ s3.download_file(bucket, key, local_path)
75
+
76
+ return local_path
77
+
78
+ agent_tools = download_tools()
79
+
80
+ @metrics.log_metrics
81
+ @tracer.capture_lambda_handler
82
+ def handler(event, context):
83
+ model_id = os.getenv("MODEL_ID")
84
+ prompt = os.getenv("PROMPT")
85
+ system_prompt = os.getenv("SYSTEM_PROMPT")
86
+ invoke_type = os.environ["INVOKE_TYPE"]
87
+
88
+ tracer.put_annotation(key="invoke_type", value=invoke_type)
89
+ metrics.add_dimension(name="invoke_type", value=invoke_type)
90
+
91
+ if prompt is None:
92
+ prompt = "Analyze the attached document and verify the information using the provided tools."
93
+
94
+ if system_prompt is None:
95
+ system_prompt = "You're a document analysis specialist. You specialized in analyzing provided documents using the tools that have been provided."
96
+
97
+ local_path_attached_doc = download_attached_document(event)
98
+
99
+ if 'classificationResult' in event:
100
+ classification = event['classificationResult']['documentClassification']
101
+ prompt = prompt.replace("[ACTUAL_CLASSIFICATION]", classification)
102
+
103
+ prompt += f" Attached document is located in {local_path_attached_doc}"
104
+
105
+ agent = Agent(model=model_id, tools=agent_tools + [file_read], system_prompt=system_prompt)
106
+ response = agent(prompt)
107
+
108
+ metrics.add_metric(name="SuccessfulAgentInvocation", unit=MetricUnit.Count, value=1)
109
+ return {
110
+ "result": extract_json_from_text(response.message["content"][0]["text"])
111
+ }
@@ -0,0 +1,6 @@
1
+ strands-agents>=1.0.0
2
+ boto3>=1.0.0
3
+ strands-agents-tools[file_read]
4
+ urllib3>=1.26.0,<2.0.0
5
+ aws-lambda-powertools
6
+ aws-xray-sdk
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
4
+ const assertions_1 = require("aws-cdk-lib/assertions");
5
+ const aws_s3_1 = require("aws-cdk-lib/aws-s3");
6
+ const cdk_nag_1 = require("cdk-nag");
7
+ const framework_1 = require("../../framework");
8
+ const agentic_document_processing_1 = require("../agentic-document-processing");
9
+ // Create app and stack
10
+ const app = new aws_cdk_lib_1.App();
11
+ const stack = new aws_cdk_lib_1.Stack(app, 'TestStack', {
12
+ env: {
13
+ account: '123456789012',
14
+ region: 'us-east-1',
15
+ },
16
+ });
17
+ const accessLog = new framework_1.AccessLog(stack, 'AccessLog');
18
+ // Create S3 bucket for agentic tools
19
+ const bucket = new aws_s3_1.Bucket(stack, 'AgenticDocumentProcessingBucket', {
20
+ serverAccessLogsBucket: accessLog.bucket,
21
+ serverAccessLogsPrefix: accessLog.bucketPrefix,
22
+ enforceSSL: true,
23
+ });
24
+ // Create the main AgenticDocumentProcessing construct
25
+ new agentic_document_processing_1.AgenticDocumentProcessing(stack, 'AgenticDocumentProcessing', {
26
+ bucket,
27
+ useCrossRegionInference: true,
28
+ processingAgentParameters: {
29
+ agentSystemPrompt: `
30
+ You're an insurance claims specialist. Use the provided tools to ensure that the submitted claims and supporting documents are valid and there are no discrepancies.
31
+ `,
32
+ toolsLocation: [
33
+ `s3://${bucket.bucketName}/agentic-tools/download_policy.py`,
34
+ `s3://${bucket.bucketName}/agentic-tools/download_supporting_documents.py`,
35
+ ],
36
+ },
37
+ processingPrompt: `
38
+ Analyze the attached insurance claim document and check if this is a valid claim or not.
39
+ Final output should in JSON format with claim_approved and justification fields.
40
+ `,
41
+ enableObservability: true,
42
+ });
43
+ // Suppress CDK-managed BucketNotificationsHandler AWS managed policy
44
+ cdk_nag_1.NagSuppressions.addResourceSuppressionsByPath(stack, '/TestStack/BucketNotificationsHandler050a0587b7544547bf325f094a3db834/Role', [
45
+ {
46
+ id: 'AwsSolutions-IAM4',
47
+ reason: 'CDK-managed BucketNotificationsHandler requires AWSLambdaBasicExecutionRole for S3 event processing',
48
+ appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'],
49
+ },
50
+ ]);
51
+ // Suppress S3 bucket wildcard permissions for Lambda roles
52
+ cdk_nag_1.NagSuppressions.addResourceSuppressions(stack, [
53
+ {
54
+ id: 'AwsSolutions-IAM5',
55
+ reason: 'Lambda functions require wildcard access to S3 bucket objects for document processing',
56
+ appliesTo: ['Resource::<AgenticDocumentProcessingBucketC4E254EC.Arn>/*'],
57
+ },
58
+ ], true);
59
+ // Suppress Bedrock foundation model wildcard permissions
60
+ cdk_nag_1.NagSuppressions.addResourceSuppressions(stack, [
61
+ {
62
+ id: 'AwsSolutions-IAM5',
63
+ reason: 'Cross-region inference requires wildcard region access to Bedrock foundation models',
64
+ appliesTo: ['Resource::arn:aws:bedrock:*::foundation-model/anthropic.claude-3-7-sonnet-20250219-v1:0'],
65
+ },
66
+ ], true);
67
+ // Suppress SQSConsumerRole wildcard permissions for Lambda log streams
68
+ cdk_nag_1.NagSuppressions.addResourceSuppressions(stack, [
69
+ {
70
+ id: 'AwsSolutions-IAM5',
71
+ reason: 'Lambda log stream ARN is only known at runtime, wildcard required for CloudWatch Logs access',
72
+ },
73
+ ], true);
74
+ // Suppress StateMachineRole wildcard permissions for Lambda invocation
75
+ cdk_nag_1.NagSuppressions.addResourceSuppressionsByPath(stack, '/TestStack/AgenticDocumentProcessing/StateMachineRole/DefaultPolicy', [
76
+ {
77
+ id: 'AwsSolutions-IAM5',
78
+ reason: 'Step Functions requires wildcard permissions to invoke Lambda functions with version-specific ARNs',
79
+ },
80
+ ]);
81
+ // Suppress Lambda log group wildcard permissions
82
+ cdk_nag_1.NagSuppressions.addResourceSuppressions(stack, [
83
+ {
84
+ id: 'AwsSolutions-IAM5',
85
+ reason: 'Lambda log stream names are generated at runtime, wildcard required for CloudWatch Logs access',
86
+ },
87
+ ], true);
88
+ // Apply CDK Nag checks
89
+ aws_cdk_lib_1.Aspects.of(app).add(new cdk_nag_1.AwsSolutionsChecks({ verbose: true }));
90
+ // Synthesize the stack and check for unsuppressed warnings and errors
91
+ const warnings = assertions_1.Annotations.fromStack(stack).findWarning('*', assertions_1.Match.stringLikeRegexp('AwsSolutions-.*'));
92
+ const errors = assertions_1.Annotations.fromStack(stack).findError('*', assertions_1.Match.stringLikeRegexp('AwsSolutions-.*'));
93
+ // Test: No unsuppressed warnings
94
+ test('No unsuppressed warnings', () => {
95
+ if (warnings.length > 0) {
96
+ console.log('CDK Nag Warnings:', JSON.stringify(warnings, null, 2));
97
+ }
98
+ expect(warnings).toHaveLength(0);
99
+ });
100
+ // Test: No unsuppressed errors
101
+ test('No unsuppressed errors', () => {
102
+ if (errors.length > 0) {
103
+ console.log('CDK Nag Errors:', JSON.stringify(errors, null, 2));
104
+ }
105
+ expect(errors).toHaveLength(0);
106
+ });
107
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnRpYy1kb2N1bWVudC1wcm9jZXNzaW5nLW5hZy50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdXNlLWNhc2VzL2RvY3VtZW50LXByb2Nlc3NpbmcvdGVzdHMvYWdlbnRpYy1kb2N1bWVudC1wcm9jZXNzaW5nLW5hZy50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsNkNBQWtEO0FBQ2xELHVEQUE0RDtBQUM1RCwrQ0FBNEM7QUFDNUMscUNBQThEO0FBQzlELCtDQUE0QztBQUM1QyxnRkFBMkU7QUFFM0UsdUJBQXVCO0FBQ3ZCLE1BQU0sR0FBRyxHQUFHLElBQUksaUJBQUcsRUFBRSxDQUFDO0FBQ3RCLE1BQU0sS0FBSyxHQUFHLElBQUksbUJBQUssQ0FBQyxHQUFHLEVBQUUsV0FBVyxFQUFFO0lBQ3hDLEdBQUcsRUFBRTtRQUNILE9BQU8sRUFBRSxjQUFjO1FBQ3ZCLE1BQU0sRUFBRSxXQUFXO0tBQ3BCO0NBQ0YsQ0FBQyxDQUFDO0FBRUgsTUFBTSxTQUFTLEdBQUcsSUFBSSxxQkFBUyxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsQ0FBQztBQUVwRCxxQ0FBcUM7QUFDckMsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFNLENBQUMsS0FBSyxFQUFFLGlDQUFpQyxFQUFFO0lBQ2xFLHNCQUFzQixFQUFFLFNBQVMsQ0FBQyxNQUFNO0lBQ3hDLHNCQUFzQixFQUFFLFNBQVMsQ0FBQyxZQUFZO0lBQzlDLFVBQVUsRUFBRSxJQUFJO0NBQ2pCLENBQUMsQ0FBQztBQUVILHNEQUFzRDtBQUN0RCxJQUFJLHVEQUF5QixDQUFDLEtBQUssRUFBRSwyQkFBMkIsRUFBRTtJQUNoRSxNQUFNO0lBQ04sdUJBQXVCLEVBQUUsSUFBSTtJQUM3Qix5QkFBeUIsRUFBRTtRQUN6QixpQkFBaUIsRUFBRTs7S0FFbEI7UUFDRCxhQUFhLEVBQUU7WUFDYixRQUFRLE1BQU0sQ0FBQyxVQUFVLG1DQUFtQztZQUM1RCxRQUFRLE1BQU0s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QVMsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakMsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBcHAsIFN0YWNrLCBBc3BlY3RzIH0gZnJvbSAnYXdzLWNkay1saWInO1xuaW1wb3J0IHsgQW5ub3RhdGlvbnMsIE1hdGNoIH0gZnJvbSAnYXdzLWNkay1saWIvYXNzZXJ0aW9ucyc7XG5pbXBvcnQgeyBCdWNrZXQgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtczMnO1xuaW1wb3J0IHsgQXdzU29sdXRpb25zQ2hlY2tzLCBOYWdTdXBwcmVzc2lvbnMgfSBmcm9tICdjZGstbmFnJztcbmltcG9ydCB7IEFjY2Vzc0xvZyB9IGZyb20gJy4uLy4uL2ZyYW1ld29yayc7XG5pbXBvcnQgeyBBZ2VudGljRG9jdW1lbnRQcm9jZXNzaW5nIH0gZnJvbSAnLi4vYWdlbnRpYy1kb2N1bWVudC1wcm9jZXNzaW5nJztcblxuLy8gQ3JlYXRlIGFwcCBhbmQgc3RhY2tcbmNvbnN0IGFwcCA9IG5ldyBBcHAoKTtcbmNvbnN0IHN0YWNrID0gbmV3IFN0YWNrKGFwcCwgJ1Rlc3RTdGFjaycsIHtcbiAgZW52OiB7XG4gICAgYWNjb3VudDogJzEyMzQ1Njc4OTAxMicsXG4gICAgcmVnaW9uOiAndXMtZWFzdC0xJyxcbiAgfSxcbn0pO1xuXG5jb25zdCBhY2Nlc3NMb2cgPSBuZXcgQWNjZXNzTG9nKHN0YWNrLCAnQWNjZXNzTG9nJyk7XG5cbi8vIENyZWF0ZSBTMyBidWNrZXQgZm9yIGFnZW50aWMgdG9vbHNcbmNvbnN0IGJ1Y2tldCA9IG5ldyBCdWNrZXQoc3RhY2ssICdBZ2VudGljRG9jdW1lbnRQcm9jZXNzaW5nQnVja2V0Jywge1xuICBzZXJ2ZXJBY2Nlc3NMb2dzQnVja2V0OiBhY2Nlc3NMb2cuYnVja2V0LFxuICBzZXJ2ZXJBY2Nlc3NMb2dzUHJlZml4OiBhY2Nlc3NMb2cuYnVja2V0UHJlZml4LFxuICBlbmZvcmNlU1NMOiB0cnVlLFxufSk7XG5cbi8vIENyZWF0ZSB0aGUgbWFpbiBBZ2VudGljRG9jdW1lbnRQcm9jZXNzaW5nIGNvbnN0cnVjdFxubmV3IEFnZW50aWNEb2N1bWVudFByb2Nlc3Npbmcoc3RhY2ssICdBZ2VudGljRG9jdW1lbnRQcm9jZXNzaW5nJywge1xuICBidWNrZXQsXG4gIHVzZUNyb3NzUmVnaW9uSW5mZXJlbmNlOiB0cnVlLFxuICBwcm9jZXNzaW5nQWdlbnRQYXJhbWV0ZXJzOiB7XG4gICAgYWdlbnRTeXN0ZW1Qcm9tcHQ6IGBcbiAgICAgIFlvdSdyZSBhbiBpbnN1cmFuY2UgY2xhaW1zIHNwZWNpYWxpc3QuIFVzZSB0aGUgcHJvdmlkZWQgdG9vbHMgdG8gZW5zdXJlIHRoYXQgdGhlIHN1Ym1pdHRlZCBjbGFpbXMgYW5kIHN1cHBvcnRpbmcgZG9jdW1lbnRzIGFyZSB2YWxpZCBhbmQgdGhlcmUgYXJlIG5vIGRpc2NyZXBhbmNpZXMuXG4gICAgYCxcbiAgICB0b29sc0xvY2F0aW9uOiBbXG4gICAgICBgczM6Ly8ke2J1Y2tldC5idWNrZXROYW1lfS9hZ2VudGljLXRvb2xzL2Rvd25sb2FkX3BvbGljeS5weWAsXG4gICAgICBgczM6Ly8ke2J1Y2tldC5idWNrZXROYW1lfS9hZ2VudGljLXRvb2xzL2Rvd25sb2FkX3N1cHBvcnRpbmdfZG9jdW1lbnRzLnB5YCxcbiAgICBdLFxuICB9LFxuICBwcm9jZXNzaW5nUHJvbXB0OiBgXG4gICAgQW5hbHl6ZSB0aGUgYXR0YWNoZWQgaW5zdXJhbmNlIGNsYWltIGRvY3VtZW50IGFuZCBjaGVjayBpZiB0aGlzIGlzIGEgdmFsaWQgY2xhaW0gb3Igbm90LlxuICAgIEZpbmFsIG91dHB1dCBzaG91bGQgaW4gSlNPTiBmb3JtYXQgd2l0aCBjbGFpbV9hcHByb3ZlZCBhbmQganVzdGlmaWNhdGlvbiBmaWVsZHMuXG4gIGAsXG4gIGVuYWJsZU9ic2VydmFiaWxpdHk6IHRydWUsXG59KTtcblxuLy8gU3VwcHJlc3MgQ0RLLW1hbmFnZWQgQnVja2V0Tm90aWZpY2F0aW9uc0hhbmRsZXIgQVdTIG1hbmFnZWQgcG9saWN5XG5OYWdTdXBwcmVzc2lvbnMuYWRkUmVzb3VyY2VTdXBwcmVzc2lvbnNCeVBhdGgoXG4gIHN0YWNrLFxuICAnL1Rlc3RTdGFjay9CdWNrZXROb3RpZmljYXRpb25zSGFuZGxlcjA1MGEwNTg3Yjc1NDQ1NDdiZjMyNWYwOTRhM2RiODM0L1JvbGUnLFxuICBbXG4gICAge1xuICAgICAgaWQ6ICdBd3NTb2x1dGlvbnMtSUFNNCcsXG4gICAgICByZWFzb246ICdDREstbWFuYWdlZCBCdWNrZXROb3RpZmljYXRpb25zSGFuZGxlciByZXF1aXJlcyBBV1NMYW1iZGFCYXNpY0V4ZWN1dGlvblJvbGUgZm9yIFMzIGV2ZW50IHByb2Nlc3NpbmcnLFxuICAgICAgYXBwbGllc1RvOiBbJ1BvbGljeTo6YXJuOjxBV1M6OlBhcnRpdGlvbj46aWFtOjphd3M6cG9saWN5L3NlcnZpY2Utcm9sZS9BV1NMYW1iZGFCYXNpY0V4ZWN1dGlvblJvbGUnXSxcbiAgICB9LFxuICBdLFxuKTtcblxuLy8gU3VwcHJlc3MgUzMgYnVja2V0IHdpbGRjYXJkIHBlcm1pc3Npb25zIGZvciBMYW1iZGEgcm9sZXNcbk5hZ1N1cHByZXNzaW9ucy5hZGRSZXNvdXJjZVN1cHByZXNzaW9ucyhcbiAgc3RhY2ssXG4gIFtcbiAgICB7XG4gICAgICBpZDogJ0F3c1NvbHV0aW9ucy1JQU01JyxcbiAgICAgIHJlYXNvbjogJ0xhbWJkYSBmdW5jdGlvbnMgcmVxdWlyZSB3aWxkY2FyZCBhY2Nlc3MgdG8gUzMgYnVja2V0IG9iamVjdHMgZm9yIGRvY3VtZW50IHByb2Nlc3NpbmcnLFxuICAgICAgYXBwbGllc1RvOiBbJ1Jlc291cmNlOjo8QWdlbnRpY0RvY3VtZW50UHJvY2Vzc2luZ0J1Y2tldEM0RTI1NEVDLkFybj4vKiddLFxuICAgIH0sXG4gIF0sXG4gIHRydWUsXG4pO1xuXG4vLyBTdXBwcmVzcyBCZWRyb2NrIGZvdW5kYXRpb24gbW9kZWwgd2lsZGNhcmQgcGVybWlzc2lvbnNcbk5hZ1N1cHByZXNzaW9ucy5hZGRSZXNvdXJjZVN1cHByZXNzaW9ucyhcbiAgc3RhY2ssXG4gIFtcbiAgICB7XG4gICAgICBpZDogJ0F3c1NvbHV0aW9ucy1JQU01JyxcbiAgICAgIHJlYXNvbjogJ0Nyb3NzLXJlZ2lvbiBpbmZlcmVuY2UgcmVxdWlyZXMgd2lsZGNhcmQgcmVnaW9uIGFjY2VzcyB0byBCZWRyb2NrIGZvdW5kYXRpb24gbW9kZWxzJyxcbiAgICAgIGFwcGxpZXNUbzogWydSZXNvdXJjZTo6YXJuOmF3czpiZWRyb2NrOio6OmZvdW5kYXRpb24tbW9kZWwvYW50aHJvcGljLmNsYXVkZS0zLTctc29ubmV0LTIwMjUwMjE5LXYxOjAnXSxcbiAgICB9LFxuICBdLFxuICB0cnVlLFxuKTtcblxuLy8gU3VwcHJlc3MgU1FTQ29uc3VtZXJSb2xlIHdpbGRjYXJkIHBlcm1pc3Npb25zIGZvciBMYW1iZGEgbG9nIHN0cmVhbXNcbk5hZ1N1cHByZXNzaW9ucy5hZGRSZXNvdXJjZVN1cHByZXNzaW9ucyhcbiAgc3RhY2ssXG4gIFtcbiAgICB7XG4gICAgICBpZDogJ0F3c1NvbHV0aW9ucy1JQU01JyxcbiAgICAgIHJlYXNvbjogJ0xhbWJkYSBsb2cgc3RyZWFtIEFSTiBpcyBvbmx5IGtub3duIGF0IHJ1bnRpbWUsIHdpbGRjYXJkIHJlcXVpcmVkIGZvciBDbG91ZFdhdGNoIExvZ3MgYWNjZXNzJyxcbiAgICB9LFxuICBdLFxuICB0cnVlLFxuKTtcblxuLy8gU3VwcHJlc3MgU3RhdGVNYWNoaW5lUm9sZSB3aWxkY2FyZCBwZXJtaXNzaW9ucyBmb3IgTGFtYmRhIGludm9jYXRpb25cbk5hZ1N1cHByZXNzaW9ucy5hZGRSZXNvdXJjZVN1cHByZXNzaW9uc0J5UGF0aChcbiAgc3RhY2ssXG4gICcvVGVzdFN0YWNrL0FnZW50aWNEb2N1bWVudFByb2Nlc3NpbmcvU3RhdGVNYWNoaW5lUm9sZS9EZWZhdWx0UG9saWN5JyxcbiAgW1xuICAgIHtcbiAgICAgIGlkOiAnQXdzU29sdXRpb25zLUlBTTUnLFxuICAgICAgcmVhc29uOiAnU3RlcCBGdW5jdGlvbnMgcmVxdWlyZXMgd2lsZGNhcmQgcGVybWlzc2lvbnMgdG8gaW52b2tlIExhbWJkYSBmdW5jdGlvbnMgd2l0aCB2ZXJzaW9uLXNwZWNpZmljIEFSTnMnLFxuICAgIH0sXG4gIF0sXG4pO1xuXG4vLyBTdXBwcmVzcyBMYW1iZGEgbG9nIGdyb3VwIHdpbGRjYXJkIHBlcm1pc3Npb25zXG5OYWdTdXBwcmVzc2lvbnMuYWRkUmVzb3VyY2VTdXBwcmVzc2lvbnMoXG4gIHN0YWNrLFxuICBbXG4gICAge1xuICAgICAgaWQ6ICdBd3NTb2x1dGlvbnMtSUFNNScsXG4gICAgICByZWFzb246ICdMYW1iZGEgbG9nIHN0cmVhbSBuYW1lcyBhcmUgZ2VuZXJhdGVkIGF0IHJ1bnRpbWUsIHdpbGRjYXJkIHJlcXVpcmVkIGZvciBDbG91ZFdhdGNoIExvZ3MgYWNjZXNzJyxcbiAgICB9LFxuICBdLFxuICB0cnVlLFxuKTtcblxuXG4vLyBBcHBseSBDREsgTmFnIGNoZWNrc1xuQXNwZWN0cy5vZihhcHApLmFkZChuZXcgQXdzU29sdXRpb25zQ2hlY2tzKHsgdmVyYm9zZTogdHJ1ZSB9KSk7XG5cbi8vIFN5bnRoZXNpemUgdGhlIHN0YWNrIGFuZCBjaGVjayBmb3IgdW5zdXBwcmVzc2VkIHdhcm5pbmdzIGFuZCBlcnJvcnNcbmNvbnN0IHdhcm5pbmdzID0gQW5ub3RhdGlvbnMuZnJvbVN0YWNrKHN0YWNrKS5maW5kV2FybmluZygnKicsIE1hdGNoLnN0cmluZ0xpa2VSZWdleHAoJ0F3c1NvbHV0aW9ucy0uKicpKTtcbmNvbnN0IGVycm9ycyA9IEFubm90YXRpb25zLmZyb21TdGFjayhzdGFjaykuZmluZEVycm9yKCcqJywgTWF0Y2guc3RyaW5nTGlrZVJlZ2V4cCgnQXdzU29sdXRpb25zLS4qJykpO1xuXG4vLyBUZXN0OiBObyB1bnN1cHByZXNzZWQgd2FybmluZ3NcbnRlc3QoJ05vIHVuc3VwcHJlc3NlZCB3YXJuaW5ncycsICgpID0+IHtcbiAgaWYgKHdhcm5pbmdzLmxlbmd0aCA+IDApIHtcbiAgICBjb25zb2xlLmxvZygnQ0RLIE5hZyBXYXJuaW5nczonLCBKU09OLnN0cmluZ2lmeSh3YXJuaW5ncywgbnVsbCwgMikpO1xuICB9XG4gIGV4cGVjdCh3YXJuaW5ncykudG9IYXZlTGVuZ3RoKDApO1xufSk7XG5cbi8vIFRlc3Q6IE5vIHVuc3VwcHJlc3NlZCBlcnJvcnNcbnRlc3QoJ05vIHVuc3VwcHJlc3NlZCBlcnJvcnMnLCAoKSA9PiB7XG4gIGlmIChlcnJvcnMubGVuZ3RoID4gMCkge1xuICAgIGNvbnNvbGUubG9nKCdDREsgTmFnIEVycm9yczonLCBKU09OLnN0cmluZ2lmeShlcnJvcnMsIG51bGwsIDIpKTtcbiAgfVxuICBleHBlY3QoZXJyb3JzKS50b0hhdmVMZW5ndGgoMCk7XG59KTtcbiJdfQ==