Skip to content

fix(ape): count each policy decision once - #2974

Open
tang-vu wants to merge 1 commit into
Osmantic:mainfrom
tang-vu:fix/ape-primary-decision-total
Open

fix(ape): count each policy decision once#2974
tang-vu wants to merge 1 commit into
Osmantic:mainfrom
tang-vu:fix/ape-primary-decision-total

Conversation

@tang-vu

@tang-vu tang-vu commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Why this matters

APE's /metrics total summed every exposed counter. Several counters are additive diagnostics for the same /verify decision: a circuit-breaker denial also increments denied, a window denial also increments denied, and an approved retry increments both allowed and grants_consumed. Approval actions were included too. Dashboards and operators therefore saw inflated decision totals.

Root cause: the aggregate did not distinguish mutually exclusive primary decision outcomes from lifecycle/diagnostic counters.

Behavioral invariant: every /verify request contributes exactly one to total; detailed additive counters remain available unchanged.

Overlap check

Searched open and closed upstream PRs for APE metrics/decision-total/counter/double-counting terms and inspected PRs touching ods/extensions/services/ape/main.py. Current APE PRs cover classification and allowlists; older governance/state work introduced the additive counters but no PR corrects their aggregate. This scope is independent.

What changed

  • Defined the four primary outcomes: allowed, denied, rate-limited, and require-approval.
  • Compute total from only those mutually exclusive outcomes.
  • Preserve every existing key in the decisions object.
  • Added a full public lifecycle regression: three allows, escalation, human approval, approved retry, then metrics.

Validation

  • Red before fix: five /verify calls produced total: 7 because approval/grant counters were counted again.
  • pytest tests/test_main.py::test_metrics_total_counts_each_verify_decision_once -q — 1 passed.
  • pytest tests -q — 45 passed.
  • python -m py_compile main.py tests/test_main.py — passed.
  • git diff --check — passed.

Compatibility and rollback

The detailed counters and their meanings do not change; only the previously inflated aggregate is corrected. Consumers relying on the incorrect total will observe a decrease that now matches actual verification decisions. No persisted state or schema changes; rollback is one commit.

Batch compatibility

Validated as an independent ten-PR batch from upstream main 6ff9b4fc5190099705043acaab7e9b6ad9c8b8f1. The final PR heads merged without conflicts in this order: #2964 -> #2965 -> #2967 -> #2969 -> #2970 -> #2971 -> #2972 -> #2973 -> #2974 -> #2975. The resulting local synthetic merge head is 4ae60eadad9696a9accb735aad71af87d2be802d.

Combined validation on that exact tree:

  • Dashboard API boundary suites: 323 passed, 4 skipped.
  • Token Spy suite: 36 passed, 1 skipped.
  • Privacy Shield suite: 55 passed.
  • APE suite: 47 passed.
  • Python compile checks and git diff --check: passed.

The scopes are behaviorally independent. The stated order is the tested rollback/merge sequence for shared-file changes; each PR remains individually useful and revertible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant