Commit graph

10 commits

Author SHA1 Message Date
5fc749bc0e
refactor(cdk): encrypt migrated log groups with seahaven-logs CMK (INFRA-114) 2026-07-06 18:09:19 -04:00
62a3a7f0be
refactor(cdk): replace deprecated logRetention with explicit LogGroup (INFRA-114) 2026-07-06 17:59:27 -04:00
Adam Moussa
0615aa5b77
Add CloudWatch alarm for po-email-processor DLQ messages (#69)
Page when any message lands in the EmailProcessorDlq, which means a PO
email was permanently dropped after po-email-processor exhausted its
async Lambda retries. Without this, the errors alarm catches the failing
invocations but nothing surfaces the resulting dead-lettered email.

AWS/SQS ApproximateNumberOfMessagesVisible, Maximum over a single 5-min
period > 0, notBreaching on missing data. Reuses the shared site-alerts
SNS topic, ALARM-only, matching the existing po-email-processor-errors
alarm. The metric helper derives the QueueName dimension from the queue
construct (Fn::GetAtt QueueName), so the alarm tracks the CDK-generated
queue name without hardcoding it.
2026-06-17 17:28:42 -04:00
Adam Moussa
86f2ebb42d
Codify S3 Block Public Access on ingest buckets (#71)
Add block_public_access=BlockPublicAccess.BLOCK_ALL to the po-ingest and
workorder-ingest EmailBucket constructs. The buckets are already private
at runtime via account-level and AWS-default BPA, so this is a no-op for
behavior; it closes the codification gap that left CKV_AWS_53-56 firing
on the synthesized templates and blocking the security pre-push gate
(and the DLQ-alarm PRs that ride on it).
2026-06-17 17:23:06 -04:00
Adam Moussa
0fdf407e1d
Add CloudWatch alarm coverage for po-ingest and workorder-ingest (#70)
Some checks are pending
Deploy / deploy (push) Waiting to run
* Add CloudWatch alarm coverage for po-ingest and workorder-ingest

Expands alarm coverage across both CDK stacks. All alarms are ALARM-only
(no OK action) to the shared site-alerts SNS topic, with TreatMissingData
NOT_BREACHING. The site-alerts topic is now imported once near the top of
each stack so every alarm reuses one Topic instance.

po-ingest (cdk/po_stack.py):
- Errors: po-ingest-site-extractor
- Throttles: po-email-processor, po-ingest-site-extractor, po-web-ui
- Duration (p99, >=45000ms, eval3/dp2): po-email-processor (orphan adoption),
  po-ingest-site-extractor, po-web-ui
- DynamoDB throttle + system-error: purchase-orders, verified-sites,
  pending-site-review

workorder-ingest (cdk/wo_stack.py):
- Throttles: workorder-email-processor
- Duration (p95, >=45000ms, eval3/dp2): workorder-email-processor (orphan adoption)
- DynamoDB throttle + system-error: WorkOrders, WorkOrderComments

DynamoDB ThrottledRequests/SystemErrors emit only at the TableName+Operation
dimension set, so each table alarm is a Sum math expression across operations
via the non-deprecated metric_*_for_operations helpers (metric_throttled_requests
is deprecated/invalid in aws-cdk-lib 2.259.0).

Refs INFRA-41 / audit H-8.

* Drop NEEDS ADAM SIGN-OFF wording from alarm comments

Duration alarm thresholds are owner-approved; remove the sign-off flag
from po_stack.py and wo_stack.py comments. Threshold values, eval config,
and orphan-delete notes are unchanged.
2026-06-17 14:46:03 -04:00
Adam Moussa
47fa35688d
fix(po): grant KMS on seahaven-dynamodb CMK to purchase-orders consumers (INFRA-104) (#56)
Some checks are pending
Deploy / deploy (push) Waiting to run
The purchase-orders table was migrated to SSE-KMS (alias/seahaven-dynamodb,
INFRA-95/M-3) out-of-band, but po_stack never declared the key, so
grant_read_write_data did not propagate kms perms. po-email-processor failed
~99.6% of invocations with kms:Decrypt AccessDeniedException, a data-loss
outage on the PO ingestion write path.

- po_stack: declare encryption_key on purchase-orders (reconciles SSE drift;
  no-op against the already-encrypted live table) so the existing grants add
  kms:Decrypt/GenerateDataKey/DescribeKey to EmailProcessor, WebUI, SiteExtractor.
- wo_stack: pre-emptive grant_encrypt_decrypt on the WO processor role ahead of
  the WorkOrders CMK migration (INFRA-6); tables left unencrypted, no table change.

GPT-4.1 cross-review: no blockers.
2026-06-10 19:31:55 -04:00
Adam Moussa
109c565cbf
Reconcile IaC with out-of-band DLQ + Function URL changes (INFRA-74, INFRA-41) (#50)
Some checks are pending
Deploy / deploy (push) Waiting to run
Make CDK the source of truth for two sets of changes applied out-of-band
via CLI to the po-ingest and WorkorderIngestStack stacks.

INFRA-74 (audit C-5): remove the public FunctionUrlAuthType.NONE Function
URL construct (and its auto-generated Principal:* invoke permission +
output) from both po-web-ui and workorder-web-ui. The URLs were already
deleted live via CLI; CFN's delete is idempotent.

INFRA-41 (audit H-8): add a CDK-managed SQS dead-letter queue
(dead_letter_queue=, 14d retention, SSL-enforced, CDK-generated name) and
an ALARM-only Errors alarm (Sum, threshold>0, site-alerts topic) for both
po-email-processor and workorder-email-processor, mirroring the
apm-wo-analysis-classifier DLQ and payments-payroll-batch alarm patterns.

Interim CLI resources (per-fn -dlq queues, -errors alarms, dlq-send inline
policies, OnFailure event-invoke-configs) removed post-deploy.
2026-06-08 16:02:29 -04:00
Adam Moussa
9370abf9ab
Drop three read-idle GSIs (audit M-20) (#35)
Some checks are pending
Deploy / deploy (push) Waiting to run
* Drop read-idle GSIs: by-state and status-index

Audit M-20: 30 days of CloudWatch metrics show 0 reads on both
indexes against 518 (by-state) and ~50k (status-index) WCU of write
amplification. No code path queries either index.

site-code-index follows in the next commit - CloudFormation allows
only one GSI change per table per deploy.

* Drop read-idle site-code-index GSI

Second half of the M-20 cleanup - deployed separately because
CloudFormation allows one GSI change per table per update.
2026-06-03 15:32:23 -04:00
Adam Moussa
a5d2bee748
Fix po-email-processor bundling for arm64 target (#34)
po-email-processor has been down since 2026-05-12: the bundling
command installed wheels for the build host's architecture, so CD
deploys from amd64 runners shipped x86_64 pydantic_core into an
ARM_64 function, crashing every INIT with Runtime.ImportModuleError.

Pin the pip platform to manylinux2014_aarch64 with --only-binary,
matching the pattern wo_stack already uses.

Verified live: deployed from branch, manual invoke OK; 920 emails
from the outage window backfilled via scripts/reprocess logic.
2026-06-03 14:56:02 -04:00
Adam Moussa
5112c1345b
Merge workorder-ingest into unified procurement repo (#22)
* Merge workorder-ingest pipeline into unified repo

Move PO lambdas under lambdas/po/, add WO pipeline under lambdas/wo/.
Two independent CloudFormation stacks in one CDK app. Fix WO stack
compliance: ARM64 architecture, 60-day log retention, aarch64 bundling,
RETAIN on Anthropic secret. Remove stale CodePipeline buildspec.

* Fix test_local.py import path and remove dead shared/models.py

test_local.py referenced the old lambdas/email_processor path. Updated
to lambdas/wo/email_processor. Removed shared/ directory entirely as
nothing imports from it.

* Escape HTML in both web UI dashboards to prevent XSS

Both Function URLs are public (auth_type=NONE) and render
email-derived content via f-strings. Attacker-crafted emails
could inject scripts. Added html.escape() on all interpolated
values in both PO and WO dashboards.

* Add pagination to WO web UI scan

get_work_orders() only fetched the first 1MB page from DynamoDB.
Loop on LastEvaluatedKey to match the PO web UI pattern.

* Fix esc(None) TypeError and javascript: scheme in PO web UI

Coerce supplier name through `or ""` before escaping to handle
nested None from DynamoDB. Add scheme allowlist on view_order_url
to block javascript:/data: hrefs from LLM-extracted URLs.

* Fix WO render_badge None guard, updated_at slice, and backfill path

Add null guard to WO render_badge matching the PO version. Use
`or ""` before slicing updated_at to handle explicit None values.
Fix backfill_sites.py sys.path to use new lambdas/po/site_extractor.

* Harden WO web UI and fix JS-context XSS in both dashboards

- Use json.dumps for onclick URLs to prevent JS string breakout
- Add .lower() to WO render_badge color lookup matching PO pattern
- Add pagination to get_comments query
- Cap get_work_orders to 500 results matching PO pattern

* Apply ruff formatting to web UI handlers
2026-05-12 15:21:06 -04:00
Renamed from cdk/stack.py (Browse further)