Repository navigation
Conversation
…steps (#1891) Adds openadapt_flow.transforms: its own tokenizer, parser and type checker (no eval, exec, compile or ast), a closed, typed standard library for strings, lists, dates, a conservative regex subset and fuzzy matching with an explicit threshold, hard bounds on size, depth and values, and apply_transforms, which computes parameters all-or-nothing and returns one receipt record per computed value. Anything outside the language refuses with a closed refusal code. Part of #1891.
…icates (#1891) One language for computed parameters and for generated effect-model predicates. LANGUAGE_VERSION and operator_set_sha256() name the exact operator set and bounds; transform records bind both, and a test pins the digest so a library change is a reviewed new version. evaluate_predicate() returns true, false or unknown and never passes on a refusal. Adds docs/EXPRESSIONS.md. Part of #1891.
Independent adversarial review: BLOCKHead: What I ran
The parser, type checker, int/date bounds, fuzzy ambiguity rule and all-or-nothing FindingsP1. Catastrophic regex backtracking through 4-digit repeat counts. P1. P2. Non-taxonomy exceptions from P3
BLOCK is for the two P1 resource-bound bypasses. Both are small, local fixes. With them and regression tests in place, I would expect to approve. |
What kind of change does this PR introduce?
New feature (engine,
packages/flow). Part of #1891.Summary
The IR can substitute parameters, check guards and loop over worklists, but it can't compute a value mid-run. This adds a small expression language for that, in a new module,
openadapt_flow.transforms.eval,exec,compileorast.today()is deliberately missing), no randomness, no I/O and no locale.lower,upper,trim,collapse_spaces,length,replace,substring,starts_with,ends_with,contains,pad_left,text,to_intsplit,join,at,countdate,parse_dateandformat_date(a locale-freeYYYY/MM/DD/M/Dformat),add_days,add_months(clamps to the end of the month),days_between,year,month,daymatchesandextract. Both use full match only, with a literal pattern from a conservative subset. The subset refuses back-references, look-around, inline flags, a repeated group that repeats or alternates inside, more than 2 unbounded or 4 variable quantifiers, and input over 512 characters.fuzzy_match(value, options, threshold): the threshold is required, must be a literal, and must be in (0, 1]. An exact match after case and space folding wins. Otherwise exactly one option must clear the threshold. If none does, it refuses withbelow_threshold. If two or more do, it refuses withambiguous.if_elseExpressionRefusedwith a closedRefusalCode. Refusal messages never repeat parameter values.ParamTransform(output_param, expression)is checked when it's declared, so a bad expression fails to load.apply_transforms(params, transforms)runs in order on a copy of the params. It's all or nothing. It won't overwrite an existing parameter. It returns oneTransformRecordper computed value with:One language, shared with effect models (second commit)
LANGUAGE_VERSION(openadapt.expr/v1) andoperator_set_sha256()name the exact functions, signatures, operators, bounds and refusal codes.TransformRecordbinds both values. A test pins the digest, so any change to the library has to come as a reviewed new version.evaluate_predicate(source, params)gives a three-valued outcome:true,falseorunknown(with the refusal code). It never raises, and a refusal is never a pass. A future effect-model evaluator can use the same language for its before/after predicates.docs/EXPRESSIONS.mddocuments the language.How this was tested
pytest -q tests/test_transforms.py: 114 passed. The original 111 are unchanged, plus 3 contract tests. On base it fails at collection, because the module doesn't exist yet.__import__,x.__class__.__mro__,().__class__.__base__.__subclasses__(),eval,exec,open,getattr,globals, lambdas, comprehensions, indexing,**, f-strings and assignment.osandbuiltinsrefuse asunknown_name.(a+)+,(a|aa)*, back-references and look-around.tests/test_program_ir_phase1.py(134 passed together with the new file, rechecked on head 46544b5).ruff check,ruff format --check,mypy, the strict safety-path mypy flags on the new module, a Python 3.10 compile,check_engine_generality.py,check_release_consistency.pyand the rootcheck_source_boundary.py.Not in this slice (follow-ups on #1891)
openadapt.expr/v1slice only. Any new operators come later as a separately reviewed version with a new digest.StepandWorkflowdon't carryParamTransformyet, and the replayer doesn't callapply_transformsor writeTransformRecords toRunReportor the run receipt. I left that out on purpose: those models feed contract digests, and that change deserves its own review.[A-Za-z_][A-Za-z0-9_]*names.Checklist