A1 — AI-fallback comment_id nondeterminism: parsed comment_time is model
output and not stable across Lambda async retries, so on the ai_fallback
path the comment_id range-key time segment now derives from the email Date
header (deterministic per S3 object) instead of the model's comment_time.
The template path is unchanged (its comment_time is a pure function of the
raw email). Bedrock invoke pins temperature 0 so retries reproduce the same
extraction. Closes the #23 reopening on the AI path.
A2 — EMF record now carries the spec-required _aws.Timestamp (epoch ms) so
CloudWatch reliably extracts the ParseOutcome datapoint that the
fallback-rate alarm depends on.
A3 — T1 New Comment capture no longer truncates at the first blank line;
multi-paragraph comments are captured through internal blanks and terminate
at the next label/separator. 17 golden files regenerated from the real
fixtures accordingly.
Hardening from the sh-security-review pass on this diff:
- _header_date_iso is total: OverflowError/OSError from an extreme Date
header fall back to 'nocomment' instead of failing the invocation.
- _capture_block trims blanks in O(n) (no pop(0)) — removes a quadratic
path on a crafted large blank run.
- work_order_id is enforced digits-only on BOTH parse paths before it is
used as a DynamoDB key, so prompt-injected AI output cannot forge '#'
range-key segments or land on an arbitrary WO.
Bring the two post-fork main merges into the feature branch: #97 (PO
revision/cancellation conditional-write logic, web-UI auth/XSS, KMS-grant
removal, Secrets Manager hardening) and #98 (fail-closed SES sender
authentication).
Conflicts resolved by union so all prior work survives:
- wo/po email_processor handlers: authenticate first (#98), then parse;
WO keeps template-first parse + Bedrock fallback + EMF metric + issue
#23 comment_id fix; PO keeps #97 conditional writes on top of the
Bedrock provider switch. The retired Anthropic Secrets Manager client
is dropped (Bedrock migration) rather than hardened.
- cdk stacks: union of #97 web-ui secret handling + KMS-grant removal,
#98 ALLOWED_DKIM_DOMAINS env + sender-auth-rejected alarm, and the
branch's Bedrock dual-ARN grant, anthropic-secret removal, and
template-fallback alarm. Restore the aws_secretsmanager import that the
auto-merge silently dropped from both stacks.
- README, conftest, requirements, .gitignore: union of all suites/docs.
- test_bedrock_fallback: bypass the new INFRA-107 auth gate in the two
dispatch tests (auth is covered by test_ses_auth).
Refs: #99
The fallback-rate alarm queries the ParseOutcome series keyed on
ParseMethod alone, but the emitter published only the joint
(ParseMethod, TemplateId) dimension set. CloudWatch materializes
exactly the listed dimension sets and does not auto-aggregate, so the
alarm's series never received data: it evaluated a constant 0 and
could never page on template-drift coverage collapse.
Publish both ["ParseMethod"] and ["ParseMethod","TemplateId"] and
update the EMF regression test to assert both sets are present.
Cover the deterministic parser with golden-file tests over 55 real
scrubbed .eml fixtures (both comment sub-shapes, username Submitted-By,
address present/absent, br+CRLF assign addresses), fail-closed
validation-gate rules, adversarial and prompt-injection cases that must
route to ai_fallback or parse without corrupting other fields, the issue
#23 comment_id idempotency invariants, and the Bedrock-fallback dispatch
plus EMF-metric emission with a mocked invoke_model.
Extend pytest.ini testpaths to discover the co-located suite, and update
tests/conftest.load_handler to put a handler's own directory on sys.path
so the WO handler's new `from template_parser import ...` resolves under
the existing shared handler tests. Point test_local.py at the new
template-first + Bedrock flow.
Refs: #23