Connected Communities. Intelligent Operations. Peaceful Living.
AI-powered SaaS platform for smart condominium and community management — simplifying operations, communication, security, and decision-making through intelligent automation.
ZenAndVillage/
├── zenvillage-backend/ # AWS CDK infrastructure + Lambda handlers (Node.js 22 / TypeScript)
├── zenvillage-web/ # React 19 PWA (Vite 8 + TypeScript + Tailwind CSS v4)
├── docs/
│ ├── architecture-guide.md # Engineering architecture (en-US — canonical)
│ ├── architecture-guide.pt_BR.md # Engineering architecture (pt-BR — translation)
│ ├── knowledge-base.md # Domain knowledge (en-US — canonical)
│ ├── knowledge-base.pt_BR.md # Domain knowledge (pt-BR — translation)
│ ├── software-vision.md # Platform requirements & business rules (en-US — canonical)
│ └── software-vision.pt_BR.md # Platform requirements & business rules (pt-BR — translation)
├── CHANGELOG.md
└── CLAUDE.md # Agent behavior rules (read by Claude Code on every session)
This guide covers the full path from a clean workstation to a running staging environment (Phases 0–9). Production deployment follows the same steps with env=prod.
| Tool | Required version | Verify |
|---|---|---|
| Node.js | ≥ 22.12 | node --version |
| npm | ≥ 10 | npm --version |
| AWS CLI | v2.x | aws --version |
| AWS CDK CLI | ≥ 2.200 | cdk --version |
Install the CDK CLI globally if not present:
npm install -g aws-cdkaws configure
# AWS Access Key ID: <your key>
# AWS Secret Access Key: <your secret>
# Default region: us-east-1
# Default output format: jsonRecommended: use a named profile to avoid mixing accounts.
aws configure --profile zenvillage export AWS_PROFILE=zenvillage
Confirm which account you are targeting:
aws sts get-caller-identity --query 'Account' --output text
# Example output: 123456789012cd zenvillage-backend
npm installBootstrap provisions the S3 bucket and IAM roles that CDK needs to deploy assets. This only needs to run once per AWS account + region pair.
cdk bootstrap aws://$(aws sts get-caller-identity --query Account --output text)/us-east-1Expected output: ✅ Environment aws://123456789012/us-east-1 bootstrapped.
Run a dry-run synthesis before deploying to validate the CDK templates:
# From zenvillage-backend/
npm run cdk -- synth --context env=stagingThis generates CloudFormation templates in cdk.out/. No AWS resources are created.
The following stacks will be provisioned:
| Stack | Key resources |
|---|---|
zenvillage-alarms-staging |
SNS topic — receives all CloudWatch alarm notifications |
zenvillage-users-staging |
DynamoDB table zenvillage-users-staging (Pre-Token Lambda + seed) |
zenvillage-cognito-staging |
Cognito User Pool + App Client + Pre-Token Generation Lambda |
zenvillage-api-staging |
HTTP API + WebSocket API + Lambda Authorizer |
zenvillage-frontend-staging |
S3 bucket + CloudFront distribution (OAC) |
zenvillage-tenants-staging |
DynamoDB + 5 Lambda CRUD handlers + CloudWatch alarms |
zenvillage-property-managers-staging |
DynamoDB + 5 Lambda CRUD handlers + CloudWatch alarms |
zenvillage-condominiums-staging |
DynamoDB + 5 Lambda CRUD handlers + CloudWatch alarms |
zenvillage-residents-staging |
DynamoDB + 5 Lambda CRUD handlers + CloudWatch alarms |
zenvillage-employees-staging |
DynamoDB + 5 Lambda CRUD handlers + CloudWatch alarms |
zenvillage-plans-staging |
DynamoDB + plan catalog Lambdas (public list + admin CRUD) |
zenvillage-subscriptions-staging |
DynamoDB + subscription request Lambdas (create + admin approve/reject) |
zenvillage-notifications-staging |
DynamoDB + WebSocket Lambdas + SQS notifier + S3 uploads |
CDK resolves cross-stack dependencies automatically. In particular, zenvillage-users-staging is always deployed before zenvillage-cognito-staging so that the table ARN is available for the IAM grant — no manual two-pass deploy is required.
# From zenvillage-backend/
npm run deploy:stagingThis runs:
cdk deploy --all --require-approval never --context env=staging
Estimated time: 15–25 minutes on a first deploy.
When complete, CDK prints the outputs for each stack. All endpoint URLs and IDs are also written to AWS Systems Manager Parameter Store — you do not need to copy them manually.
Confirm that CDK exported all values the frontend build will need:
aws ssm get-parameters-by-path \
--path "/zenvillage/staging" \
--query 'Parameters[*].[Name,Value]' \
--output tableExpected parameters:
| SSM path | Description |
|---|---|
/zenvillage/staging/api-url |
HTTP API base URL |
/zenvillage/staging/ws-url |
WebSocket API endpoint |
/zenvillage/staging/cognito-pool-id |
Cognito User Pool ID |
/zenvillage/staging/cognito-client-id |
Cognito App Client ID |
/zenvillage/staging/cognito-region |
AWS region (us-east-1) |
/zenvillage/staging/cloudfront-dist-id |
CloudFront distribution ID |
/zenvillage/staging/cloudfront-url |
Public app URL (https://xxxx.cloudfront.net) |
/zenvillage/staging/frontend-bucket-name |
S3 bucket name for frontend assets |
VAPID keys are required for the Web Push notification feature. Generate them once and store the private key securely in AWS Secrets Manager.
npx web-push generate-vapid-keys
# Public Key: BBxxxxxx... (goes to VITE_VAPID_PUBLIC_KEY in the frontend build)
# Private Key: xxxxxxxx... (goes to Secrets Manager — never in code or .env)Store the private key:
aws secretsmanager create-secret \
--name "/zenvillage/staging/vapid-private-key" \
--secret-string "YOUR_VAPID_PRIVATE_KEY_HERE"Keep the public key at hand — you will need it in Phase 7.
cd zenvillage-web
npm install --legacy-peer-deps
# Read backend outputs from SSM
API_URL=$(aws ssm get-parameter --name /zenvillage/staging/api-url --query Parameter.Value --output text)
WS_URL=$(aws ssm get-parameter --name /zenvillage/staging/ws-url --query Parameter.Value --output text | sed 's|^https://|wss://|')
POOL_ID=$(aws ssm get-parameter --name /zenvillage/staging/cognito-pool-id --query Parameter.Value --output text)
CLIENT_ID=$(aws ssm get-parameter --name /zenvillage/staging/cognito-client-id --query Parameter.Value --output text)
BUCKET=$(aws ssm get-parameter --name /zenvillage/staging/frontend-bucket-name --query Parameter.Value --output text)
DIST_ID=$(aws ssm get-parameter --name /zenvillage/staging/cloudfront-dist-id --query Parameter.Value --output text)
# Build — all runtime config injected at build time, nothing hardcoded
VITE_API_BASE_URL=$API_URL \
VITE_WS_ENDPOINT=$WS_URL \
VITE_COGNITO_USER_POOL_ID=$POOL_ID \
VITE_COGNITO_CLIENT_ID=$CLIENT_ID \
VITE_COGNITO_REGION=us-east-1 \
VITE_VAPID_PUBLIC_KEY=YOUR_VAPID_PUBLIC_KEY_HERE \
VITE_APP_VERSION=$(git rev-parse --short HEAD) \
npm run build# From zenvillage-web/
aws s3 sync dist/ s3://$BUCKET/ --deleteaws cloudfront create-invalidation \
--distribution-id $DIST_ID \
--paths "/*"The app is now reachable at the cloudfront-url SSM value:
aws ssm get-parameter --name /zenvillage/staging/cloudfront-url --query Parameter.Value --output textThe seed script reads the Cognito User Pool ID from SSM automatically and populates:
- the public plan catalog —
starter,pro,enterprise(allactive+public); - 4 demo tenants — 3 active + 1 awaiting approval — with matching property managers, condominiums, residents, and employees;
- a Cognito admin user per tenant, a platform admin (the approver), and a pending owner;
- one pending subscription request, so the platform admin has something to approve out of the box.
# From zenvillage-backend/
npm run seed -- --env=stagingFor a clean demo, add
--clean. Because the seed generates fresh UUIDs on every run, repeated runs accumulate duplicate data. The--cleanflag wipes all data tables first (the users table is preserved so Cognito sign-in keeps working):npm run seed -- --env=staging --clean
All accounts share the temporary password ZenV1llage!2026 (change after first login).
| Role | Purpose | |
|---|---|---|
platform-admin@zenvillage.dev |
platform_admin |
Approve/reject subscription requests; manage the plan catalog |
admin-agatha@zenvillage.dev |
tenant_admin |
Management company — 3 condominiums, 5 residents, 4 employees |
admin-vistaverde@zenvillage.dev |
tenant_admin |
Independent condo — subscription in trial |
admin-habitex@zenvillage.dev |
tenant_admin |
Management company — enterprise plan |
pending-owner@zenvillage.dev |
tenant_admin |
Account in pending_approval (Condomínio Aurora) |
After running
--clean, log out and back in before testing: the run generates new tenant IDs, and each JWT only picks up its tenant claim on the next sign-in.
Run these manual checks to confirm the staging environment is healthy before enabling the CI/CD pipeline or inviting users. Use the seeded accounts from Phase 8 (password ZenV1llage!2026). Test in an incognito window so a stale service worker does not serve an old bundle.
# Open the public app URL:
aws ssm get-parameter --name /zenvillage/staging/cloudfront-url --query Parameter.Value --output text| # | Test | What it validates |
|---|---|---|
| 1 | Open cloudfront-url in a browser |
Frontend served from CloudFront; SPA routing works |
| 2 | Log in with admin-agatha@zenvillage.dev |
Cognito auth → Pre-Token Lambda → JWT with tenantId + roles claims |
| 3 | Open Condominiums (3), Property Managers (1), Residents (5), Employees (4) | API Gateway → Lambda Authorizer → tenant-scoped DynamoDB reads |
| 4 | Create a new record | POST with idempotency key; DynamoDB write |
| 5 | Edit and save the record | PATCH; DynamoDB update |
| 6 | Delete the record | DELETE → HTTP 204 |
| 7 | DevTools → Network tab; look for the WebSocket connection | WebSocket API → $connect Lambda → connections table |
| 8 | Trigger any action that sends a notification | EventBridge → SQS → notifier Lambda → WebSocket push |
| 9 | Log out | Cognito sign-out; redirect to /login |
| # | Test | What it validates |
|---|---|---|
| 10 | Log in with platform-admin@zenvillage.dev |
Tenantless authorization (no X-Tenant-Id); platform_admin role |
| 11 | Open Platform Admin → Plans | GET /v1/admin/plans returns the 3 seeded plans |
| 12 | Open Platform Admin → Subscriptions | The pending request for Condomínio Aurora is listed |
| 13 | Approve the pending request (status trial or active) |
POST /v1/subscriptions/{id}/approve; updates tenant + user onboarding status |
| 14 | In a separate incognito window, log in as pending-owner@zenvillage.dev and click Refresh status |
Approval propagates; the owner advances out of pending_approval |
If all checks pass, the staging environment is healthy. ✅
⚠️ Destructive and irreversible. All DynamoDB data, Cognito users, S3 files, and CloudWatch logs will be permanently deleted. Only proceed when you are certain the environment is no longer needed.
CDK cannot delete buckets that still contain objects. The uploads bucket (zenvillage-uploads-staging) is configured with autoDeleteObjects: true, so CDK handles it automatically. Only the frontend bucket needs to be emptied manually:
BUCKET=$(aws ssm get-parameter --name /zenvillage/staging/frontend-bucket-name --query Parameter.Value --output text)
aws s3 rm s3://$BUCKET --recursive# From zenvillage-backend/
npm run cdk -- destroy --all --context env=staging --forceCDK destroys stacks in reverse dependency order. Estimated time: 10–20 minutes.
Note: in
stagingall resources haveRemovalPolicy.DESTROY, so DynamoDB tables, the Cognito User Pool, and the CloudFront distribution are all deleted automatically. Inprod, the Cognito User Pool hasRemovalPolicy.RETAINand must be deleted manually from the AWS Console or CLI after the stack is destroyed.
CDK stacks do not manage Lambda Log Groups — CloudWatch creates them on first invocation and leaves them behind after a destroy. CloudFormation also truncates function names beyond 64 characters, so log group names may not contain the word staging literally.
List everything under the zenvillage- prefix and delete in one pass:
aws logs describe-log-groups \
--log-group-name-prefix "/aws/lambda/zenvillage-" \
--query 'logGroups[*].logGroupName' \
--output text \
| tr '\t' '\n' \
| xargs -I {} aws logs delete-log-group --log-group-name {}aws secretsmanager delete-secret \
--secret-id "/zenvillage/staging/vapid-private-key" \
--force-delete-without-recovery
⚠️ Only do this if you are completely done with CDK in this AWS account and region. The Bootstrap stack is shared — removing it will break any other CDK project targeting the same account/region.
The bootstrap bucket has versioning enabled, which means the AWS CLI --force flag and list-object-versions do not reliably enumerate all versions (objects uploaded before versioning was enabled have a null VersionId and are silently skipped). The safest approach is to empty the bucket via the AWS Console:
- Open https://s3.console.aws.amazon.com
- Click the bucket
cdk-hnb659fds-assets-<account-id>-us-east-1 - Enable Show versions (toggle above the object list)
- Select all objects using the header checkbox
- Click Delete → confirm by typing
permanently delete - Return to the bucket list → select the now-empty bucket → Delete bucket
Then delete the Bootstrap CloudFormation stack:
aws cloudformation delete-stack --stack-name CDKToolkitCloudWatch metrics cannot be deleted via API — they expire automatically (standard 5-minute resolution: 63 days; 1-minute detailed: 15 days). No action needed.
| Resource | Reason | How to remove |
|---|---|---|
| CloudWatch Log Groups | Not managed by CDK stacks | Phase 10c above |
| VAPID secret | Not part of any CDK stack | Phase 10d above |
CDK Bootstrap stack (CDKToolkit) |
Shared across all CDK projects in the account | Phase 10e above — only if done with CDK entirely |
| CloudWatch metrics | AWS does not expose a delete API for metrics | Expire automatically (15 days for 1-min resolution) |
| Cognito User Pool (prod only) | RemovalPolicy.RETAIN |
AWS Console → Cognito → Delete user pool |
| Variable | Stack | Description |
|---|---|---|
TENANTS_TABLE |
tenants | DynamoDB table name |
PROPERTY_MANAGERS_TABLE |
property-managers | DynamoDB table name |
CONDOMINIUMS_TABLE |
condominiums | DynamoDB table name |
RESIDENTS_TABLE |
residents | DynamoDB table name |
EMPLOYEES_TABLE |
employees | DynamoDB table name |
PLANS_TABLE |
plans | DynamoDB table name |
SUBSCRIPTIONS_TABLE |
subscriptions | DynamoDB table name |
NOTIFICATIONS_TABLE |
notifications | DynamoDB table name |
CONNECTIONS_TABLE |
notifications | DynamoDB table name (WebSocket connections) |
UPLOADS_BUCKET |
notifications | S3 bucket for file uploads |
USERS_TABLE |
cognito | DynamoDB table for Pre-Token Lambda |
WS_ENDPOINT |
notifications | WebSocket Management API endpoint |
| Variable | Description |
|---|---|
VITE_API_BASE_URL |
HTTP API base URL |
VITE_WS_ENDPOINT |
WebSocket API endpoint |
VITE_COGNITO_USER_POOL_ID |
Cognito User Pool ID |
VITE_COGNITO_CLIENT_ID |
Cognito App Client ID |
VITE_COGNITO_REGION |
AWS region |
VITE_VAPID_PUBLIC_KEY |
VAPID public key for Web Push |
VITE_APP_VERSION |
Git commit SHA (injected by CI/CD) |
VITE_SENTRY_DSN |
Optional — error tracking DSN |
# CDK commands (from zenvillage-backend/)
npm run cdk -- synth --context env=staging # preview templates
npm run cdk -- diff --context env=staging # diff against deployed state
npm run cdk -- deploy --context env=staging <stack> # deploy a single stack
npm run deploy:staging # deploy all stacks (staging)
npm run deploy:prod # deploy all stacks (prod — requires manual approval)
# Frontend (from zenvillage-web/)
npm run dev # local dev server (http://localhost:3000)
npm run build # production build
npm run preview # preview production build locally
# Seed
npm run seed -- --env=staging # seed staging (adds to existing data)
npm run seed -- --env=staging --clean # wipe data tables first, then seed (clean demo)
npm run seed -- --env=prod # prod (use with care)Monthly cost estimate for a production deployment in us-east-1 across three scale tiers. All figures are derived from AWS public pricing as of May 2026 and cover the services provisioned by the CDK stacks. Actual costs vary with traffic patterns, data transfer destinations, and log volumes.
AWS Free Tier note: a new AWS account includes 12-month free-tier credits (e.g. 1 TB CloudFront egress, 5 GB S3) and always-free allowances (1 M Lambda requests/month, 400K GB-seconds/month, 25 GB DynamoDB storage, 1 M SQS requests/month, first 100K X-Ray traces/month). The estimates below do not apply free-tier credits so they remain accurate after the first year.
| MVP | Small Production | Medium Scale | |
|---|---|---|---|
| Monthly Active Users | 100 | 1,000 | 10,000 |
| Tenants | 3 | 20 | 100 |
| API requests / month | 300 K | 1.5 M | 6 M |
| WebSocket messages / month | 150 K | 750 K | 3 M |
| Async Lambda invocations / month | 60 K | 300 K | 1 M |
| Total Lambda invocations / month | 510 K | 2.55 M | 10 M |
| S3 storage (assets + uploads) | 5 GB | 50 GB | 500 GB |
| CloudFront egress | 5 GB | 50 GB | 500 GB |
| CloudWatch log ingestion | 0.5 GB | 2 GB | 10 GB |
| Lambda memory / avg duration | 512 MB / 200 ms | 512 MB / 200 ms | 512 MB / 200 ms |
| X-Ray sampling rate | 5 % | 5 % | 5 % |
| Service | MVP | Small Production | Medium Scale | Pricing basis |
|---|---|---|---|---|
| AWS Lambda | $0.00 | $0.31 | $11.80 | $0.20 / 1M requests; $0.0000166667 / GB-s |
| API Gateway HTTP API | $0.30 | $1.50 | $6.00 | $1.00 / 1M requests |
| API Gateway WebSocket API | $0.15 | $0.79 | $3.38 | $1.00 / 1M messages; $0.25 / 1M connection-minutes |
| Amazon DynamoDB (on-demand) | $0.40 | $2.50 | $25.00 | $1.25 / 1M WCU; $0.25 / 1M RCU; $0.25 / GB storage |
| Amazon Cognito | $0.00 | $0.00 | $0.00 | Free up to 50K MAU |
| Amazon S3 | $0.20 | $1.85 | $18.50 | $0.023 / GB storage; $0.005 / 1K PUT; $0.0004 / 1K GET |
| Amazon CloudFront | $0.73 | $5.75 | $48.50 | $0.085 / GB egress (US); $0.010 / 10K HTTPS requests |
| Amazon SQS | $0.00 | $0.00 | $0.80 | $0.40 / 1M requests after 1M free |
| Amazon CloudWatch (logs + alarms) | $2.25 | $5.00 | $13.00 | $0.50 / GB ingested; $0.10 / alarm / month |
| AWS X-Ray | $0.00 | $0.00 | $1.00 | $5.00 / 1M traces after 100K free |
| AWS SSM Parameter Store | $0.00 | $0.00 | $0.00 | Standard parameters are free |
| Total | ~$4 / mo | ~$18 / mo | ~$128 / mo |
$4 / mo ─────── MVP (100 MAU, 3 tenants)
$18 / mo ──────── Small Prod (1 K MAU, 20 tenants)
$128 / mo ─────── Medium Scale (10 K MAU, 100 tenants)
The largest cost driver at medium scale is CloudFront egress (~38 %) followed by DynamoDB (~20 %) and Lambda compute (~9 %). At MVP and small-production scale, CloudWatch alarms are the dominant line item — the architecture provisions ~20–80 alarms depending on the number of active Lambda domains.
| Lever | Applicable tier | Expected saving |
|---|---|---|
| Enable DynamoDB provisioned capacity (+ auto-scaling) once traffic is predictable | Medium Scale | 20–50 % on DynamoDB |
| Add CloudFront caching for API GET responses (stale-while-revalidate) | Small Prod + | 30–60 % on API Gateway + Lambda |
| Use CloudWatch Log Groups with 7-day retention for non-audit logs | All tiers | 40–60 % on CloudWatch storage |
| Reduce X-Ray sampling to 1 % in production (adjust in CDK) | Medium Scale | Keeps traces within free tier |
| Compress S3 assets with Brotli/gzip + use S3 Intelligent-Tiering for uploads | Small Prod + | 10–30 % on S3 + CloudFront |
Reserve Lambda concurrency + use Graviton2 (arm64) runtime |
Medium Scale | ~20 % on Lambda compute |
The full architecture — stack, patterns, API contracts, auth, multi-tenancy, data strategy, CI/CD, and UI stack — is documented in docs/architecture-guide.md.