Reject --all combined with --prefix/--since in reprocess.py

--all is a distinct mode (the demoted full-prefix sweep), but the args.all
branch unconditionally set prefix=inbound/ and since=None, so passing it
alongside a narrower selector silently discarded that selector. `--all
--since 2026-07-01` swept the entire corpus instead of the bounded window,
triggering every documented --all hazard (Bedrock re-bill, metric double-
count, merged-field regression) on objects the operator never targeted --
contradicting the tool's safety goal. Add the missing mutual-exclusion
guard alongside the existing --key one, and pin --all+--prefix,
--all+--since, and all three together as argparse rejections.
This commit is contained in:
Adam Moussa 2026-07-17 17:03:44 -04:00
parent 7765111781
commit 88805d906d
No known key found for this signature in database
2 changed files with 58 additions and 0 deletions

View file

@ -171,6 +171,13 @@ def main():
# --key is mutually exclusive with the listing selectors and with --all.
if args.key and (args.prefix or args.since or args.all):
parser.error("--key cannot be combined with --prefix, --since, or --all")
# --all is a distinct MODE (the demoted full-prefix sweep), not a modifier:
# reject it alongside the targeting selectors. Without this, the args.all
# clobber branch below would silently discard a narrower --prefix/--since
# (e.g. `--all --since 2026-07-01` would sweep the ENTIRE corpus, not the
# bounded window) and trigger every --all hazard on unintended objects.
if args.all and (args.prefix or args.since):
parser.error("--all cannot be combined with --prefix or --since")
account_id = boto3.client("sts").get_caller_identity()["Account"]
function_name = PIPELINES[args.pipeline]["function"]

View file

@ -21,6 +21,9 @@ replay single-decode-correct if a future handler ever adds unquote_plus.
import importlib.util
import os
import pathlib
import sys
import pytest
# reprocess.py builds its boto3 clients inside main()/functions, so importing
# it is side-effect-free (no AWS calls, no clients at module scope). Set dummy
@ -71,3 +74,51 @@ def test_build_s3_event_shape_and_raw_key():
assert "+" in emitted
# '%41' NOT decoded to 'A':
assert "%41" in emitted and "A .eml" not in emitted
# --- Selector mutual-exclusion: --all is a MODE, not a modifier ---------------
# reprocess.main() reaches its first AWS call (sts get_caller_identity) only
# AFTER the argparse mutual-exclusion gate, so an invalid selector combination
# raises SystemExit(2) with no AWS access needed. This pins that `--all` cannot
# be silently combined with a narrower --prefix/--since (which the args.all
# clobber branch would otherwise discard -> unintended whole-corpus sweep).
def _expect_argparse_reject(monkeypatch, capsys, argv):
monkeypatch.setattr(sys, "argv", ["reprocess.py", *argv])
with pytest.raises(SystemExit) as exc:
reprocess.main()
assert exc.value.code == 2 # argparse error exit code
return capsys.readouterr().err
def test_all_rejects_prefix(monkeypatch, capsys):
err = _expect_argparse_reject(
monkeypatch,
capsys,
["--pipeline", "po", "--all", "--prefix", "inbound/2026/07/"],
)
assert "--all cannot be combined with --prefix or --since" in err
def test_all_rejects_since(monkeypatch, capsys):
err = _expect_argparse_reject(
monkeypatch,
capsys,
["--pipeline", "po", "--all", "--since", "2026-07-01T00:00:00Z"],
)
assert "--all cannot be combined with --prefix or --since" in err
def test_all_rejects_prefix_and_since_together(monkeypatch, capsys):
_expect_argparse_reject(
monkeypatch,
capsys,
[
"--pipeline",
"po",
"--all",
"--prefix",
"inbound/2026/",
"--since",
"2026-07-01T00:00:00Z",
],
)