Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions arbiter/fabricator/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,9 @@ def store_agent_config_registry_bound(
)
raise

# Fallback: ensure progress is published even if the LLM didn't call complete_task
publish_intake_progress(orchestration_id, agent_index, total_agents, agent_use_id)


def lambda_handler(event, context):
print(f"processing event {event}")
Expand Down
15 changes: 13 additions & 2 deletions arbiter/seedConfig/cfnresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@

json_responseBody = json.dumps(responseBody)

print("Response body:")
print(json_responseBody)
# Do not log the full response body: the 'Data' field may contain sensitive
# values (generated secrets, passwords, ARNs). Log only non-sensitive metadata.
print(
"Response status: {}; physicalResourceId={}; stackId={}; requestId={}; "
"logicalResourceId={}; dataKeys={}".format(
responseStatus,
responseBody['PhysicalResourceId'],
event['StackId'],
event['RequestId'],
event['LogicalResourceId'],
list(responseData.keys()) if isinstance(responseData, dict) else [],
)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
Comment on lines +33 to +41
)

headers = {
'content-type': '',
Expand Down
9 changes: 5 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@aws-sdk/client-opensearchserverless": "^3.1007.0",
"@aws-sdk/client-rds": "^3.1007.0",
"@aws-sdk/client-redshift": "^3.1007.0",
"@aws-sdk/client-s3": "^3.400.0",
"@aws-sdk/client-s3": "^3.1070.0",
"@aws-sdk/client-s3tables": "^3.1007.0",
"@aws-sdk/client-sagemaker": "^3.1007.0",
"@aws-sdk/client-secrets-manager": "^3.955.0",
Expand All @@ -59,12 +59,12 @@
"@aws-sdk/client-timestream-write": "^3.1007.0",
"@aws-sdk/credential-provider-node": "^3.400.0",
"@aws-sdk/lib-dynamodb": "^3.400.0",
"@aws-sdk/s3-request-presigner": "^3.400.0",
"@aws-sdk/s3-request-presigner": "^3.1070.0",
"@aws-sdk/signature-v4": "^3.300.0",
"@databricks/sql": "^1.12.0",
"@smithy/protocol-http": "^3.0.0",
"ajv": "^6.14.0",
"aws-cdk-lib": "^2.100.0",
"aws-cdk-lib": "^2.260.0",
"aws-lambda": "^1.0.7",
"constructs": "^10.3.0",
"jsonwebtoken": "^9.0.2",
Expand All @@ -73,14 +73,15 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@smithy/util-stream": "^4.5.25",
"@types/aws-lambda": "^8.10.119",
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^20.19.33",
"@types/uuid": "^9.0.4",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"aws-cdk": "^2.1118.4",
"aws-cdk": "^2.1128.0",
"aws-sdk-client-mock": "^4.1.0",
"cdk-nag": "^2.38.1",
"esbuild": "^0.25.11",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@
*/

import * as fc from 'fast-check';
import { STSClient } from '@aws-sdk/client-sts';
import { mockClient } from 'aws-sdk-client-mock';
import { processHealthChecks, HealthCheckResult } from '../health-monitor';

// processHealthChecks assumes a scoped STS role per store (GetCallerIdentity +
// AssumeRole). With no real AWS credentials in the test environment, the
// un-mocked STS client hangs on credential resolution (IMDS) and retries,
// blowing past Jest's 30s timeout. Mock STS so those calls resolve instantly;
// the assumed credentials are irrelevant to the permission-error invariants
// under test (driven by the per-store testConnection mocks below).
const stsMock = mockClient(STSClient);

beforeEach(() => {
stsMock.reset();
stsMock.onAnyCommand().resolves({});
});

// ---- Generators ----

const dataStoreIdArb = fc.stringMatching(/^ds-[a-z0-9]{8}$/);
Expand Down
16 changes: 16 additions & 0 deletions backend/src/lambda/__tests__/health-monitor.property.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@
// Validates: Requirements 6.3, 6.4, 10.4

import * as fc from 'fast-check';
import { STSClient } from '@aws-sdk/client-sts';
import { mockClient } from 'aws-sdk-client-mock';

// Import the testable health check logic (to be implemented)
import { processHealthChecks, HealthCheckResult } from '../health-monitor';

// processHealthChecks assumes a scoped STS role per store (GetCallerIdentity +
// AssumeRole). With no real AWS credentials in the test environment, the
// un-mocked STS client hangs on credential resolution (IMDS) and retries,
// which blows past Jest's 30s timeout and leaks open handles. Mock STS so
// those calls resolve instantly. The assumed credentials are irrelevant to
// the idempotency invariant under test — the per-store testConnection mocks
// (wired below) are what drive the property assertions.
const stsMock = mockClient(STSClient);

beforeEach(() => {
stsMock.reset();
stsMock.onAnyCommand().resolves({});
});

// ---- Generators ----

const dataStoreIdArb = fc.stringMatching(/^ds-[a-z0-9]{8}$/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { SSMClient } from '@aws-sdk/client-ssm';
import { BedrockAgentCoreControlClient } from '@aws-sdk/client-bedrock-agentcore-control';
import { EventBridgeClient } from '@aws-sdk/client-eventbridge';
import { mockClient } from 'aws-sdk-client-mock';

// Mock AWS SDK clients
const dynamoMock = mockClient(DynamoDBDocumentClient);
const secretsMock = mockClient(SecretsManagerClient);
const ssmMock = mockClient(SSMClient);
const bedrockAgentMock = mockClient(BedrockAgentCoreControlClient);
// createIntegration / deleteIntegration emit EventBridge events via
// `eventBridge.send(PutEventsCommand)`. Left un-mocked, that real client hangs
// on credential resolution (IMDS) and retries, pushing this 100-run property
// over Jest's 30s timeout under full-suite worker contention. Mock it so the
// event handoff resolves instantly — no assertion in these properties inspects
// EventBridge, so the invariants are unchanged.
const eventBridgeMock = mockClient(EventBridgeClient);

// Import the handler after mocking
import { handler } from '../integration-resolver';
Expand All @@ -28,6 +36,8 @@ describe('Integration Resolver - Property-Based Tests', () => {
secretsMock.reset();
ssmMock.reset();
bedrockAgentMock.reset();
eventBridgeMock.reset();
eventBridgeMock.onAnyCommand().resolves({});

// Set environment variables
process.env.INTEGRATIONS_TABLE = 'test-integrations-table';
Expand Down
15 changes: 13 additions & 2 deletions backend/src/lambda/cognito-secret-handler/cfnresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@

json_responseBody = json.dumps(responseBody)

print("Response body:")
print(json_responseBody)
# Do not log the full response body: the 'Data' field may contain sensitive
# values (generated secrets, passwords, ARNs). Log only non-sensitive metadata.
print(
"Response status: {}; physicalResourceId={}; stackId={}; requestId={}; "
"logicalResourceId={}; dataKeys={}".format(
responseStatus,
responseBody['PhysicalResourceId'],
event['StackId'],
event['RequestId'],
event['LogicalResourceId'],
list(responseData.keys()) if isinstance(responseData, dict) else [],
)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
Comment on lines +33 to +41
)

headers = {
'content-type': '',
Expand Down
56 changes: 36 additions & 20 deletions backend/src/lambda/project-progress-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,49 @@ export const handler = async (event: any) => {
return;
}

// Atomic monotonic update: only advance progress, never regress.
// This prevents concurrent fabrication events from overwriting each other.
const phaseKey = `progress.${phase}`;

let currentPhase = 'CREATED';
if (phase === 'implementation') currentPhase = completionPercentage === 100 ? 'IMPLEMENTATION_COMPLETE' : 'IMPLEMENTATION_IN_PROGRESS';
else if (phase === 'planning') currentPhase = completionPercentage === 100 ? 'PLANNING_COMPLETE' : 'PLANNING_IN_PROGRESS';
else if (phase === 'design') currentPhase = completionPercentage === 100 ? 'DESIGN_COMPLETE' : 'DESIGN_IN_PROGRESS';
else if (phase === 'assessment') currentPhase = completionPercentage === 100 ? 'ASSESSMENT_COMPLETE' : 'ASSESSMENT_IN_PROGRESS';

try {
await client.send(new UpdateCommand({
TableName: projectsTable,
Key: { id: sessionId },
UpdateExpression: 'SET #phase = :pct, progress.currentPhase = :cp, updatedAt = :now',
ConditionExpression: 'attribute_not_exists(#phase) OR #phase < :pct',
ExpressionAttributeNames: { '#phase': phaseKey },
ExpressionAttributeValues: {
':pct': completionPercentage,
':cp': currentPhase,
':now': new Date().toISOString(),
},
}));
} catch (err: any) {
if (err.name === 'ConditionalCheckFailedException') {
console.log(`Skipping stale progress: ${phase}=${completionPercentage}% (already higher)`);
return;
}
throw err;
}

// Recompute overall from the now-updated record
const getResult = await client.send(new GetCommand({
TableName: projectsTable,
Key: { id: sessionId },
}));

const current = getResult.Item?.progress || {};
const assessment = phase === 'assessment' ? completionPercentage : (current.assessment || 0);
const design = phase === 'design' ? completionPercentage : (current.design || 0);
const planning = phase === 'planning' ? completionPercentage : (current.planning || 0);
const implementation = phase === 'implementation' ? completionPercentage : (current.implementation || 0);

const overall = Math.round((assessment + design + planning + implementation) / 4);

let currentPhase = 'CREATED';
if (implementation > 0) currentPhase = implementation === 100 ? 'IMPLEMENTATION_COMPLETE' : 'IMPLEMENTATION_IN_PROGRESS';
else if (planning > 0) currentPhase = planning === 100 ? 'PLANNING_COMPLETE' : 'PLANNING_IN_PROGRESS';
else if (design > 0) currentPhase = design === 100 ? 'DESIGN_COMPLETE' : 'DESIGN_IN_PROGRESS';
else if (assessment > 0) currentPhase = assessment === 100 ? 'ASSESSMENT_COMPLETE' : 'ASSESSMENT_IN_PROGRESS';

const p = getResult.Item?.progress || {};
const overall = Math.round(((p.assessment || 0) + (p.design || 0) + (p.planning || 0) + (p.implementation || 0)) / 4);
await client.send(new UpdateCommand({
TableName: projectsTable,
Key: { id: sessionId },
UpdateExpression: 'SET progress = :p, updatedAt = :now',
ExpressionAttributeValues: {
':p': { overall, assessment, design, planning, implementation, currentPhase },
':now': new Date().toISOString(),
},
UpdateExpression: 'SET progress.overall = :o',
ExpressionAttributeValues: { ':o': overall },
}));

console.log(`Updated ${sessionId}: ${phase}=${completionPercentage}%, overall=${overall}%, currentPhase=${currentPhase}`);
Expand Down
15 changes: 13 additions & 2 deletions backend/src/lambda/seed-admin-user/cfnresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@

json_responseBody = json.dumps(responseBody)

print("Response body:")
print(json_responseBody)
# Do not log the full response body: the 'Data' field may contain sensitive
# values (generated secrets, passwords, ARNs). Log only non-sensitive metadata.
print(
"Response status: {}; physicalResourceId={}; stackId={}; requestId={}; "
"logicalResourceId={}; dataKeys={}".format(
responseStatus,
responseBody['PhysicalResourceId'],
event['StackId'],
event['RequestId'],
event['LogicalResourceId'],
list(responseData.keys()) if isinstance(responseData, dict) else [],
)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
Comment on lines +33 to +41
)

headers = {
'content-type': '',
Expand Down
15 changes: 13 additions & 2 deletions backend/src/lambda/seed-organizations/cfnresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@

json_responseBody = json.dumps(responseBody)

print("Response body:")
print(json_responseBody)
# Do not log the full response body: the 'Data' field may contain sensitive
# values (generated secrets, passwords, ARNs). Log only non-sensitive metadata.
print(
"Response status: {}; physicalResourceId={}; stackId={}; requestId={}; "
"logicalResourceId={}; dataKeys={}".format(
responseStatus,
responseBody['PhysicalResourceId'],
event['StackId'],
event['RequestId'],
event['LogicalResourceId'],
list(responseData.keys()) if isinstance(responseData, dict) else [],
)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
Comment on lines +33 to +41
)

headers = {
'content-type': '',
Expand Down
15 changes: 13 additions & 2 deletions backend/src/lambda/update-email-templates/cfnresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@

json_responseBody = json.dumps(responseBody)

print("Response body:")
print(json_responseBody)
# Do not log the full response body: the 'Data' field may contain sensitive
# values (generated secrets, passwords, ARNs). Log only non-sensitive metadata.
print(
"Response status: {}; physicalResourceId={}; stackId={}; requestId={}; "
"logicalResourceId={}; dataKeys={}".format(
responseStatus,
responseBody['PhysicalResourceId'],
event['StackId'],
event['RequestId'],
event['LogicalResourceId'],
list(responseData.keys()) if isinstance(responseData, dict) else [],
)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
Comment on lines +33 to +41
)

headers = {
'content-type': '',
Expand Down
5 changes: 2 additions & 3 deletions backend/src/lambda/user-management-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,6 @@ async function adminResendInvitation(event: any, userId: string) {
function generateTemporaryPassword(): string {
const length = 12;
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
const randomBytes = crypto.randomBytes(length);
let password = '';

// Ensure password meets Cognito requirements
Expand All @@ -580,13 +579,13 @@ function generateTemporaryPassword(): string {
password += '!'; // symbol

for (let i = password.length; i < length; i++) {
password += charset.charAt(randomBytes[i] % charset.length);
password += charset.charAt(crypto.randomInt(charset.length));
}

// Shuffle the password using crypto-secure randomness
const arr = password.split('');
for (let i = arr.length - 1; i > 0; i--) {
const j = crypto.randomBytes(1)[0] % (i + 1);
const j = crypto.randomInt(i + 1);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.join('');
Expand Down
Loading
Loading