From 4b5991bd144d1658ee5530bcde9f0681327e7ac2 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Wed, 15 Jul 2026 18:49:05 -0400 Subject: [PATCH] Remove state-listing path from wo-po-lookup site lookups The verified-sites table's by-state GSI was deleted by its owning stack (procurement-ingest, audit M-20, 2026-06-03, "0 reads in 30d"), so the state-listing branch of lookup_site has failed at runtime ever since. Per the owner's decision, remove the path end-to-end instead of restoring the GSI: drop the by-state Query from the Lambda, remove the state parameter from the WO_PO_Lookup lookup_site function schema (site_code is now required), strip state listings from the agent instruction, and update the README data-contract table. siteCode point lookups are unaffected. A stale state parameter from an older prepared agent version now returns a clear "no longer supported" message. Refs: INFRA-180 --- README.md | 6 +-- lambda/wo-po-lookup/index.ts | 82 +++++++++++---------------------- lib/constructs/bedrock-agent.ts | 13 ++---- 3 files changed, 35 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index bd354d0..dd7ff72 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ EventBridge (daily 02:00 UTC) - **Payment & Invoice Status** — check if a vendor has been paid, if an invoice is scheduled, look up by check number - **Work Order Lookups** — status, assignments, comments, and full history by WO number - **Purchase Order Lookups** — status, line items, supplier, and ship-to details by PO number -- **Site Lookups** — Amazon facility address and details by site code, or list all sites in a state +- **Site Lookups** — Amazon facility address and details by site code - **Company Policies & SOPs** — employee handbook, SA8000 compliance, and operational procedures ### Vendor Query Priority @@ -93,10 +93,10 @@ Owner-side changes to any of these tables must be coordinated with this repo bef | `WorkOrders` | `procurement-ingest` / `WorkorderIngestStack` (`cdk/wo_stack.py`) | PK `work_order_id` (S). `GetItem` by id; full-table `Scan` in `workorder-sync`. | `work_order_id`, `description`, `wo_status`, `customer`, `site_code`, `building`, `address`, `severity`, `priority`, `assigned_to`, `date_reported`, `scheduled_start`, `due_date`, `updated_at` | Shared CMK (`/seahaven/dynamodb/cmk-arn`) | | `WorkOrderComments` | `procurement-ingest` / `WorkorderIngestStack` (`cdk/wo_stack.py`) | PK `work_order_id` (S), SK `comment_id` (S). `Query` by `work_order_id`. | `work_order_id`, `created_at` (used for sort), `commenter`, `text` | Shared CMK (`/seahaven/dynamodb/cmk-arn`) | | `purchase-orders` | `procurement-ingest` / `po-ingest` (`cdk/po_stack.py`) | PK `po_number` (S). `GetItem` by number; full-table `Scan` in `po-sync`. | `po_number`, `po_status`, `email_type`, `cancelled_at`, `total_amount`, `currency`, `supplier.name`, `source_system`, `order_date`, `revision_date`, `payment_terms`, `requisition_number`, `department`, `submitted_by`, `on_behalf_of`, `ship_to.{name,address,location_code,attn}`, `line_items[].{description,amount,currency,need_by,category}` | Shared CMK (`/seahaven/dynamodb/cmk-arn`) | -| `verified-sites` | `procurement-ingest` / `po-ingest` (`cdk/po_stack.py`) | PK `siteCode` (S). `GetItem` by code, **and `Query` on GSI `by-state`** for state listings. | `siteCode`, `address`, `fullAddress`, `city`, `state`, `zip`, `latitude`, `longitude`, `notes` | AWS-managed (NOT CMK) | +| `verified-sites` | `procurement-ingest` / `po-ingest` (`cdk/po_stack.py`) | PK `siteCode` (S). `GetItem` by code only. | `siteCode`, `address`, `fullAddress`, `city`, `state`, `zip`, `latitude`, `longitude`, `notes` | AWS-managed (NOT CMK) | | `PaymentsDashboard` | `payments-dashboard` / `payments-dashboard` (`template.yaml`) | PK `pk` (S), format `payment#`. `GetItem` by pk; full-table `Scan` filtered `begins_with(pk, "payment#")`. | `pk`, `check_number`, `payee`, `amount_usd`, `method`, `status`, `send_payment_on`, `clear_status`, `cleared_date`, `invoice_numbers`, `company_subsidiary`, `bank_reference` | CMK (shared) | -**Known broken contract (verified-sites `by-state` GSI):** `lambda/wo-po-lookup/index.ts` still queries `IndexName: 'by-state'` for state-based site listings, but the owner removed that GSI on 2026-06-03 (procurement-ingest audit M-20, "0 reads in 30d"). State listings (`lookup_site` with a `state` arg) therefore fail at runtime today. Point-lookups by `siteCode` are unaffected. This needs either the GSI restored owner-side or the state-listing path removed from the bot. +**Resolved contract break (verified-sites `by-state` GSI, INFRA-180):** the owner removed the `by-state` GSI on 2026-06-03 (procurement-ingest audit M-20, "0 reads in 30d"), which broke the bot's state-based site listings at runtime. Per the owner's decision the state-listing path was removed from the bot entirely (query code, `lookup_site` `state` parameter, and agent instructions) rather than restoring the GSI. `lookup_site` now supports `siteCode` point lookups only. ## Documentation diff --git a/lambda/wo-po-lookup/index.ts b/lambda/wo-po-lookup/index.ts index caec4fc..b99e1d5 100644 --- a/lambda/wo-po-lookup/index.ts +++ b/lambda/wo-po-lookup/index.ts @@ -192,60 +192,28 @@ async function lookupPurchaseOrder(poNumber: string): Promise { // ── Site lookup ───────────────────────────────────────────────────────────── -async function lookupSite(siteCode: string, state?: string): Promise { - // Exact site code lookup - if (siteCode) { - const res = await dynamo.send( - new GetItemCommand({ - TableName: process.env.SITES_TABLE!, - Key: { siteCode: { S: siteCode.toUpperCase() } }, - }), - ); +async function lookupSite(siteCode: string): Promise { + const res = await dynamo.send( + new GetItemCommand({ + TableName: process.env.SITES_TABLE!, + Key: { siteCode: { S: siteCode.toUpperCase() } }, + }), + ); - if (res.Item) { - const s = unmarshall(res.Item) as Record; - const lines: string[] = []; - lines.push(`SITE ${s.siteCode}`); - lines.push(`Address: ${s.address === 'TBD' ? 'TBD (not yet established)' : s.fullAddress || s.address}`); - if (s.city) lines.push(`City: ${s.city}`); - if (s.state) lines.push(`State: ${s.state}`); - if (s.zip) lines.push(`Zip: ${s.zip}`); - if (s.latitude && s.longitude) lines.push(`Coordinates: ${s.latitude}, ${s.longitude}`); - if (s.notes) lines.push(`Notes: ${s.notes}`); - return lines.join('\n'); - } - - return `No site found for code "${siteCode}". Please provide a valid Amazon site code (e.g., "ABE2", "DFW6").`; - } - - // State-based search via GSI - if (state) { - const res = await dynamo.send( - new QueryCommand({ - TableName: process.env.SITES_TABLE!, - IndexName: 'by-state', - KeyConditionExpression: '#st = :state', - ExpressionAttributeNames: { '#st': 'state' }, - ExpressionAttributeValues: { ':state': { S: state.toUpperCase() } }, - }), - ); - - const sites = (res.Items ?? []).map((item) => unmarshall(item) as Record); - if (sites.length === 0) { - return `No sites found in state "${state}".`; - } - - sites.sort((a, b) => (a.siteCode ?? '').localeCompare(b.siteCode ?? '')); - const lines = [`Sites in ${state.toUpperCase()} (${sites.length} total):`]; - for (const s of sites) { - const addr = s.address === 'TBD' ? 'TBD' : `${s.address}, ${s.city}`; - const note = s.notes ? ` — ${s.notes}` : ''; - lines.push(`- ${s.siteCode}: ${addr}${note}`); - } + if (res.Item) { + const s = unmarshall(res.Item) as Record; + const lines: string[] = []; + lines.push(`SITE ${s.siteCode}`); + lines.push(`Address: ${s.address === 'TBD' ? 'TBD (not yet established)' : s.fullAddress || s.address}`); + if (s.city) lines.push(`City: ${s.city}`); + if (s.state) lines.push(`State: ${s.state}`); + if (s.zip) lines.push(`Zip: ${s.zip}`); + if (s.latitude && s.longitude) lines.push(`Coordinates: ${s.latitude}, ${s.longitude}`); + if (s.notes) lines.push(`Notes: ${s.notes}`); return lines.join('\n'); } - return 'Please provide a site code (e.g., "ABE2") or a state abbreviation (e.g., "TX") to search.'; + return `No site found for code "${siteCode}". Please provide a valid Amazon site code (e.g., "ABE2", "DFW6").`; } // ── Payment lookups ───────────────────────────────────────────────────────── @@ -355,10 +323,16 @@ export const handler = async (event: BedrockActionEvent): Promise