* feat(menu): publish the weekly menu from the job worker
Monday publish parses the catalog embedded in the Redefine menu page and runs on the Fargate worker, so the GitHub Actions scrape cron can go away.
* fix(menu): address review feedback
Use the form deadline in the Monday Slack post, and compare that message exactly so CodeQL does not treat the test as URL sanitization.
* ci: convert onto org HCP reusables
Switch Fargate CD and CI to the v1.0.13 org workflows, emit ci-complete, and retarget githubdeploy OIDC to the reusable plus the thin caller.
* chore(security): retarget githubdeploy Checkov suppression
The OIDC dual-claim edit shifted CKV_AWS_111 from line 40 to 49. Permissions are unchanged.
* style: apply formatter
* ci: pin org reusables to v1.0.14
Drop collect-only and requirements from the python lint caller now that ci-python-app is lint-only.
* test(ci): probe autofix with a ruff format violation
* style: apply formatter
---------
Co-authored-by: sea-haven-auto-fix[bot] <5037331+sea-haven-auto-fix[bot]@users.noreply.github.com>
* feat(infra): export attached VPC ids and lock prod to afterhours (DEV-289)
Prod must keep existing_vpc_id pointed at the afterhours VPC. Outputs
expose the resolved vpc_id and public subnet IDs.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* feat(api): add OpenAPI 3.1 and Redocly lint in CI (DEV-289)
Same extends: recommended ruleset and @redocly/cli 2.52.1 as
internal-portal. Documents current { error: string } JSON errors.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* fix(api): document 4xx and reject invalid form-status weeks (DEV-289)
Health, form-status, and roster document 400. form-status now maps
current and returns 400 for a week that is not current or YYYY-WNN.
Redocly treats 302 as a success response, matching the portal.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* style(test): format VPC contract assertions for ruff (DEV-289)
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* fix(api): fail Redocly on missing 4xx and 2xx/3xx (DEV-289)
Promote operation-4xx-response and the 2xx-or-3xx success rule to error.
Replace unused health and roster 400s with 403, matching portal health.
Form-status keeps its real 400 for invalid week.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* fix(api): split week params and allow live menu nulls (DEV-289)
Menu and form-status take current or YYYY-WNN. Orders take YYYY-WNN or a
calendar date and reject current. Menu payloads may emit null menu_url,
calories, protein, and image_url.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* fix(infra): fail prod apply without the afterhours VPC (DEV-289)
Prod never creates the 10.60 fallback VPC. A terraform_data precondition
fails plan and apply when existing_vpc_id is empty, instead of a check
block that only warns.
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* feat(api): serve meals on ECS Fargate instead of Lambda
Keep the Flask app always-on with in-process jobs so CloudFront no longer fronts a cold-start API Gateway.
* fix(jobs): run delayed close and reminder deliveries
Wall-clock skip windows dropped the only weekly SQS attempt when Scheduler already fired in Eastern time. Dev schedules stay disabled.
* fix(api): return JSON objects and stop logging job payloads
Flask now jsonify-s handler dicts so API responses are not HTML, and the worker logs only event and status.
* fix(ci): restore the reusable workflow so the required check is named ci / ci
Inlining the job reported `ci` instead of the org ruleset's `ci / ci`.
* fix(secrets): drop unused os import so ruff check passes
* style: apply ruff format so ci-python-app lint passes
* fix(infra): give meals its own VPC because prod has none
* chore(security): re-key ALB SG checkov suppression after vpc.tf
* fix(auth): accept federated portal Cognito tokens for meals admin
Google Workspace federation stores email_verified=false, which 403'd the
portal Admin probe while the public menu still loaded.
* fix(iam): grant plan role CloudFront DescribeFunction
* chore(meals): remove email_report payroll SES path (PLAT-135)
Stop Monday SES deduction emails now that Flex checkcomponents owns payroll posting.
* chore(meals): delete email_report handler and SAM resources
Remove the leftover SES Lambda source so it cannot be redeployed from template.yaml.
* feat(api): add IAM-authenticated menu publication
Keep weekly menu writes behind Lambda so the GitHub runtime role cannot access the shared DynamoDB table directly.
* refactor(workflow): publish weekly menus through API
Use SigV4 requests for settings and menu publication so the scheduled workflow no longer needs direct DynamoDB access.
* fix: address review comments
* style(python): apply Ruff formatting
* fix(auth): require Google authentication in cloud mode
Remove the public shared-key mechanism and fail closed on Google auth while adding submit-route throttling.
* chore(form): lint template JavaScript in CI
* docs(form): record frontend delivery decisions
* fix: resolve remaining merge conflicts
* fix(auth): require Google authentication in cloud mode
Remove the public shared-key mechanism and fail closed on Google auth while adding submit-route throttling.
* fix(auth): address review follow-ups
Fail closed on whitespace-only Google configuration and centralize shared authentication behavior.
* test(auth): use non-secret Google client fixture
Make the public test identifier explicit so secret scanning does not misclassify it as an API key.
* test(auth): avoid OAuth-shaped fixture
Use a format-neutral audience value so secret scanning can distinguish the fixture from a real client identifier.
* chore(security): suppress public OAuth fixture
Document the scanner false positive without suppressing any runtime credential flow.
* Add CloudWatch alarm coverage for the meal-order-manager stack
Add CloudWatch alarms (all notifying the shared site-alerts SNS topic,
no OKActions, TreatMissingData notBreaching) across the stack:
- Lambda Errors + Throttles alarms for all 7 functions (Sum, 5min,
threshold 0).
- Lambda Duration p99 alarms at ~80% of each function's timeout;
API-fronted functions eval 3/3, cron/async functions eval 1/1.
Thresholds pending sign-off.
- DynamoDB orders-table Read/WriteThrottleEvents alarms (TableName dim).
ThrottledRequests/SystemErrors are not published at the table-only
dimension, so they are intentionally omitted.
- API Gateway (OrderApi v2) 5xx, 4xx (threshold 20, 3/2 to absorb
routine authorizer 401s), and p99 Latency alarms.
Update README with a Monitoring section and correct the Lambda count
to 7 (admin-authorizer was missing).
* Drop pending-sign-off wording from alarm docs
Duration/Latency thresholds are owner-approved; remove PENDING ADAM
SIGN-OFF / pending-sign-off notes from template.yaml comments and README.
The weekly summary PDF generated at Thursday close was stored in the
reports bucket with no way to reach it from the UI — admins had to pull
it from S3 manually. Surface it in the admin panel:
- submit_order: GET /api/admin/summary-pdf?week= (admin-gated) returns
a 5-minute presigned URL from the SUMMARY item's stamped PDF key;
404 for weeks that haven't closed.
- template.yaml: REPORTS_BUCKET env var + read-only s3:GetObject on
reports/* for SubmitOrderFunction (needed so the presigned URL is
signed with sufficient permissions).
- generate_form.py: "Download summary PDF" button in the admin header;
explains Thursday-close timing on 404.
- Tests: presign happy path, 404 open week, 400 missing week, 401
unauthenticated.
- README updated.
Generate a per-person weekly summary PDF at Thursday close and store it
alongside the CSV reports, plus a client-side admin download that rolls
orders up into item -> total quantity for bulk ordering.
- shared/pdf.py: build_weekly_summary_pdf() via fpdf2 (pure-Python,
ARM64-safe; first non-boto3 layer dep). Per-person employee -> item ->
quantity, no pricing.
- aggregate_orders: write reports/{week}/weekly-summary-{week}.pdf
(application/pdf) and stamp weekly_summary_pdf_s3_key on the SUMMARY.
No new IAM (existing S3CrudPolicy). No email/Slack delivery.
- generate_form.py: "Download order list" admin button aggregates the
loaded week's orders into an item->qty CSV (no per-employee breakdown,
no prices) via a Blob download. Works for open weeks too.
- Tests: tests/test_pdf.py; aggregate happy-path now asserts 3 S3
uploads + the pdf key.
- README updated.
* Add admin panel, fix dual-domain auth, harden weekly scrape schedule
Accept both seahavenind.com and seahaven.com Google Workspace domains
for employee sign-in. Add admin panel with order management (view by
week, edit quantities, add/remove items, delete orders) behind Google
auth + DynamoDB admin_emails allowlist. Shift weekly menu scrape from
8:00am to 7:30am ET and add timezone guard to prevent duplicate runs
from dual EST/EDT crons.
* Rename Secrets Manager env vars to avoid CI false positive
The reusable CI workflow greps for keywords like TOKEN and API_KEY in
Lambda environment variables. Our env vars hold Secrets Manager lookup
names, not actual secrets, but the heuristic matched the SM key name
meal-order-manager/slack-bot-token. Rename SLACK_BOT_TOKEN_SECRET to
SLACK_BOT_SM_NAME and FORM_API_KEY_SECRET to FORM_APIKEY_SM_NAME, and
reorder the Globals block so the value falls outside the grep window.
- Change custom domain from orders.seahavenind.com to
orders.seahaven.com to match other subdomain conventions
- Add GET /api/roster endpoint returning employee names/emails
- Replace name/email text inputs with dropdown populated from
roster API (falls back to embedded roster for local dev)
- Fix order deadline text from Wednesday to Thursday 11:59 PM
- Fix Slack API calls: use form-urlencoded for conversations and
users methods that reject JSON body encoding
Apply ruff check --fix and ruff format across all Python files to
pass CI pipeline. Remove unused imports (os, sys), fix f-strings
without placeholders. Update README to reflect sync-roster Lambda,
corrected shared layer path, and current project structure.
Playwright-based menu scraper for Redefine Meals, self-contained HTML
order form with S3/CloudFront hosting, DynamoDB-backed order submission
via API Gateway, and automated payroll deduction reports via SES.