Skip to content

fix(cel): recognise 'me' as a context identifier (dotted DCI validation false-positive)#342

Draft
gonzalesedwin1123 wants to merge 4 commits into
19.0from
security-dci-cel-me-validation
Draft

fix(cel): recognise 'me' as a context identifier (dotted DCI validation false-positive)#342
gonzalesedwin1123 wants to merge 4 commits into
19.0from
security-dci-cel-me-validation

Conversation

@gonzalesedwin1123

@gonzalesedwin1123 gonzalesedwin1123 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Do not merge yet — held for human review.

Summary

validate_expression() and validate_formula_expression() wrongly rejected valid dotted DCI CEL expressions with Undefined variables: me.

Root cause: the DCI resolver override (cel_variable_resolver_dci.py) rewrites a dotted cached accessor such as r.dci.crvs.is_alive into metric('r.dci.crvs.is_alive', me) in a top-level pre-pass, before the base resolver extracts identifiers. The base _extract_variable_names then lexes that rewritten string and returns the bare me (an IDENT outside the quoted accessor) as a potential variable. me was missing from CEL_CONTEXT_IDENTIFIERS (cel_parser.py had m, e, r but not me), so it wasn't in the resolver's reserved words → looked up as a variable → not found → reported as a missing/undefined variable. compile_expression() was unaffected because it does not reject missing_variables, which is why enrollment/eligibility worked while validation-only callers (UI checks, API) failed.

me is a genuine context identifier, not a typo:

  • bound in the eval namespace — spp_programs/models/cel/entitlement_amount_cel.py:299"me": SafeRecordProxy(beneficiary);
  • treated as a record root — spp_cel_domain/services/cel_predicate_guard.py:35_RECORD_ROOTS = ("r", "me");
  • emitted by the base resolver itself — metric('<var>', me). It previously only ever appeared in replacement output, never in a scanned expression, so only the DCI dotted path tripped.

Fix

One line in the source layer: add "me" to CEL_CONTEXT_IDENTIFIERS in spp_cel_domain/services/cel_parser.py. That set feeds reserved_words in both consumers (cel_variable_resolver and cel_expression), so the first-pass filter skips me everywhere. Fixes all callers, not just DCI — chosen over patching the DCI module, which would leave the same latent gap for any other caller that puts me in a scanned expression. Version bump spp_cel_domain 19.0.2.1.0 → 19.0.2.1.1. No migration (a constant change; no schema/data).

Tests (TDD)

  • spp_cel_domain (tests/test_cel_me_identifier.py): me is in CEL_CONTEXT_IDENTIFIERS; expand_expression / validate_expression on an expression containing bare me (metric('foo', me) == true) does not flag it; cel.service.validate_formula_expression likewise. Red before the fix, green after.
  • spp_dci_indicators (tests/test_dci_cel_validation.py, test-only, no version bump): the seeded dotted accessor r.dci.crvs.is_alive expands to metric('r.dci.crvs.is_alive', me) and validate_expression("r.dci.crvs.is_alive == true") is valid with no Undefined variables: me — the exact reported scenario.
  • Green: spp_cel_domain 652/652, spp_dci_indicators 84/84; ruff/ruff-format clean. Verified no spp.cel.variable is literally named/accessed me (adding the reserved word only removes such a name from variable lookup; me would be an illegal name anyway since it is the record root).

Merge order (spp_cel_domain pair)

Per the cross-PR interaction analysis (2026-07-24, internal/plans/security-prs-interaction-analysis.md): merge after #272. Both PRs bump spp_cel_domain to the identical 19.0.2.1.1, and that manifest line auto-merges with no conflict — so this PR's post-#272 rebase must deliberately:

  1. Re-bump spp_cel_domain to 19.0.2.1.2;
  2. Move this PR's HISTORY bullet under a new ### 19.0.2.1.2 heading above security(cel): key metric cache lookups strictly by requested params #272's ### 19.0.2.1.1;
  3. Resolve the trivial keep-both conflict in spp_cel_domain/tests/__init__.py;
  4. Let CI's oca-gen regenerate README.rst/index.html.

Code paths are provably disjoint (this PR: validation identifier set; #272: execution cache keying) — no semantic interaction.

…iable

TDD red: the DCI resolver rewrites dotted accessors to metric('...', me)
before the base resolver extracts identifiers, and 'me' is missing from
CEL_CONTEXT_IDENTIFIERS, so validate_expression/validate_formula_expression
report 'Undefined variables: me' for valid dotted DCI expressions. New tests
assert me is recognised (base resolver + cel.service formula path + DCI
dotted-accessor end-to-end).
'me' is the individual record proxy in the CEL eval context (bound as
'me': SafeRecordProxy(beneficiary) and treated as a record root in
cel_predicate_guard), and the resolver emits metric('<accessor>', me) for
cached/dotted variables. Because 'me' was missing from
CEL_CONTEXT_IDENTIFIERS, once the DCI override rewrites a dotted accessor
into metric('...', me) before identifier extraction, validate_expression /
validate_formula_expression reported valid expressions as
'Undefined variables: me' (compile_expression was unaffected as it does
not reject missing variables). Add 'me' to CEL_CONTEXT_IDENTIFIERS so it
is skipped as a reserved context identifier in both consumers
(resolver + cel_expression). Bump spp_cel_domain to 19.0.2.1.1.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.28%. Comparing base (1caf794) to head (35c2814).
⚠️ Report is 3 commits behind head on 19.0.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #342      +/-   ##
==========================================
- Coverage   74.28%   69.28%   -5.00%     
==========================================
  Files         372      266     -106     
  Lines       25385    20612    -4773     
==========================================
- Hits        18857    14282    -4575     
+ Misses       6528     6330     -198     
Flag Coverage Δ
spp_analytics 93.25% <ø> (+0.11%) ⬆️
spp_api_v2_change_request 66.53% <ø> (?)
spp_api_v2_cycles 71.03% <ø> (+0.38%) ⬆️
spp_api_v2_data 77.77% <ø> (?)
spp_api_v2_entitlements 70.23% <ø> (+0.27%) ⬆️
spp_api_v2_gis 71.57% <ø> (+0.04%) ⬆️
spp_api_v2_programs 92.22% <ø> (+0.19%) ⬆️
spp_api_v2_simulation 71.19% <ø> (+0.07%) ⬆️
spp_approval 50.34% <ø> (?)
spp_audit_programs ?
spp_base_common 91.07% <ø> (+0.80%) ⬆️
spp_case_cel 89.50% <ø> (?)
spp_case_demo ?
spp_case_entitlements 100.00% <ø> (+2.38%) ⬆️
spp_case_programs 100.00% <ø> (+2.85%) ⬆️
spp_cel_domain 63.79% <ø> (?)
spp_cel_event 85.34% <ø> (?)
spp_cr_type_assign_program ?
spp_dci_compliance ?
spp_dci_demo ?
spp_dci_indicators 96.23% <ø> (?)
spp_dci_server_social ?
spp_demo ?
spp_demo_phl_luzon ?
spp_drims ?
spp_drims_sl ?
spp_drims_sl_demo ?
spp_farmer_registry_demo ?
spp_gis_report ?
spp_grm_demo ?
spp_indicator ?
spp_indicator_studio ?
spp_metric_service ?
spp_mis_demo_v2 ?
spp_programs 65.27% <ø> (+<0.01%) ⬆️
spp_registry 86.94% <ø> (+0.10%) ⬆️
spp_security 69.56% <ø> (+2.89%) ⬆️
spp_simulation ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_cel_domain/models/cel_variable_resolver.py 78.15% <ø> (ø)
spp_cel_domain/services/cel_parser.py 82.44% <ø> (ø)

... and 262 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…d set

Staff-review nit: the fallback set used when the cel_parser import fails
omitted 'me', so the fix would not hold on that (catastrophic) path.
Mirror 'me' into the fallback for consistency.
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