PAYCARGO · AI-SANDBOX · PROCESS

4Substrate Completion

Six modules wired into the composition: data, observability, guardrails, memory, gateway, lambda-tools. The platform layer the vibe apps run on.
Block: 4 of N · Operator: CI via OIDC (the first multi-PR substrate change to go entirely through the wired CI path) · Cost: ~$35-50/month additional · Permanence: Permanent
Completed

Executed: All six modules deployed and verified. CloudTrail logging, GuardDuty detecting, Bedrock invocation logging active, audit S3 with 7-year Object Lock, three CloudWatch dashboards (operators / exec / audit) live, Bedrock default guardrail READY.

Cost added: ~$35-50/month. Total deployed: ~$464/mo (full substrate baseline).

Apply complexity: Five PRs, four apply errors mid-flight, one bootstrap permission boundary update. The substrate's audit doctrine empirically enforced itself during the apply — documented as "what actually happened" in § 12.

Executive Summary

Complete the platform layer that vibe apps run on: data storage, observability, guardrails, memory, gateway, and the Lambda-tool execution roles.

After this step, the substrate is structurally complete. Every property the 60-day plan called for in Phase 1 is now provisioned: audit S3 with WORM Object Lock for compliance retention, CloudTrail multi-region trail capturing every API call, GuardDuty actively scanning for threats, Bedrock invocation logging recording every agent call, content moderation + prompt injection guardrails enforced platform-wide, and three CloudWatch dashboards (operators / exec / audit) tracking everything that moves. Phase 1 apps build on this; Phase 2 PII workflows extend it without substrate migration because the lockbox-tier patterns are already provisioned (just not exercised in Phase 1).

StatusCompleted
Cost~$35-50/month
EffortPlan: ~3 weeks
Apply: ~1 hour (5 PRs)
Review: ~30 min
RiskLow

If skipped: No audit retention (regulatory liability). No invocation logging (cannot prove which agent did what). No guardrails (vibe apps could be prompt-injected or leak PII). No dashboards (no operational visibility). Phase 1 cannot ship without these.

Executed configuration

AI-sandbox AWS account
959228203854
Audit S3 bucket
ai-sandbox-audit-959228203854 (Object Lock COMPLIANCE, 2555 days, audit CMK encryption)
CloudTrail trail
ai-sandbox-trail (multi-region, logging, KMS-encrypted, CloudWatch + S3 destinations)
GuardDuty detector
4adc191317244e8cb728deace0934f69
Bedrock invocation log group
/aws/bedrock/invocations/ai-sandbox
Bedrock default guardrail
ai-sandbox-default (id: 1m7x21hg855u, status: READY)
Lockbox DDB table
ai-sandbox-lockbox-tokens (ACTIVE, SSE-KMS, PITR, Streams enabled, NOT exercised in Phase 1)
Agent state DDB table
ai-sandbox-agent-state (ACTIVE, SSE-KMS with agent CMK, PITR)
CloudWatch dashboards
ai-sandbox-operators, ai-sandbox-exec, ai-sandbox-audit
Lambda execution roles
ai-sandbox-sor-adapter-execution (agent tier), ai-sandbox-reveal-lambda-execution (lockbox tier, provisioned not exercised)

1. Why this step exists

Steps 0-3 deployed the substrate's foundations: human identity (SSO), state backend + permission boundary (bootstrap), KMS CMKs + tier roles (identity), and the network surface (VPC + NFW + endpoints). After Step 3 the substrate existed but the platform layer the vibe apps need was still empty: no audit storage, no observability, no guardrails, no Lambda execution roles, no AgentCore Memory or Gateway scaffolding.

Step 4 closes that gap. Six modules go in, each filling a specific Phase 1 plan deliverable. After this step, vibe apps have somewhere to write audit records, somewhere to read observability data from, guardrails enforced platform-wide, and execution roles ready to host Lambda functions.

The one-line summary

Phase 1 of the 60-day plan called for "governed data layer + WORM audit storage + observability pipeline + module library." Step 4 ships exactly that, as six independently versionable Terraform modules wired into one composition.

2. What this step creates (6 modules)

module/platform/data

ResourceWhy
Audit S3 bucket ai-sandbox-audit-959228203854Destination for CloudTrail, GuardDuty findings, future flow-log archive. Object Lock COMPLIANCE mode for 7 years (2555 days). KMS-encrypted with audit CMK.
Lockbox DDB token tableThe PII vault. Provisioned in Phase 1 for completeness; not exercised (Phase 1 has no PII workflows). KMS-encrypted with lockbox CMK, PITR on, DDB Streams enabled for audit trail of writes.
Agent state DDB tablePer-session state for AgentCore Runtime and lambda-tools coordination. KMS-encrypted with agent CMK.
Per-zone Secrets Manager namespacesTwo secrets: agent/sor-credentials (agent CMK, holds SoR API keys for lambda-tools) and lockbox/service-credentials (lockbox CMK, holds reveal Lambda credentials, Phase 2).
Gateway endpoint policiesLayer 1 of the four-layer DB access boundary. S3 and DDB gateway endpoints scoped so agent subnets can only reach ai-sandbox-agent-* resources; lockbox subnets only reach ai-sandbox-lockbox-* resources.

module/platform/observability

ResourceWhy
CloudTrail multi-region trail ai-sandbox-trailCaptures every AWS API call across the account. S3 + CloudWatch destinations, KMS-encrypted, log file validation enabled. Phase 1 uses default management-event selectors (data-plane events deferred — see § 12).
GuardDuty detectorActive threat scanning. S3 logs data source enabled. Finding publishing every 15 minutes. CloudWatch Events stream available for downstream alerting.
Bedrock invocation loggingEvery InvokeModel / InvokeAgent call across the account logs to /aws/bedrock/invocations/ai-sandbox CloudWatch group + S3. Read by the observability vibe app for per-agent activity panels.
Three CloudWatch dashboardsai-sandbox-operators (real-time platform health), ai-sandbox-exec (cost / activity / posture), ai-sandbox-audit (CloudWatch Logs Insights query templates).

module/platform/guardrails

ResourceWhy
Default Bedrock guardrail ai-sandbox-defaultPlatform-wide content moderation (sexual/violence/hate/insults/misconduct HIGH), prompt-injection filter (HIGH on input), PII filter (anonymize email/phone/name, block SSN/credit-card). PII paths provisioned-not-exercised in Phase 1.

module/platform/memory

ResourceWhy
AgentCore Memory service roleSubstrate scaffolding. Per-agent memory stores are created in agent modules and reference this role. Encrypts with agent CMK.

module/platform/gateway

ResourceWhy
AgentCore Gateway service roleSubstrate scaffolding. Per-app gateways reference this role with their own inbound auth + rate limits. Default rate limit: 60 req/min/app.

module/platform/lambda-tools

ResourceWhy
SoR adapter execution roleShared by all agent-tier Lambda tools (Salesforce, Monday, GitHub, GitBook clients). Reads SoR credentials from Secrets Manager; decrypts with agent CMK.
Reveal Lambda execution roleFor the lockbox-tier reveal Lambda. Provisioned in Phase 1 for completeness; function code deploys in Phase 2.

3. Cost breakdown

ResourceMonthlyNote
Audit S3 bucket (Object Lock)~$0.02 + storageEmpty today; grows as CloudTrail + GuardDuty + Bedrock logs accumulate. Lifecycle to Glacier Deep Archive after 90 days.
Lockbox DDB (PAY_PER_REQUEST)$0Not exercised in Phase 1.
Agent state DDB (PAY_PER_REQUEST)$0Only billed on actual reads/writes.
Secrets Manager (2 secrets)$0.80$0.40/secret/month.
CloudTrail multi-region trail~$2-5$2 per 100k events; Phase 1 management events only.
GuardDuty detector~$3-10Based on CloudTrail event volume + VPC flow log volume.
Bedrock invocation logging~$1-5CloudWatch ingest + S3 storage; grows with agent activity.
CloudWatch Logs (CloudTrail + Bedrock)~$5-15~$0.50/GB ingested + storage.
CloudWatch dashboards (3)$9$3/dashboard/month.
Bedrock guardrail$0 standingPer-token charge at invocation: $0.075 per 1k input + 0.1 per 1k output.
Total added~$35-50/monthCumulative substrate: ~$464/month

4. How it was applied

Step 4 is the first multi-PR substrate change to go entirely through CI (the wired path from Step 3a). Five PRs landed across one session:

  1. PR #2 — Six new modules + composition wiring + MODULES.md. Plan: 36 add, 62 change (tag enrichments), 0 destroy. Applied; hit three errors mid-flight.
  2. PR #3 — Three fixes: PROMPT_ATTACK strength (AWS validation), GuardDuty S3 export deferred (requires audit CMK policy update), CloudTrail/data depends_on (bucket policy race).
  3. PR #4 — Defer CloudTrail event_selectors (permission boundary denies cloudtrail:PutEventSelectors). Trail uses default management-events selectors for Phase 1.
  4. PR #5 — Bootstrap permission boundary update: split DenyCloudTrailTampering into DenyCloudTrailHardTampering (StopLogging + DeleteTrail + RemoveTags on all trails) and DenyCloudTrailConfigTamperingOnNonProjectTrails (UpdateTrail + PutEventSelectors only on trails NOT matching ai-sandbox-*). Applied via laptop SSO admin — the boundary itself is a bootstrap-level concern by design.
  5. Final apply — CI dispatched, succeeded: 0 added, 3 changed, 0 destroyed. Substrate complete.

Total elapsed time: ~1 hour of apply attempts + boundary update. Plan and review time excluded.

5. Verification (the 7 checks that passed)

# 1. CloudTrail logging
AWS_PROFILE=ai-sandbox aws cloudtrail get-trail-status \
  --name ai-sandbox-trail \
  --query '{IsLogging:IsLogging,LatestDeliveryTime:LatestDeliveryTime}'
# Expected: IsLogging=true, recent LatestDeliveryTime

# 2. GuardDuty detector active
AWS_PROFILE=ai-sandbox aws guardduty list-detectors
# Expected: array with one detector ID

# 3. Bedrock invocation logging
AWS_PROFILE=ai-sandbox aws bedrock get-model-invocation-logging-configuration \
  --query 'loggingConfig.cloudWatchConfig.logGroupName'
# Expected: "/aws/bedrock/invocations/ai-sandbox"

# 4. Bedrock guardrail READY
AWS_PROFILE=ai-sandbox aws bedrock list-guardrails \
  --query 'guardrails[?name==`ai-sandbox-default`].{Name:name,Status:status}'
# Expected: Status=READY

# 5. Audit S3 Object Lock
AWS_PROFILE=ai-sandbox aws s3api get-object-lock-configuration \
  --bucket ai-sandbox-audit-959228203854 \
  --query 'ObjectLockConfiguration.Rule.DefaultRetention'
# Expected: Mode=COMPLIANCE, Days=2555

# 6. Lockbox DDB
AWS_PROFILE=ai-sandbox aws dynamodb describe-table \
  --table-name ai-sandbox-lockbox-tokens \
  --query 'Table.{Status:TableStatus,SSE:SSEDescription.Status,Stream:StreamSpecification.StreamEnabled}'
# Expected: Status=ACTIVE, SSE=ENABLED, Stream=true

# 7. CloudWatch dashboards
AWS_PROFILE=ai-sandbox aws cloudwatch list-dashboards \
  --dashboard-name-prefix ai-sandbox
# Expected: 3 entries (operators, exec, audit)

6. How to view the work in AWS Console

WhatWhereWhat confirms it
Audit S3 bucket with Object LockS3 → Buckets → ai-sandbox-audit-959228203854 → Properties tab → Object LockStatus: Enabled. Default retention: 2555 days, COMPLIANCE mode. SSE-KMS encryption with audit CMK.
CloudTrail trailCloudTrail → Trails → ai-sandbox-trailStatus: Logging. Multi-region: yes. Log file validation: enabled. KMS encryption: audit CMK. Most recent delivery within the last few minutes.
GuardDuty findings (will be empty initially)GuardDuty → FindingsDetector ID shown. Status: Active. Findings list (likely empty for a fresh account).
Bedrock guardrailBedrock → Guardrails → ai-sandbox-defaultStatus: READY. Content filters: 6 enabled. PII filter: anonymize/block configured.
CloudWatch dashboardsCloudWatch → DashboardsThree rows: operators, exec, audit. Click any to see panels.
Bedrock invocation log groupCloudWatch → Log groups → /aws/bedrock/invocations/ai-sandboxEncrypted with audit CMK. Will populate as agents make Bedrock calls.
Permission boundary update (verify)IAM → Policies → ai-sandbox-permission-boundary → JSONTwo CloudTrail Deny statements: DenyCloudTrailHardTampering (all trails) and DenyCloudTrailConfigTamperingOnNonProjectTrails (NotResource = ai-sandbox-*).

The dashboards are the artifact

The three CloudWatch dashboards are now the first thing to look at when an executive asks "what is the AI platform doing right now." They're not pretty (they're standard AWS dashboards), but they're the data source the observability vibe app reads from. Step 4.5 will wrap them in a polished front end.

7. Doctrine implications

The permission boundary defended itself

Four of the five PRs in this step exist because the bootstrap permission boundary refused operations that look reasonable but compromise audit guarantees. cloudtrail:PutEventSelectors denied. cloudtrail:DeleteTrail denied. guardduty publishing destinations require additional principal grants. Each refusal was the doctrine working as designed.

The boundary update in PR #5 wasn't a relaxation — it was a scope refinement. Hard tampering (StopLogging, DeleteTrail, RemoveTags) stays globally denied. Configuration tampering (PutEventSelectors, UpdateTrail) is now denied only on trails this project does not own. The deploy role can manage ai-sandbox-* trails as IaC requires; cannot touch other trails.

The substrate is now structurally watching itself

From this step forward, every API call is in CloudTrail (encrypted, 7-year Object Lock). Every Bedrock invocation is in CloudWatch (encrypted, 90-day retention). Every threat-class finding is in GuardDuty. Every workload that uses Bedrock goes through the guardrail. The substrate's "I know what I did" property is no longer aspirational.

Phase 1 / Phase 2 boundary is structural, not aspirational

The lockbox DDB table, the reveal Lambda execution role, the lockbox CMK, and the per-zone lockbox secrets are all provisioned. Phase 1 apps don't exercise them. When a Phase 2 PII workflow needs them, no substrate migration is required: the resources exist, the IAM patterns exist, the doctrine is documented. Phase 2 is feature work, not infrastructure work.

The takeaway

Step 4 is where the substrate stops being a checklist of deployed resources and starts being a self-watching, self-defending platform. Every property the 60-day plan called for is empirically demonstrated: WORM audit retention is enforced by Object Lock; trail tampering is empirically denied by the permission boundary; PII isolation is structural at the KMS layer. The platform now has the properties it claims to have.

8. What actually happened during apply (5-PR saga)

One session, four apply failures, five PRs, one bootstrap update. Recorded honestly because the failures are themselves substrate validation.

Gotcha A — Bedrock Guardrail PROMPT_ATTACK output strength

Initial guardrail had output_strength = "HIGH" on the PROMPT_ATTACK filter. AWS validation rejected: "PROMPT ATTACK content filter strength for response must be NONE." Prompt-injection is an input-side concern; response-side has no analogue. Fix: output_strength = "NONE".

Gotcha B — CloudTrail bucket policy race (cross-module dependency)

CloudTrail validates that the destination bucket policy permits it BEFORE creating the trail. Without explicit dependency between the observability and data modules in the composition, Terraform tried to create the trail before the bucket policy was fully applied. Error: InsufficientS3BucketPolicyException. Fix: depends_on = [module.data] on the observability module.

Gotcha C — GuardDuty publishing destination requires audit CMK policy update

GuardDuty exporting findings to S3 requires the guardduty.amazonaws.com service principal in BOTH the audit S3 bucket policy AND the audit CMK key policy. The audit CMK lives in the identity module, which would need to know about GuardDuty — doctrine violation (modules shouldn't depend on services that don't exist yet). Decision: defer S3 export. Findings still flow via CloudWatch Events; export becomes a Phase 2 follow-on PR that updates both module policies.

Gotcha D — cloudtrail:PutEventSelectors denied by permission boundary

The AWS Terraform provider always calls PutEventSelectors when creating a trail, even when no event_selector block is defined (it sets defaults). The bootstrap permission boundary denied this. Two-step recovery: (1) PR #4 removed the explicit event_selector block to confirm the provider behavior; (2) PR #5 + laptop bootstrap apply split the boundary deny into "hard tampering" (universal) and "config tampering on non-project trails" (scoped). The doctrine "compromised deploy role cannot disable audit" is preserved at the strongest scoping consistent with IaC.

Gotcha E — cloudtrail:DeleteTrail denied (the recovery of the tainted trail)

After Gotcha B, the CloudTrail trail existed in AWS but was marked tainted in Terraform state. The next plan wanted to destroy + recreate. The boundary correctly denied DeleteTrail. Recovery: terraform untaint as SSO admin (boundary doesn't apply to SSO sessions). The trail's actual config matched the desired config after the event_selector removal, so no functional change needed — just state reconciliation.

The pattern: doctrine refusing convenient operations

Three of the five gotchas were the permission boundary refusing operations that look like normal IaC. That's not a bug. It's the property the boundary was deployed to provide: the deploy role cannot silence audit logging, ever. Recovery requires either (a) a deliberate boundary scoping change applied via laptop SSO admin (bootstrap-level break-glass), or (b) accepting reduced functionality (defer data-plane event selectors to Phase 2). Both are legitimate. The doctrine is harder to live with than ignoring it. That's the point.

9. What comes next

Substrate complete. Two things next, in this order:

  1. Step 4.5 — Observability vibe app. Static HTML/JS on S3+CloudFront + Lambda backend + Cognito auth (manual enrollment with TOTP 2FA + future Identity Center federation as a parallel path). Reads from the CloudWatch dashboards and log groups deployed in this step. Three audience tabs: operators, exec, audit. The first real test that vibe apps can be built on the substrate.
  2. Step 5 — NFW enforcement flip. nfw_enforcement_mode = "drop", dns_firewall_action = "BLOCK". Doctrine originally said "after 24-48h of real workload observation"; with no workloads yet, the SoR allowlist is design-correct rather than observation-tuned. Flipping now means future workloads deploy against the right enforcement floor instead of having to live through a transition. The obs app from Step 4.5 will let us watch the flip happen.

At Day 28 end (today), Phase 1 of the 60-day plan is structurally complete. Phase 2 (module library) is ~50% in the form of MODULES.md doctrine + vibecode SDK API surface defined. Phase 3 (three POCs) is 0%, but two are now identified (observability vibe app + Sales monthly reports). Phase 4 (observability pipeline) is ~70% — audit stream end-to-end live; dashboards exist but the polished exec-facing front end is what Step 4.5 delivers.