You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As an NLDS Product Ops analyst, I want Glow's learning data to land automatically in the NLDS S3 landing bucket on a daily batch cadence, so that it can be ingested into the NLDS data warehouse for centralized analytics and reporting without manual data pulls.
Background
NLDS wants Glow's learning data centralized in its warehouse, but Glow has no automated pipeline today: data is moved by hand or not at all.
ADR 0005 (accepted; spike in #597, documented in PR #602) settled the connector architecture: an S3 data-lake intermediary with RDS snapshot export as the producer. Glow's database exports whole-database snapshots as Parquet (no export code) cross-account into a scoped prefix of a landing bucket owned by the NLDS account (S3 Object Ownership: bucket-owner-enforced, SSE-KMS). NLDS ingests from there natively, on its own schedule. There is no requirement to curate what is sent.
This issue realizes that decision on the Glow side: the configured snapshot export, the cross-account write path, and monitoring of export success (a producer responsibility per the ADR). The NLDS-side bucket and ingestion are owned by NLDS; this issue covers agreeing the bucket/prefix/key contract with them, not building their side.
Acceptance criteria
Learning data lands in the NLDS bucket on schedule
Given the NLDS landing bucket exists with Glow granted scoped write access to its dedicated prefix
When the export runs on its daily snapshot schedule
Then a complete whole-database export in Parquet format is present under Glow's prefix, owned by the NLDS account and encrypted at rest, and NLDS can read it without a cross-account role
Failed export is surfaced to Glow operators
Given the connector is in place
When an export run fails or does not complete
Then Glow operators are alerted through the team's alerting channel, and the live application is unaffected
Live database stays unreachable from NLDS
Given the connector is in place
When NLDS consumes Glow data
Then the only access path is the S3 landing bucket: no network path, database endpoint, or replica is reachable from the NLDS account
Out of scope
NLDS-side ingestion, warehouse schema and table mapping (decided separately by NLDS)
Historical backfill
Curating the export to specific datasets (whole-DB export per the ADR; re-explorable later without superseding it)
Near-real-time freshness (documented upgrade path is PrivateLink, per the ADR)
flowchart LR
subgraph GlowAcct["Glow AWS account"]
Glow["Glow database<br/>(RDS snapshot export)"]
end
subgraph NldsAcct["NLDS AWS account (same Organization)"]
Bucket["S3 landing bucket<br/>(Parquet, SSE-KMS,<br/>bucket-owner-enforced)"]
Ingest["NLDS ingestion"]
Warehouse["NLDS data warehouse"]
Bucket --> Ingest
Ingest --> Warehouse
end
Glow -->|"batch export (per snapshot schedule):<br/>cross-account write to a scoped prefix"| Bucket
Loading
Technical context
Implementation lives in the infra repo, not this application repo (no changes to src/ or prisma/ in onward): https://sgts.gitlab-dedicated.com/wog/moe/dxdtransform/dxd-transform/dxd-transform-infrastructure (Terraform + Terragrunt; Terragrunt states under infra/states/provider.aws/, reusable modules under infra/modules/, Lambda source under lambda/). Onward's per-environment stacks live under acct.lower/env.dev/svc.onward/, acct.stg/env.stg/svc.onward/, and acct.prd/env.prd/svc.onward/; the RDS instances (transform-{env}-onward-db, automated daily snapshots with 7-day retention) and KMS stacks already exist in all three.
RDS does not export snapshots automatically, so the producer needs a small scheduler. The end product lives in production: the standing daily export runs in the production account against transform-prd-onward-db. Dev is used only to test the pipeline end to end before promoting; it is not a standing data feed. Build:
IAM export role (new stack svc.onward/iam/rds-s3-export/): trusts the export.rds.amazonaws.com service principal; grants S3 write scoped to the agreed prefix on the NLDS landing bucket and the KMS actions the export task needs (Encrypt, Decrypt, ReEncrypt, GenerateDataKey, CreateGrant, DescribeKey, RetireGrant) on the NLDS-provided key. Use the repo's infra/modules/aws/iam/assumable-role module.
Export scheduler Lambda (new stack svc.onward/lambda/nlds-export/, source lambda/onward-nlds-export/): Python, no VPC, control-plane only. Looks up the latest automated snapshot of the environment's onward DB and calls rds:StartExportTask targeting the NLDS bucket with the export role and KMS key, landing each run under a Hive-style date-partitioned prefix (glow/year=YYYY/month=MM/day=DD/). Needs rds:DescribeDBSnapshots, rds:StartExportTask, rds:DescribeExportTasks, iam:PassRole (on the export role), kms:DescribeKey.
EventBridge daily rule (new stack svc.onward/eventbridge/nlds-export/): triggers the Lambda daily at 02:30 SGT (cron(30 18 * * ? *), UTC), after the RDS backup window (17:00-18:00 UTC / 01:00-02:00 SGT) so the Lambda exports the same night's snapshot.
CloudWatch alarms (added to the environment's cloudwatch/metric-alarms/ stack, module infra/modules/aws/cloudwatch/metric-alarms): see Error contract. Notify the account-level yellow/red alert SNS topics that stack already uses.
NLDS handshake: give NLDS the export role ARN; NLDS must grant that role and the export.rds.amazonaws.com service principal in their bucket policy and KMS key policy, and provide the KMS key ARN back. Working values to confirm with NLDS Product Ops: bucket glow-nlds-data-lake-prod (account 671830948389, ap-southeast-1), prefix glow.
End-to-end verification in dev before promoting to production: a scheduled run lands a complete Parquet export under the prefix and an NLDS principal can read it. Once production is live, the dev deployment serves testing only (for example, leave its schedule disabled outside test runs).
Deployment conventions: dev applies locally via aws-vault exec dxd-transform-lower-dev-role -- terragrunt plan|apply; production applies only through Atlantis on a GitLab MR (atlantis plan / atlantis apply comments, approval required). Commit .terraform.lock.hcl with darwin_arm64, darwin_amd64, and linux_amd64 hashes. Angular-style commits scoped like chore(dev/onward): ....
Patterns to follow: the per-service stack layout under svc.onward/ (existing rds/ and kms/ stacks); infra/states/provider.aws/acct.lower/env.dev/svc.onward/s3/app/terragrunt.hcl for S3/bucket-policy style; infra/states/provider.aws/acct.lower/env.dev/cloudwatch/metric-alarms/terragrunt.hcl for alarms (onward's DB is already listed there); terraform-aws-modules/lambda/aws and terraform-aws-modules/eventbridge/aws for the new stacks, matching versions used elsewhere in the repo. Architecture rationale and rejected alternatives are in ADR 0005; the data-flow diagram above is normative.
Data model
N/A: no application schema or Prisma changes. The export is a whole-database snapshot produced by RDS; its shape is the existing database schema serialized to Parquet by AWS under the agreed prefix.
API contract
N/A: no application API is added or changed. The interface between Glow and NLDS is the S3 landing prefix, date-partitioned Hive-style with one dated prefix per daily run, for example s3://glow-nlds-data-lake-prod/glow/year=2026/month=07/day=02/<table>/*.parquet (working values above; confirm with NLDS Product Ops).
Error contract
Operational (no HTTP surface). Alarms follow the repo's metric-alarms pattern and notify the account-level yellow/red alert SNS topics:
Failure
Detection
Operator-visible outcome
Lambda fails to start the export (snapshot lookup, permissions)
CloudWatch alarm on the Lambda Errors metric
Alarm enters ALARM state, notifies the alert SNS topic
Export task fails or is canceled after starting
CloudWatch alarm on export-task failure (RDS export task events/metrics)
Alarm enters ALARM state, notifies the alert SNS topic
Export does not run in its daily window
Alarm treats missing invocation data as breaching
Alarm enters ALARM state
Cross-account write denied (NLDS bucket policy or KMS drift)
Surfaces as export-task failure; covered by the same alarm
Alarm enters ALARM state
CloudWatch alarms only (per grooming decision): no new Slack/chat pipeline in this issue beyond the existing SNS alert topics.
Additional test scenarios
Exported objects are readable by the bucket owner
Given an export run has completed
When an NLDS-account principal reads an object under the agreed prefix
Then the read succeeds without any cross-account role
Export role cannot write outside its prefix
Given the Glow export role's credentials
When a write is attempted to the landing bucket outside the agreed prefix
Then the write is denied
Alarm fires on a failed export
Given the CloudWatch alarms are deployed
When an export run is forced to fail in dev (for example, temporarily revoking the KMS grant)
Then the relevant alarm transitions to ALARM state within its evaluation period
Production export uses the production database
Given the stacks are deployed to production
When the daily export runs in the production account
Then the export reads a snapshot of transform-prd-onward-db and lands under the agreed production prefix, with no cross-environment references copied from the dev stack
Hard constraints
Do not change application code in this repo: no new endpoints, no Prisma changes, no export code in src/.
Do not create a network path between the accounts: no VPC peering, PrivateLink endpoint, database replica, or DMS task (all rejected by ADR 0005).
Do not add curation/filtering logic: the export is whole-database by decision.
The landing bucket is NLDS-owned; do not create a Glow-owned long-lived landing bucket.
Objects must be encrypted with the NLDS-provided KMS key; do not fall back to SSE-S3, and do not ship with a placeholder KMS key ARN.
The export scheduler Lambda stays out of any VPC and never connects to the database: control-plane API calls only.
Do not apply production changes locally: production goes through Atlantis on a GitLab MR only.
🤖 Generated with aif-create-issue 🤖 Groomed with aif-groom-issue
User story
As an NLDS Product Ops analyst, I want Glow's learning data to land automatically in the NLDS S3 landing bucket on a daily batch cadence, so that it can be ingested into the NLDS data warehouse for centralized analytics and reporting without manual data pulls.
Background
NLDS wants Glow's learning data centralized in its warehouse, but Glow has no automated pipeline today: data is moved by hand or not at all.
ADR 0005 (accepted; spike in #597, documented in PR #602) settled the connector architecture: an S3 data-lake intermediary with RDS snapshot export as the producer. Glow's database exports whole-database snapshots as Parquet (no export code) cross-account into a scoped prefix of a landing bucket owned by the NLDS account (S3 Object Ownership: bucket-owner-enforced, SSE-KMS). NLDS ingests from there natively, on its own schedule. There is no requirement to curate what is sent.
This issue realizes that decision on the Glow side: the configured snapshot export, the cross-account write path, and monitoring of export success (a producer responsibility per the ADR). The NLDS-side bucket and ingestion are owned by NLDS; this issue covers agreeing the bucket/prefix/key contract with them, not building their side.
Acceptance criteria
Learning data lands in the NLDS bucket on schedule
Failed export is surfaced to Glow operators
Live database stays unreachable from NLDS
Out of scope
Design assets
flowchart LR subgraph GlowAcct["Glow AWS account"] Glow["Glow database<br/>(RDS snapshot export)"] end subgraph NldsAcct["NLDS AWS account (same Organization)"] Bucket["S3 landing bucket<br/>(Parquet, SSE-KMS,<br/>bucket-owner-enforced)"] Ingest["NLDS ingestion"] Warehouse["NLDS data warehouse"] Bucket --> Ingest Ingest --> Warehouse end Glow -->|"batch export (per snapshot schedule):<br/>cross-account write to a scoped prefix"| BucketTechnical context
Implementation lives in the infra repo, not this application repo (no changes to
src/orprisma/in onward): https://sgts.gitlab-dedicated.com/wog/moe/dxdtransform/dxd-transform/dxd-transform-infrastructure (Terraform + Terragrunt; Terragrunt states underinfra/states/provider.aws/, reusable modules underinfra/modules/, Lambda source underlambda/). Onward's per-environment stacks live underacct.lower/env.dev/svc.onward/,acct.stg/env.stg/svc.onward/, andacct.prd/env.prd/svc.onward/; the RDS instances (transform-{env}-onward-db, automated daily snapshots with 7-day retention) and KMS stacks already exist in all three.RDS does not export snapshots automatically, so the producer needs a small scheduler. The end product lives in production: the standing daily export runs in the production account against
transform-prd-onward-db. Dev is used only to test the pipeline end to end before promoting; it is not a standing data feed. Build:svc.onward/iam/rds-s3-export/): trusts theexport.rds.amazonaws.comservice principal; grants S3 write scoped to the agreed prefix on the NLDS landing bucket and the KMS actions the export task needs (Encrypt, Decrypt, ReEncrypt, GenerateDataKey, CreateGrant, DescribeKey, RetireGrant) on the NLDS-provided key. Use the repo'sinfra/modules/aws/iam/assumable-rolemodule.svc.onward/lambda/nlds-export/, sourcelambda/onward-nlds-export/): Python, no VPC, control-plane only. Looks up the latest automated snapshot of the environment's onward DB and callsrds:StartExportTasktargeting the NLDS bucket with the export role and KMS key, landing each run under a Hive-style date-partitioned prefix (glow/year=YYYY/month=MM/day=DD/). Needsrds:DescribeDBSnapshots,rds:StartExportTask,rds:DescribeExportTasks,iam:PassRole(on the export role),kms:DescribeKey.svc.onward/eventbridge/nlds-export/): triggers the Lambda daily at 02:30 SGT (cron(30 18 * * ? *), UTC), after the RDS backup window (17:00-18:00 UTC / 01:00-02:00 SGT) so the Lambda exports the same night's snapshot.cloudwatch/metric-alarms/stack, moduleinfra/modules/aws/cloudwatch/metric-alarms): see Error contract. Notify the account-level yellow/red alert SNS topics that stack already uses.export.rds.amazonaws.comservice principal in their bucket policy and KMS key policy, and provide the KMS key ARN back. Working values to confirm with NLDS Product Ops: bucketglow-nlds-data-lake-prod(account 671830948389, ap-southeast-1), prefixglow.Deployment conventions: dev applies locally via
aws-vault exec dxd-transform-lower-dev-role -- terragrunt plan|apply; production applies only through Atlantis on a GitLab MR (atlantis plan/atlantis applycomments, approval required). Commit.terraform.lock.hclwith darwin_arm64, darwin_amd64, and linux_amd64 hashes. Angular-style commits scoped likechore(dev/onward): ....Patterns to follow: the per-service stack layout under
svc.onward/(existingrds/andkms/stacks);infra/states/provider.aws/acct.lower/env.dev/svc.onward/s3/app/terragrunt.hclfor S3/bucket-policy style;infra/states/provider.aws/acct.lower/env.dev/cloudwatch/metric-alarms/terragrunt.hclfor alarms (onward's DB is already listed there);terraform-aws-modules/lambda/awsandterraform-aws-modules/eventbridge/awsfor the new stacks, matching versions used elsewhere in the repo. Architecture rationale and rejected alternatives are in ADR 0005; the data-flow diagram above is normative.Data model
N/A: no application schema or Prisma changes. The export is a whole-database snapshot produced by RDS; its shape is the existing database schema serialized to Parquet by AWS under the agreed prefix.
API contract
N/A: no application API is added or changed. The interface between Glow and NLDS is the S3 landing prefix, date-partitioned Hive-style with one dated prefix per daily run, for example
s3://glow-nlds-data-lake-prod/glow/year=2026/month=07/day=02/<table>/*.parquet(working values above; confirm with NLDS Product Ops).Error contract
Operational (no HTTP surface). Alarms follow the repo's metric-alarms pattern and notify the account-level yellow/red alert SNS topics:
CloudWatch alarms only (per grooming decision): no new Slack/chat pipeline in this issue beyond the existing SNS alert topics.
Additional test scenarios
Exported objects are readable by the bucket owner
Export role cannot write outside its prefix
Alarm fires on a failed export
Production export uses the production database
transform-prd-onward-dband lands under the agreed production prefix, with no cross-environment references copied from the dev stackHard constraints
src/.🤖 Generated with aif-create-issue
🤖 Groomed with aif-groom-issue
{"metadata":{"sourceType":"github"},"rank":"Zw","taskId":"82bdf8dd-d933-402a-abad-c69112e5164a","version":1}