Commit graph

4 commits

Author SHA1 Message Date
0aef18029b
Fix WO parser advisories A1-A3 (PR #99 follow-ups)
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.
2026-07-16 12:35:37 -04:00
042a03b3a4
Merge main into WO template-parser + Bedrock branch
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
2026-07-15 21:15:11 -04:00
fcee3b9c78
Emit ParseMethod-only EMF set so fallback alarm can fire
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.
2026-07-15 20:30:53 -04:00
89bf47c1bb
Add offline WO parser test suite
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
2026-07-15 20:11:04 -04:00