Promote the #103 minimal alias map to the documented public table
GITEA_OPERATION_ALIASES and add the #106 enforcement layer:
- normalize_operation(op, service): canonical namespaced names; legacy
spellings accepted only via the explicit table; unknown, ambiguous,
and cross-service names fail closed.
- check_operation(op, allowed, forbidden, service): normalizes BOTH the
requested operation and the profile lists before any membership
check; forbidden always overrides allowed; unnormalizable allowed
entries grant nothing and unnormalizable forbidden entries deny the
request, so normalization can never silently widen permissions;
empty/missing allowed list denies everything.
- gitea_check_pr_eligibility now routes its capability check through
check_operation, fixing the mismatch where canonical namespaced
profile ops (gitea.pr.merge) never matched the raw action (merge)
and namespaced forbidden entries were never enforced.
- Document the normalization table and enforcement rules in
docs/gitea-execution-profiles.md, replacing the stale 'enforcement
out of scope' caveat.
- tests/test_op_normalization.py: full #106 matrix (27 tests) —
qualified/legacy allowed and forbidden, unknown, ambiguous, service
mismatch, forbidden-overrides-allowed, empty/missing allowed,
duplicates after normalization, no permission widening, and
eligibility integration proving normalization happens before
enforcement. Existing v1/env unqualified behaviour stays compatible.
Closes#106
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>