* fix(form): add status regions, live total, and a11y CSS
Dual alert/status slots for form and admin, aria-live on the sticky
total, success heading focus target, More/Less affordance styles, and
44px coarse-pointer chip padding.
* fix(form): wire a11y labels, status helper, and desc expand
Meal-scoped qty labels at card create time, aria-pressed filter chips,
dual-node showStatus replacing all alert() calls (confirm retained),
overflow-gated More/Less, and success-heading focus after submit.
* test(form): cover a11y labels, status region, and desc toggle
Structural checks for dual status nodes and no alert(); Playwright for
init-time qty labels, aria-pressed chips, overflow More/Less, and
failed-submit text landing in role=alert.
* fix: address review comments
* fix(form): wrap Google user bar at phone widths
Allow the signed-in Google identity and admin controls to wrap below 480px while preserving the desktop row, with generated-form Playwright coverage for phone widths.
* fix(form): preserve 480px user bar boundary
Keep the mobile layout below 480px and lock both sides of the breakpoint with browser coverage.
* style(tests): apply ruff formatting
* test(form): guarantee Playwright browser cleanup
* test(form): strengthen phone-width coverage
* fix: add @classmethod and use cls in tests/test_generate_form.py
Co-authored-by: seahaven-openswe[bot] <296972425+seahaven-openswe[bot]@users.noreply.github.com>
* fix(tests): restore class fixture discovery
---------
Co-authored-by: seahaven-openswe[bot] <296972425+seahaven-openswe[bot]@users.noreply.github.com>
* refactor(form): extract Jinja templates and lock form JS in CI
Split the monolithic generate_form f-string into form.html.j2/css/js
plus admin.js, inject a single window.CONFIG blob, and add structural
plus Playwright coverage so qty delegation and clamp stay green in CI.
* Update src/server/generate_form.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix(form): isolate admin script bindings
* fix(form): address admin and form review findings
* fix(form): resolve remaining review nitpicks
* ci(workflow): restore required check context
Keep the reusable workflow caller job compatible with the organization-required ci / ci status check.
* fix(form): address remaining review findings
* fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 1 unresolved review comment.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* ci: add least-privilege permissions blocks to workflow callers
Resolves code scanning alerts #9 and #11 (actions/missing-workflow-permissions). Both callable workflows only need contents: read; the dependency-review callable already declares it internally, this caps the caller token to match.
* fix: turn off debug mode in Flask app configuration.
Resolves code scanning alert #2 (Flask app is run in debug mode)
* ci: bump reusable workflow pin to f71002a (ruff 0.15.22 pin)
Picks up Sea-Haven-Industries/.github#88, which pins ruff in
ci-python-sam so unpinned installs no longer float to new releases
with changed default rule sets (0.16.0 broke CI with 89 pre-existing
findings). Refs Sea-Haven-Industries/.github#87.
Moves the false-positive suppression for tests/test_submit_order.py:45 (a dummy
test API key, proof-or-kill verified 2026-07-13) from machine-level to a tracked
repo-local .security-review/suppressions.json so the Open SWE daily-report
automation — which cannot see ~/.config on the Mac — resolves it. Machine-level
copy retained until this merges.
* fix(test): mock get_settings/get_roster in aggregated tests + enable CI tests (INFRA-72)
handle_orders_aggregated now delivers the summary via admin DMs (PR #15),
adding get_settings/get_roster calls the aggregated tests never mocked, so
they hit live DynamoDB. Mock both and assert the message content on the
send_dm path. Set run-tests: true so the suite actually runs in CI.
* fix(test): default AWS region in conftest so CI collection doesn't hit NoRegionError (INFRA-72)
Handlers build boto3 clients at module load; CI runners have no AWS config,
so test collection raised NoRegionError once the suite actually ran. Set a
region default before imports (offline client construction; calls are mocked).
* fix(test): add repo root to sys.path so CI's bare pytest collects functions.* (INFRA-72)
test_aggregate_orders imports functions.aggregate_orders.handler, which needs
the repo root on sys.path. python -m pytest injects CWD automatically but CI
runs pytest directly, so these 11 tests errored at collection in CI only.
* 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.
aggregate_orders uploads the weekly PDF/CSVs to S3 and then calls
put_summary(), but put_summary spread the summary dict (which contains
float prices/totals) straight into put_item without Decimal conversion.
boto3 rejects floats (TypeError: Float types are not supported), so the
SUMMARY DynamoDB item was never written for any week (W20-W23).
The summary-PDF download endpoint gates on get_summary(week), so it got
None and returned 404 -- 'No summary PDF for <week> yet' -- even though
the PDF was sitting in S3.
Convert via _to_decimal in put_summary, matching put_order/put_settings.
Scope-down requirement from INFRA-97: github-cfn-execution-role
needs iam:CreateRole scoped to roles that carry the org boundary,
so every role this stack creates must declare it.
- Globals.Function.PermissionsBoundary: applies to all six
SAM auto-generated Lambda execution roles
- AdminAuthorizerInvokeRole: adds PermissionsBoundary + Path
/cfn-managed/ (explicit AWS::IAM::Role)
The only consumer of AdminAuthorizerInvokeRole is the HttpApi
authorizer's FunctionInvokeRole, which references it via
!GetAtt AdminAuthorizerInvokeRole.Arn — no hardcoded ARN
strings, so the path change is safe.
Refs: INFRA-103
The /api/admin/* routes (GET/PUT/DELETE /api/admin/orders and
GET /api/admin/summary-pdf) were AuthorizationType NONE, relying entirely on
the in-handler _verify_admin Google-token + admin-email check. This adds an
HTTP API Lambda authorizer that enforces the same check at the gateway, so
unauthenticated requests are rejected before reaching the integration.
- New admin_authorizer Lambda: validates the Authorization: Bearer Google ID
token (aud + allowed Workspace domain) and the admin_emails allow-list from
DynamoDB, returning the HTTP API simple response {isAuthorized}. Fails closed
on missing config, unavailable client ID, bad token, or DynamoDB error.
- OrderApi gains an AdminGoogleAuthorizer with result caching disabled
(AuthorizerResultTtlInSeconds: 0) so expired tokens / removed admins can't be
served from cache. Wired onto all four admin events; no DefaultAuthorizer, so
public routes (submit-order, form-status, roster) stay NONE.
- IAM role for API Gateway to invoke the authorizer; 60-day log group.
- 10 unit tests for the authorizer.
No client change: the admin panel already sends Authorization: Bearer
<google_id_token>. The in-handler _verify_admin check stays as defense-in-depth.
Cross-reviewed by GPT-4.1 (APPROVE-WITH-FIXES); both BLOCK items applied
(disable authorizer caching, fail-closed on DynamoDB error).
Address GPT-4.1 cross-review of the IAM change:
- Validate the week param (YYYY-WNN) and the DynamoDB-sourced PDF key
shape before presigning, so a tampered SUMMARY record can't mint URLs
for other report files (payroll CSVs).
- Narrow the IAM resource from reports/* to
reports/*/weekly-summary-*.pdf — least privilege over the bucket.
- Reuse a module-level S3 client; name the URL TTL constant.
- Distinguish "week not found" from "PDF key missing" 404s.
- Tests: malformed-week 400 and tampered-key 500 (asserts no presign).
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.
Bump aws-actions/configure-aws-credentials to @v6 (org target) in the
weekly-menu workflow. v6 is the verified org standard alongside
actions/checkout@v6.
Ref: engineering-handbook cicd.md (workflow standardization).
* Add dependency-review caller workflow
Add a pull_request-triggered caller that invokes the org-level
callable-dependency-review workflow to scan dependency changes and
fail on high-severity advisories.
* chore: retrigger checks
* chore: retrigger dep review (post-fix)
Re-adds WebACLId (from SSM /seahaven/waf/app-web-acl-arn) now that the
github-cfn-execution-role has wafv2 perms. Deployed + verified: orders.seahaven.com
distribution now fronted by seahaven-app-waf.
* Add WAF + API access logging/throttling (audit Day 3: M-17, M-18)
- M-17: associate the shared seahaven-app-waf CloudFront WebACL (ARN from SSM
/seahaven/waf/app-web-acl-arn) with the orders.seahaven.com distribution.
- M-18: enable HTTP API access logging to /aws/apigateway/meal-order-manager
(90d) + default route throttling (100 rps, 50 burst) on OrderApi.
* Defer M-17 WAF association (deploy role lacks wafv2)
The github-cfn-execution-role (sticky CFN service role on this stack) has
cloudfront:* + ssm:* but no wafv2:*, so associating the WebACL fails with
'Unable to verify read permissions on Web ACL'. Landing M-18 (access logging +
throttling) now; WAF association re-added once the deploy role gets wafv2 perms
(tracked separately).
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.
The closed overlay (z-index 2000) was blocking Google sign-in from
firing, so the admin check never ran. Now the overlay is deferred
when Google auth is configured — checkAdmin() shows or bypasses
it after auth completes.
Admins (by email in settings) can now view and submit orders even
after the form closes. The orders-aggregated Slack summary is sent
as a DM to each admin instead of posting to the channel.