Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
Return-Path: =?utf-8?q?=3C0100abcdef012345-abcdef01-2345-6789-abcd-ef0123456?=
|
|
|
|
|
=?utf-8?q?789-6789ab=40mail=2Ecoupahost=2Ecom=3E?=
|
|
|
|
|
Received: from a41-238.smtp-out.amazonses.com (a41-238.smtp-out.amazonses.com
|
|
|
|
|
[203.0.113.25]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id
|
|
|
|
|
fixture0123456789fixture0123456789fixtur for amazon_po@int.seahaven.com; Tue,
|
|
|
|
|
12 May 2026 14:02:54 +0000 (UTC)
|
|
|
|
|
Received-SPF: pass (spfCheck: domain of mail.coupahost.com designates
|
|
|
|
|
203.0.113.25 as permitted sender) client-ip=203.0.113.25; =?utf-8?q?envelop?=
|
|
|
|
|
=?utf-8?q?e-from=3D0100abcdef012345-abcdef01-2345-6789-abcd-ef0123456789-67?=
|
|
|
|
|
=?utf-8?q?89ab=40mail=2Ecoupahost=2Ecom=3B?=
|
|
|
|
|
helo=a41-238.smtp-out.amazonses.com;
|
|
|
|
|
Authentication-Results: amazonses.com; spf=pass (spfCheck: domain of
|
|
|
|
|
mail.coupahost.com designates 203.0.113.25 as permitted sender)
|
|
|
|
|
client-ip=203.0.113.25; =?utf-8?q?envelope-from=3D0100abcdef012345-abcdef01?=
|
|
|
|
|
=?utf-8?q?-2345-6789-abcd-ef0123456789-6789ab=40mail=2Ecoupahost=2Ecom=3B?=
|
|
|
|
|
helo=a41-238.smtp-out.amazonses.com; dkim=pass header.i=@amazonses.com;
|
|
|
|
|
dkim=pass header.i=@amazon.coupahost.com; dmarc=pass
|
|
|
|
|
header.from=amazon.coupahost.com;
|
|
|
|
|
X-SES-RECEIPT: =?utf-8?q?ScrubbedFixturePlaceholderTokenValue0123456789QqZzX?=
|
|
|
|
|
=?utf-8?q?xJjKkWwYyVvScrubbedFixturePlaceholderTokenValue0123456789QqZzXxJj?=
|
|
|
|
|
=?utf-8?q?KkWwYyVvScrubbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkW?=
|
|
|
|
|
=?utf-8?q?wYyVvScrubbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYy?=
|
|
|
|
|
=?utf-8?q?VvScrubbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvS?=
|
|
|
|
|
=?utf-8?q?crubbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvScru?=
|
|
|
|
|
=?utf-8?q?bbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvScrubbe?=
|
|
|
|
|
=?utf-8?q?dFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvScrubbedFi?=
|
|
|
|
|
=?utf-8?q?xturePlaceholderTokenVal=3D=3D?=
|
|
|
|
|
X-SES-DKIM-SIGNATURE: a=rsa-sha256; q=dns/txt; =?utf-8?q?b=3DScrub/bedFixtur?=
|
|
|
|
|
=?utf-8?q?ePlaceholderTokenValue01234+56789QqZzXxJjKkWwYyVvScrubbedFixtureP?=
|
|
|
|
|
=?utf-8?q?laceholder/Tok+enValue0123456789QqZzXxJjKkWwYyVvScrubbedFixturePl?=
|
|
|
|
|
=?utf-8?q?aceh/olderTokenValue012345=3D=3B?= c=relaxed/simple;
|
|
|
|
|
s=224i4yxa5dv7c2xz3womw6peuasteono; d=amazonses.com; t=1778594574; v=1;
|
|
|
|
|
bh=ScrubbedFi+xture+PlaceholderTokenValue01234=; =?utf-8?q?h=3DFrom=3ATo=3A?=
|
|
|
|
|
=?utf-8?q?Cc=3ABcc=3ASubject=3ADate=3AMessage-ID=3AMIME-Version=3AContent-T?=
|
|
|
|
|
=?utf-8?q?ype=3AX-SES-RECEIPT=3B?=
|
|
|
|
|
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple;
|
|
|
|
|
s=vtqzvxxcuybp44vxc3gxytxk7c3q5pf4; d=coupahost.com; t=1778594574;
|
|
|
|
|
i=@amazon.coupahost.com; =?utf-8?q?h=3DDate=3AFrom=3ATo=3AMessage-ID=3ASubj?=
|
|
|
|
|
=?utf-8?q?ect=3AMime-Version=3AContent-Type=3AContent-Transfer-Encoding=3B?=
|
|
|
|
|
bh=ScrubbedFi+xture+PlaceholderTokenValue01234=;
|
|
|
|
|
b=ScrubbedFixt+urePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvS
|
|
|
|
|
crubbedFixturePla/ceholderTokenValue0123456789QqZzXxJ/jK/kWwYyV+vSc
|
|
|
|
|
rubbedFixturePlaceholderTokenValue012345=
|
|
|
|
|
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple;
|
|
|
|
|
s=224i4yxa5dv7c2xz3womw6peuasteono; d=amazonses.com; t=1778594574;
|
|
|
|
|
=?utf-8?q?h=3DDate=3AFrom=3ATo=3AMessage-ID=3ASubject=3AMime-Version=3ACont?=
|
|
|
|
|
=?utf-8?q?ent-Type=3AContent-Transfer-Encoding=3AFeedback-ID=3B?=
|
|
|
|
|
bh=ScrubbedFi+xture+PlaceholderTokenValue01234=;
|
|
|
|
|
b=ScrubbedFixturePlaceholderTokenValue0123456789QqZzXxJjKkWwYyVvSc
|
|
|
|
|
rubbedFixturePlaceholder+TokenValue0123456789QqZzXxJjK+kWwYyVvScrub
|
|
|
|
|
bedFixturePlaceholderTokenValue012345678=
|
|
|
|
|
Date: Tue, 12 May 2026 14:02:53 +0000
|
|
|
|
|
From: Amazon <do_not_reply@amazon.coupahost.com>
|
|
|
|
|
To: amazon_po@int.seahaven.com
|
|
|
|
|
Message-ID: <abcdef0123456789-abcdef01-2345-6789-abcd-ef0123456789-abcdef@email.amazonses.com>
|
|
|
|
|
Subject: ***Copy for Reference*** New Purchase Order 2D-12503765 has been
|
|
|
|
|
issued
|
|
|
|
|
Mime-Version: 1.0
|
|
|
|
|
Content-Type: multipart/alternative;
|
|
|
|
|
boundary="--==_mimepart_6a03330dccc3f_2224e2ae416689";
|
|
|
|
|
charset=UTF-8
|
|
|
|
|
Content-Transfer-Encoding: 7bit
|
|
|
|
|
auto-submitted: auto-generated
|
|
|
|
|
X-Auto-Response-Suppress: All
|
|
|
|
|
X-Coupa-Message-ID: abcdef01-2345-6789-abcd-ef0123456789@amazon.coupahost.com
|
|
|
|
|
Feedback-ID:
|
2026-07-16 16:03:16 -04:00
|
|
|
::1.us-east-1.ScrubbedFixturePlaceholderTokenValue0123456=:AmazonSES
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
X-SES-Outgoing: 2026.05.12-203.0.113.25
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----==_mimepart_6a03330dccc3f_2224e2ae416689
|
|
|
|
|
Content-Type: text/plain; charset="utf-8"
|
|
|
|
|
Content-Transfer-Encoding: quoted-printable
|
|
|
|
|
MIME-Version: 1.0
|
|
|
|
|
|
|
|
|
|
Powered by
|
|
|
|
|
|
|
|
|
|
http://www.coupa.com
|
|
|
|
|
|
|
|
|
|
Amazon Purchase Order #2D-12503765
|
|
|
|
|
|
|
|
|
|
Hi Amazon,
|
|
|
|
|
|
|
|
|
|
This is to inform you Purchase Order 2D-12503765 from Amazon has been issued,=
|
|
|
|
|
and this same notification has been sent to your fellow team members with ac=
|
|
|
|
|
cess to the Coupa Supplier Portal.
|
|
|
|
|
|
|
|
|
|
Submitted By
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
Robin Sampleton
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
|
|
|
|
|
On Behalf Of
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
Robin Sampleton
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
|
|
|
|
|
Supplier
|
|
|
|
|
SEA HAVEN INDUSTRIES:CORPORATE AP:SERVICES(OK6HE)
|
|
|
|
|
|
|
|
|
|
Total
|
|
|
|
|
|
|
|
|
|
55,206.00
|
|
|
|
|
|
|
|
|
|
USD
|
|
|
|
|
|
|
|
|
|
Items
|
|
|
|
|
|
|
|
|
|
GENERAL LABOURER
|
|
|
|
|
|
|
|
|
|
1.0 EACH x 55,206.00
|
|
|
|
|
|
|
|
|
|
55,206.00
|
|
|
|
|
|
|
|
|
|
USD
|
|
|
|
|
|
|
|
|
|
https://supplier.coupahost.com/orders/12503765?instance_url=3Dhttps%3A%2F%2Fa=
|
|
|
|
|
mazon.coupahost.com%2F&supplier_id=3D648018&
|
|
|
|
|
|
|
|
|
|
More Detail
|
|
|
|
|
|
|
|
|
|
PO ID
|
|
|
|
|
2D-12503765
|
|
|
|
|
|
|
|
|
|
Department
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
Status
|
|
|
|
|
Issued - Created
|
|
|
|
|
|
|
|
|
|
Last Opened
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
Order Date
|
|
|
|
|
05/12/26
|
|
|
|
|
|
|
|
|
|
Acknowledged At
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
Revision Date
|
|
|
|
|
05/12/26
|
|
|
|
|
|
|
|
|
|
Payment Term
|
|
|
|
|
90 NET
|
|
|
|
|
|
|
|
|
|
Req #
|
|
|
|
|
19886224
|
|
|
|
|
|
|
|
|
|
Shipping
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
Supplier
|
|
|
|
|
|
|
|
|
|
SEA HAVEN INDUSTRIES:CORPORATE AP:SERVICES(OK6HE)
|
|
|
|
|
|
|
|
|
|
80 ORVILLE DR STE 100
|
|
|
|
|
BOHEMIA, NY 11716-2505
|
|
|
|
|
United States
|
|
|
|
|
|
|
|
|
|
Shipping
|
|
|
|
|
|
|
|
|
|
Amazon.com Services LLC (DYO1)
|
|
|
|
|
500 Hudson Valley
|
|
|
|
|
New Windsor, NY 12553-4744
|
|
|
|
|
United States
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
Attn: Robin Sampleton
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
|
|
|
|
|
Lines
|
|
|
|
|
|
|
|
|
|
1.0 EA
|
|
|
|
|
GENERAL LABOURER for 55,206.00 USD
|
|
|
|
|
|
|
|
|
|
Supplier SEA HAVEN INDUSTRIES:CORPORATE AP:SERVICES(OK6HE) =E2=80=A2 Need B=
|
|
|
|
|
y 05/12/26 =E2=80=A2 Category Maintenance - Facilities =E2=80=A2 Part Numbe=
|
|
|
|
|
r "HVAC/Heating System Remova" =E2=80=A2 Account 1D-2S31-2231-73970-0000-000=
|
|
|
|
|
0-0000 =E2=80=A2 Period 2D Ops 2026
|
|
|
|
|
|
|
|
|
|
=C2=A0
|
|
|
|
|
|
|
|
|
|
Total
|
|
|
|
|
|
|
|
|
|
55,206.00
|
|
|
|
|
|
|
|
|
|
USD
|
|
|
|
|
|
|
|
|
|
https://www.coupa.com
|
|
|
|
|
|
|
|
|
|
Total Spend Management - Make Margins Multiply
|
|
|
|
|
|
|
|
|
|
To get immediate updates via SMS or change notification preferences, go https=
|
|
|
|
|
://supplier.coupahost.com/notification_preferences
|
|
|
|
|
|
|
|
|
|
----==_mimepart_6a03330dccc3f_2224e2ae416689
|
|
|
|
|
Content-Type: text/html;
|
|
|
|
|
charset=UTF-8
|
|
|
|
|
Content-Transfer-Encoding: quoted-printable
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body align=3D"left" style=3D"width: 100% !important; min-width: 100%; box-=
|
|
|
|
|
sizing: border-box; font-weight: normal; text-align: left; line-height: 1.3=
|
|
|
|
|
; font-size: 16px; margin: 0; padding: 0;" width=3D"100">
|
|
|
|
|
<table bgcolor=3D"#f5f7fa" class=3D"new_email_body_container" color=3D"#1b1=
|
|
|
|
|
f28" style=3D"background-color: #f5f7fa; width: 100%; color: #1b1f28; font-=
|
|
|
|
|
family: Roboto Regular,Arial,sans-serif;" width=3D"100">
|
|
|
|
|
<tr>
|
|
|
|
|
<td align=3D"center">
|
|
|
|
|
<center style=3D"width: 100%;">
|
|
|
|
|
<table align=3D"center" class=3D"email_container" style=3D"max-width: 700px=
|
|
|
|
|
; width: inherit; text-align: center;" width=3D"0">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<table align=3D"right" class=3D"email_header_footer email_header" role=3D"p=
|
|
|
|
|
resentation" style=3D"border-collapse: collapse;">
|
|
|
|
|
<tr align=3D"right" style=3D"text-align: right; padding-top: 5px; line-heig=
|
|
|
|
|
ht: 20px;">
|
|
|
|
|
<td>
|
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<span color=3D"#6a6969" style=3D"font-size: 12px; line-height: 14px; paddin=
|
|
|
|
|
g-right: 2px; color: #6a6969;">
|
|
|
|
|
Powered by
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<a target=3D"_blank" href=3D"http://www.coupa.com"><img style=3D"vertical-a=
|
|
|
|
|
lign: middle;" height=3D"21" alt=3D"Coupa Software" title=3D"Coupa Software=
|
|
|
|
|
" src=3D"https://amazon.coupahost.com/images/email/coupa_new_logo.png"></a>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<table bgcolor=3D"white" class=3D"email_primary_card" style=3D"width: 100%;=
|
|
|
|
|
background-color: white; min-height: 250px; border-color: #d5dee7; border-=
|
|
|
|
|
style: solid; border-width: 4px 0;" width=3D"100">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<table class=3D"email_header_footer" role=3D"presentation" style=3D"width: =
|
|
|
|
|
100%; text-align: left; padding: 30px 30px 0;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<img style=3D"display: inline-block;" height=3D"30" alt=3D"Amazon" title=3D=
|
|
|
|
|
"Amazon" src=3D"https://amazon.coupahost.com/public_attachments/EY3V23j4?et=
|
|
|
|
|
ag=3D1d7b8acf01de02751f58c89e0463a4a2ba236c4741f2625455da15700a6fac93&s=
|
|
|
|
|
tyle=3Dsmall">
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<p color=3D"#0971f1" style=3D"color: #0971f1; font-size: 22px; margin: 5px =
|
|
|
|
|
0 0;">Amazon Purchase Order #2D-12503765</p>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<table class=3D"email_primary_card_body" role=3D"presentation" style=3D"wid=
|
|
|
|
|
th: 100%; padding: 15px 30px;">
|
|
|
|
|
<tr align=3D"left" color=3D"#1b1f28" style=3D"font-size: 16px; color: #1b1f=
|
|
|
|
|
28; line-height: 23px; text-align: left;">
|
|
|
|
|
<td>
|
|
|
|
|
<p>Hi Amazon,</p>
|
|
|
|
|
<p style=3D"width: 688px; max-width: 688px; padding-bottom: 12px;" width=3D=
|
|
|
|
|
"688">This is to inform you Purchase Order 2D-12503765 from Amazon has been=
|
|
|
|
|
issued, and this same notification has been sent to your fellow team membe=
|
|
|
|
|
rs with access to the Coupa Supplier Portal.</p>
|
|
|
|
|
<br>
|
|
|
|
|
<table style=3D"width: 700px;" width=3D"700">
|
|
|
|
|
<tr valign=3D"top">
|
|
|
|
|
<td align=3D"left" style=3D"vertical-align: top; text-transform: capitalize=
|
|
|
|
|
; padding-right: 3px; padding-bottom: 4px; text-align: left; width: 100px; =
|
|
|
|
|
white-space: nowrap;" valign=3D"top" width=3D"100">Submitted By</td>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-left: 3px; padding=
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
-bottom: 4px;">Robin Sampleton</td>
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
</tr>
|
|
|
|
|
<tr valign=3D"top">
|
|
|
|
|
<td align=3D"left" style=3D"vertical-align: top; text-transform: capitalize=
|
|
|
|
|
; padding-right: 3px; padding-bottom: 4px; text-align: left; width: 100px; =
|
|
|
|
|
white-space: nowrap;" valign=3D"top" width=3D"100">On Behalf Of</td>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-left: 3px; padding=
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
-bottom: 4px;">Robin Sampleton</td>
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
</tr>
|
|
|
|
|
<tr valign=3D"top">
|
|
|
|
|
<td align=3D"left" style=3D"vertical-align: top; text-transform: capitalize=
|
|
|
|
|
; padding-right: 3px; padding-bottom: 4px; text-align: left; width: 100px; =
|
|
|
|
|
white-space: nowrap;" valign=3D"top" width=3D"100">Supplier</td>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-left: 3px; padding=
|
|
|
|
|
-bottom: 4px;">SEA HAVEN INDUSTRIES:CORPORATE AP:SERVICES(OK6HE)</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr valign=3D"top">
|
|
|
|
|
<td align=3D"left" style=3D"vertical-align: top; text-transform: capitalize=
|
|
|
|
|
; padding-right: 3px; padding-bottom: 4px; text-align: left; width: 100px; =
|
|
|
|
|
white-space: nowrap;" valign=3D"top" width=3D"100">Total</td>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-left: 3px; padding=
|
|
|
|
|
-bottom: 4px;">
|
|
|
|
|
<b><span title=3D"USD">55,206.00</span></b>
|
|
|
|
|
<span style=3D"font-weight: normal; font-size: 9px;">
|
|
|
|
|
USD
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr valign=3D"top">
|
|
|
|
|
<td align=3D"left" style=3D"vertical-align: top; text-transform: capitalize=
|
|
|
|
|
; padding-right: 3px; padding-bottom: 4px; text-align: left; width: 100px; =
|
|
|
|
|
white-space: nowrap;" valign=3D"top" width=3D"100">Items</td>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-top: 0; padding-le=
|
|
|
|
|
ft: 0; padding-bottom: 4px;">
|
|
|
|
|
<table style=3D"width: 450px;" width=3D"700">
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"min-width: 100px; max-width: 190px; padding-left: 3px; padding=
|
|
|
|
|
-bottom: 4px;"><b>GENERAL LABOURER</b></td>
|
|
|
|
|
<td align=3D"left" style=3D"max-width: 150px; vertical-align: top; text-tra=
|
|
|
|
|
nsform: capitalize; padding-right: 3px; padding-bottom: 4px; text-align: le=
|
|
|
|
|
ft; width: 100px; white-space: unset;" valign=3D"top" width=3D"100">
|
|
|
|
|
1.0 EACH x <span title=3D"USD">55,206.00</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td align=3D"right" style=3D"text-align: right; min-width: 100px; max-width=
|
|
|
|
|
: 190px; padding-left: 3px; padding-bottom: 4px;">
|
|
|
|
|
<b>
|
|
|
|
|
<span title=3D"USD">55,206.00</span>
|
|
|
|
|
</b>
|
|
|
|
|
<span style=3D"font-weight: normal; font-size: 9px;">
|
|
|
|
|
USD
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<br>
|
|
|
|
|
<br><table style=3D"width: 100%; text-align: center;" width=3D"100" align=
|
|
|
|
|
=3D"center"><tr><td style=3D"text-align: center;" align=3D"center"><table a=
|
|
|
|
|
lign=3D"center" cellspacing=3D"0" style=3D"display: inline-block;"><tr>
|
|
|
|
|
<td><table cellpadding=3D"0" cellspacing=3D"0" align=3D"center" style=3D"ma=
|
|
|
|
|
rgin: 0px 10px 0 0;" class=3D"email_button"><tr><td style=3D"border-width: =
|
|
|
|
|
0;"><table cellpadding=3D"0" cellspacing=3D"0" align=3D"center"><tr class=
|
|
|
|
|
=3D"button_without_background"><td style=3D"text-align: center; border-radi=
|
|
|
|
|
us: 28px; height: 32px; box-sizing: border-box; min-width: 125px; backgroun=
|
|
|
|
|
d-color: #0971f1; padding: 6px 16px;" align=3D"center" height=3D"32" bgcolo=
|
|
|
|
|
r=3D"#0971f1"><a style=3D"text-decoration: none; text-align: center; font-w=
|
|
|
|
|
eight: 500; font-size: 14px; line-height: 20px; color: white;" align=3D"cen=
|
|
|
|
|
ter" color=3D"white" href=3D"https://supplier.coupahost.com/orders/12503765=
|
|
|
|
|
?instance_url=3Dhttps%3A%2F%2Famazon.coupahost.com%2F&supplier_id=3D648=
|
|
|
|
|
018&">View Order</a></td></tr></table></td></tr></table></td>
|
|
|
|
|
</tr></table></td></tr></table>
|
|
|
|
|
<br><br>
|
|
|
|
|
<table style=3D"margin: 0;">
|
|
|
|
|
<td bgcolor=3D"transparent" color=3D"#2f2f2f" height=3D"32" style=3D"height=
|
|
|
|
|
: 32px; background-color: transparent; font-size: 24px; font-weight: 700; c=
|
|
|
|
|
olor: #2f2f2f; font-family: Roboto Regular,Arial,sans-serif; padding: 0;">
|
|
|
|
|
More Detail
|
|
|
|
|
</td>
|
|
|
|
|
</table>
|
|
|
|
|
<div style=3D"padding: 10px;">
|
|
|
|
|
<table style=3D"width: 100%;" width=3D"100">
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">PO ID=
|
|
|
|
|
</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">2D-12503765</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Depar=
|
|
|
|
|
tment</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">None</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Statu=
|
|
|
|
|
s</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">Issued - Created</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Last =
|
|
|
|
|
Opened</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
"><span class=3D"show none">None</span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Order=
|
|
|
|
|
Date</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">05/12/26</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Ackno=
|
|
|
|
|
wledged At</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
"><span class=3D"show none">None</span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Revis=
|
|
|
|
|
ion Date</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">05/12/26</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Payme=
|
|
|
|
|
nt Term</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">90 NET</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Req #=
|
|
|
|
|
</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
">19886224</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td align=3D"left" cellspacing=3D"5" style=3D"font-weight: bold; cell-spaci=
|
|
|
|
|
ng: 5px; text-transform: capitalize; text-align: left; padding: 3px;">Shipp=
|
|
|
|
|
ing</td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
"><span class=3D"show none">None</span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan=3D"6">
|
|
|
|
|
<br>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
<table style=3D"width: 100%;" width=3D"100">
|
|
|
|
|
<tr>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
" valign=3D"top">
|
|
|
|
|
<div height=3D"42" style=3D"font-weight: bold; font-size: 14px; text-transf=
|
|
|
|
|
orm: capitalize; height: 42px;">Supplier</div>
|
|
|
|
|
<hr align=3D"left" bgcolor=3D"black" color=3D"black" height=3D"1" style=3D"=
|
|
|
|
|
width: 70%; height: 1px; color: black; background-color: black; border-widt=
|
|
|
|
|
h: 0;" width=3D"70">
|
|
|
|
|
SEA HAVEN INDUSTRIES:CORPORATE AP:SERVICES(OK6HE)
|
|
|
|
|
<span class=3D"address">
|
|
|
|
|
80 ORVILLE DR STE 100<br>BOHEMIA, NY 11716-2505<br>United States
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td style=3D"width: 0;" width=3D"0"></td>
|
|
|
|
|
<td cellspacing=3D"5" style=3D"cell-spacing: 5px; padding: 3px 3px 3xp 7px;=
|
|
|
|
|
" valign=3D"top">
|
|
|
|
|
<div height=3D"42" style=3D"font-weight: bold; font-size: 14px; text-transf=
|
|
|
|
|
orm: capitalize; height: 42px;">Shipping</div>
|
|
|
|
|
<hr align=3D"left" bgcolor=3D"black" color=3D"black" height=3D"1" style=3D"=
|
|
|
|
|
width: 70%; height: 1px; color: black; background-color: black; border-widt=
|
|
|
|
|
h: 0;" width=3D"70">
|
|
|
|
|
<span class=3D"address">
|
|
|
|
|
Amazon.com Services LLC (DYO1)<br>500 Hudson Valley<br>New Windsor, NY 1255=
|
Scrub fixture-body PII and harden cancellation gate (sec review)
/sh-security-review of PR #105 (5 fresh-context detectors + proof-or-kill
verifier) confirmed two diff-introduced findings; both fixed here.
F3 (medium, real PII in new fixtures): the harvest scrub replaced header
tokens but left real third-party PII in message BODIES -- an Amazon
contact's name/phone/personal email in non-coupa-02.eml and an internal
t.corp.amazon.com ticket URL in comment-02.eml, plus real submitter/attn
names recurring across the new_po corpus. Replaced every personal name,
phone, personal email, and internal URL with synthetic placeholders
(QP-soft-wrap aware) across both .eml bodies and expected goldens.
Extended test_fixture_hygiene to scan BODIES (phone shapes, corp URLs,
the leaked tokens), closing the header-only gap that let this through.
F1 (medium, cancellation gate): _CANCELLATION_SUBJECT was unanchored and
matched with .search(), unlike the anchored new_po pattern -- a subject
merely ending with the cancellation phrase could be routed to the sticky-
Cancelled write. Fully anchored it and switched to .match, and added a
body-corroboration gate (the real Coupa body independently restates
'Purchase Order #<po> ... has been cancelled'); a near-miss/misrouted
subject whose body does not corroborate now fails closed to the LLM
(new reason code cancellation_body_unconfirmed).
Pre-existing (advisory, not this PR): the LLM-fallback else->save_new_po
dispatch and undelimited extraction prompt (issue #101 family) are
byte-identical to main and unchanged here.
401 tests pass; ruff/format clean; cdk synth po-ingest clean.
2026-07-16 17:17:35 -04:00
|
|
|
3-4744<br>United States<br>Location Code: 207735629<br>Attn: Robin Sampleton
|
Add offline PO parser suite with scrubbed fixture corpus
132 tests: golden-file comparison for all 25 positive fixtures
(17 single-line new-PO + 8 cancellations, Decimal-exact via
parse_float=Decimal), every fail-closed gate reason code covered
(body-level triggers via 17 synthetic adversarial .eml mutations,
candidate-level via direct validate() unit tests), real multi-line
and comment/non-Coupa fallback fixtures, dual line-ending parse
identity, two-path enrich/save parity (site-extractor stream
guard), V10 URL-id corpus sweep, fixture hygiene (ses_auth pass +
scrub-marker leak sweep), and Bedrock dispatch/EMF assertions.
The suite loads handler/template_parser via importlib under
unique module names and binds the handler's bare sibling imports
around exec (tests/conftest.py load_handler gets the same
treatment) -- the WO suite caches bare 'handler'/'template_parser'
names in sys.modules, and bare imports here would silently bind
to the wrong pipeline. moto is imported before the handler so its
botocore stubber hook precedes boto3 session creation (the PO
conftest chain now loads at pytest session start).
Fixtures are scrubbed real S3 samples: transport/auth header
values replaced with same-shape placeholders (structure kept so
ses_auth still passes), per-file digit ciphers, amounts remapped
with sum==total re-established. The .gitignore exception is
scoped to the PO fixtures path only.
Refs: #99
2026-07-16 15:15:49 -04:00
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table style=3D"margin: 0;"><tr valign=3D"middle"><td style=3D"font-size: 2=
|
|
|
|
|
4px; font-weight: 700; color: #2f2f2f; background-color: transparent; paddi=
|
|
|
|
|
ng: 0;" color=3D"#2f2f2f" bgcolor=3D"transparent">Lines
|
|
|
|
|
</td></tr></table>
|
|
|
|
|
<span style=3D"height: 5px;"></span><span style=3D"font-size: 12px; margin-=
|
|
|
|
|
left: 12px;">
|
|
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
<table style=3D"width: 650px; margin-left: 10px; border-collapse: separate;=
|
|
|
|
|
" width=3D"650">
|
|
|
|
|
<tr>
|
|
|
|
|
<td bgcolor=3D"#2d90d9" style=3D"width: 4px; background-color: #2d90d9;" wi=
|
|
|
|
|
dth=3D"4"></td>
|
|
|
|
|
<td style=3D"padding-left: 3px; font-size: 12px;">
|
|
|
|
|
<span style=3D"font-size: 13px;">
|
|
|
|
|
1.0 EA
|
|
|
|
|
<b>GENERAL LABOURER</b> for <b>55,206.00 USD</b>
|
|
|
|
|
<br>
|
|
|
|
|
</span>
|
|
|
|
|
<span style=3D"font-size: 11px;">
|
|
|
|
|
Supplier <strong><span title=3D"35755339-USUSD00-101 80 ORVILLE DR STE 100=
|
|
|
|
|
, BOHEMIA, NY 11716-2505, United States">SEA HAVEN INDUSTRIES:CORPORATE AP:=
|
|
|
|
|
SERVICES(OK6HE)</span></strong> <span class=3D"bull">=E2=80=A2</span> Need=
|
|
|
|
|
By <strong>05/12/26</strong> <span class=3D"bull">=E2=80=A2</span> Catego=
|
|
|
|
|
ry <strong>Maintenance - Facilities</strong> <span class=3D"bull">=E2=80=A2=
|
|
|
|
|
</span> Part Number <strong>"HVAC/Heating System Remova"</strong> =E2=80=
|
|
|
|
|
=A2 Account <strong>1D-2S31-2231-73970-0000-0000-0000</strong> <span class=
|
|
|
|
|
=3D"bull">=E2=80=A2</span> Period <strong>2D Ops 2026</strong>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr height=3D"8" style=3D"height: 8px; font-size: 5px;">
|
|
|
|
|
<td height=3D"8" style=3D"height: 8px; font-size: 5px;">
|
|
|
|
|
=C2=A0
|
|
|
|
|
</td>
|
|
|
|
|
<td></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td align=3D"right" colspan=3D"2">
|
|
|
|
|
<div style=3D"width: 50%;"></div>
|
|
|
|
|
<div align=3D"right" bgcolor=3D"#f7f7f7" style=3D"text-align: right; backgr=
|
|
|
|
|
ound-color: #f7f7f7; border-top-width: 1px; border-top-color: black; border=
|
|
|
|
|
-top-style: solid; float: right; width: 50%; padding: 10px;" width=3D"50">
|
|
|
|
|
<span style=3D"text-align: left;">
|
|
|
|
|
<span style=3D"float: left;">
|
|
|
|
|
<b>Total</b>
|
|
|
|
|
</span>
|
|
|
|
|
<span style=3D"float: right;">
|
|
|
|
|
<b><span title=3D"USD">55,206.00</span></b>
|
|
|
|
|
<span style=3D"font-weight: normal; font-size: 9px;">
|
|
|
|
|
USD
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span style=3D"font-size: 13px;">
|
|
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<table style=3D"width: 100%;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td class=3D"email_header_footer">
|
|
|
|
|
<table style=3D"width: 100%;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<div style=3D"text-align: center;" align=3D"center">
|
|
|
|
|
<a target=3D"_blank" role=3D"link" tabindex=3D"-1" href=3D"https://www.coup=
|
|
|
|
|
a.com"><img height=3D"40px" alt=3D"Coupa Software" title=3D"Coupa Software"=
|
|
|
|
|
src=3D"https://amazon.coupahost.com/images/email/coupa_new_logo.png"></a>
|
|
|
|
|
<br>
|
|
|
|
|
<span align=3D"center" color=3D"#1565c0" style=3D"text-align: center; font-=
|
|
|
|
|
size: 14px; color: #1565c0; margin: 10px;">
|
|
|
|
|
Total Spend Management - Make Margins Multiply
|
|
|
|
|
</span>
|
|
|
|
|
<br>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
<table color=3D"#6a6a6a" style=3D"width: 100%; font-size: 12px; font-family=
|
|
|
|
|
: Roboto Regular,Arial,sans-serif; line-height: 12px; color: #6a6a6a; margi=
|
|
|
|
|
n: 0;" width=3D"100">
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<div style=3D"text-align: center; padding: 5px 0;" align=3D"center">
|
|
|
|
|
To get immediate updates via SMS or change notification preferences, go <a =
|
|
|
|
|
href=3D"https://supplier.coupahost.com/notification_preferences">here and a=
|
|
|
|
|
djust your settings</a>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</center>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
|
|
|
|
|
----==_mimepart_6a03330dccc3f_2224e2ae416689--
|