Skip to content

feat(ops): abstract op groups with TOML-driven concrete instances#116

Open
jellllly420 wants to merge 32 commits into
RPL-Toolchain:masterfrom
jellllly420:feature/abstract-ops
Open

feat(ops): abstract op groups with TOML-driven concrete instances#116
jellllly420 wants to merge 32 commits into
RPL-Toolchain:masterfrom
jellllly420:feature/abstract-ops

Conversation

@jellllly420

@jellllly420 jellllly420 commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new ops { ... } block to .rpl pattern files for declaring abstract,
parameterised op groups, plus [[ops.<group>]] tables in rpl.toml for
supplying concrete instances. At match time, the driver iterates the
cartesian product over each pattern's referenced groups, threading one
ResolvedOpBindings assignment per combination through the matcher.

This lets a single pattern model a primitive (e.g. lock/unlock, intrusive
list transfer) across many concrete implementations (std::sync::Mutex,
parking_lot::Mutex, RwLock, …) without duplicating the pattern body.

Worked example

pattern lock_unlock
ops {
    sync[$T: type, $U: type] = {
        fn $lock(&mut $T) -> $U;
    }
}
patt {
    p[$U: type] = fn _ (..) -> _ {
        'lock:
        let $guard: $U = $sync::$lock(move _);
    }
}
[[ops.sync]]
type = ["$1"]
T    = "std::sync::Mutex<$1>"
U    = "std::sync::MutexGuard<$1>"
lock = "std::sync::Mutex<$1>::lock"

The driver iterates one combination per [[ops.sync]] instance and tries
to match the pattern body against the user crate's MIR with $sync::$lock
resolved to each binding in turn.

Design

Phase Crate What it does
Parse rpl_parser Grammar rules opsBlock, opsItem, OpFnDecl, OpRef
AST rpl_context OpsBlock, OpGroup, OpSignature, Operand::OpRef
WF rpl_context R1–R3 on the raw parse tree (kind/decl/concrete-type checks); R4–R6 on the lowered IR (op-ref validity, arity, op-meta-var leakage)
Resolve rpl_context C1–C7 instance validation (unknown group, missing/extra bindings, undeclared placeholders, cycles, parse failures); fixed-point textual $var expansion
Config rpl_config RawOpInstance deserialiser, RplConfig.ops
Match rpl_match + rpl_driver Cartesian-product iteration over referenced_op_groups; match_op_ref resolves Operand::OpRef → DefId via the expanded path string

Tests

Unit / integration (run by cargo test --workspace):

  • crates/rpl_config/tests/ops_loading.rs — TOML deserialisation
  • crates/rpl_context/tests/ops_lowering.rs — parse-tree → AST lowering
  • crates/rpl_context/tests/ops_wf.rs — R1–R6 well-formedness checks
  • crates/rpl_context/tests/ops_resolution.rs — C2/C4/C7 resolver paths
  • crates/rpl_context/tests/referenced_op_groups.rs — use-site OpRef collection
  • crates/rpl_resolve/tests/ops_resolve.rs — end-to-end resolver flow
  • crates/rpl_driver/tests/cartesian.rs — cartesian-product helper

End-to-end UI fixtures under tests/features/ops/:

  • lock_unlock — happy path (one group, one instance, one diagnostic)
  • folded — zero instances → fold to zero combos → no diagnostics
  • partial_bad — one malformed instance is skipped; remaining instances fire
  • set_op_with_ops — composition with set-op subtraction (p_lock - p_uncovered)
  • two_groups — cartesian over two groups (Mutex/RwLock × black_box)

CVE-2025-68260 POC (tests/features/ops_cve_2025_68260/) — real
intrusive-list-under-lock pattern modelled after the Linux kernel Rust
Node::release race; includes a buggy.rs that should fire and a
fixed.rs that should not.

⚠️ Known limitation: the tests/features/ops/** UI fixtures are not
yet wired into tests/compile-test.rs (which only registers the
tests/ui directory). The //~^ annotations in the .rs sources and
their previous .stderr companions were therefore never asserted on by
cargo test. This PR deletes the broken .stderr files (they
contained raw cargo run shell output and literal line numbers — neither
of which survives any source edit) and tracks proper wiring as
follow-up. In the meantime, tests/features/test.sh runs the same
commands manually for visual inspection.

What changed since CI last passed-or-failed

The previous CI run on this branch (25031080228)
failed in 30–44 s at cargo fmt --check, blocking clippy and tests from
running. Subsequent fix-up commits address that, the substantive issues
surfaced by a careful review, the CI breakage exposed by the new
rpl.toml, and two correctness findings from Copilot's code review:

  1. style(ops): apply cargo fmt — trailing commas on match arms,
    multi-line struct literals, import merging per rustfmt.toml.

  2. **fix(context): replace UB \&PatternItem` -> `*mut` cast with
    OnceCell** — Pattern::check_and_populate_op_refsmutated arena-shared items via#[allow(invalid_reference_casting)]and a raw-pointer cast. Writing through&Tis UB per the Rust reference regardless of aliasing. Replaced withOnceCell<FxHashSet>so the field admits write-once interior mutability via a shared reference; bothpatt_block(owned) andutil_block(shared) iterations now use the same code path with nounsafe`.

  3. refactor(context): use FxHashMap for deterministic ops ordering; drop empty testOpsConfig::instances and
    ResolvedOpBindings::by_group moved from std::collections::HashMap
    to FxHashMap so diagnostic ordering and cartesian iteration are
    stable across runs.

  4. test(ops): align black_box paths, uniform -Zinline-mir, drop broken stderr

    • set_op_with_ops.rs and two_groups.rs called
      std::hint::black_box but rpl.toml bound the matching op to
      std::intrinsics::black_box; the two paths resolve to different
      DefIds at MIR level. Switched to the intrinsic directly.
    • //@compile-flags: -Zinline-mir=false applied uniformly to all ops
      tests so they're stable against the toolchain's inlining-policy.
    • Allowed internal_features, dead_code, unused_must_use in the
      CVE fixtures so the diagnostic isn't drowned by churning rustc
      warnings.
    • The .stderr fixtures (raw cargo run output, literal line
      numbers, not consumed by compile-test.rs) are deleted.
    • rpl.toml's logger_2g comment corrected to "1 instance".
  5. fix(ops): silence clippy lints — eight -D warnings violations:
    collapsible_match ×2, needless_borrow, useless_lifetime ×2,
    useless_conversion ×3.

  6. style(ops): re-fmt after if-let collapse — small format fixup
    the previous push missed.

  7. fix(config): treat empty resolved pattern paths as no-RPL_PATS
    CI's Test installed rpl step (cargo install --path . && cargo rpl --workspace --all-targets --verbose) was failing with
    rpl_meta::cli E100: Cannot locate RPL pattern file "". Root cause:
    resolve_patterns has a vacuous-true branch on
    selected_groups.iter().all(is_remote_spec) when selected_groups
    is empty (identity element of conjunction). With an rpl.toml that
    contains no [patterns] table — exactly the shape this PR adds — the
    branch is taken, resolve_remote_selection(&[]) returns
    Ok(ResolvedPatterns { paths: vec![] }), and the empty paths get
    joined into "" and propagated as RPL_PATS="" to the rpl-driver
    child process. Fixed defensively at the env-string boundary: map
    empty paths to None so the driver falls back to the built-in
    pattern set.

  8. fix(driver): respect outer pat_op bindings in nested RustItems cartesian — Copilot review thread on lines 568/758. When a
    PatternOperation (set-op composition like p = p_a - p_b)
    pre-computed a combo over the union of referenced groups and
    threaded it down to impl_matched_pat_item / fn_matched_pat_item,
    the RustItems arm of those functions discarded the outer combo
    and started a fresh local cartesian over the sub-item's own
    referenced_op_groups(). In a composition where the two arms
    reference overlapping or disjoint subsets of the union, that
    silently picked mismatched combos across positive/negative arms.
    The fix splits the two paths cleanly: descend with the outer
    bindings when non-empty; iterate the local cartesian only when
    bindings is empty (top-level direct entry).

  9. fix(context): wire R6 (ops meta-vars must not leak into patt bodies) — Copilot review thread on context.rs:206. R6 was
    implemented as check_r6_patt_vs_ops and pub use-exported, but
    add_parsed_pattern never called it; the rule was silently
    unenforced. Wired alongside R1–R3 against the raw parse tree.

Local verification before pushing each commit:

  • cargo fmt --all -- --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test --workspace — 0 failures across all suites (including the
    207-test compile-test.rs UI run)

CI: 25688906756
already passed both ubuntu and windows on the pre-Copilot HEAD; the
two new fix commits will trigger a fresh run.

Deferred work (follow-up issues to file post-merge)

These were surfaced during review (mine + Copilot's) but each deserves
its own focused PR.

  • Surface ResolveDiagnostics as real rustc diagnostics. C1–C7
    errors from resolve_ops_config are currently dropped in the driver
    (let (ops, _ops_diags) = …). Users get "didn't fire" instead of
    "missing binding for unlock".
  • Reserved-key collision (also raised by Copilot). The TOML key
    type is the existential free-placeholder list, so an op named
    type silently loses its binding. Either reject op name type in
    R2 or rename the TOML key.
  • Cycle detection. Replace the 0..16 magic iteration cap in
    resolve_one with topological-sort-first detection — current code
    exponentially grows strings before declaring a cycle for inputs like
    T = "Mutex<$U>", U = "Vec<$T>".
  • Path-matching robustness. strip_generics_and_split in
    rpl_match/src/statement.rs doesn't handle UFCS
    <T as Trait>::method. Document the limitation or parse the path
    with the real parser.
  • UI test wiring. Register tests/features/ops/** and
    tests/features/ops_cve_2025_68260/** in tests/compile-test.rs and
    bless .stderr files with the project-standard LL | line-number
    placeholder (via cargo uibless).
  • Diagnostic spans. Three // TODO(task-6): replace DUMMY_SP
    markers in pat/mod.rs mean ops diagnostics point at DUMMY_SP
    rather than the user's .rpl source line.
  • Driver performance (also raised by Copilot).
    resolve_ops_config is currently called once per RPL pattern per fn
    body; hoist it out of the per-fn loop so the TOML is parsed and
    resolved once per crate analysis run.
  • ResolvedOpBindings::from_combo shares instance ownership
    (raised by Copilot). from_combo currently clones full
    ResolvedOpInstance values (containing BTreeMap<Symbol, String>)
    into the per-attempt map. Switching to Rc<ResolvedOpInstance> (or
    Arc if matching ever becomes parallel) and using
    FxHashMap<Symbol, Rc<ResolvedOpInstance>> would reduce
    per-iteration cost.
  • Stronger resolver test coverage. Add unit tests for
    ResolveDiagnostic variants UnknownGroup (C1), MissingBinding
    on op-binding (C3 path), UndeclaredPlaceholder (C5),
    ParseFailure (C6).
  • patterns::resolve_patterns vacuous-all branch. Commit 7
    fixes the symptom at the env-string boundary; the upstream branch in
    resolve_patterns (vacuous-true all on empty selected_groups)
    remains and would benefit from a guard that requires non-empty
    selected_groups before taking the remote-selection path.

Notes for reviewers

  • crates/rpl_parser/src/parser.rs is auto-generated from RPL.pest
    (build.rs); only the +37 .pest lines and any hand-written sections
    are worth reviewing.
  • 30+ small commits with feat(layer): / fix(layer): / test(ops): /
    style(ops): messages aid bisecting.
  • No breaking changes to existing .rpl files — the ops {} block is
    additive. ops deliberately remains usable as a non-block identifier
    (e.g. in path segments like core::ops::Range) — reserving it
    globally would break the cve_2020_35892_35893 parser test and every
    pattern that references core::ops::*.

— Generated by Claude Opus 4.7 (1M context) on behalf of @jellllly420

@jellllly420 jellllly420 changed the title Feature/abstract ops feat(ops): abstract op groups with TOML-driven concrete instances May 11, 2026
@jellllly420 jellllly420 force-pushed the feature/abstract-ops branch from 03b4f36 to 352e8a9 Compare May 11, 2026 18:20
@jellllly420 jellllly420 marked this pull request as ready for review May 11, 2026 18:33
Copilot AI review requested due to automatic review settings May 11, 2026 18:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “abstract ops” to the RPL toolchain: .rpl patterns can declare parameterized op groups in a new ops { ... } block and reference them via $group::$op, while concrete instances are supplied via [[ops.<group>]] in rpl.toml. The driver/matcher then iterates over the cartesian product of referenced op-group instances and resolves $group::$op to concrete Rust DefIds during MIR matching.

Changes:

  • Extend the parser/grammar and pattern IR to support ops { ... } blocks and OpRef operands ($group::$op).
  • Load [[ops.<group>]] instances from rpl.toml, resolve/expand them, and thread op bindings through the matcher/driver with cartesian-product enumeration.
  • Add unit/integration tests plus end-to-end “features” fixtures (including a CVE POC) and a design spec document.

Reviewed changes

Copilot reviewed 49 out of 51 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/features/test.sh Expands the manual features test script to run new ops fixtures; adjusts shell strictness.
tests/features/ops/two_groups.rs New feature fixture exercising cartesian matching across two op groups.
tests/features/ops/two_groups.rpl New pattern using two op refs (sync_2g and logger_2g).
tests/features/ops/set_op_with_ops.rs New feature fixture covering set-op subtraction with ops-based util patterns.
tests/features/ops/set_op_with_ops.rpl New pattern demonstrating p_lock - p_uncovered with op refs.
tests/features/ops/partial_bad.rs New feature fixture for “skip malformed instance, keep others” behavior.
tests/features/ops/partial_bad.rpl New pattern for partial-bad instance scenario.
tests/features/ops/lock_unlock.rs New basic feature fixture for a single op group instance.
tests/features/ops/lock_unlock.rpl New lock pattern referencing $sync::$lock.
tests/features/ops/folded.rs New fixture verifying fold-to-zero when no instances exist.
tests/features/ops/folded.rpl New “folded” pattern referencing an op group with no instances.
tests/features/ops_cve_2025_68260/pattern.rpl New multi-group ops pattern modelling the CVE bug class.
tests/features/ops_cve_2025_68260/fixed.rs New “fixed” variant fixture that should not match.
tests/features/ops_cve_2025_68260/buggy.rs New “buggy” variant fixture that should match.
rust-toolchain Adds rust-analyzer component to the pinned nightly toolchain config.
rpl.toml Adds workspace ops instances ([[ops.*]]) used by feature fixtures.
docs/superpowers/specs/2026-04-28-abstract-ops-design.md Design doc describing the abstract-ops feature and validation/matching model.
crates/rpl_resolve/tests/ops_resolve.rs Adds resolver-side tests for ops WF / op-ref checks (delegating to rpl_context).
crates/rpl_resolve/Cargo.toml Adds dev-dependencies needed by new tests.
crates/rpl_parser/tests/test.rs Adds parser tests for opsBlock and OpRef parsing.
crates/rpl_parser/src/grammar/RPL.pest Extends grammar with opsBlock, opsItem, OpFnDecl, and OpRef.
crates/rpl_meta/src/meta.rs Updates block collection to include ops blocks.
crates/rpl_meta/src/check.rs Updates MIR operand checking for the new OpRef grammar variant.
crates/rpl_match/src/statement.rs Adds runtime OpRef matching that resolves to MIR DefId via expanded paths.
crates/rpl_match/src/mir.rs Threads op bindings into MIR matching context.
crates/rpl_match/src/matches/color.rs Threads op bindings into the “color” matcher context.
crates/rpl_driver/tests/ops_threading.rs Smoke test ensuring ops config threading compiles and can be empty.
crates/rpl_driver/tests/cartesian.rs Unit tests for the new cartesian-product helper.
crates/rpl_driver/src/lib.rs Loads raw ops from rpl.toml, resolves them, iterates cartesian products, and threads bindings into matching.
crates/rpl_driver/Cargo.toml Adds dependency on rpl_config for ops loading.
crates/rpl_context/tests/referenced_op_groups.rs Tests referenced op-group collection in lowered patterns.
crates/rpl_context/tests/ops_wf.rs Tests ops-block WF checks (R1–R3).
crates/rpl_context/tests/ops_resolution.rs Tests ops instance substitution/validation (resolve_ops_config).
crates/rpl_context/tests/ops_lowering.rs Tests lowering of ops blocks into Pattern.ops_block.
crates/rpl_context/src/pat/ops.rs Introduces OpsBlock, OpGroup, and OpSignature IR structures.
crates/rpl_context/src/pat/ops_wf.rs Implements WF checks (R1–R3) plus parse-tree R6 helper.
crates/rpl_context/src/pat/ops_uses.rs Implements post-lowering op-ref use-site checks (R4–R5) and group collection.
crates/rpl_context/src/pat/ops_resolved.rs Implements ops instance expansion/validation and per-attempt bindings structures.
crates/rpl_context/src/pat/mod.rs Integrates ops blocks/op refs into the pattern IR and population pipeline.
crates/rpl_context/src/pat/mir/visitor.rs Updates visitor to traverse the new Operand::OpRef variant.
crates/rpl_context/src/pat/mir/pretty.rs Adds formatting/debug output for Operand::OpRef.
crates/rpl_context/src/pat/mir/mod.rs Adds Operand::OpRef and parsing/lowering support for it.
crates/rpl_context/src/context.rs Wires ops blocks into pattern loading and triggers op-ref validation/population.
crates/rpl_context/Cargo.toml Adds rpl_config dependency for ops instance types in context/tests.
crates/rpl_config/tests/ops_loading.rs Adds TOML deserialization tests for ops instances.
crates/rpl_config/src/patterns.rs Fixes empty resolved pattern paths mapping to avoid RPL_PATS="" failures.
crates/rpl_config/src/ops.rs Adds RawOpInstance TOML deserializer (type = [...] + string bindings).
crates/rpl_config/src/lib.rs Exposes ops loading (load_raw_ops) and adds ops to config schema.
Cargo.lock Updates lockfile for new/changed crate dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rpl_driver/src/lib.rs
Comment thread crates/rpl_driver/src/lib.rs Outdated
Comment thread crates/rpl_driver/src/lib.rs
Comment thread crates/rpl_context/src/context.rs
Comment thread crates/rpl_config/src/ops.rs
Comment thread crates/rpl_context/src/pat/ops_resolved.rs
@jellllly420 jellllly420 force-pushed the feature/abstract-ops branch from 352e8a9 to 8c041cd Compare May 11, 2026 18:53
jellllly420 added a commit to jellllly420/RPL that referenced this pull request May 11, 2026
When a `PatternOperation` (set-op composition like `p = p_a - p_b`)
references multiple op groups, the outer `{impl,fn}_matched_pat_op`
iterates the cartesian product over the union of all referenced
groups and threads one combo through to each sub-item.

`{impl,fn}_matched_pat_item` then matched on the sub-item.  In the
`RustItems` arm it inspected the sub-item's own
`referenced_op_groups()` and started a *fresh* local cartesian
iteration — silently discarding the outer combo's choices for any
group this particular sub-item happened to reference.  In a
`set_op_with_ops`-style composition where each util references a
different (or overlapping) subset of the union, this could pick
mismatched combos across positive/negative sub-items, breaking the
subtraction semantics.

Bug surfaced by Copilot's review of PR RPL-Toolchain#116 (review thread on lines
568 and 758).

The fix splits the two paths cleanly:

  - bindings non-empty (came from an outer pat_op cartesian): descend
    with bindings unchanged.
  - bindings empty (top-level direct entry into a RustItems): iterate
    the local cartesian over this RustItems' own referenced groups,
    which is the original intended behaviour.

cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, and
cargo test --workspace are all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jellllly420 added a commit to jellllly420/RPL that referenced this pull request May 11, 2026
`check_r6_patt_vs_ops` was implemented in `pat::ops_wf` and
`pub use`-exported from `pat::mod` but never actually invoked by
`add_parsed_pattern`.  As a result, R6 was silently unenforced: an
`.rpl` file declaring `sync[$T: type] = { ... }` and then
referencing `$T` inside a `patt {}` body (where it would be an
undeclared meta-var at the pattern level) would slip through
well-formedness checking and either produce surprising matches or
crash later during lowering.

Wired in alongside the existing R1–R3 check, after `add_ops_block`
runs for every ops block.  R6 inspects the raw parse tree (it
walks `MirBody` for `TypeMetaVariable` references) so it must
run before `add_pattern_item` lowers the patt block.

Iteration variable in the surrounding ops loop is now borrowed
(`for ops_block in &ops`) so `ops` remains available for the R6
call below.

Bug surfaced by Copilot's review of PR RPL-Toolchain#116 (review thread on
`crates/rpl_context/src/context.rs` line 206).

cargo build --workspace, cargo fmt --check, cargo clippy
--workspace --all-targets -- -D warnings, cargo test --workspace
all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread crates/rpl_context/tests/ops_lowering.rs Outdated
Comment thread rust-toolchain Outdated
@jellllly420

Copy link
Copy Markdown
Contributor Author

@TheVeryDarkness ready for another look when you have time — both of your comments have been addressed:

  • tests/ops_lowering.rs:47 (Box::leak duplication): collapsed into shared tests/common/mod.rs helpers (make_static_mctx + a LazyLock<&'static Arena<'static>>) in c783ea7. Eight call sites across five test files now go through the helpers; all 34 ops integration tests still pass.
  • rust-toolchain:3 (rust-analyzer addition): reverted in a8daebf — it was an inadvertent IDE-tooling addition not needed by cargo build/test/clippy/fmt. rust-toolchain now matches its pre-PR state.

The 6 Copilot threads on the previous round are also disposed of (3 fixed in e8e1ae6/ac0f3c6/73b85f7, 3 deferred-with-rationale into the PR body's "Deferred work" section). All 8 review threads are marked resolved; please re-open any you'd like to discuss further.

CI green on the latest HEAD (c783ea7): both ubuntu and windows passing.

Since GitHub doesn't let fork contributors re-request reviewers, this comment is the closest I can do to a rebadge. Thanks for the careful review!

— Generated by Claude Opus 4.7 (1M context) on behalf of @jellllly420

jellllly420 and others added 17 commits May 27, 2026 10:52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… idiom)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 2 added OpRef as a sixth alternative to MirFnOperand, which
shifted the auto-generated pest_typed Choice5 -> Choice6.  Two
hand-written match sites needed updating to compile.  The new arm
delegates to a Task 4 stub since OpRef IR lowering lands there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the OpRef IR node so $group::$op call targets in patterns become
typed Operand::OpRef { group, op }.  Replaces the Task 2 placeholder
todo! in from_fn_op with real lowering, and adds stub arms wherever
matches over Operand are now non-exhaustive (resolver and matcher
implementations land in later tasks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous Operand::OpRef lowering preserved the $-prefix from the
MetaVariable span, producing $-prefixed symbols.  But OpGroup.name
will be lowered from Identifier, which is bare.  Task 7's resolver
does a direct lookup against ops_block.groups, so the keys must match.

Adds a parser-round-trip test to catch this kind of regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an ops_block field to Pattern<'pcx> and an add_ops_block
lowering pass that walks pest's pairs::opsBlock and populates
ops_block.groups with OpGroup entries.  Hooks into the top-level
Block dispatcher alongside pattBlock/utilBlock/diagBlock.

Extends collect_blocks in rpl_meta to also return opsBlock slices so
that add_parsed_pattern in context.rs can dispatch them.  A local
OpsMetaLookup struct implements GetType for resolving type meta-
variables ($T, $U) when lowering op-fn parameter and return types;
path types are not supported in ops items and will panic if attempted.

Adds crates/rpl_context/tests/ops_lowering.rs with two integration
tests that exercise the full parse→collect→lower pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses code review on Task 5:

- Verify and document the index-counter alignment between
  OpsMetaLookup and NonLocalMetaVars::from_meta_decls (Path A:
  indices are correct as-is — both count only type-kind decls,
  starting independently at 0, matching NonLocalMetaVars' separate
  IndexVec per kind).
- Replace panics in OpsMetaLookup with unreachable! and messages
  that explicitly reference resolver check R1 as the precondition.
- Add a TODO marker on DUMMY_SP uses tying them to Task 6.
- Add a test exercising multiple op groups within a single block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three resolver checks that reject malformed ops blocks before
lowering hits the unreachable! contracts:

- R1: op-level meta-vars must be of kind 'type' (v1 restriction).
- R2: op signatures may only reference meta-vars declared on the
  same op group.
- R3: op signatures must be abstract — concrete Rust paths belong
  in rpl.toml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three more well-formedness checks plus per-pattern accounting:

- R4: $group::$op references must resolve to a declared op group
  and op signature.  Wrong group → "op group '<g>' is not declared";
  wrong op within a known group → "op '<o>' is not declared in op
  group '<g>'".
- R5: Op-call arity must match the op signature's arity at the
  call site.
- R6: Pattern bodies cannot reference op-level meta-vars; those are
  scope-isolated to the ops block.

Adds RustItems::referenced_op_groups() returning the set of op
groups referenced by OpRefs anywhere in the pattern body.  This
feeds Task 11's cartesian-product iteration over instances.

Also: replace the Task 4 todo!() stub in super_operand for
Operand::OpRef with a proper no-op (OpRef carries no sub-operands
to visit).  Includes parser.rs changes from Tasks 1-4 that were
uncommitted in the working tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds resolve_ops_config() that turns rpl_config::RawOpInstance into
typed ResolvedOpInstance for the matcher.

Implements C2-C7:
- C2/C3: missing meta-var/op binding -> warning, skip instance.
- C4: unknown extra key -> warning, skip.
- C5: undeclared placeholder in template -> warning, skip.
- C6: post-expansion parse failure -> warning, skip (deferred to match time).
- C7: cyclic substitution -> warning, skip.

Eager $T/$U expansion at load time via 16-iteration fixed-point loop;
only $1/$2-style existentials remain when the matcher consumes the result.

Deferred-parse path chosen for Step 5: binding values stored as
post-expansion strings (BTreeMap<Symbol, String>) rather than typed
Ty<pcx>/Path<pcx>. This keeps the implementation self-contained and
avoids threading PatCtxt through resolution. C6 parse-clean check
deferred to match time (Task 12).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Loads OpsConfig in check_crate from RplConfig.ops via
resolve_ops_config() and threads it through impl_matched_pat_op so
Task 11's cartesian-product iteration can consume the resolved
instances. Surfaces ResolveDiagnostics as rustc warnings.

Adds load_raw_ops() to rpl_config for the driver process to read
rpl.toml without going through the cargo-wrapper path. Stores the
raw ops vec on CheckFnCtxt and resolves per-pattern OpsConfig inside
each for_each_rpl_pattern closure in check_fn/check_assoc_fn.

The OpsConfig consumer code (cartesian iteration, OpRef resolution
during matching) lands in Tasks 11 and 12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps impl_matched_pat_op (and fn_matched_pat_op) with a cartesian-
product loop over op-instance combinations.  For each combination,
builds a ResolvedOpBindings view and invokes the renamed
impl_matched_pat_op_with_bindings (the original body, lifted out).

The "fold on empty instances" property falls out of the cartesian
helper: an empty factor folds the product to zero combinations,
which contributes the empty match-set to set-op composition.

Adds PatternOperation::referenced_op_groups() returning the union of
op-group names referenced by any operand of this set-op (recursive
through nested PatternOperations).

Exports pub fn cartesian<T,I,II> (odometer-order iterator) and
pub struct ResolvedOpBindings from rpl_driver; four unit tests
cover the four corner-cases including fold-on-no-instances.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
At match time, when an Operand::OpRef { group, op } is encountered,
look up the active ResolvedOpInstance for the group, fetch the
expanded path string for the op, strip generic-arg brackets, split
on "::", intern to Symbols, build an ItemPath, and delegate to the
existing match_item_path_by_def_path matcher.

ResolvedOpBindings moved from rpl_driver (lifetime-parameterised) to
rpl_context::pat::ops_resolved as an owned struct (clones instances).
CheckMirCtxt gains an op_bindings field and a new_with_bindings
constructor. MatchStatement gains an op_bindings() method implemented
by both CheckMirCtxt and MatchCtxt.

Op-typed parameter handling: v1 punt — pattern-level type meta-vars
continue to match permissively; only call-target OpRef resolution is
wired end-to-end. The lock_unlock e2e tests (Task 13) will determine
if this is sufficient.

The bindings flow through impl_matched_pat_op_with_bindings and
fn_matched_pat_op_with_bindings (Task 11) into impl_matched /
fn_matched, which construct CheckMirCtxt::new_with_bindings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jellllly420 and others added 15 commits May 27, 2026 10:52
Exercises every layer of the abstract-ops feature: parser surface,
ops block lowering, OpRef IR, OpsConfig substitution, cartesian
iteration, and matcher OpRef resolution against std::sync::Mutex.

A single rpl.toml [[ops.sync]] instance binds T -> Mutex<$1>,
U -> MutexGuard<$1>; the pattern's $sync::$lock matches the
m.lock() call site.

Also fixes a gap found during testing: fn_matched_pat_item and
impl_matched_pat_item for RustItems patterns were passing empty
ResolvedOpBindings to CheckMirCtxt instead of iterating over the
cartesian product of (group -> instance) assignments.  The fix
mirrors the existing cartesian-product logic in fn_matched_pat_op,
using referenced_op_groups() to determine which groups to expand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add four end-to-end UI tests for abstract-ops properties:

- folded: ops group declared in .rpl with no rpl.toml instances →
  cartesian product of zero combos → zero matches → no diagnostics.

- partial_bad: three [[ops.sync_pb]] entries, the third missing the
  'unlock' binding (fails C3 validation and is silently skipped); the
  first valid instance (Mutex) still fires the lint.

- set_op_with_ops: util patterns p_lock / p_uncovered both reference
  ops; patt = p_lock[] - p_uncovered[]. lock_only() fires, lock_and_mark()
  is subtracted. Demonstrates set-op composition with ops.

- two_groups: two independent op groups (sync_2g × logger_2g); pattern
  uses both in one function body. Cartesian product (2×1=2 combos) fires
  only for the (Mutex, black_box) combination present in the test file.

Bug fix (context): check_and_populate_op_refs previously only processed
patt_block items. util_block items (arena-allocated, shared refs) were
not populated with referenced_op_groups, causing the set-op and two-groups
patterns to produce zero matches. Fixed by also iterating util_block items
using an unsafe cast (sound: bump-arena items, single-threaded construction,
field written only once before any reader observes it).

test.sh: comment out the broken +rpl-dbg invocation; remove -e from
set -euxo pipefail; add || true after invocations expected to exit 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ttern

Demonstrates the abstract-ops feature catching a real-world bug
class.  CVE-2025-68260 is the first Rust CVE in the Linux kernel:
a race condition in Node::release where intrusive list elements
get drained outside the lock that owned them.

The pattern uses two op groups (sync_cve and intrusive_list_cve)
to describe the bug structurally; rpl.toml binds them to
std::sync::Mutex and stub free-function paths.  buggy.rs mirrors
the pre-fix call shape (lock -> transfer -> unlock -> drain) and
matches; fixed.rs omits the explicit unlock sink (guard drops
naturally) so the pattern's $unlock step finds no candidate and
structurally fails to match.

Key implementation notes:
- #[inline(never)] on stub fns prevents MIR-inline from erasing
  the call sites that the pattern matches.
- std::intrinsics::black_box (via core_intrinsics feature) is used
  as the opaque unlock sink; it is not inlined and resolves to a
  stable DefId matching the rpl.toml "unlock" binding.
- Pattern arguments use wildcards (_) for the mutable-reference
  parameters since optimized MIR emits copy operands for &mut locals
  (which are not Copy, so move/copy cross-kind check would reject).

This is the headline e2e validation: one pattern, swappable
primitives, real bug class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's cargo fmt --check step was failing on both ubuntu-latest and
windows-latest, blocking clippy and test runs. The diff is purely
mechanical:

  - trailing commas on match-arm closing braces (rustfmt.toml has
    match_block_trailing_comma = true)
  - multi-line struct literals where the single-line form exceeded
    max_width = 120
  - merged use-statements per imports_granularity = "Module"
  - comment wrapping per comment_width = 100, wrap_comments = true

No behavioural change. cargo build --workspace passes after the
formatting pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Pattern::check_and_populate_op_refs` mutated a field on
`util_block` items via a shared reference, casting `&PatternItem`
through a raw pointer to obtain a `&mut PatternItem`.  Although
guarded by `#[allow(invalid_reference_casting)]` and a SAFETY
comment, writing through a `&T` is undefined behaviour per the
Rust reference regardless of aliasing — the compiler is free to
assume `&T` is immutable for optimisation.

The fix wraps `referenced_op_groups` in a `std::cell::OnceCell`,
which exposes write-once interior mutability via a shared
reference.  Both the `patt_block` (owned) and `util_block`
(arena-shared) iterations now use the same code path with no
`unsafe` and no raw-pointer surgery.  Reads before population
return a durable empty set via `OnceCell::get_or_init`; a second
`set` on an already-initialised cell is silently ignored, which
preserves the existing invariant that this function runs once.

All rpl_context, rpl_driver, rpl_match, rpl_resolve, and
rpl_config unit/integration tests pass after the change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… empty test

Two small hygiene fixes consolidated into one commit:

1. `OpsConfig::instances` and `ResolvedOpBindings::by_group` switch
   from `std::collections::HashMap<Symbol, _>` to `FxHashMap`.
   `HashMap` uses a randomly-seeded `RandomState` by default, which
   makes diagnostic ordering and cartesian-product iteration order
   non-deterministic across runs.  rustc-internal data structures
   consistently use `FxHashMap` (deterministic seed, faster hash) for
   this exact reason.

2. Drop the `wf_no_errors_for_valid_ops_block` test in
   `ops_wf.rs` — it was `assert!(true, "placeholder")` with a comment
   admitting the real coverage lives in `tests/ops_lowering.rs` and
   `tests/ops_wf.rs`.  Replaced with a one-line comment pointing at
   the integration tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tderr

Three coupled fixes to the end-to-end UI fixtures:

H5 (DefId mismatch).  set_op_with_ops.rs and two_groups.rs called
`std::hint::black_box`, but rpl.toml binds the matching op to
`std::intrinsics::black_box`.  The two paths resolve to different
DefIds at MIR level; tests previously passed only because the default
MIR inliner happened to devirtualise the hint::black_box re-export.
Switch both sources to call the intrinsic directly (with
`#![feature(core_intrinsics)]` + `#![allow(internal_features)]`),
matching the convention already used by the CVE POC.

H6 (inlining-policy independence).  Only lock_unlock.rs set
`-Zinline-mir=false`; every other ops test relied on the default
inliner not devirtualising `Mutex::lock` / `RwLock::read`.  Add
the flag uniformly so the tests are stable against toolchain
inlining changes.

H7 (signal-to-noise in CVE fixtures).  buggy.rs and fixed.rs emitted
four unrelated warnings (`internal_features`, two `dead_code` on
struct fields, `unused_must_use` on a discarded
`black_box(g)`-of-Result).  These drown the actual lint and rot
with each rustc upgrade.  Allow the three categories at file scope
(narrow, targeted, easy to audit).

Bonus: rpl.toml's two_groups comment claimed two logger_2g instances
when only one is declared; corrected to match reality.  test.sh's
matching comment updated likewise.

The .stderr fixtures under tests/features/ops/ and
tests/features/ops_cve_2025_68260/ are deleted: they contained raw
`cargo run` output ("Finished `dev` profile…", absolute target
paths) plus literal line numbers, and are not consumed by
`tests/compile-test.rs` (which only registers tests/ui).  Wiring
those directories into compile-test.rs and re-blessing properly is
tracked as a follow-up (see PR body "Deferred work").

cargo build --workspace passes; rpl_context, rpl_driver, rpl_match,
rpl_resolve, rpl_config, rpl_parser unit/integration tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…seless_lifetime, useless_into_iter)

`cargo clippy --workspace --all-targets -- -D warnings` (the CI
invocation, which fmt was previously masking) surfaced 8 lints in
the new ops code:

  - rpl_context/src/pat/ops_resolved.rs: two functions named a
    `'pcx` lifetime parameter that only appeared in a single
    parameter type; elided to `'_` (clippy::needless_lifetimes).

  - rpl_context/src/pat/ops_uses.rs: triple-nested `if let ... {
    if let ... { if let ... { } } }` for the
    `Option<TerminatorKind::Call(Operand::OpRef { .. })>` walk;
    flattened into a single pattern (clippy::collapsible_match).

  - rpl_context/src/pat/ops_wf.rs: `check_op_sig(group_name, &sig,
    ...)` — `sig` is already `&pairs::OpFnSig`, the `&`
    is redundant (clippy::needless_borrow).

  - rpl_driver/tests/cartesian.rs (x3): `cartesian(iters.into_iter())`
    where `cartesian` accepts `IntoIterator` directly; dropped the
    redundant `.into_iter()` (clippy::useless_conversion).

`cargo clippy --workspace --all-targets -- -D warnings` is now
clean.  `cargo test --workspace` reports 0 failures across all
suites (including the 207-test compile-test.rs UI run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ush)

The collapsed `if let Some(TerminatorKind::Call { ... })` pattern in
the clippy-cleanup commit exceeded rustfmt's heuristic for
single-line struct destructuring, and the single-line
`errors.push(format!(...))` invocation underneath it crossed the
implicit wrap-at-comma threshold.  Both are now multi-line per
rustfmt's preference; the CI's `cargo fmt --check` step was the
canary.

Pure formatting; no functional change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's 'Test installed rpl' step (cargo install --path . && cargo rpl
--workspace --all-targets --verbose) failed with:

   WARN rpl_meta::cli Error[E100]: Cannot locate RPL pattern file `""`
   error: An error was found with input RPL pattern(s)

Root cause: when an `rpl.toml` exists that has no `[patterns]`
table (e.g. one carrying only `[run]` or `[[ops.<group>]]` —
exactly the shape of the rpl.toml this PR introduces) AND
`--patterns` is not passed,
`patterns::resolve_patterns` falls through to the `Some(config)`
branch, finds `patterns = None`, and takes the
`selected_groups.iter().all(is_remote_spec)` branch.  `all` is
vacuously `true` on an empty iterator (the identity element of
conjunction), so it calls `resolve_remote_selection(.., &[])`,
which loops over nothing and returns
`Ok(ResolvedPatterns { paths: vec![] })`.
`resolve_patterns_env` then joins zero paths into the empty
string and returns `Some(String::new())`.  cargo-rpl sets
`RPL_PATS=""` in the child env; rpl-driver splits on `:`,
gets `vec![""]`, and rpl_meta fails to open the empty path.

This was masked on master because no rpl.toml existed, so the
earlier `config: None` branch (line 54-62) short-circuits to
`Ok(None)`.  My PR adds the workspace rpl.toml (for ops
instances), which exposes the path.

The defensive fix is at the env-string construction boundary:
`Some("")` is never a valid `RPL_PATS` value — it would always
fail downstream — so map empty paths to `None` and let the
driver fall back to the built-in pattern set, the same behaviour
as when no rpl.toml exists.

Local: cargo fmt --check, cargo clippy --workspace --all-targets
-- -D warnings, cargo test --workspace all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a `PatternOperation` (set-op composition like `p = p_a - p_b`)
references multiple op groups, the outer `{impl,fn}_matched_pat_op`
iterates the cartesian product over the union of all referenced
groups and threads one combo through to each sub-item.

`{impl,fn}_matched_pat_item` then matched on the sub-item.  In the
`RustItems` arm it inspected the sub-item's own
`referenced_op_groups()` and started a *fresh* local cartesian
iteration — silently discarding the outer combo's choices for any
group this particular sub-item happened to reference.  In a
`set_op_with_ops`-style composition where each util references a
different (or overlapping) subset of the union, this could pick
mismatched combos across positive/negative sub-items, breaking the
subtraction semantics.

Bug surfaced by Copilot's review of PR RPL-Toolchain#116 (review thread on lines
568 and 758).

The fix splits the two paths cleanly:

  - bindings non-empty (came from an outer pat_op cartesian): descend
    with bindings unchanged.
  - bindings empty (top-level direct entry into a RustItems): iterate
    the local cartesian over this RustItems' own referenced groups,
    which is the original intended behaviour.

cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, and
cargo test --workspace are all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`check_r6_patt_vs_ops` was implemented in `pat::ops_wf` and
`pub use`-exported from `pat::mod` but never actually invoked by
`add_parsed_pattern`.  As a result, R6 was silently unenforced: an
`.rpl` file declaring `sync[$T: type] = { ... }` and then
referencing `$T` inside a `patt {}` body (where it would be an
undeclared meta-var at the pattern level) would slip through
well-formedness checking and either produce surprising matches or
crash later during lowering.

Wired in alongside the existing R1–R3 check, after `add_ops_block`
runs for every ops block.  R6 inspects the raw parse tree (it
walks `MirBody` for `TypeMetaVariable` references) so it must
run before `add_pattern_item` lowers the patt block.

Iteration variable in the surrounding ops loop is now borrowed
(`for ops_block in &ops`) so `ops` remains available for the R6
call below.

Bug surfaced by Copilot's review of PR RPL-Toolchain#116 (review thread on
`crates/rpl_context/src/context.rs` line 206).

cargo build --workspace, cargo fmt --check, cargo clippy
--workspace --all-targets -- -D warnings, cargo test --workspace
all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three-layer substitution model is:

  1. `type = ["$1", "$2", ...]` declares EXISTENTIAL numbered
     placeholders (wildcards at match time after `<...>` stripping).
  2. Each TYPE meta-var binding (T, U, L, G, List, ...) is a string
     that may reference `$1`, `$2`, ... — these define the
     CONCRETE path each abstract type meta-var resolves to.
  3. Each OP-NAME binding (lock, unlock, log, ...) is a string that
     may reference any TYPE meta-var (`$T`, `$L`, ...) AND the
     existential placeholders.  The resolver runs a fixed-point
     textual substitution in `resolve_one`: `$<type-meta-var>` is
     expanded before the matcher sees the resolved string.

The implementation has supported this since the resolver was added
(see `happy_path_eager_expansion` test in
`crates/rpl_context/tests/ops_resolution.rs`), but `rpl.toml`
spelled out the full `std::sync::Mutex<$1>::lock` form for every
op binding instead of using the shorter `$T::lock` form.  The
short form expands to byte-identical resolved paths and is the
syntax the design intended.

Updated entries:

  - sync, sync_pb[0..2], sync_so, sync_2g[0..1]:
      lock = "$T::lock" / "$T::read"
      unlock = "$U::drop" (where applicable)
  - sync_cve: lock = "$L::lock"
  - sync_pb[2] (the intentionally-malformed instance for C3):
      lock = "$T::read"

Op bindings to free functions (sync_so/sync_cve unlock = black_box;
logger_2g log = black_box; intrusive_list_cve transfer/drain =
crate::* free fns) remain literal — they're not methods on a type
meta-var so the `$T::method` form does not apply.  The expanded
file header documents the three layers so future readers don't
have to reverse-engineer the model.

cargo build --workspace, cargo fmt --check, cargo clippy
--workspace --all-targets -- -D warnings, cargo test --workspace
all clean.  The 207-test compile-test.rs UI suite passes without
change, confirming the resolved paths are byte-identical post-
substitution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`rust-analyzer` was added to the toolchain components list but is
IDE-only tooling — it isn't required by `cargo build`,
`cargo test`, `cargo clippy`, or `cargo fmt` runs on CI, and users
who want rust-analyzer can install it independently via rustup.

Surfaced by review comment from @TheVeryDarkness on rust-toolchain:3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion tests

Eight occurrences of identical 5-line `Box::leak` boilerplate were
sprinkled across the new ops integration tests:

  crates/rpl_context/tests/ops_lowering.rs       (3 sites)
  crates/rpl_context/tests/ops_wf.rs             (2 sites)
  crates/rpl_context/tests/ops_resolution.rs     (1 site)
  crates/rpl_context/tests/referenced_op_groups.rs (1 site)
  crates/rpl_resolve/tests/ops_resolve.rs        (3 sites)

Each one leaked a fresh `Arena<'static>`, a fresh path/content
vector, and a fresh `MetaContext<'static>` — all so
`PatternCtxt::entered_no_tcx` could see them as `'static`.  The
arena allocation was wasted: each test could just as well share one.

Consolidate the boilerplate into per-crate `tests/common/mod.rs`
modules exposing:

  * `shared_arena()` — `LazyLock`-backed `&'static Arena<'static>`,
    initialised once per integration-test binary and shared across
    every `#[test]` in that binary.
  * `make_static_mctx(filename, src)` — parse `src` and return a
    `&'static MetaContext<'static>`, panicking on any
    parse/collect error.  Replaces 7 of the 8 call sites.
  * `make_static_mctx_with(filename, src, handler)` — same with a
    caller-supplied error handler.  Used by the R6 fixture in
    `ops_resolve.rs` that intentionally feeds undeclared
    pattern-level meta-vars and needs to swallow the
    `NonLocalMetaVariableNotDeclared` error from
    `parse_and_collect` so the R6 check itself can run.

The two `tests/common/mod.rs` files are near-identical clones (the
two crates are independent integration-test binaries and Cargo
doesn't support sharing a test sub-module across crate boundaries
without a dedicated dev-dep crate, which would be heavier than the
~50 duplicated LOC).

All 34 ops integration tests still pass:
  - referenced_op_groups: 3 ok
  - ops_lowering:         3 ok
  - ops_wf:              13 ok
  - ops_resolution:       5 ok
  - ops_resolve:         10 ok

cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings,
cargo test --workspace all clean.

Surfaced by review comment from @TheVeryDarkness on
crates/rpl_context/tests/ops_lowering.rs:47.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jellllly420 jellllly420 force-pushed the feature/abstract-ops branch from c783ea7 to 2f10724 Compare May 27, 2026 02:54
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.

3 participants