Commit graph

15 commits

Author SHA1 Message Date
seahaven-openswe[bot]
bf235e70d7
feat: add cash-position tile from boa_balance snapshots to App Home (#80)
Some checks are pending
Deploy / deploy (push) Waiting to run
* feat: add cash-position tile from boa_balance snapshots to App Home

Read the latest previous-day boa_balance snapshot (authoritative)
to display current ledger and available balance on the App Home
summary bar. Optionally surfaces today's intraday snapshot as
provisional. Walks backward up to 14 days to handle weekend/holiday
gaps, using targeted GetItem by computed key instead of a scan.

Refs: #77

* fix: null-guard cash-position tile, add error logging, drop UTC date skew and serial GetItems

- Null-guard current_ledger on the authoritative cash-position tile so a
  missing ledger renders 'Not available' instead of the false '/bin/bash.00'
- Log GetCommand failures with console.error + logSafe instead of silent
  catch, matching the rest of the codebase
- Derive dates from local midnight instead of toISOString UTC, so the
  current-day lookup doesn't miss from 8pm ET to midnight
- Fetch all 14 balance keys in parallel with Promise.allSettled instead of
  14 serial GetCommands; start previous-day walk at i=1 since today's
  previous-day snapshot can't exist until tomorrow
- Use Item.as_of_date directly instead of a synthetic _asOfDate field
- Remove _asOfDate from test fixtures; add test for the null-ledger guard

---------

Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
2026-07-22 18:41:32 -04:00
Adam Moussa
a6a8132a69
feat(apphome): surface returned payments as a needs-action queue (#74)
* feat(apphome): surface returned payments as a needs-action queue

Bank-returned payments carry status "Cleared" (the CSV ladder has no
Returned rung), so the status skip-list hid them from every App Home
bucket; five Feb-Apr returns ($5,256.62) surfaced only via a manual
statement reconciliation. Route on clear_status ahead of the status
skip-list: non-canceled Returned records render in an always-visible
action queue (oldest return first) plus a summary tile, and are
excluded from Outstanding totals. Terminal voided-and-bounced records
stay out of both (audit trail only). Membership keys off clear_status,
not returned_date, so redeposited checks drop back out of the queue.

Refs: #70

* docs(apphome): restore returned-queue README bullet dropped in rebase
2026-07-22 16:38:56 -04:00
Adam Moussa
77fb8e4ad0
fix(csv): M/D/YY date parsing with loud rejects; stop canceled rows zeroing stored fields (#72)
Some checks are pending
Deploy / deploy (push) Waiting to run
* fix(csv): parse M/D/YY dates with loud rejects; stop canceled rows zeroing stored fields (#65, #68)

Stampli exports switched from MM/DD/YYYY to M/D/YY, which toISODate
mangled into garbage ISO strings and slackAppHome's parseMDYLocal turned
into year-1926 dates. Canceled rows also blank 'Amount in USD' and can
blank dates, which the unconditional upsert wrote over previously stored
real values (116 records at amount_usd=0 as of the 2026-07-21
reconciliation).

- src/dates.js: shared strict parser for M/D/YY + MM/DD/YYYY (real-date
  validation, 2-digit years 2000-based, plausibility window helper)
- processPaymentCsv: canonicalize send_payment_on to MM/DD/YYYY; reject
  rows with unparseable/implausible dates and fail the invocation after
  valid rows are processed (surfaces via errors alarm + async DLQ;
  retry-safe since upserts are idempotent and existing checks are not
  re-offered to BoA); only overwrite amount_usd/send_payment_on with
  real values, falling back to the 'Amount' column on cancels
- BoA add_Issue/cancel_Issue now use stored record values when the CSV
  row is blank (cancel_Issue previously sent amount 0.00 for voids) and
  skip registration on missing date/amount instead of sending garbage;
  same guard on the backfill path
- slackAppHome: use the shared parser (fixes 1926 aging)

* fix(csv): validate BoA registration data before writes; harden logging (security review)

Hardening from the /sh-security-review pass on this branch:

- BoA eligibility (resolvable amount + issue date) is now decided and
  validated BEFORE the DDB upsert: a cancel-transition row we cannot act
  on is rejected with the record untouched, so the transition gate stays
  open for a later clean file instead of silently losing the cancel
  forever (the skip guard previously ran after the status write)
- First-seen rows that are already canceled are stored but never
  offered to add_Issue — registering a voided check as an active issue
  created a live issue with no cancel to follow (worsened by the Amount
  fallback making the previously-failing call succeed)
- isPlausibleSendYear window is now relative (year-7..year+2) instead
  of hardcoded 2020-2035
- Untrusted CSV values are JSON-encoded and truncated before log
  interpolation (quoted CSV cells carry newlines -> CloudWatch log-line
  forgery, CWE-117)
- OAuth token-exchange failures no longer throw the raw BoA authn
  response body (aligns with the PR #63 log-scrub posture)

Verified: unit smoke on the date module; full replay of the real
2026-07-21 export (1,197 rows) against live table state produces 1,197
upserts, 0 rejects, 0 spurious BoA submissions.

* fix(csv): comma-tolerant amount parsing in backfill (PR #72 review)

Number() on a hand-inserted comma-formatted string amount would NaN and
skip the check during backfill; hoist the CSV path's parseAmount to
module scope and share it. No live records are affected (all amount_usd
values are DDB Number type) — defensive consistency.
2026-07-22 00:28:13 +00:00
Adam Moussa
0eeca1e7a5
fix(security): verify Slack signatures on /slack/events and stop logging raw BoA responses (#63)
Some checks failed
Deploy / deploy (push) Has been cancelled
Two agentic-review findings on the payments-dashboard security surface:

- CRITICAL (CWE-862): the /slack/events endpoint (slackAppHome) performed no
  Slack signing-secret verification, so an unauthenticated caller could forge
  app_home_opened/block_actions events and exfiltrate payment data via
  DynamoDB scans + views.publish. Add HMAC-SHA256 signature verification with
  a 5-minute replay window over the raw request body, mirroring the existing
  expenseReceiver pattern. Requests failing verification get a 401 before any
  body parsing, DynamoDB access, or Slack API call. Adds the
  SLACK_SIGNING_SECRET_NAME env var and an additive secretsmanager grant for
  payments-dashboard/slack-signing-secret.

- HIGH (CWE-532): full BoA CashPro response bodies + headers were logged to
  CloudWatch and persisted to DynamoDB (response_body/response_headers), which
  could expose account numbers/PII. Drop those fields from both boa_txn#
  records and stop logging raw bodies/headers on both the main submit path and
  the backfill retry path; log status + txnId only (txnId still correlates to
  BoA support for the full body).

Verification: sam validate, node --check both handlers. /sh-security-review
(detector fan-out + verifier) and GPT-4.1 cross-family review run; the
cross-family review confirmed the IAM grant is purely additive.
2026-07-10 17:04:35 -04:00
Adam Moussa
90accf2f39 Migrate secrets from SSM to Secrets Manager
API tokens and credentials must live in Secrets Manager per
secrets-and-config.md, but the four original payment Lambdas still
read 10 SecureString SSM params. Move them to three grouped secrets
(slack-bot-token plaintext, boa-check-mgmt and boa-reporting as JSON),
matching the pattern the expense Lambdas already use. IAM is scoped to
secretsmanager:GetSecretValue per secret; the VPC Lambdas reach the
public endpoint over the existing NAT path. Test/reissue scripts and
the client-ssm dependency are updated/removed accordingly.

Refs: #3
2026-06-02 20:29:18 -04:00
Adam Moussa
b7feb4a914
Collapsible dashboard sections and clean up BoA audit log (#22)
* Make dashboard sections collapsible and clean up BoA audit log

- Scheduled Checks, Scheduled ACH, and Outstanding Checks sections
  default to collapsed with summary line; Expand/Collapse button
  toggles detail view via private_metadata state
- Filter backfill failure records from BoA submissions display
- Limit Recent BoA Submissions to 5 most recent entries

* Share Slack home publish pipeline

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>
2026-05-08 11:24:32 -04:00
Adam Moussa
b242df15b5 Log BoA submissions to DDB and surface in Slack App Home
Capture response headers, body, and transactionId on every check-issue
API call; persist as boa_txn#<ts>#<action> records with 90-day TTL.
Add "Recent BoA Submissions" section to App Home showing the last 10
with click-to-copy transactionId.

Motivation: BoA support asked for a transactionId from a past
successful call and we had no way to recover it from CloudWatch
summary logs alone.

Also adds simulate-csv.cjs (dry-run preview) and stampli-uploader.sh
(launchd-invoked S3 uploader), and gitignores debug artifacts.
2026-04-20 17:40:55 -04:00
Adam Moussa
cb07421282 Show Reference # instead of Check # for ACH payments in modal 2026-04-10 18:14:22 -04:00
Adam Moussa
f769ea5872 Fix base64 body decoding for Slack interactivity payloads 2026-04-10 18:11:54 -04:00
Adam Moussa
941cd094fc Add drill-down modals: View buttons open payment detail for each row 2026-04-10 18:08:06 -04:00
Adam Moussa
7f94eee308 Redesign dashboard: 6 outstanding age buckets, summary bar, improved layout 2026-04-10 18:03:10 -04:00
Adam Moussa
53842f0821 Update dashboard: split scheduled by method, outstanding checks by age
- Scheduled section split into Scheduled Checks and Scheduled ACH
- Remove Cleared section from dashboard
- Outstanding renamed to Outstanding Checks with <=90 day and >90 day totals
2026-04-10 17:28:01 -04:00
Adam Moussa
c43228831d Add unified payment status, ACH auto-clear, and categorized dashboard
- processPaymentCsv: switch from BatchWrite to upsert, auto-mark ACH
  payments as Cleared when send date has passed
- slackAppHome: categorize payments into Scheduled/Outstanding/Cleared
  sections using unified status field
- Add seed scripts for bulk-loading Stampli and bank CSV exports
2026-04-10 17:23:54 -04:00
Adam Moussa
7150028bd3 Add VPC/NAT for static IP, dedup payments by check number, SSM token
- VPC with NAT Gateway for static outbound IP (52.86.95.107) for BoA API
- Payments stored as individual DynamoDB items keyed by check number
- Slack bot token fetched from SSM at runtime instead of CF parameter
- Slack Lambda scans payment items instead of reading single blob
2026-04-09 14:27:34 -04:00
Adam Moussa
f1c2063f52 Initial SAM stack: payments CSV to Slack App Home
Two Lambda functions:
- processPaymentCsv: S3 trigger, parses CSV, stores dashboard in DynamoDB
- slackAppHome: API Gateway endpoint for Slack events, publishes Home tab view
2026-04-09 13:29:28 -04:00