From 5b3e20bd35c95e3d0a694ccabf4d425a825ae13a Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:24:38 -0400 Subject: [PATCH] chore(docs): drop mgmt dual-delivery rollback language (#159) --- README.md | 2 +- cdk/common.py | 6 +++--- docs/po-template-parser.md | 7 ++++--- docs/runbook-dlq-recovery.md | 5 +---- docs/shoc-webhook-contract.md | 4 ++-- docs/shoc-webhook-plan.md | 10 +++++----- infra/deploy-role/README.md | 6 +++--- infra/deploy-role/create-deploy-role.sh | 4 ++-- 8 files changed, 21 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a848ba5..1b278cd 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ A read-only REST API (API Gateway + the `procurement-api` Lambda, `lambdas/api/` ## Architecture -**IaC:** AWS CDK (Python), three stacks in one app, region `us-east-1`. The `cdk.Environment` is deliberately **account-agnostic** (region-only, no `account=`): the stacks deploy to whichever account the deploy credentials target (`011934824531` seahaven-prod since the 2026-07 account migration; formerly mgmt `328440206208`), and every account-derived template value — bucket names, `Lambda::Permission` source account, the site-alerts SNS action ARN, the Bedrock ARN below — renders as the CloudFormation `AWS::AccountId` pseudo-parameter rather than a literal. Pinning `account=` was evaluated in Phase 4 and rejected: it would resolve those tokens to literals, and against the deployed (account-agnostic) templates CloudFormation flags the `RemovalPolicy.RETAIN` email buckets as requiring replacement — a data-loss risk — for no functional gain. +**IaC:** AWS CDK (Python), three stacks in one app, region `us-east-1`. The `cdk.Environment` is deliberately **account-agnostic** (region-only, no `account=`): the stacks deploy to whichever account the deploy credentials target. **Live target is only seahaven-prod `011934824531`** (migrated 2026-07; mgmt stacks deleted in PLAT-67 on 2026-08-05). Never deploy from `main` to mgmt `328440206208` — templates would recreate against cold-archive RETAIN leftovers. Every account-derived template value — bucket names, `Lambda::Permission` source account, the site-alerts SNS action ARN, the Bedrock ARN below — renders as the CloudFormation `AWS::AccountId` pseudo-parameter rather than a literal. Pinning `account=` was evaluated in Phase 4 and rejected: it would resolve those tokens to literals, and against the deployed (account-agnostic) templates CloudFormation flags the `RemovalPolicy.RETAIN` email buckets as requiring replacement — a data-loss risk — for no functional gain. All Lambdas: Python 3.12, ARM64, 60-day log retention. diff --git a/cdk/common.py b/cdk/common.py index f4b4be3..830ac03 100644 --- a/cdk/common.py +++ b/cdk/common.py @@ -87,9 +87,9 @@ def make_function_log_group(scope, id_prefix, function_name): RETAIN matches the repo convention for stateful resources and mirrors the old behavior (LogRetention never deleted groups on stack delete). NOTE: - in an account where ``/aws/lambda/`` already exists out-of-band - (mgmt), deploying this CREATE would collide -- acceptable because mgmt is - frozen post-migration and never redeployed from main. + never ``cdk deploy`` this app to mgmt (328440206208) — PLAT-67 deleted the + mgmt stacks and left RETAIN cold-archive log groups; a redeploy would + collide with those leftovers. """ return logs.LogGroup( scope, diff --git a/docs/po-template-parser.md b/docs/po-template-parser.md index 193677f..7d9ade7 100644 --- a/docs/po-template-parser.md +++ b/docs/po-template-parser.md @@ -29,10 +29,11 @@ The PO email processor (`lambdas/po/email_processor/handler.py`) sends **every** Two phases: a read-only multi-agent (ultracode) feasibility study over a 120-email sample, then a full-bucket triage over **all 3,448** inbound emails to get real distribution numbers. ### 2.1 Data access -> **Migration note (2026-07):** this section is historical — the harvest ran -> against the management account. Post-migration the live bucket is +> **Migration note (2026-07 / PLAT-67 2026-08-05):** this section is historical — the harvest ran +> against the management account. The live bucket is > `s3://po-ingest-emails-011934824531/inbound/` (seahaven-prod, profile -> `seahaven-prod`); the mgmt bucket persists only until decommission. +> `seahaven-prod`). The mgmt bucket `po-ingest-emails-328440206208` remains as +> PLAT-67 cold archive (tagged; wipe deferred). - PO email bucket: `s3://po-ingest-emails-328440206208/inbound/` (AWS account **328440206208**, us-east-1). - ~~Reached via AWS profile `amoussa-mgmt` (SSO); default CLI creds are the personal account `681986854588`~~ **Stale (corrected 2026-07-16 during the fixture harvest):** the default CLI session is now authenticated to **328440206208** directly — no `--profile` flag needed. - ⚠️ **Corpus is aging out:** `inbound/` objects carry an S3 lifecycle expiration (~90-day rolling window; oldest object 2026-04-17 at harvest time, 3,422 objects vs 3,448 at triage). Any further harvesting should not be deferred long. diff --git a/docs/runbook-dlq-recovery.md b/docs/runbook-dlq-recovery.md index 31331a6..2a23d20 100644 --- a/docs/runbook-dlq-recovery.md +++ b/docs/runbook-dlq-recovery.md @@ -26,11 +26,8 @@ target. Recovery is manual, via targeted re-invoke. | WO | `workorder-email-processor` | _CDK-generated; fill from stack resources after the first prod deploy_ | `workorder-ingest-emails-011934824531` | `workorder-email-processor-dlq-messages` | DLQ URLs are `https://sqs.us-east-1.amazonaws.com/011934824531/`. -(Until mgmt decommission completes, the pre-migration mgmt-account queues -`po-ingest-EmailProcessorDlqA753DED5-az8LUZE3ubtz` and -`WorkorderIngestStack-EmailProcessorDlqA753DED5-Q8H555LrqSU1` still exist in -328440206208 with any pre-cutover dead letters.) Both DLQs: 14-day retention, SSE, TLS-enforced, `VisibilityTimeout` 30s. +(Mgmt-account DLQs were removed with the PLAT-67 stack teardown on 2026-08-05.) ## Recovery procedure (no redrive — receive → extract key → targeted re-invoke → verify → purge) diff --git a/docs/shoc-webhook-contract.md b/docs/shoc-webhook-contract.md index 0eba8d3..526aeec 100644 --- a/docs/shoc-webhook-contract.md +++ b/docs/shoc-webhook-contract.md @@ -1,12 +1,12 @@ # SHOC Work-Order Webhook — Delivery Contract (v1) **Status:** DRAFT — for SHOC team (Luby) review. **Rev 2026-07-23** (supersedes the 2026-07-16 draft: producer account corrected to seahaven-prod; reconciliation backstop changed to the new read API; §4.1 `unknown` status note; §3 `write_origin` forward-compat note. Sections 2, 5, 6, 7, and 10 are unchanged from the 07-16 draft). -**Producer:** `workorder-shoc-emitter` Lambda, Sea Haven **seahaven-prod** AWS account (**011934824531**, us-east-1). The former management account (328440206208) is frozen/rollback-only and will never host this feed, its secret, or its streams. +**Producer:** `workorder-shoc-emitter` Lambda, Sea Haven **seahaven-prod** AWS account (**011934824531**, us-east-1). Mgmt-account procurement-ingest stacks were deleted in PLAT-67 (2026-08-05); this feed, its secret, and its streams exist only in seahaven-prod. **Consumer:** SHOC backend (.NET 8), initially `https://api.dev.seahaven.com`. Endpoint path is SHOC's choice — suggested `POST /api/webhooks/work-orders`; map entities onto the work-orders domain from shoc-backend PR #10. ## 1. Overview -Every work-order mutation the procurement-ingest pipeline writes to DynamoDB is pushed to SHOC as an HTTPS POST within seconds. The feed is driven by DynamoDB Streams, so events are emitted **in the exact order the pipeline committed them**, per work order. DynamoDB remains the source of truth; this webhook is a realtime feed. The reconciliation backstop (and the initial-history load — the feed starts at activation time, not from history) is the **procurement read API** (`GET /work-orders`, `GET /work-orders/{id}/comments`, IAM SigV4 — see its OpenAPI doc). SHOC's existing SyncController DynamoDB scan is transitional: it points at the old management account and retires when those stacks are decommissioned. `SyncVendorReplies` should be dropped on the SHOC side — the `VendorReplies` table is dead (its writer was deleted), and no `vendor_reply` webhook event exists or is planned. +Every work-order mutation the procurement-ingest pipeline writes to DynamoDB is pushed to SHOC as an HTTPS POST within seconds. The feed is driven by DynamoDB Streams, so events are emitted **in the exact order the pipeline committed them**, per work order. DynamoDB remains the source of truth; this webhook is a realtime feed. The reconciliation backstop (and the initial-history load — the feed starts at activation time, not from history) is the **procurement read API** (`GET /work-orders`, `GET /work-orders/{id}/comments`, IAM SigV4 — see its OpenAPI doc). Mgmt stacks are gone (PLAT-67); SHOC should retire SyncController's DynamoDB scan of the old management account and use the read API only. `SyncVendorReplies` should be dropped on the SHOC side — the `VendorReplies` table is dead (its writer was deleted), and no `vendor_reply` webhook event exists or is planned. ## 2. Transport diff --git a/docs/shoc-webhook-plan.md b/docs/shoc-webhook-plan.md index 4b47635..1788733 100644 --- a/docs/shoc-webhook-plan.md +++ b/docs/shoc-webhook-plan.md @@ -2,8 +2,8 @@ **Branch:** `feat/shoc-wo-webhook` (re-cut on `main` 2026-07-23; all build work targets the post-refactor layout — `cdk/common.py` helpers, explicit LogGroups, decomposed handlers, consolidated test roots — not the pre-refactor style earlier drafts implied). **Companion docs:** `docs/shoc-webhook-contract.md` (Rev 2026-07-23 — the producer/consumer contract; Luby builds the receiver against it) and the procurement read API OpenAPI spec (`lambdas/api/openapi.json`, built in the sibling `procurement-api` effort — the reconciliation/backfill path). -**Requestor:** Luby (SHOC team). Decisions captured 2026-07-16: all WO event types, seconds-latency, full-payload push, HMAC + automated rotation, in-order per WO, prod-data-in-dev accepted, dual-write retained (DynamoDB retirement deferred for scope discipline — the original blocker, seahaven-slack-bot, was decommissioned 2026-07-23; retirement is now sequenced after SHOC prod + mgmt decommission, not this PR). -**Account (2026-07-23):** everything here builds in **seahaven-prod (011934824531)**. The old management account (328440206208) is frozen/rollback-only. +**Requestor:** Luby (SHOC team). Decisions captured 2026-07-16: all WO event types, seconds-latency, full-payload push, HMAC + automated rotation, in-order per WO, prod-data-in-dev accepted, dual-write retained (DynamoDB retirement deferred for scope discipline — the original blocker, seahaven-slack-bot, was decommissioned 2026-07-23; revisit after SHOC prod). +**Account:** everything here builds in **seahaven-prod (011934824531)**. Mgmt procurement-ingest stacks were deleted in PLAT-67 (2026-08-05); RETAIN leftovers there are cold archive only. ## Architecture @@ -22,7 +22,7 @@ Rationale (vs. inline call from the processor): DynamoDB Streams gives per-parti 1. Branch re-cut on `main` (done 2026-07-23). 2. Luby signs off `docs/shoc-webhook-contract.md` Rev 2026-07-23 (endpoint path, PR #10 entity mapping, the `unknown`-status mapping, `SyncVendorReplies` retirement). 3. Confirm the SHOC receiver role ARN for the resource policies (`arn:aws:iam::396287094661:role/shoc-backend-dev`) — the role lives in SHOC's account, outside this repo's control; confirm it still exists before the CDK references it (the first cross-account `get-secret-value` test is the hard proof). -4. **⚠️ ACCOUNT GATE: every deploy in this plan targets seahaven-prod (011934824531) ONLY.** Never deploy this stack to mgmt (328440206208), and never "temporarily" enable streams on mgmt's copies of the WO tables — that is the only path by which the SES dual-delivery bake could ever double-send to SHOC. Verify `aws sts get-caller-identity` resolves to 011934824531 before `cdk deploy`. +4. **⚠️ ACCOUNT GATE: every deploy in this plan targets seahaven-prod (011934824531) ONLY.** Never deploy this app to mgmt (328440206208) — templates are account-agnostic and would recreate stacks against cold-archive RETAIN data (PLAT-67). Verify `aws sts get-caller-identity` resolves to 011934824531 before `cdk deploy`. 5. Confirm with Luby which SHOC environment should receive the feed now — "prod-data-in-dev" was accepted 2026-07-16, pre-migration; re-confirm the target URL. ## Phase 1 — Secret + rotation (CDK, `wo_stack.py`) @@ -98,11 +98,11 @@ Optional EMF `DeliveryOutcome` metric (delivered/rejected, mirroring the ParseMe 2. Luby builds the receiver against the contract + shared test vectors and loads history via the read API; receiver passes the vectors at the target env. 3. Activation: one-line `enabled=True` PR. The ESM starts at `LATEST` — no historical flood. 4. Verify end-to-end with a live APM email; watch `iterator-age` + `rejected` alarms for 48h. -5. SyncController's Dynamo scan (incl. `SyncVendorReplies`) retires with the mgmt decommission; the read API is the standing reconciliation path. +5. SyncController's Dynamo scan (incl. `SyncVendorReplies`) is **SHOC-owned** retirement work now that mgmt stacks are gone (PLAT-67); the read API is the standing reconciliation path. ## Explicit non-goals (this PR) -- No DynamoDB retirement / write-path switch (kept out for scope discipline — the former blocker, seahaven-slack-bot, is decommissioned; revisit after SHOC prod + mgmt decommission). +- No DynamoDB retirement / write-path switch (kept out for scope discipline — the former blocker, seahaven-slack-bot, is decommissioned; revisit after SHOC prod). - No PO-pipeline webhook. - No change to `workorder-email-processor` or its parser. - No write-back endpoints (phase 2 of the read API, own PR + own IAM cross-review). diff --git a/infra/deploy-role/README.md b/infra/deploy-role/README.md index 94ea7a1..0b9ceeb 100644 --- a/infra/deploy-role/README.md +++ b/infra/deploy-role/README.md @@ -1,9 +1,9 @@ # Deploy role: githubdeploy-procurement-ingest (seahaven-prod) OIDC deploy role for this repo's GitHub Actions pipeline in AWS account -`011934824531` (seahaven-prod), us-east-1. Created as part of the migration -from the management account (328440206208); the mgmt role of the same name -stays untouched until decommission as the emergency mgmt deploy path. +`011934824531` (seahaven-prod), us-east-1. Created during the migration from +the management account (328440206208). The former mgmt twin of this role was +deleted in PLAT-67 (2026-08-05); do not recreate it. ## Files diff --git a/infra/deploy-role/create-deploy-role.sh b/infra/deploy-role/create-deploy-role.sh index ee2587b..c251b3b 100755 --- a/infra/deploy-role/create-deploy-role.sh +++ b/infra/deploy-role/create-deploy-role.sh @@ -6,8 +6,8 @@ # `githubdeploy-procurement-ingest` in AWS account 011934824531 (seahaven-prod), # us-east-1, for the Sea-Haven-Industries/procurement-ingest repo (main branch). # -# Part of the mgmt -> seahaven-prod migration. The mgmt-account role of the -# same name is left untouched until decommission (emergency mgmt deploy path). +# Prod-only deploy role (seahaven-prod). The former mgmt twin was deleted in +# PLAT-67 (2026-08-05); do not recreate a mgmt githubdeploy-procurement-ingest. # # GATE - DO NOT EXECUTE until BOTH of the following have passed on these # exact artifact files: