chore(docs): drop mgmt dual-delivery rollback language (#159)
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
Adam Moussa 2026-08-04 20:24:38 -04:00 • committed by GitHub
parent d677358801
commit 5b3e20bd35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 21 additions and 23 deletions

View file

@ -119,7 +119,7 @@ A read-only REST API (API Gateway + the `procurement-api` Lambda, `lambdas/api/`
## Architecture ## 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. All Lambdas: Python 3.12, ARM64, 60-day log retention.

View file

@ -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 RETAIN matches the repo convention for stateful resources and mirrors the
old behavior (LogRetention never deleted groups on stack delete). NOTE: old behavior (LogRetention never deleted groups on stack delete). NOTE:
in an account where ``/aws/lambda/<fn>`` already exists out-of-band never ``cdk deploy`` this app to mgmt (328440206208) — PLAT-67 deleted the
(mgmt), deploying this CREATE would collide -- acceptable because mgmt is mgmt stacks and left RETAIN cold-archive log groups; a redeploy would
frozen post-migration and never redeployed from main. collide with those leftovers.
""" """
return logs.LogGroup( return logs.LogGroup(
scope, scope,

View file

@ -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. 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 ### 2.1 Data access
> **Migration note (2026-07):** this section is historical — the harvest ran > **Migration note (2026-07 / PLAT-67 2026-08-05):** this section is historical — the harvest ran
> against the management account. Post-migration the live bucket is > against the management account. The live bucket is
> `s3://po-ingest-emails-011934824531/inbound/` (seahaven-prod, profile > `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). - 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. - ~~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. - ⚠️ **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.

View file

@ -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` | | 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/<queue-name>`. DLQ URLs are `https://sqs.us-east-1.amazonaws.com/011934824531/<queue-name>`.
(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. 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) ## Recovery procedure (no redrive — receive → extract key → targeted re-invoke → verify → purge)

View file

@ -1,12 +1,12 @@
# SHOC Work-Order Webhook — Delivery Contract (v1) # 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). **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. **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 ## 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 ## 2. Transport

View file

@ -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). **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). **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). **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 (2026-07-23):** everything here builds in **seahaven-prod (011934824531)**. The old management account (328440206208) is frozen/rollback-only. **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 ## 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). 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). 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). 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. 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`) ## 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. 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. 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. 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) ## 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 PO-pipeline webhook.
- No change to `workorder-email-processor` or its parser. - 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). - No write-back endpoints (phase 2 of the read API, own PR + own IAM cross-review).

View file

@ -1,9 +1,9 @@
# Deploy role: githubdeploy-procurement-ingest (seahaven-prod) # Deploy role: githubdeploy-procurement-ingest (seahaven-prod)
OIDC deploy role for this repo's GitHub Actions pipeline in AWS account 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 `011934824531` (seahaven-prod), us-east-1. Created during the migration from
from the management account (328440206208); the mgmt role of the same name the management account (328440206208). The former mgmt twin of this role was
stays untouched until decommission as the emergency mgmt deploy path. deleted in PLAT-67 (2026-08-05); do not recreate it.
## Files ## Files

View file

@ -6,8 +6,8 @@
# `githubdeploy-procurement-ingest` in AWS account 011934824531 (seahaven-prod), # `githubdeploy-procurement-ingest` in AWS account 011934824531 (seahaven-prod),
# us-east-1, for the Sea-Haven-Industries/procurement-ingest repo (main branch). # 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 # Prod-only deploy role (seahaven-prod). The former mgmt twin was deleted in
# same name is left untouched until decommission (emergency mgmt deploy path). # 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 # GATE - DO NOT EXECUTE until BOTH of the following have passed on these
# exact artifact files: # exact artifact files: