A polished, exec-facing dashboard that shows what the AI platform is doing, what it costs, and whether it is safe — built using the same substrate it watches.
The observability vibe app is the first concrete consumer of the substrate. It reads from the CloudWatch dashboards + log groups deployed in Step 4, renders them in three audience-specific views (operators, exec, audit), and generates a daily narrative summary via Bedrock. Critically, it is itself a vibe app — built on the same patterns the Sales monthly reports app and every future PayCargo internal app will use. The tech leader value is twofold: (1) you can show this to anyone — executive leadership, strategic leadership, an investor, a CISO — and they will understand what the platform does in 60 seconds; (2) it proves the doctrine, because the same audit/encryption/guardrail rules that protect production data also protect the app that watches the platform.
If skipped: Operators and execs continue to read three separate CloudWatch dashboards in the AWS Console. No polished demo for strategic leadership / investors. No proof that vibe apps can be built on the substrate. the external developer next week has no reference implementation.
A web app at https://obs.ai-sandbox.paycargo.com (CloudFront-served). Three audience tabs: Operators, Exec, Audit. Each tab is a single-page view with 4-8 panels. Each panel pulls live data from CloudWatch / CloudTrail / GuardDuty / Bedrock invocation logs via a Lambda backend. A daily 09:00 ET job invokes Bedrock to generate a narrative paragraph ("Yesterday the platform processed 1,247 agent calls across 3 tiers; cost was $4.31; one NFW alert fired against an undocumented destination, investigated and confirmed as test traffic"). The paragraph appears at the top of the Exec tab.
It is itself an agent-assisted application. It reads from the same audit streams every other agent writes to. The same guardrail enforces the narrative generation. The same KMS keys encrypt its data. It is the substrate watching itself.
It is the dashboard you show an investor to prove the AI platform is operating safely, profitably, and visibly. Every chart corresponds to a real AWS resource that costs real money and produces real audit records. Nothing is mocked.
Every box below is a discrete AWS resource Terraform deploys as part of the vibe app module. Each one is priced in § 4.
Hosts index.html, the three tab views, the CSS, and the JS bundle that drives the UI. About 2-5 MB total. Bucket is private — public access disabled. Only CloudFront can read from it via Origin Access Identity (OAI).
ai-sandbox-obs-app-frontendHTTPS termination, asset caching, custom domain. Origin: the S3 bucket above. ACM certificate for obs.ai-sandbox.paycargo.com. WAF deliberately deferred (Phase 2 if the app ever sees external traffic; today it's internal-only and Cognito gates access).
/api/* — bypass cache, forward to Lambda Function URL/* — cache from S3, default TTL 1 day for assets, 60s for HTML
The "open today, lock down later" auth path. PayCargo execs and operators enroll with email+password. MFA is required via TOTP authenticator app (Authy, Google Authenticator, 1Password). No SMS MFA — TOTP is more secure and free. Email domain restricted to @paycargo.com.
software_token_mfa_configuration { enabled = true }mfa_configuration = "ON" (required for all users)auto_verified_attributes = ["email"]
var.enable_manual_enrollment = true (today)false once SSO federation lands and users have migrated. Manual users stay enrolled; new manual signups blocked.
The "real" auth path. Once Identity Center is configured (Phase 2 doctrine), Cognito federates to it via SAML/OIDC. PayCargo SSO becomes the only way in. The Cognito User Pool stays — it just becomes a thin facade over Identity Center for the JS SDK.
var.enable_sso_federation = falseenable_sso_federation = true, point at Identity Center, flip enable_manual_enrollment = false. One identity source. Revoke once = revoke everywhere.
One function, multiple routes. Invoked via Lambda Function URL (no API Gateway — saves cost, faster cold start). Receives the Cognito JWT, validates it, queries CloudWatch / CloudTrail / GuardDuty / Bedrock invocation log group, returns JSON.
GET /api/operators/health — real-time platform healthGET /api/exec/summary — exec dashboard data + narrativeGET /api/audit/query — pre-built Logs Insights queriesGET /api/cost/breakdown — Cost Explorer pull
Cached panel data + the daily narrative. The Lambda backend caches CloudWatch query results for 60 seconds to keep request latency low. The narrative paragraph (generated daily) lives here for fast read.
ai-sandbox-agent-state already exists (Step 4). The obs app uses a partition session_id = "obs-app" for its cached data. This is the doctrine in action — no new table per app, just well-scoped partitions on existing tables.
Once per day at 09:00 ET, EventBridge fires a rule that invokes the Lambda backend's generateNarrative handler. The handler queries the last 24h of platform data, formats a prompt, calls Bedrock (Claude Haiku for speed and cost), gets back a 2-paragraph executive summary, and writes it to the DDB cache.
ai-sandbox-default (Step 4 guardrail). Even though the narrative reads platform metadata (no PII), the call passes through the same guardrail every other agent uses. Doctrine: no agent invocation bypasses the guardrail.
Daily cron at 09:00 ET. Triggers the Lambda's narrative-generation handler. Nothing else. ~30 invocations per month.
Lambda role with the bootstrap permission boundary attached. Permissions: read CloudWatch Logs, read CloudWatch Metrics, read Cost Explorer, read GuardDuty findings, read CloudTrail events, invoke Bedrock with the platform guardrail, read/write the obs-app partition of agent-state DDB, decrypt with audit CMK.
No Fargate, no ECS, no EKS (no containers — Lambda is sufficient). No RDS, no Aurora (no relational data — DDB existing table handles cache). No API Gateway (Lambda Function URL with Cognito JWT auth is cheaper and sufficient). No ElastiCache (60-second DDB cache is fast enough). No WAF (Cognito gates all access; no public endpoints). No CloudFront Functions / Lambda@Edge (no per-request edge compute needed). No Step Functions (single-function workflows). No separate observability stack (the app uses the substrate's CloudWatch + CloudTrail like everything else).
End-to-end, from "the user clicks the bookmark" to "the user sees the Operators dashboard." Every arrow is a real AWS API call that produces a CloudTrail event.
So the "the user opened the dashboard" event itself is part of the audit trail the dashboard displays. Recursive observability in literal form.
| AWS Service | What it's billed for | Unit cost | Volume | Monthly |
|---|---|---|---|---|
| S3 (static hosting) | Storage + GET requests | $0.023/GB-mo + $0.0004/1k GET | 5 MB stored, 100k GETs (cached) | $0.04 |
| CloudFront | Data transfer + requests | $0.085/GB + $0.0075/10k req (US) | ~5 GB out, 100k requests | $0.50 |
| ACM certificate | TLS cert for custom domain | — | 1 cert | Free |
| Route 53 hosted zone | DNS for obs.ai-sandbox.paycargo.com | $0.50/zone/mo + $0.40/M queries | 1 zone, ~10k queries | $0.50 |
| Subtotal: frontend delivery | $1.04 | |||
| Cognito User Pool | Active users (MAU) | $0.0055/MAU after 50k free tier | ~10 MAU | Free (within tier) |
| Cognito TOTP MFA | — | — | All sign-ins | Free |
| Subtotal: auth | $0.00 | |||
| Lambda backend | Requests + GB-seconds | $0.20/M req + $0.0000133/GB-sec (Graviton) | 45k req, ~500ms × 1 GB avg | $0.31 |
| Lambda Function URL | — | — | — | Free |
| DDB on-demand reads | Read request units (eventually consistent) | $0.25/M RRU | ~45k reads | $0.01 |
| DDB on-demand writes | Write request units | $1.25/M WRU | ~300 writes (cache+narrative) | $0.001 |
| Subtotal: API + cache | $0.32 | |||
| EventBridge scheduled rule | Rule invocations | $1.00/M events | 30 events/mo | Free |
| Bedrock (narrative) | Tokens (Claude Haiku 4.5) | $0.25/M input, $1.25/M output | 90k input, 18k output tokens/mo | $0.05 |
| Bedrock Guardrails | Guardrail invocations | $0.075/1k text units | ~30 invocations | Negligible |
| Subtotal: scheduled + AI | $0.05 | |||
| CloudWatch metrics API (read) | GetMetricData calls | $0.01/1k metrics queried | ~30k metrics queried/mo | $0.30 |
| CloudWatch Logs Insights queries | Data scanned | $0.005/GB scanned | ~5 GB/mo | $0.03 |
| Cost Explorer API | Cost & usage report calls | $0.01/call | ~30 calls/mo | $0.30 |
| Lambda audit log writes | CloudWatch log ingestion | $0.50/GB ingested | ~50 MB | $0.03 |
| Subtotal: data reads + audit | $0.66 | |||
| Total monthly run cost (steady state, 10 users) | ~$2.07 | |||
| Rounded planning estimate (with headroom) | $5-10/month | |||
Everything is serverless and idle-zero. When nobody is using the app: zero Lambda invocations, zero DDB requests, zero Bedrock tokens, zero CloudFront traffic (CDN cache serves the cached HTML). Only S3 storage (pennies) and Route 53 ($0.50/mo) bill regardless. The app effectively turns off when nobody is looking at it. A traditional Fargate-based dashboard with a 0.5 vCPU container running 24/7 would cost ~$30/month just to exist, plus an RDS instance at $15/month minimum.
| Users | Monthly cost | Bottleneck |
|---|---|---|
| 10 (today's plan) | ~$2-10 | None — everything is free-tier-adjacent |
| 100 (Phase 2) | ~$15-25 | Lambda invocation count, CloudWatch query data scanned |
| 1,000 (hypothetical product-mode) | ~$80-150 | CloudWatch metric API calls dominate; would refactor to push cached metrics into DDB on a schedule |
| 10,000 (would never deploy this app at that scale) | ~$500-800 | Wholesale rearchitecture: CloudFront-side caching, dedicated metric ingest, possibly EventBridge fan-out |
@paycargo.commfa_configuration = "ON") via TOTP authenticator app — no SMS, no email codes (both phishable)# cartridges/administrative/obs/variables.tf
variable "enable_manual_enrollment" {
description = "Allow Cognito-native email+password+TOTP signup. Flip to false once SSO migration is complete."
type = bool
default = true
}
variable "enable_sso_federation" {
description = "Wire Identity Center as a SAML IdP. Flip to true once Identity Center is configured."
type = bool
default = false
}
Cutover steps:
enable_sso_federation = true, apply — users can now sign in either wayenable_manual_enrollment = false, apply — new manual signups blocked. Existing manual users can sign in but must migrate or be disabled.Identity Center setup is a separate work stream that may take days or weeks to coordinate. The the external developer arrives next week. The exec demo to strategic leadership is imminent. We need the app accessible today, with a credible promise of "this will route through SSO once it's available." Dual path is the only honest way to make that promise.
Question this tab answers: "Is the platform healthy right now?"
Question this tab answers: "What is the platform doing, what is it costing, is it safe?"
Question this tab answers: "Who did what, when, on what data?"
Once a day at 09:00 ET, the Lambda backend's generateNarrative handler fires. It:
session_id=obs-app, item_key=narrative-{date}Example output:
Yesterday the AI platform processed 1,247 agent invocations across 2 active tiers, returning 542k output tokens at a measured cost of $4.31. Activity was steady through the day with a peak around 14:00 ET coinciding with the weekly Sales report generation.
Three notable events: (1) one NFW alert fired against api.example-test.com at 11:34, investigated and confirmed as a the external developer integration test from a documented sandbox flow; (2) GuardDuty reported one Low severity finding regarding console-MFA-disabled, which applies to AWS Identity Center and is by design (Identity Center handles MFA at its layer, not at the AWS console layer); (3) the daily backup of the agent state table completed in 11 seconds. No PII workflows were exercised. No deploys.
The narrative is generated by an agent, against the same Bedrock + guardrail every other agent uses. It is itself an agent invocation that appears in the Bedrock invocation log group, that this very dashboard displays. The recursion is intentional and documented.
The vibe app deploys via the same CI workflow (Step 3a) that deploys the substrate. There is no separate pipeline.
cartridges/administrative/obs/
├── versions.tf
├── variables.tf (5 inputs: substrate references + feature flags)
├── frontend.tf (S3 bucket, OAI, CloudFront, ACM cert, Route 53)
├── auth.tf (Cognito User Pool, manual enrollment, SSO IdP attachment)
├── backend.tf (Lambda function, Function URL, IAM role + policies)
├── eventbridge.tf (daily schedule)
├── outputs.tf (URL, Cognito Pool ID, Lambda ARN — for downstream apps)
├── lambda/
│ ├── package.json
│ ├── handler.js (route handlers + cache logic)
│ └── narrative.js (Bedrock invocation for daily summary)
└── frontend/
├── index.html
├── operators.html
├── exec.html
├── audit.html
├── app.js (Cognito client + fetch wrapper + render)
└── styles.css (matches Sales mockup visual language)
terraform-apply.yml with confirm=APPLYai-sandbox-github-actions-deploy via OIDC, applies the change/index.html and /*.html only (not /static/* which is content-hashed)lambda/ into a zip, updates the functionEvery component of the vibe app produces logs that the vibe app displays. The recursion is deliberate.
| What the app does | Where it shows up in the app |
|---|---|
| A user signs in (Cognito event) | Audit tab → "Recent role assumptions" (Cognito uses AssumeRole under the hood) |
| The Lambda backend invokes a CloudWatch query | Operators tab → Lambda invocation count goes up by 1 |
| The Bedrock narrative is generated | Exec tab → Bedrock invocations chart shows +1; the narrative panel itself displays the output |
| Cost Explorer is queried | Exec tab → MTD cost includes the cost of the Cost Explorer query itself |
| A failed sign-in (e.g., wrong TOTP code) | Audit tab → Cognito failed sign-in event in CloudTrail; Operators tab → Lambda errors count if the JS triggers a 4xx |
This is a useful property for two reasons. First, it proves the substrate is honest — there are no "magic" data sources that aren't real AWS resources. Second, it means the app is self-debugging: if the dashboard isn't updating, the very dashboard tells you why (Lambda errors, throttles, KMS denies).
You asked specifically about Fargate. The choice not to use it is deliberate and worth documenting.
| Concern | Fargate-based design | Serverless design (this) |
|---|---|---|
| Standing cost when idle | ~$30-50/month (smallest task running 24/7) + RDS minimum $15 | $0 idle; only S3 storage (cents) and Route 53 ($0.50) |
| Cold start latency | None (always running) | ~400ms first call, then warm for ~10 min |
| Scaling | Configure ASG / target tracking | Automatic, no config |
| Patching | Operator updates base image, redeploys | AWS handles runtime patching |
| VPC attachment | Required for RDS access (adds ENI overhead) | Not needed; reads AWS APIs from default Lambda network |
| Audit complexity | Container logs separate from app logs | Lambda invocation + app logs in one CloudWatch group |
| "Stop the app" cost | Stop the task, RDS still bills | Just stop deploying; everything goes to zero |
The cold start trade-off is real. For an internal app that 10 people use occasionally, a 400ms cold start once per visit is invisible. For a customer-facing app with strict SLAs, the calculus is different and Fargate (or Lambda with provisioned concurrency, ~$5/month per concurrency unit) might win.
Documented in MODULES.md under "What we DON'T have, deliberately" so this choice doesn't get reconsidered every quarter.
The observability vibe app is a working proof of every doctrine the substrate makes structural. A tech leader walking through it should see each property enforced in code, not in policy.
| Doctrine | How the obs app proves it |
|---|---|
| Every action is attributable to a named human | Audit tab → AssumeRole events show mtang@paycargo.com + the Cognito principal for every sign-in |
| No long-lived AWS credentials anywhere | App uses Cognito JWTs (1 hour); Lambda uses its execution role (rotating with IAM); CI uses OIDC. No long-lived keys anywhere visible. Show the empty ~/.aws/credentials. |
| Audit logging cannot be silenced | Show the CloudTrail status panel: "Logging: true" — and the permission boundary that prevents it from being stopped. The 5-PR substrate apply saga is the empirical proof. |
| Vibe apps inherit guardrail enforcement automatically | The narrative generation goes through the same default guardrail every other Bedrock call does. Show the Bedrock invocation log entry for the daily narrative — guardrail ID is present. |
| Encryption at rest is structural, not optional | Show the S3 bucket properties: SSE-KMS with audit CMK. Show the DDB encryption status. Show the CloudWatch log group encryption. All three reference the audit CMK from identity. |
| Audit retention meets compliance | Show the audit S3 bucket Object Lock configuration: COMPLIANCE mode, 2555 days. Even AWS root cannot delete within retention. |
| Every code change is reviewable | Click "Last successful deploy" on the Exec tab → opens the GitHub workflow run → shows the PR + the approver + the apply output. |
The vibe app does not stand alone. It consumes substrate built in Steps 0-4, follows the doctrine in MODULES.md, and serves the audiences identified in the 60-day plan. Tech leaders reviewing the app should have the full doc trail available.
| Doc | What it provides |
|---|---|
| _process/index.html | The landing page for the whole substrate build. Step cards 0-5, cost roll-up, supplementary reference docs, hero stats showing current substrate completion state. |
| /MODULES.md | Module discipline doctrine. Three tiers (foundational / security-load-bearing / discuss-first / wrap-instead), AWS service inventory, planned vibecode SDK packages, deliberate omissions. Read first by the the external developer. |
| Step.OutOfScope.html | Per-step "what we deliberately did not build." Includes the org separation rationale (PayCargoDevOps separate from PayCargo Enterprise), why no Fargate, why no RDS, etc. |
| Step | What was built | Doc |
|---|---|---|
| 0 | Human admin access via PayCargo Identity Center SSO | Step.0.html |
| 1 | Bootstrap: state backend, OIDC, permission boundary | Step.1.html + diagram |
| 2 | Identity: 3 trust-zone KMS CMKs + tier roles | Step.2.html + diagram |
| 3 | Network: VPC + NFW + endpoints (alert mode) | Step.3.html + diagram |
| 3a | CI wiring: GitHub Actions OIDC apply path | Step.3a.html |
| 4 | Substrate completion: data, observability, guardrails, memory, gateway, lambda-tools | Step.4.html + diagram |
| 4.5 | This doc — observability vibe app | Step.4.5.html (here) |
| 5 | NFW enforcement flip (queued after 4.5) | (pending) |
| Doc | What it covers |
|---|---|
| Step.3.routing-tables.html | The nine route tables in the VPC. Why lockbox has no 0.0.0.0/0 route. The structural enforcement of "lockbox cannot reach the internet." |
| Step.3.db-access-boundaries.html | The four-layer DB access boundary: endpoint policy + IAM tier policy + KMS key policy + reveal Lambda chokepoint. Why agent and lockbox tiers share the DDB endpoint but cannot share data. |
| Doc | What it covers |
|---|---|
| _docs/ai-platform/roadmap.html | The 60-day Phase 1 roadmap that this whole build is executing against. |
| _docs/PayCargo Forward Deployment - Memo to executive leadership.docx | The CEO-facing strategy memo that frames why the platform is being built this way. |
| _docs/PayCargo AI Macro Strategy.docx | The macro AI doctrine documents. |
| Diagram | What it shows |
|---|---|
| diagram-process/diagram-ai-sandbox-infrastructure.html | The full AWS infrastructure as a trust-zone map. Click-to-drill, costs per object, why-implemented panels. |
| Step.4.diagram.html (in _process) | Substrate-completion view with all six new modules colored active-blue, prior steps in green gradient. |
| Step.4.5.diagram.html (next) | This vibe app's architecture diagram. |
An interactive mock of the obs vibe app lives at Step.4.5.clickthrough.html. It demonstrates:
The click-through is the artifact you can hand to strategic leadership or an investor in advance of a demo, so they can explore at their own pace. It is not a working app; it is a high-fidelity prototype for stakeholder review.
The first deploy of this app hit a CloudFront 403 AccessDenied because the audit substrate CMK didn't grant CloudFront permission to decrypt the frontend S3 objects. The fix was not to broaden the audit CMK (which would dilute the most important key in the substrate). The fix was to recognize that application data has its own classification axis, distinct from substrate trust zones, and to add a grid of CMKs that the substrate provides and vibe apps consume by name.
The pattern (sensitivity tier × organizational boundary), the policy templates per tier, the cross-org access pattern ("higher audience as natural owner; lower audience via IAM grant"), and the per-vibe-app onboarding flow are all documented in Step 4.5.a — Data Governance CMKs. The obs app's frontend bucket now lands on ceo-office-low-risk; the Lambda + log group land on eng-low-risk. Every future vibe app inherits the pattern by declaring its governance cell.