2026-04-13 14:33:53 -04:00
|
|
|
import * as cdk from 'aws-cdk-lib';
|
|
|
|
|
import { Construct } from 'constructs';
|
|
|
|
|
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
|
|
|
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
|
2026-04-30 16:38:54 -04:00
|
|
|
import * as logs from 'aws-cdk-lib/aws-logs';
|
2026-04-13 14:33:53 -04:00
|
|
|
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
|
|
|
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
2026-06-09 12:33:06 -04:00
|
|
|
import * as kms from 'aws-cdk-lib/aws-kms';
|
|
|
|
|
import * as ssm from 'aws-cdk-lib/aws-ssm';
|
2026-04-13 14:33:53 -04:00
|
|
|
import * as events from 'aws-cdk-lib/aws-events';
|
|
|
|
|
import * as targets from 'aws-cdk-lib/aws-events-targets';
|
|
|
|
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
|
|
|
|
|
export interface PoSyncProps {
|
|
|
|
|
region: string;
|
|
|
|
|
kbDocsBucket: s3.Bucket;
|
|
|
|
|
knowledgeBaseId: string;
|
|
|
|
|
dataSourceId: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class PoSyncConstruct extends Construct {
|
|
|
|
|
public readonly syncLambda: lambdaNodejs.NodejsFunction;
|
|
|
|
|
|
|
|
|
|
constructor(scope: Construct, id: string, props: PoSyncProps) {
|
|
|
|
|
super(scope, id);
|
|
|
|
|
|
|
|
|
|
// Import the purchase-orders DynamoDB table (owned by po-ingest stack)
|
|
|
|
|
const poTable = dynamodb.Table.fromTableName(
|
|
|
|
|
this, 'PurchaseOrdersTable', 'purchase-orders',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Lambda — scans purchase-orders table, uploads markdown to S3, triggers KB ingestion
|
|
|
|
|
this.syncLambda = new lambdaNodejs.NodejsFunction(this, 'SyncLambda', {
|
|
|
|
|
functionName: 'seahaven-po-sync',
|
|
|
|
|
entry: path.join(__dirname, '../../lambda/po-sync/index.ts'),
|
2026-07-04 05:26:03 +00:00
|
|
|
runtime: lambda.Runtime.NODEJS_24_X,
|
2026-04-30 16:38:54 -04:00
|
|
|
architecture: lambda.Architecture.ARM_64,
|
|
|
|
|
logRetention: logs.RetentionDays.TWO_MONTHS,
|
2026-04-13 14:33:53 -04:00
|
|
|
memorySize: 512,
|
2026-04-13 15:36:07 -04:00
|
|
|
timeout: cdk.Duration.minutes(15),
|
2026-04-13 14:33:53 -04:00
|
|
|
environment: {
|
|
|
|
|
PO_TABLE: poTable.tableName,
|
|
|
|
|
KB_BUCKET_NAME: props.kbDocsBucket.bucketName,
|
|
|
|
|
KNOWLEDGE_BASE_ID: props.knowledgeBaseId,
|
|
|
|
|
DATA_SOURCE_ID: props.dataSourceId,
|
|
|
|
|
REGION: props.region,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Grant read access to the purchase-orders table
|
|
|
|
|
poTable.grantReadData(this.syncLambda);
|
|
|
|
|
|
2026-06-09 12:33:06 -04:00
|
|
|
// purchase-orders is SSE-encrypted with the shared customer-managed CMK
|
|
|
|
|
// (INFRA-95 / M-3). The table is imported by name, so grantReadData does not
|
|
|
|
|
// add KMS perms — grant kms:Decrypt explicitly or scans fail with
|
|
|
|
|
// AccessDenied. (CMK key policy delegates to IAM via kms:ViaService.)
|
|
|
|
|
const dynamodbCmk = kms.Key.fromKeyArn(
|
|
|
|
|
this,
|
|
|
|
|
'DynamoDbCmk',
|
|
|
|
|
ssm.StringParameter.valueForStringParameter(this, '/seahaven/dynamodb/cmk-arn'),
|
|
|
|
|
);
|
|
|
|
|
dynamodbCmk.grantDecrypt(this.syncLambda);
|
|
|
|
|
|
2026-04-13 14:33:53 -04:00
|
|
|
// Grant read/write to the KB docs bucket (read to list+delete old, write new)
|
|
|
|
|
props.kbDocsBucket.grantReadWrite(this.syncLambda);
|
|
|
|
|
|
|
|
|
|
// Allow Lambda to start a Bedrock KB ingestion job
|
|
|
|
|
this.syncLambda.addToRolePolicy(
|
|
|
|
|
new iam.PolicyStatement({
|
|
|
|
|
actions: ['bedrock:StartIngestionJob'],
|
|
|
|
|
resources: [
|
|
|
|
|
`arn:aws:bedrock:${props.region}:*:knowledge-base/${props.knowledgeBaseId}`,
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// EventBridge rule — fires daily at 02:00 UTC
|
|
|
|
|
const dailyRule = new events.Rule(this, 'DailySyncRule', {
|
|
|
|
|
ruleName: 'seahaven-po-daily-sync',
|
|
|
|
|
description: 'Daily purchase-orders → KB sync at 02:00 UTC',
|
|
|
|
|
schedule: events.Schedule.cron({ minute: '0', hour: '2' }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dailyRule.addTarget(new targets.LambdaFunction(this.syncLambda));
|
|
|
|
|
}
|
|
|
|
|
}
|