Skip to content

Replay the fork's commits onto upstream main (83a3afc3) - #121

Merged
AdaWorldAPI merged 285 commits into
mainfrom
ccr-e6037cd5-gl8du4
Oct 14, 2026
Merged

AdaWorldAPI merged 285 commits into
mainfrom
ccr-e6037cd5-gl8du4

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

main now matches astral-sh/ruff at 83a3afc3. This PR puts the fork's own work back on top of it, as a linear replay of the fork's history rather than a merge commit.

Fork main before the sync (9407c748, 405 fork commits) is kept at backup/main-2026-10-14-a and backup/main-2026-10-14-b.

What was replayed

How conflicts were resolved

Conflicts came only from fork commits that edit upstream-owned files.

  • Root Cargo.toml: upstream's crate versions, plus the fork crates in [workspace.dependencies].
  • Fork clippy sweeps of upstream code (the 1.97 and 1.98 rounds): upstream's version of those files is kept. Upstream has since rewritten them and passes clippy on its own toolchain. The parts of those sweeps that touch fork crates are replayed.
  • CI workflows:
    • the Windows ruff_cpp_spo exclusion is kept, run through upstream's uv run --locked --only-dev wrapper;
    • the step that lowercases the GHCR image owner is kept;
    • the fork's longer timeouts are kept;
    • upstream's runner labels and action versions are taken.
  • AGENTS.md: the fork's sections, followed by upstream's.
  • Fork crates touched by two parallel fork PRs: replaying them in order left ruff_r2il/examples/slag_partition.rs different from fork main, so a separate commit restores it. Every fork crate and .claude/ file now matches fork main (9407c748).

Where parallel fork branches overlapped, a few individual replayed commits may not build on their own. The final tree builds.

Commits added on top

  • ruff_python_dto_check, ruff_python_spo: upstream replaced ExprCompare's left and comparators fields with an operands slice. Both crates now read the new layout, and their output is unchanged.
  • ruff_python_dto_check: sha2 is requested with default-features = false. Upstream's scripts/check-cargo-bans.py rejects external dependencies that keep default features.
  • Cargo.lock: re-resolved to add the fork crates.
  • Rust 1.99 is now the minimum supported version. workspace.package.rust-version goes from upstream's 1.97 to 1.99, matching the 1.99.0 toolchain. The fork crates inherit it instead of pinning 1.98.1; ruff_r2il, which sits outside the workspace, pins 1.99 by hand.
  • AGENTS.md: Haiku agents may now do scoped grindwork, and the main thread checks every result it relies on.

Verification

Local runs on the 1.99.0 toolchain, all passing:

  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --all-features for the fork crates
  • scripts/check-cargo-bans.py
  • cargo fmt --all --check

Apart from the order of two dependency lines in Cargo.toml, the final tree is identical to an ordinary merge of upstream into fork main that was also checked.

🤖 Generated with Claude Code

https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 76e51b3d-7c55-44d1-9026-eef71a13642f
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude added 29 commits October 14, 2026 20:58
The first concrete artifact PROBE-OGAR-ADAPTER-UNICHARSET consumes (see
lance-graph core-first-transcode-doctrine.md): a reproducible generator for
tesseract::UNICHARSET's has_function / virtually_overrides method-resolution
manifest, harvested from real Tesseract source via the libclang walker.

Run (corpus stays UPSTREAM, never vendored):
  TESSERACT_SRC=/tmp/tesseract LIBCLANG_PATH=/usr/lib/llvm-18/lib \
    cargo run -p ruff_cpp_spo --features libclang --example harvest_unicharset

Measured against tesseract-ocr/tesseract@5.5.0 src/ccutil/unicharset.h:
16 classes, UNICHARSET = 121 methods, 880 triples / 97 KB ndjson. The v1
probe's leaf targets unichar_to_id / id_to_unichar are present as
rdf:type ogit:Function; UNICHARSET has no base class, so its ClassView
composes purely via has_function (no MRO needed for the probe).

required-features = ["libclang"] keeps a featureless --all-targets build
from compiling it. fmt + clippy -D warnings clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…n (CPP-SCHEMA-FIT 82%->90%)

Ran the CPP-SCHEMA-FIT real-corpus coverage probe (new gated test
cpp_schema_fit_real_corpus_coverage + class_body_cursor_histogram walker
instrumentation) over all 31 src/ccutil headers of tesseract@5.5.0:

  6570 class-body cursors, 5420 mapped = 82%.

The walker matched only EntityKind::Method, silently dropping 495 member
functions libclang reports under distinct cursor kinds: Constructor (268),
Destructor (139), FunctionTemplate (64), ConversionFunction (24). A real
correctness gap — the harvester claimed to capture methods but dropped every
ctor/dtor.

Fix: build_class now maps all five function-like cursor kinds to a has_function;
MAPPED_CURSOR_KINDS updated in lockstep so the coverage metric and the
extraction can't drift. Hermetic test gains a ctor + virtual-dtor assertion.
After: 5915 mapped = 90% (~97% excluding AccessSpecifier noise + recursed
nested types). Next follow-up by frequency: FriendDecl (79).

11/11 ruff_cpp_spo tests pass (libclang feature); clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
… — CPP-SCHEMA-FIT 90%->92%

Next two data-driven coverage follow-ups from the CPP-SCHEMA-FIT histogram:

- VarDecl (84 cursors): a VarDecl in a class body is a static data member
  (`static T x;`); captured as has_field via a `FieldDecl | VarDecl` arm.
- FriendDecl (79 cursors): captured as is_friend_of. Grounded against real
  libclang-18 rather than guessed — the FriendDecl cursor is anonymous, and its
  TypeRef child's resolved TYPE display is the clean fully-qualified befriended
  name (Tesseract::TessdataManager); the cursor spelling carries a `class `
  elaboration, so build_friend reads the type, not the spelling.

MAPPED_CURSOR_KINDS extended in lockstep (9 kinds). Hermetic fixture gains a
static member + friend class with assertions. Coverage 90% -> 92% (6078/6570);
~99% of meaningful constructs now mapped (remaining unmapped is AccessSpecifier
noise + recursed nested types + a handful of type-alias/using/enum).

11/11 ruff_cpp_spo tests pass (libclang feature); clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…rvest across runs)

cpp_ast_rt_determinism (gated on TESSERACT_SRC) walks all of src/ccutil twice
in-process and asserts byte-identical ndjson. Green: the harvest is reproducible
end-to-end (no RNG in the walker; walk_files dedups into a sorted BTreeMap;
expand sorts + dedups). The plan's "do NOT claim faithful harvest until
CPP-AST-RT is green" gate is now satisfied for the in-process path.

2 of 3 primary probes green (CPP-SCHEMA-FIT + CPP-AST-RT); only CPP-TEMPLATE-DET
remains, gated on class-level template extraction.

clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…0->67 classes

Researched template handling against the corpus before implementing:
- Observed (instrumented libclang walk on genericvector.h): libclang FLATTENS a
  ClassTemplate cursor — its children are the template params + members directly —
  so build_class handles it unchanged.
- Measured (grep ccutil): 57 primary class templates, 0 explicit specialisations,
  pervasive instantiation-uses.

Shape A: collect_classes + the coverage tally now treat ClassTemplate and
ClassTemplatePartialSpecialization as classes. ccutil harvest 50 -> 67 classes
(+17 template containers: GenericVector, PointerVector, ...), 1652 -> 2184
triples — container classes + their methods previously invisible to the graph.
Deterministic; hermetic class-template fixture asserts capture (Box::get).

Measured B-vs-C finding: B's template_specialises captures nothing on ccutil
(0 specs) — B is identical to A here, refuting "B>A" on this corpus. The
structure that actually exists is instantiation-use (template_instantiates =
Shape C), which is the Inferred/per-TU-incomplete tier CPP-TEMPLATE-DET gates;
deferred for the data-driven C round. CPP-TEMPLATE-DET is therefore
deferred-with-C (A emits no template-relationship predicate).

12/12 ruff_cpp_spo tests pass (libclang feature); clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…types (CPP-TEMPLATE-DET GREEN)

Research-first per operator's "best possible C, then compare":

Measured first (before implementing): ccutil has 7 instantiation uses in field
types (std::vector x5, GenericVector x1, std::function x1), 0 in bases (build_base
already resolves to the primary template name). Verified the gap is non-redundant:
expand::cpp_field explicitly drops type_name, so field/signature template-uses
were INVISIBLE in the triples.

Best-shape: capture template-id type strings SYNTACTICALLY from (a) field types
(FieldDecl/VarDecl get_type), (b) method signatures (return + parameter types via
get_result_type / get_arguments). Not implicit-instantiation cursors (that's the
per-TU-incomplete thing the Inferred provenance flags). Determinism is structural:
cursor children are in source order; expand sorts the triple set.

Helpers:
- template_instantiation(&type_display): strips const/volatile prefixes + trailing
  */&, returns the verbatim template-id (GenericVector<char>).
- collect_signature_instantiations(method, decls): one Instantiation declaration
  per template-id in a method signature.

Measured result: ccutil 2184 -> 2215 triples (+31 deterministic
template_instantiates edges); hermetic fixture asserts both field-type (Box<int>)
and signature-type (Box<char>) capture; cpp_template_det_determinism probe runs
extract_dir twice, asserts identical template_instantiates set — GREEN.

C vs A vs A+C (now measured): A captured 0 template-relationship triples; C adds
31 strictly non-redundant ones. A+C composes cleanly: A makes GenericVector a
node, C makes Recognizer template_instantiates GenericVector<char> an edge to it.
All three primary CPP-* probes now green (SCHEMA-FIT ~91%, AST-RT deterministic,
TEMPLATE-DET deterministic + non-degenerate).

13/13 ruff_cpp_spo tests pass (libclang feature); clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…ter scales to OCR data model

Free exploration of "what's next beyond CPP-SCHEMA-FIT / AST-RT / TEMPLATE-DET,"
with measure-first discipline:

Option survey (measured against corpus):
- explicit `template Foo<int>;` instantiations: 0 in ccutil. Skip.
- B-revisited (namespace-qualified template_specialises): IR correctness fix
  but 0 specs in ccutil. Hold pending paired test update.
- is_const / is_static method flags: high value, blocked on closed-vocab
  approval (predicate_count_locked_at_47 -> 49). Council-review, not autoattended.
- has_param_type / returns_type: biggest enrichment, same closed-vocab gate. Defer.
- Walk src/ccstruct (OCR motherlode): existing extract_tree, no predicate
  change. SHIPPED below.

Permanent ccstruct_motherlode_smoke test (gated on TESSERACT_SRC):
extract_tree("src/ccstruct") reaches the OCR data model. Measured:
- 155 classes, 5264 triples, 32 deterministic template_instantiates edges
  (vs ccutil's 67 / 2215 / 31).
- Captures EVERY OCR core class (BLOCK/WERD/TBLOB/C_BLOB/POLY_BLOCK/TWERD/
  BLOBNBOX/...) plus template_instantiates edges to GenericVector<T>,
  BandTriMatrix<T>, GENERIC_2D_ARRAY<T>, KDPair<Key,Data>, PointerVector<T>.
- Honest nuance: signature template-ids in template definitions resolve to
  canonical-parameter form (GenericVector<T>, not concrete args). Still
  deterministic and useful (links to primary template).

Harvester scales past the utility shell to the load-bearing OCR surface with
the same deterministic shape (CPP-AST-RT applies).

14/14 ruff_cpp_spo tests pass (libclang feature); clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…erator-approved vocab 48->50)

Operator chose option 2 ("AR AST DLL shape preferred"): method signature types
as edges — what the tesseract-rs-ast-dll-codegen needs to generate adapter
signatures. Option 1 (is_const/is_static, "ORM-shape downcast") is the optional
follow-up.

Closed-vocab bump (operator-authorized): +2 predicates ReturnsType, HasParamType.
predicate_count_locked_at_48 -> _at_50; ALL array + doc invariant +
default_provenance (both CppExtracted, syntactically present) in lockstep.

IR: CppMethod gains return_type: Option<String> + param_types: Vec<String>
(#[serde(default)]); 10 construction sites updated.

Shape: returns_type = one edge per non-void method; has_param_type = one edge
per parameter, object "<index>:<type>" so the unordered triple SET preserves
signature order + arity (codegen sorts by index prefix). Determinism structural:
get_arguments is source-order; expand sorts.

Walker: build_method reads get_result_type (skips void/ctor/dtor) + get_arguments.
Hermetic test asserts int Recognize(int) -> returns_type int + has_param_type
0:int, and void stash(const Box<char>&) -> no returns_type + has_param_type
0:const Box<char> &.

Measured enrichment: ccutil 2215 -> 3527 triples (+1312); ccstruct 5264 -> 8164
(+2900). Signatures are now first-class graph structure (codegen-ready AST-DLL
shape). CPP-AST-RT determinism still green.

46 ruff_spo_triplet + 14 ruff_cpp_spo tests pass; clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…th operator shapes shipped)

Operator's optional "1": is_const / is_static method flags (const = read
accessor, static = class-level). Same pattern as the signature shape.

Closed-vocab bump: +2 predicates IsConst, IsStatic. predicate_count_locked_at_50
-> _at_52; ALL / doc / default_provenance (CppExtracted) in lockstep. 17 C++
machine-plane predicates now.

IR: CppMethod gains is_const + is_static bools; #[derive(Default)] added
(future construction-site churn relief) + #[expect(struct_excessive_bools)]
(4 independent C++ qualifiers, not a state machine — enums would be artificial).

Walker: build_method reads is_const_method() / is_static_method(). Hermetic test
asserts bool operator==(...) const -> is_const true, not static.

Measured: ccutil 3527 -> 3850 triples (+323); ccstruct 8164 -> 8972 (+808).
CPP-AST-RT still deterministic.

Both operator-requested shapes now shipped. C++ machine-plane vocab = 17
predicates (was 13): + returns_type, has_param_type (AST-DLL signature shape)
+ is_const, is_static (ORM-downcast shape). Harvester emits a codegen-ready
surface: identity, inheritance, fields, function membership, full signatures,
const/static qualifiers, template use, friendship.

46 ruff_spo_triplet + 14 ruff_cpp_spo tests pass; clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…dentity

Two real correctness bugs codex flagged on PR #17:

P2 #1 — overload collision (load-bearing for the codegen-ready claim).
Before: void f(int) and void f(double) both attached their signature triples
(returns_type / has_param_type / is_noexcept / ...) to the same cpp:C.f node,
so the codegen could not reconstruct the two overloads. Fix: per-overload
method IRI in cpp_method appends "(<comma-joined-param-types>)" — cpp:C.f(int)
vs cpp:C.f(double). The walker's virtually_overrides target uses the same
suffix on the base overload (via get_arguments on the overridden cursor), so
the override edge joins the EXACT base overload (not just any same-name
method). Hermetic probe: two process(int)/process(double) overloads in the
fixture must yield distinct param signatures at the IR level.

P2 #2 — partial-spec identity collision. A ClassTemplatePartialSpecialization
shares its primary's get_name() (libclang spells it as the bare template name);
the cross-TU BTreeMap dedup by qualified_name() then dropped one side. Fix:
build_class uses get_display_name() for partial specs (Box<T *>) and get_name()
for everything else (Box). Hermetic probe: primary Box + partial spec Box<T *>
must coexist with distinct members.

Measured impact: ccutil 3850 -> 4129 triples (+279, overload discriminator
splits methods that previously aliased); ccstruct 8972 -> 9507 (+535). Class
counts unchanged.

Also resolves the rebase conflict with PR #15 (association_kind) — vocab count
gate adjusted to 53 (7 core + 29 AR-shape + 17 C++ machine-plane).

48 ruff_spo_triplet + 14 ruff_cpp_spo tests pass; clippy -D warnings + fmt clean.

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…errides

Rails `belongs_to :owner, class_name: 'User'` declares a relation
(`:owner`) that points at a class (`User`) whose name doesn't
follow the camelcase-singular convention on the relation name.
Without surfacing this override, downstream Schema consumers
invent a phantom `record<Owner>` for what should be `record<User>`.

The `AssocDecl.options: Vec<(String, String)>` already captures
the verbatim option list from the parser (D-AR-2). The expand
match arm explicitly dropped them (`let _ = a.options;`); this PR
lifts the `class_name` key into a sibling triple keyed by the
relation IRI:

  (openproject:WorkPackage.owner, class_name, "User")

Symmetry with the existing `association_kind` triple — both are
relation-scoped facts the FK-gating consumer needs. Absent
triple means "use the Rails convention" — preserves pre-#15
ndjson dumps bit-for-bit.

Predicate enum count moves 49 → 50; `predicate_count_locked_at_*`
test updated. Added a focused regression
`ar_shape_emits_class_name_override_when_option_present` that
asserts both the positive case (override emitted) and the
negative (no override → no class_name triple). The existing
`ar_shape_emits_every_ar_predicate` exhaustiveness check picks
up the new predicate via the fixture's pre-existing
`class_name: 'Project'` option on its first association.
…_module)

Rails STI (Single Table Inheritance — `class Foo < Issue` with a
type-discriminator column) is semantically distinct from method-
body composition (`include Acts::Customizable`), but `expand`
collapsed both into `includes_module` triples. Downstream
consumers (graph build, schema visualizer) lose the type-
hierarchy signal — about 26% (~223 / 872) of `includes_module`
triples on the OP corpus are likely STI parents (Base / *Query /
*Filter / *Strategy classes).

Reuse the existing `inherits_from` predicate (originally
introduced for C++ class inheritance) for Rails STI: wire shape
is identical (subject = class, object = base IRI), per-emission
provenance differs (`OpenProjectExtracted` for Rails,
`CppExtracted` for C++). The C++ frontend's `inherits_from`
emission and behavior are unchanged.

Updated the predicate doc to reflect dual-frontend use; updated
the `ar_shape_emits_every_ar_predicate` test to expect
`inherits_from` instead of `includes_module:Issue` for the STI
parent, and added a negative assertion that the parent must NOT
also appear under `includes_module`. Renamed the C++-leakage
guard to `non_cpp_fixtures_emit_no_cpp_only_predicates` and
removed `inherits_from` from its list (since it's no longer
C++-only).

Tests: 51 pass.

Downstream consumer (openproject-nexgen-rs) follow-up PR will
lift `inherits_from` to a dedicated `inherits:<Parent>` table
annotation, separating STI semantics from concern composition
in the schema output.
…ection

The AST-DLL codegen's stage-1: groups a Triple set back into a per-class
ModelGraph (members, methods, bases, templates, friends, macro uses, static
asserts). It is the inverse of expand()'s C++ machine-plane projection.

Method identity is recovered from the index-prefixed has_param_type triples
(0:int, 1:const Image &), and the (params) IRI suffix is reconstructed from
them and stripped to recover the method name — never split on `,`. This
sidesteps the comma-bearing-templated-param problem (std::map<int, int>) that
a naive suffix split would hit. Class attribution is anchor-first: identity
comes from the explicit (class, rdf:type, ogit:ObjectType) triple and the
(class, has_function, method) edges, never a rightmost-`.` split.

Round-trip property (the falsifier): reassemble(expand(g)) == cpp_projection(g)
for any C++-plane graph with distinct method IRIs, where cpp_projection blanks
the three fields expand never emits (CppField::type_name, CppBase::access,
CppBase::virtual_base). It compares against the live graph, not a frozen
golden, so it is immune to harvester-vocabulary drift — only a genuine
reassembly bug turns it red.

In-crate tests cover the full C++ surface plus three adversarial cases that
make the property a real measurement: two classes sharing a method name
(no cross-attribution), one class with overloads (no overload collapse),
and a method with comma-bearing templated params (no `,`-split corruption).

ruff_cpp_spo gains CPP-REASSEMBLE-RT, the same falsifier against the REAL
corpus (gated on TESSERACT_SRC). Measured on Tesseract 5.5.0 ccutil: 67
classes; class-set preservation and idempotence hold; 48/67 round-trip
byte-exact, 19 differ — quantifying a real harvester limitation where
const/non-const overloads collide on one method IRI (const-ness is not in
the (params) suffix). Surfaced by the falsifier, not asserted away.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
… finding

The reassembler (generator stage 1) lands as the re-scoped C-FIRST gate: a
round-trip structural-equivalence falsifier, not the tautological self-golden
the brutal council rejected. CPP-REASSEMBLE-RT runs green on real ccutil
(67 classes; class-set preservation + idempotence hold).

New measured finding (GAP-CONST-OVERLOAD, queued): 48/67 classes round-trip
byte-exact vs projection; 19 differ because const/non-const overloads with
identical param types collide on one method IRI (const-ness is not in the
(params) suffix). Quantified by the falsifier, not synthesized; queued as an
operator-gated harvester IRI-scheme change, not a silent drop.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…EMBLE-RT 67/67

The method IRI now carries the cv-qualifier: expand::cpp_method appends ` const`
when is_const, clang_walker does the same for the virtually_overrides target
(keyed on base_m.is_const_method()), and reassemble reconstructs the suffix from
the recovered is_const. A const/non-const overload pair (T& at(i) vs const T&
at(i) const) shares name AND params, so without ` const` in the identity they
collapsed under expand's (s,p,o) dedup. Correctness fix, not a vocab change
(predicate count stays 54).

The falsifier then corrected the council's assumption with data: the cv-aware IRI
fixed only 3 of the 19 ccutil "differ" classes (48->51). Tracing the rest
(per-class methods/templates/fields delta) showed 13 were benign duplicate
template_instantiates that expand dedups but cpp_projection did not, 2 were
duplicate-harvested methods (same cause), and 1 (UnicityTable) was an equal-sort-
key ordering artifact. Two round-trip-metric fixes followed:

- canonicalize_cpp now de-duplicates every C++ collection after sorting,
  mirroring expand's (s,p,o) dedup (real collisions, same key/different content,
  are not equal so survive; only exact duplicates collapse).
- the methods sort key includes is_const so a const/non-const pair sorts
  deterministically (non-const first) on both the reassembled and projected side.

Result: CPP-REASSEMBLE-RT is 67/67 byte-exact, 0 differ, now a HARD gate
(differing.is_empty()) — a regression or a new collision source (ref-qualified
overloads in a future corpus) reopens it. New in-crate test
const_and_nonconst_overload_stay_distinct. 58 ruff_spo_triplet + 15 ruff_cpp_spo
green; clippy -D warnings + fmt clean.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…djson)

Pure formatting: fmt reorders the re-exports alphabetically. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…ethodSig manifest

Stage 2 of the C++->Rust AST-DLL pipeline, per the 8-agent step-2 council. A new
ruff_cpp_codegen crate (depends on ruff_spo_triplet ONLY — no lance-graph edge):

- project(&ModelGraph) -> Vec<ClassManifest>: extract the C++ method plane
  (one MethodSig per method: name, ordered params, return, is_const, is_static,
  override target; body-shaping flags intentionally dropped).
- decompile(&[ClassManifest], ns) -> Vec<Triple>: regenerate the signature-plane
  triples (cv-aware IRI), kept in lockstep with expand::cpp_method.
- render(&[ClassManifest]) -> String: emit-text-only Rust source naming
  lance_graph_contract::codegen_manifest::MethodSig (compiled later in the
  consumer repo, after the MethodSig EXTEND-CORE). Deterministic, ASCII, escaped
  (a comma-bearing templated param is one quoted element, never split). Every
  file carries the PARITY: UNRUN (operator-blocked: leptonica) marker, and the
  hand-curated Frankenstein deny-list annotates intrusive leaves
  (id_to_unichar_ext, CleanupString) with // HAND-PORT.

The gate has teeth (not a self-golden, per the honest-tester): the round-trip
decompile(project(g)) must equal expand(g) restricted to the signature plane —
a dropped method or mangled param fails it (round_trip_detects_a_dropped_method
proves it can fail). This is the codegen_spine::roundtrip_eq pattern over the
live harvested triples, implemented over ruff_spo_triplet::Triple to keep the
crate lance-graph-free.

CPP-CODEGEN-RT (gated end-to-end in ruff_cpp_spo, dev-dep on the emitter): real
ccutil harvest -> 67 classes, 857 methods -> 124 KB MethodSig manifest; the
signature-plane round-trip holds and the render carries the PARITY marker with
one MethodSig literal per method. classid minting stays OGAR-side (name-keyed
text). 10 ruff_cpp_codegen + 16 ruff_cpp_spo tests green; clippy + fmt clean.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
Strengthen render_emits_every_method to assert all six MethodSig fields
(name/params/ret/is_const/is_static/overrides) appear in the output. This pins
the render-side boundary to lance_graph_contract::codegen_manifest::MethodSig so
field drift is caught in-env rather than only at the operator-gated tesseract-rs
compile (baton-auditor boundary guard).

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
… specifier

The C++ harvest dropped member access (public/protected/private) everywhere.
has_visibility emits it: a new closed-vocab predicate (54 -> 55), CppMethod gains
an `access: CppAccess` field (CppAccess now defaults to Public), the libclang
walker reads m.get_accessibility() (reusing the base-specifier mapping), expand
emits (class.method, has_visibility, "public"|"protected"|"private"), and
reassemble recovers it so CPP-REASSEMBLE-RT stays 67/67 byte-exact.

It is the OO API-surface fact and the nearest thing to an intrusiveness signal
for the codegen's hand-port deny-list (public = adapter surface; private =
likely internal). Scoped to methods + the harvest layer (not threaded into the
codegen MethodSig manifest, which round-trips the signature plane only — so
CPP-CODEGEN-RT is unaffected). Authorized via the "keep building here" pick.

59 ruff_spo_triplet + 16 ruff_cpp_spo + 10 ruff_cpp_codegen green; clippy
-D warnings + fmt clean.

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…dation key

The `validates_constraint` triple today carries only the
attribute name (the existence-of-validation fact), so the
downstream Schema consumer can't distinguish `validates :email,
presence: true` from `validates :email, uniqueness: true` —
every validation lowers to the same generic `ASSERT $value != NONE`.
But Rails' validation kinds map to very different SurrealQL
constraints:

| Rails kind     | SurrealQL lowering                |
|----------------|-----------------------------------|
| presence       | ASSERT $value != NONE             |
| uniqueness     | DEFINE INDEX … UNIQUE             |
| length         | ASSERT string::len($value) <= MAX |
| format         | ASSERT $value REGEX "…"           |
| numericality   | ASSERT type::is::number($value)   |
| inclusion      | ASSERT $value IN [...]            |
| exclusion      | ASSERT $value NOT IN [...]        |

Add `Predicate::ValidationKind` (50 → 56 — net +1 since main has
been advancing). In `Expander::validation`, scan
`Validation.options` for the canonical 9-key Rails set and emit
a sibling triple per recognised key:

  (openproject:User.email, validation_kind, "presence")
  (openproject:User.email, validation_kind, "uniqueness")

Subject is the attribute IRI (mirrors `field_type`'s shape),
NOT the model — multiple kinds per attribute each emit their
own triple. The existence-of-validation
`validates_constraint` triple still fires unchanged for every
declaration (backward compat).

Non-kind option keys (`if:`, `on:`, `allow_nil:`, `message:`)
pass through silently — they're filter-side conditions that
don't change the schema semantics. Block-form `validate { … }`
(no kind options on IR) emits zero `validation_kind` triples.

Tests: 2 new regressions —
`ar_shape_emits_validation_kind_per_recognised_option` locks
multi-kind + block-form + unknown-option behaviour;
`extract_validation_kinds_recognises_canonical_rails_set` is
the unit-level lock on the helper.
`ar_shape_emits_every_ar_predicate` exhaustiveness check
extended with `"validation_kind"`. 60 tests pass.

Downstream consumer (openproject-nexgen-rs) follow-up PR will
lift each recognised kind to a richer SurrealQL clause.
…ating options (codex P2 on #21)

Two codex P2 fixes to the validation_kind emitter from #21:

1. **Missing Rails validators** — the recognised set omitted
   `absence` (inverse of presence — `validates :archived_at,
   absence: true`) and `comparison` (`validates :end_date,
   comparison: { greater_than: :start_date }`). Add both keys.
   The downstream consumer (nexgen-rs #42) already has arms for
   both — `absence` → `$value == NONE`, `comparison` →
   presence-style fallback (parametric).

2. **Gating suppression** — Rails validators with `if:`,
   `unless:`, `on:`, `allow_nil:`, or `allow_blank:` are
   conditional. Emitting a `validation_kind` triple for
   `validates :age, numericality: true, allow_nil: true` makes
   the downstream consumer generate an unconditional
   `ASSERT type::is_number($value)` that rejects NONE — Rails
   would have allowed it. Safer to suppress kind emission and
   let the consumer fall back to the catch-all presence
   behaviour from the `validates_constraint` triple.

3. **Falsy validator values** — `validates :foo, presence: false`
   is a no-op in Rails. Skip such keys so the schema doesn't
   invent constraints where none exist.

Tests: existing `extract_validation_kinds_recognises_canonical_rails_set`
updated to use non-gating keys; 3 new regressions —
`extract_validation_kinds_suppresses_on_gating_options` (each
gating key suppresses individually),
`extract_validation_kinds_skips_falsy_validator_values`
(`false` / `nil` values skipped),
`extract_validation_kinds_recognises_absence_and_comparison`
(new keys lift correctly). 63 tests pass.
…oject corpus dump

The downstream `op-surreal-ast::triples_to_schema` consumer in
[openproject-nexgen-rs](https://github.com/AdaWorldAPI/openproject-nexgen-rs)
has been growing fast (FK direction gating, polymorphic
fallback, field_type, class_name override, inherits_from, +15
class-level annotation lifts, validation_kind composition,
UNIQUE indices) — but the corpus dump it tests against was a
month stale (pre-ruff#15) so the consumer-side tests against
real OP data weren't exercising any of the new predicates.

Add a small example binary `harvest_op` mirroring the existing
`ruff_cpp_spo::harvest_unicharset` shape:

  OP_SRC=/path/to/openproject \
      cargo run -p ruff_ruby_spo --example harvest_op
  # writes /tmp/op_triples.ndjson

Calls `ruff_ruby_spo::extract(<source>)` then
`ruff_spo_triplet::expand(&graph)` and serialises via
`to_ndjson`. The OP corpus stays UPSTREAM and is never vendored
(iron rule of the harvester family).

Verified on the live OP source tree:

  models found: 694
  triples emitted: 9183
  bytes: 1.14MB

Predicate frequencies on the fresh dump (showing the new
predicates that the previous dump lacked entirely):

  inherits_from:    485  (STI parents, separated from includes_module)
  validation_kind:  178  (presence/uniqueness/numericality/...)
  class_name:       122  (FK target overrides)

Downstream verification: re-running
nexgen-rs's `op_schema_explore` against the fresh dump produces
the expected schema-quality jumps:

  - 485 `inherits:` annotations (vs 0 on stale dump)
  - 6 UNIQUE indices
  - 2 `type::is_number($value)` ASSERTs (numericality kind)
  - 123 record-kind FKs (up from 92 — class_name overrides
    resolve more targets)
…t (codex P2 on #23)

The workspace's clippy config (`clippy::exit = "warn"` →
promoted via `cargo clippy --workspace --all-targets --all-features
-- -D warnings`) flags `process::exit` calls in examples too,
so the new `harvest_op` made the documented clippy invocation
fail.

Convert main to `fn main() -> Result<(), Box<dyn std::error::Error>>`
(matching the `harvest_unicharset` shape in `ruff_cpp_spo`).
Both error paths now return descriptive `Err(...)` strings:

- Missing OP source path → returns an error that surfaces via
  the example process's non-zero exit code (set automatically
  by Rust's main-returns-Result protocol).
- Failed `fs::write` → wrapped in `map_err` to add the output
  path to the message.

Also wrapped a few previously-bare identifiers in doc comments
in backticks so `clippy::doc-markdown` stays quiet:
`OpenProject` and `AdaWorldAPI`.

Verified: `cargo clippy -p ruff_ruby_spo --example harvest_op
-- -D warnings` is clean; the binary still extracts 9183
triples from /home/user/openproject as before.
…lidators (length / numericality / inclusion / format)

Parametric Rails validators carry their constraint values on
inner hash options:

  validates :name, length: { maximum: 255 }
  validates :age, numericality: { greater_than: 0, less_than: 150 }
  validates :status, inclusion: { in: %w[open closed] }
  validates :email, format: { with: /.+@.+/ }

PR #21 emitted `validation_kind` for the OUTER key (`length`,
`numericality`, etc.) but dropped the inner-hash values, so the
downstream schema consumer collapsed parametric kinds to the
catch-all `$value != NONE` ASSERT — `length: { maximum: 255 }`
got the same constraint as `presence: true`. On the live OP
corpus that's 53 lifts wasted (29 length + 16 inclusion + 7
format + 1 confirmation).

This PR adds `Predicate::ValidationParam` (count 56 → 57) and
emits one triple per inner-hash key:

  (User.name, validation_param, "length:maximum=255")
  (User.age, validation_param, "numericality:greater_than=0")
  (User.age, validation_param, "numericality:less_than=150")

Object format: `<kind>:<inner_key>=<inner_value>` — mirrors the
existing `validation_kind` shape so consumers can pattern-match.

`extract_hash_options(s)` parses the verbatim Ruby hash-literal
output from `walk::format_hash_inline`. Depth-tracks
`{}` / `[]` / `()` to handle nested values like
`inclusion: { in: [1,2,3] }`. Best-effort on malformed input
(never panics). Non-hash option values (`presence: true`,
`uniqueness: nil`) emit zero `validation_param` triples.

The downstream nexgen-rs consumer follow-up will compose
richer ASSERT clauses (`length:maximum=255` →
`string::len($value) <= 255`).

Tests: 2 new regressions —
`extract_hash_options_parses_inline_hash_literals` (single
pair, multi-pair, nested, empty, non-hash, depth-tracking),
`ar_shape_emits_validation_param_per_inner_hash_key`
(parametric kind emits per-key params; non-hash kind emits
zero; `validation_kind` still fires for both — backward
compat). `predicate_count_locked_at_57` (renamed from `_56`).
`ar_shape_emits_every_ar_predicate` extended with
`"validation_param"` via the fixture's new
`length: { maximum: 255 }` validation. 65 tests pass.
The producer was emitting one Model per RubyClass entry from parse_models,
but Ruby's class-reopen idiom can have N declaration sites for the same
class across files (e.g. OpenProject's app/models/work_package.rb defines
the rich WorkPackage; the sub-files under app/models/work_package/ — like
inexistent_work_package.rb, pdf_export/work_package_to_pdf.rb,
progress_hint.rb — reopen `class WorkPackage` to add nested classes,
producing empty reopener entries with 0 declarations).

Two Models with the same name then collided downstream:
- naive .find(|c| c.name == "WorkPackage") landed on the empty reopener
  (graph index 680) instead of the rich one (index 682)
- consumers saw WorkPackage with 0 associations / 0 mixins on the live
  OP corpus — body was there, just not in the slot anyone reached

Fix: merge same-named entries at extract time. Refactor the extract loop
into build_models, which deduplicates by name using a HashMap and merges
duplicates in-place: every Vec field concatenates in encounter order;
sti is first-non-None-wins so empty reopens cannot strip inheritance.
First-occurrence keeps its slot, so existing positional consumers stay
stable.

Verified end-to-end against /home/user/openproject:
  before: 2x WorkPackage (one with 0/0, one with 18/19)
  after:  1x WorkPackage (18 associations, 19 concerns)

3 new unit tests pinning the merge contract:
- build_models_merges_same_named_reopens_into_one
- build_models_preserves_first_occurrence_slot_and_source_order
- build_models_sti_first_non_none_wins
ruff_ruby_spo 25/25 green; clippy clean.
…e API

The hardcoded NAMESPACE = 'openproject' tagged every harvest's IRI prefix
as 'openproject:' regardless of source. A Redmine harvest, an OSB
(Open-Source-Billing) harvest, or a Spree harvest all came out prefixed
'openproject:', forcing downstream consumers to either rename in a second
pass or be wrong about the curator.

Add extract_with(source_tree, namespace) — same parser, caller supplies
the namespace string the resulting ModelGraph is tagged with. The
existing extract(source_tree) is preserved as a thin wrapper that
defaults to NAMESPACE for back-compat: every existing caller
(ogar-from-rails::extract is the only one in-tree) keeps working
unchanged.

Two new tests:
- extract_defaults_to_openproject_namespace pins the back-compat default.
- extract_with_tags_caller_supplied_namespace pins the new API for
  'redmine', 'osb', etc.

Unblocks the parallel session's OSB harvest from being mis-tagged
'openproject:' and lets ogar-from-rails distinguish Redmine vs
OpenProject curators downstream.

ruff_ruby_spo 27 green (+2); clippy clean.
extract/extract_with walk only <root>/app/models, so a modular Rails app
hides much of its domain. OpenProject keeps ~half its models in
modules/*/app/models (e.g. TimeEntry in modules/costs) — invisible to the
core-only walk.

Add extract_app / extract_app_with: collect the core app/models PLUS every
mounted engine's app/models (modules/*, engines/*) and feed all files into
one build_models pass, so a class reopened ACROSS roots still merges into a
single Model (the cross-root reopen-merge).

- parse::parse_models_with_engines + collect_model_roots (bounded by
  convention to modules/* and engines/*, so vendored gems / spec fixtures
  are never mistaken for model roots).
- extract / extract_with stay core-only (backward compatible); engines are
  opt-in via the new entry points.

Tests:
- extract_app_walks_engines_and_merges_across_roots: synthetic core+engine
  tree; core-only extract misses the engine model, extract_app harvests it,
  and a class reopened in both roots merges to one Model (assocs unioned).
- extract_app_harvests_openproject_engines (env-gated, OPENPROJECT_PATH):
  on the real corpus, core=681 -> core+engines=922, and TimeEntry
  (modules/costs/app/models) is harvested.

29 unit tests pass.
The C# sibling of ruff_cpp_spo / ruff_ruby_spo / the Python-Odoo frontend.
Roslyn (Microsoft.CodeAnalysis) is the professional C# AST+semantic tool, the
analog of libclang (C++) and ruff_python (Python). Because Roslyn is .NET-only
the parse step is an out-of-process tool under harvester/ (vs ruff_cpp_spo's
in-Rust libclang walk); the seam is the ndjson Triple contract.

- harvester/ (.NET): CSharpSyntaxTree walk emitting Triple ndjson with the
  closed Predicate vocab (rdf:type / inherits_from = is_a; has_field /
  has_function = part_of; field_type; is_static).
- src/lib.rs (Rust): load() over from_ndjson + unknown_predicates() validating
  every predicate against the closed vocab so harvester drift is a hard error.
- README: the per-language tool table, the predicate mapping, and the
  SemanticModel upgrade path (MSBuildWorkspace + EF/Mongo attribute binding).

NOTE: the Rust crate is written but NOT build-verified in this environment —
the sandbox proxy blocks ruff's astral-sh/lsp-types git dep (403/offline), so
cargo can't resolve the workspace. CI / a networked build is the verification
gate. The .NET tool needs a dotnet 8 SDK (absent here) to build.

Generated by [Claude Code](https://claude.com/claude-code)
…he duplication-vs-conflation law

The deterministic (part_of:is_a) rank-minter (`mint` / `mint_with_classid`) plus
the `medcare_probe` example that runs a REAL Roslyn harvest of the MedCare C#
corpus through it and measures the **256-cap-is-a-lint law**: every part_of:is_a
sibling set over 255 is either **duplication** (what you get by NOT resolving
through a ClassView) or a **separation-of-concerns** failure (data `has_field` ⊥
behaviour `has_function`) — never a reason to widen the field. That is the
Redmine-renderer-converging-into-askama-after-deduplication direction.

Probe sections [A]-[G]: scale, injectivity, truncation, prefix-routability,
documentSymbol, is_a redundancy, and [G] the 256-cap classification — 0
counterexamples on the real corpus.

Deliberately EXCLUDES the radix-tried "solution" (the `RadixCodebook`, the
base-255 `mint_factored` cascade, and the ontological-radix-trie doc): the right
resolution is ClassView dedup, not a trie or a cascade.

Test: `cargo test -p ruff_spo_address` -> 6 passed. Verified on a branch with the
`lsp-types` git dep vendored (this sandbox's proxy blocks the unrelated
astral-sh/lsp-types git dep, so a clean branch can't resolve the full workspace
here; it resolves in CI). The crate is a leaf (deps: ruff_spo_triplet; dev-dep:
ruff_csharp_spo for the harvest probe).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
claude and others added 21 commits October 14, 2026 21:00
Both bots reviewed 1abdbe5, before the six-set gate landed, so some findings
were already fixed and some were not. Each was verified at source before
changing anything; the gate itself found none of these because its fixture
lacked the shapes.

1. `EventKind::Param` had NO EMITTER ANYWHERE (Codex P2).
   The variant was public, documented "a method parameter, in declaration
   order", and the census measured exactly ZERO across 5502 real methods —
   an alphabet variant that was pure fiction. Cause: `defs_of` kept only
   `(name, body)` and discarded the argument list. It now keeps the whole
   `Def` node and `Walk::params` emits every parameter kind (required /
   optional / rest / keyword / keyword_optional / keyword_rest / block) in
   declaration order, with defaults walked as ordinary expressions.
   Census: 0 -> 3708 Param events.

2. Body-less defs were dropped (CodeRabbit + Codex).
   `def noop; end` produced no `MethodOre` while the shipped extractor makes
   a `Function` for it, so the two arms covered different method sets — the
   one thing the side-by-side must never do.

3. The `For` arm double-counted and mis-filed its iteratee (both bots).
   It tagged the relation `iteratee` (so the fold filed it under `reads`)
   and then walked the iteratee again, sending `self.items` through
   `self_send` for a second untagged Read. The shipped arm files it under
   `traverses` and never walks it. Now tagged `traversal`, not walked. This
   is the same defect class as the `general_send` double count fixed in
   beb9c01 — a second instance the earlier fix did not reach.

4. Raise arguments inflated `reads` (Codex P2).
   `raise Error, self.message` really does read `message`, but the shipped
   arm records the exception and returns. The observation is kept — that is
   the point of this arm — inside a new `ScopeKind::RaiseArgs`, and the fold
   skips that subtree. A scope, not a `control` tag, because raise arguments
   nest and a flat tag marks only the outermost event.

5. The non-mutator self-send fallback inflated `calls` (Codex P1).
   `self.valid?` emitted an untagged `Call` where the shipped arm records
   nothing at all. Now tagged `self_send` and excluded from the fold. This
   NARROWS the one named divergence: `self.foo(arg)` now disagrees only in
   `reads`, not in `calls` as well, and the test asserts the other five sets
   agree so the disagreement cannot silently widen.

6. The census denominator omitted `set_guarded` (Codex P2).
   It summed five of the six shipped sets, so the preservation ratio was
   overstated on any corpus with a blank-guarded default — while `MethodOre`
   carried the sixth count all along.

Also: name-keyed shipped-row lookup became an occurrence-ordered queue, so a
class defining the same method name twice no longer hands every `MethodOre`
the last definition's counts (Codex P2); and `ore_census` now REQUIRES
`RAILS_SRC` instead of defaulting to a machine-specific absolute path, which
produced a silent all-zero census — measured once in this session and briefly
mistaken for a real result (CodeRabbit).

MEASURED, same 1708 OpenProject files:
  events           40034 -> 44170   (Param 0 -> 3708; RaiseArgs scopes added)
  scopes           11987 -> 12172
  symbols           3526 -> 4352
  shipped entries     850 -> 857    (set_guarded contributes 7 corpus-wide)
  preservation      5.25x -> 5.21x

The headline has now moved twice, both times DOWN and both times because a
number was wrong: 5.40 -> 5.25 (traversal receiver counted twice) -> 5.21
(denominator missing a set). Worth noting `set_guarded` is only 7 entries
across the entire corpus, so the fold's guarded path is barely exercised by
real data — which is exactly why the gate fixture constructs it synthetically.

The gate fixture now carries every shape above: a relation `for` loop, a raise
with a field-reading argument, a non-mutator self-send, and parameters — each
with an anti-vacuity assertion that the shape is actually present, so none of
these can silently re-open.

NOT changed, with reasons:
- Codex P1 "correlate bodies instead of transcribing them": answered on the
  PR. The recipe codebook needs ordered body facts to correlate FROM; this
  arm is that input, not a competing consumer contract.
- Codex P2 "nested declarations share the outer anchor": real, but fixing it
  needs `walk_class_body` to return per-declaration locations — a change to a
  shipped API that deserves its own PR rather than a drive-by.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JRoXmYPrE8KubXVrDB71L
…ivers

The suppression added in beb9c01 was over-broad, and CodeRabbit caught it.

The shipped walker walks EVERY receiver of a traversed send. The two forms
then diverge, because the receiver node is different:

  lines.each       recv is `lines`      -> Send{recv: None}  -> shipped's
                                          general Send arm records NOTHING
  self.lines.each  recv is `self.lines` -> Send{recv: Self_} -> shipped's
                                          self-send arm records reads["lines"]

So suppressing the ore's own receiver re-walk is correct for the bare form
(it was emitting a second untagged Read of a name the set arm files only
under `traverses`) and WRONG for the explicit-self form, where it deleted a
read the shipped arm genuinely has. Now conditioned on the receiver actually
being a bare send.

Every traversal fixture in the crate used the bare form, so nothing caught
it — the third time in this arc that a fix was right about the case in the
fixture and wrong about the case that was not. The new test carries both
forms side by side and asserts they are DIFFERENT shipped facts
(bare: reads 0 / traverses 1; explicit: reads 1 / traverses 1), so neither
can be silently collapsed into the other again.

Disable-verified: restoring the blanket `recv_consumed = true` fails the new
test with reads folding 0 where the shipped arm has 1.

Also adds a Delegation section to AGENTS.md: grindwork (call-site sweeps,
existence checks, scoped command runs, corpus censuses) goes to Sonnet
subagents; judgment (is this finding right, what does this measurement mean,
is this divergence defensible) stays on the main thread. Operator-directed,
and general across repositories rather than specific to this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JRoXmYPrE8KubXVrDB71L
Four, each from a measured incident in this session rather than from general
advice:

1. Commit before disabling. The disable cycle restores with `git checkout
   <file>`, which reverts to the last COMMIT — so uncommitted work under test
   is deleted by its own verification step. A fix and its new test were both
   lost this way; the loss surfaced only when a later grep showed the old code
   back in place.

2. A disable that does not apply looks exactly like a guard that is not
   load-bearing. Twice a patch script anchored on text `cargo fmt` had
   reformatted, no-opped silently, and the green run nearly entered the record
   as evidence of inertness. Assert the replacement count.

3. `git status` says clean, not current. A stale branch manufactures findings
   that do not exist on main — with the fetch/merge-base commands to check,
   and the rule that a measurement must be re-taken after a rebase rather than
   carried across it.

4. A green fixture proves only the shape it contains. Three fixes in
   ruff_ruby_spo were right about the case in the fixture and wrong about the
   case that was not; when a fix distinguishes two shapes, the test carries
   both and asserts they differ.

Operator-directed, and general rather than crate-specific.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JRoXmYPrE8KubXVrDB71L
The delegation table added in 07d5c34 used a compact pipe table; mdformat
pads every cell to the widest in its column, so the prek CI job failed with
"Files were modified by following hooks". Replaced with bold-lead prose,
which matches the surrounding style and mdformat leaves alone.

Recorded as a trap in the same section: prek is unrunnable in this sandbox
(uv cannot parse the repo's exclude-newer), so a Markdown edit here ships
unchecked, and a green prek on the previous commit is not evidence about
this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JRoXmYPrE8KubXVrDB71L
CodeRabbit flagged docstring coverage at 79.69%. Documented the five
helpers where the invariant is not evident from the signature: an empty
scope stack means the method scope rather than an error; parent() is what
lets the fold attribute a Write to the branch guarding it; attr() consults
the class's own declarations, so the same identifier is a relation in one
class and an attribute in another, which is why it cannot be free.

Left the two Display impls and the two test helpers undocumented — a doc
comment there would narrate the signature, which AGENTS.md rules out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JRoXmYPrE8KubXVrDB71L
…d to end

The C-library arm could name a function but not its signature: CppFunction
carried namespace/name/calls only, while the class plane's CppMethod
already carried return_type/param_types/is_static. So a downstream
MethodSig manifest built from a C corpus would have held an empty
parameter list and ret: None for every entry — a signature plane with no
signatures.

This captures the three missing facts in collect_functions, using the
SAME idiom the class walker already uses (void filtered out, types
verbatim from the cursor) so the two planes agree and one manifest can
read both. No new predicate is needed: returns_type / has_param_type /
is_static already exist in the closed vocabulary as the AST-DLL signature
shape, so the ontology gate is untouched.

File-scope `static` is read from the storage class, not
is_static_method(): in C it means internal linkage, and
is_static_method() is false for every free function, so reading the
member flag would have made the field constant-false. The paired test
asserts both arms so the flag has to carry information.

Also adds examples/harvest_openggs.rs — the arm wired from libclang to
rendered Rust on a real C corpus (OpenGGS, 55 TUs of plain C: 25
file-scope structs, 181 free functions, no classes, so the class plane
harvests nothing from it). A translation unit is the Model, because that
is what the C programmer used for grouping and for internal linkage.

Measured: 55 TUs, 0 parse failures, 181 functions, 763 triples, 527 on
the signature plane.

The round-trip oracle gates the run: decompile(project(g)) must equal
expand(g) restricted to the signature plane, computed by two different
code paths, and a mismatch writes nothing. It earned its place on the
first run by catching a real bug — the graph was built with
ModelGraph::default(), leaving the namespace empty, so every manifest IRI
was keyed differently from the expanded triples (527 lost, 527
invented). A self-golden check would have passed.

What this generates is signature manifests, not bodies. That is the
85/15 split: the mechanical structure that classifies and orders a
transcode is minted; the function bodies remain the essential-15%
hand-port.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
CodeRabbit finding, verified real: walk_free_functions parsed a TU and
returned Ok without inspecting tu.get_diagnostics(), so an unresolved
#include produced an incomplete function model with no Err and no marker.
libclang recovers from a missing header by DROPPING the declarations that
needed it; the functions are simply absent.

The sharp half of the finding is what it says about the oracle. The
round-trip check compares decompile(project(g)) against expand(g) — both
derived from the SAME graph — so when a parse silently drops functions,
both sides are equally short and the oracle passes. It proves the
projection faithful; it never proved the harvest complete, and the PR
description let "computed by two different code paths" imply more than
that.

The class plane already had this guard and its doc already described this
exact failure mode, with the worked Tesseract STATS/scrollview.h case and
the warning that "0 failed" does not mean the sweep is complete. It sits
thirty lines above the function used here. The mechanism existed; it was
not reached for.

- walk_free_functions_with_diagnostics mirrors walk_tu_with_diagnostics:
  one parse, functions plus every diagnostic at Severity::Error or above.
  walk_free_functions becomes the thin wrapper, matching how walk_tu
  wraps its own diagnostics-aware form.
- harvest_openggs skips any TU with an error diagnostic and reports the
  count separately from parse failures, rather than minting a manifest
  from a partial parse.

Measured on the corpus before and after: 55 TUs, 0 failed to parse, 0
skipped on error diagnostics, 181 functions, 763 triples, 527 on the
signature plane. So the defect was latent here — the numbers already
published are unaffected — and this closes the hole rather than fixing a
wrong result.

Two tests, because a guard that has never been shown to fire carries no
information and one that fires on everything carries none either: an
unresolvable include must surface a "file not found" diagnostic while the
parse still returns Ok, and a clean TU must report none while still
harvesting its functions.

Also documents main's two failure postures, which the docstring-coverage
check wanted: a missing corpus is a skip, a failed oracle is a hard error
that writes nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
Second codex finding, verified real. The example mapped
CppFunction::is_static (C file-scope linkage) onto CppMethod::is_static,
but that predicate means "a class-level member with no implicit this".
A free function is not a class member at all, so neither value is a true
claim: true asserts class membership, false asserts an implicit receiver
these functions do not have. The result was that exported C functions
rendered as though instance-bound while only TU-private ones rendered
static, and a consumer had no way to recover linkage without
reinterpreting an established predicate.

The justification written into the doc comment does not survive contact
with the finding, and is corrected rather than quietly dropped: if
is_static really meant "no implicit receiver", EVERY free function would
carry it, not only the translation-unit-private ones.

The expander emits is_static only when the field is true, so leaving it
at the default makes NO claim — which is the honest encoding for a
function the predicate does not describe. Linkage stays on CppFunction
for a consumer reading the harvest directly; representing it in the
shared IR needs its own predicate, and that is a deliberate ontology
change, not something to obtain by overloading an existing one.

Measured: the rendered manifest is BYTE-IDENTICAL before and after, and
the corpus explains why — OpenGGS contains zero file-scope static
functions, so the flag was constant-false across all 181 and the overload
never fired here. Latent, like the diagnostics gap: this is a correctness
fix for the next C corpus, where leptonica and zlib are full of them, not
a repair of a wrong result. Harvest unchanged at 55 TUs, 0 failed, 0
skipped, 181 functions, 763 triples, 527 on the signature plane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
corpus's parity blocker

Two CodeRabbit findings on 5fb4785, both verified.

Overload loss (Major). The per-model dedup keyed on f.name alone. This
corpus is compiled as C++, so f(int) and f(double) are two distinct
functions a name-only key collapses into one, dropping the second before
it reaches the graph. The oracle is blind to it for the same reason it
was blind to the diagnostics gap: it compares a projection of the graph
against an expansion of the SAME graph, and both are equally short. The
key is now (name, ordered parameter types), matching the identity the
method IRI already uses.

Measured: 181 functions before, 181 after, and a scan of the rendered
manifest finds zero duplicate names within any translation unit. So
OpenGGS has no overloaded free functions and the key never collided
here — latent, like the previous two, and a correctness fix for the next
C++-compiled corpus rather than a repair of a wrong count.

Parity marker (Minor; codex, CodeRabbit and the PR description all
flagged it). render hardcoded "PARITY: UNRUN (operator-blocked:
leptonica)", so every OpenGGS artifact asserted an operator block from an
unrelated corpus. An artifact stating something false about its own
verification is worse than one that says nothing, because a reader who
trusts the marker is misled about what was checked.

Fixed additively so no existing caller changes behaviour:
render_with_parity(manifests, parity) takes the status as a caller fact —
which corpus is blocked, and on what, is known where the harvest runs,
not in the renderer — and render delegates with the Tesseract arm's
existing leptonica string. harvest_openggs now states its own: "UNRUN (no
execution oracle: signatures only, bodies are hand-port)", which is the
true status of this arm.

Harvest unchanged: 55 TUs, 0 failed, 0 skipped, 181 functions, 763
triples, 527 on the signature plane, oracle green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
…ng an

unchecked round-trip conformance

Two findings from the review on b13168b, both verified in-tree before fixing.

The calls overload is the unguarded one. The is_static comment closes on the
right principle -- a fact needing its own predicate must not be smuggled in by
overloading an existing one -- and says nothing about `calls`, which is the
other deliberately-absent field and the only one where that overload is a
one-token edit.

Verified, all four halves of the blindness claim:

- CppFunction::calls is "EVERY CallExpr callee" as a bare name
  (ruff_cpp_spo/src/lib.rs:148-151); CppMethod::calls is the closed
  ActiveRecord mutator set as "<receiver>.<method>" and means "this method
  calls a writer" (ruff_spo_triplet/src/ir.rs:321-328). Same name, same
  Vec<String>, and the construction site uses ..CppMethod::default().
- The oracle cannot see it: `calls` is absent from is_signature_plane
  (ruff_cpp_codegen/src/manifest.rs:178-189).
- A truth-leg check could not either: both Predicate::Calls emission sites
  stamp Provenance::Inferred (expand.rs:305 and :941), so f/c are identical
  whichever field feeds them.

So the edit would mint every-callee facts as lifecycle-mutator claims with a
green build, green clippy and a green oracle. The only thing standing in the
way was a doc comment on the other struct, and AGENTS.md says a doc-comment
claim is not a behaviour. It is now an assert on the built graph.

One correction to the review's framing, checked rather than accepted: the
Predicate::Calls site at expand.rs:305 is the AR-shape Function arm, not the
CppFunction arm -- its reads are model-qualified and it carries traverses and
guarded_writes. So the conclusion stands (the free-function call graph reaches
no triple) but for the stated reason, not because no arm emits Calls at all.

The round-trip conformance was an unchecked cross-repo claim. Three doc
comments named codegen_spine::roundtrip_eq as the pattern this crate
implements. That symbol appears nowhere in this repo outside those comments;
this crate's only dependency is ruff_spo_triplet and its own description says
it never compiles against lance-graph, so the function cannot be called and
nothing fails if the two drift. One divergence is already real: roundtrip_eq's
truth leg is documented as always run, while the comparison here keys on
(s, p, o) and drops f/c -- sound on this plane only because expand and
decompile assign identical provenance to all seven signature predicates, which
is a property of the current expander rather than a guarantee.

The three sites now say "mirrors" rather than "is", and the module doc records
that the correspondence is asserted, not checked, and partial. Same class of
fix as the PARITY marker in the previous commit: an artifact should not state
something false about its own verification.

Out of scope and deliberately not done: the missing dispatches_to predicate for
the free-function call graph. That is an ontology change, which by this arm's
own principle is a separate deliberate act.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
Both self-inflicted, and the first is a small lesson worth recording.

The assert message was written as a backslash-continued string literal. cargo
fmt collapsed it onto one line and kept the continuation's leading whitespace
as literal spaces, so the committed message read "mapped onto          "
with ten embedded spaces across 116 columns. The point of that message is to
tell a developer which of two same-named fields they just conflated, and a
mangled message is a poor way to say it. It is now one short single-line
string.

The second is a sentence added to the lib.rs gate doc that ran onto the
existing following line, making it 114 columns. Split into its own paragraph.

The 202-column line remaining in harvest_openggs.rs is pre-existing (it was
line 180 before this branch's previous commit) and is left alone rather than
widening this change.

Re-verified after the edits: ruff_cpp_spo 74 + 6 passing, ruff_cpp_codegen 10
passing, clippy clean on both with --all-targets, cargo fmt --check clean, and
the harvest unchanged at 55 TUs / 0 failed / 0 skipped / 181 functions / 763
triples / 527 on the signature plane with the oracle green.

The guard was disable-verified before this fix: adding calls: f.calls.clone()
to the CppMethod literal -- the exact one-token overload it exists to catch --
panics the harvest, and removing it returns the run to green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
CodeRabbit finding on f9bd0be, verified before fixing.

CppFunction::name is bare and the enclosing scope is captured separately in
CppFunction::namespace, which the walker does populate (clang_walker.rs:324 via
enclosing_scopes, and per its own doc that covers an owning class scope too, so
Widget::helper yields ["Widget"]). The dedup key used only (name, param_types),
so a::f(int) and b::f(int) collapsed to one entry and the second was dropped
before it reached the graph.

The finding's second half is the sharper one and also checks out: the method IRI
is "{model_iri}.{name}({params})" with no scope component (expand.rs:784-792),
so even had both survived the dedup they would have collided there. Fixing the
key alone would have moved the silent loss rather than closed it.

This mirrors an identity the class plane already established rather than
inventing one: CppClass::qualified_name is used as the dedup key in walk_tu
(lib.rs:364) and as the Model name in model_from_class (lib.rs:435).
CppFunction::qualified_name is the free-function counterpart, with the same
shape and the same global-scope-stays-bare rule, and it now feeds both the
dedup key and the name that reaches the IR.

Putting the qualified name in CppMethod::name is deliberate and is not the
overload this arm keeps rejecting: a scope-qualified name is still a name, it is
how C++ writes the identity, and the alternative -- a namespace field on the
shared CppMethod -- is an ontology change. The renderer is unaffected because it
emits the name as an escaped string literal, never as a Rust identifier.

The regression test asserts both arms, since one alone passes for the wrong
reason: a global-scope function must keep its bare name (the C-library norm,
where qualifying would change every identity), and a namespaced pair must come
out distinct. It also carries the anti-vacuity arm -- the fixture is asserted to
still collide on the bare-name key being replaced, so the qualification is
load-bearing rather than cosmetic.

Measured: 75 + 6 passing (one new), clippy clean, fmt clean, and the harvest is
BYTE-IDENTICAL before and after on both artifacts -- the rendered manifest and
the NDJSON triples. OpenGGS is plain C with no namespaces, so all 181 functions
have an empty scope and the qualified name equals the bare one. Latent here,
like the diagnostics gap, the is_static overload and the overload dedup before
it; this is a correctness fix for the next namespaced corpus, not a repair of a
wrong count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d
Workspace-wide sweep to 1.98.1 (rust-toolchain, rust-version, Dockerfile
base images, CI MSRV pins). Docker tags rust:1.98.1, -bookworm and
-slim-bookworm verified to exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HnekoM1EidTwQLS3oFVFm
An Odoo extension view patches its parent with locators (an <xpath
expr position> element, or any element carrying `position`, e.g.
<field name="amount" position="after">). The harvester docked every
control in an extension view at the fallback token "root", and the
shorthand form was worse: the locator itself was emitted as a control,
and the controls it added were skipped as comodel fields because they
sat under a <field> element.

Now a two-pass join:
- views are collected per record with their inherit_id (joined on the
  local part of the xml id, the odoo_nav convention);
- each view is walked after its parent; it records anchors (fields,
  buttons, containers, named elements) with the region each sits in;
- a locator resolves its target against the parent chain's anchors:
  after/before/replace -> the target's region, inside a container ->
  that container, inside a field -> comodel subview (not docked),
  attributes -> no controls. A container opened inside the locator block
  still wins for its contents. A locator is never a control.
- only bare-tag or @name-filtered xpath steps resolve; [hasclass(..)],
  [@string=..], positional or * steps stay unresolved (-> "root") rather
  than matching the first element of that tag.

Also fixes stack balance: a self-closing non-container element
(<separator/>, <filter .../>) pushed a frame no close ever popped, so
the arch wrapper's </field> popped it instead of ending the arch.

Measured on Odoo 19 addons/ (old = origin/main, same harness):
  facts 17,154 -> 17,298; screens 3,171 -> 3,092;
  docked at root 3,513 (20.5%) -> 2,170 (12.5%), -38%.
Remaining root: non-@name xpaths and parents outside the scanned tree.

Tests: 7 new on an invented fixture (each position, both locator forms,
inside-a-field comodel, unresolvable predicate, missing parent,
extension of an extension, stack balance, xpath step parsing).
ruff_python_spo 91/91; clippy -D warnings clean on 1.98.1.
Disable runs: no join -> 3 red; self-closing frame push -> 1 red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NSfzujtWL1BnHgnJGAhg9N
…edicates

Two review findings on the inherit_id join:

- Parents were indexed by the local part of their xml id only, so an
  extension in module_b inheriting module_b.view_form could bind to the
  first-scanned module_a.view_form. Views now carry a module-qualified id
  (module = first path component under the scanned root; an unqualified
  ref is qualified with the declaring module, as Odoo does). Lookup is
  exact on the qualified id, falling back to the local part only when
  exactly one scanned view carries it; an ambiguous local id stays
  unresolved and docks at root.
- name_in_predicate took the first `@name` anywhere in the predicate, so
  `[@name='x'][2]`, `[not(@name='x')]`, `and`/`or` compounds resolved as
  `field x`. Only a predicate that is exactly `[@name=<quoted>]` resolves
  now.

Tests: new inherit_id_resolves_against_the_qualified_parent (two modules,
same local id, different containers; plus the ambiguous fallback), falsified
by disabling the qualified lookup (fails). Four new None assertions on
xpath_target. ruff_python_spo: 92 passed; clippy -D warnings clean.

Odoo 19 addons/: root-docked facts 2170 -> 1648 of 17297 (12.5% -> 9.5%).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NSfzujtWL1BnHgnJGAhg9N
Four further review findings, each matching how Odoo applies an
inheriting view:

- A missing `position` means `inside`: on any <xpath>, and on a named
  <field> at the top level of an extension's arch (inside an optional
  <data> wrapper). Such a top-level field is a spec, not a control.
- Specs apply in order: a locator resolves against the parent chain's
  anchors, then against what earlier specs in the SAME view added.
- A positioned shorthand spec is matched by Odoo on all its attributes;
  only `name` is tracked, so a shorthand carrying anything else
  (`<label for="x" position="after">`) is left unresolved instead of
  binding to the first element of that tag.
- `<field name="inherit_id">` is the parent reference only when it has a
  `ref`; without one (a form for ir.ui.view) it stays an ordinary control.

Test spec_defaults_order_and_identity covers all four; disabling each
fix separately makes it fail. ruff_python_spo: 93 passed; clippy -D
warnings clean. Module docs updated (also the qualified-id join).

Odoo 19 addons/: facts 17297 -> 17252 (top-level spec fields no longer
counted as controls), root-docked 1648 -> 1609 (9.3%).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NSfzujtWL1BnHgnJGAhg9N
Two parallel fork PRs added this example and the fork merge commit reconciled
them; replaying the commits linearly kept one side. This puts back the file as
it was on fork main (9407c74).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
Upstream replaced ExprCompare's `left` and `comparators` fields with a single
`operands` slice (the first operand is the old `left`). Walking every operand
in order visits the same expressions as before, and `operands.len()` equals
the old `1 + comparators.len()`, so the observation text is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
…-bans check

- ruff_python_spo: `x is None` detection now reads the comparison through
  `ExprCompare::as_single`, which upstream added when it replaced the `left`
  and `comparators` fields with `operands`. It still matches exactly one `is`
  operator with a `None` right-hand side.
- ruff_python_dto_check: `sha2` is requested with `default-features = false`.
  Upstream's scripts/check-cargo-bans.py rejects external dependencies that
  keep default features; only `Sha256` and `Digest` are used, which need none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
Operator ruling 2026-10-14 replaces the earlier "never Haiku" line: Haiku runs
pre-written scoped commands, lists candidates, counts and quotes; the main
thread verifies every Haiku result it relies on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
The replay kept upstream's lockfile at each step; this adds the entries the
fork crates need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
Seven fork crates pinned `rust-version = "1.98.1"`, the fork's old workspace
minimum. Upstream's workspace minimum is 1.97, and CI's `cargo build (msrv)`
job builds the whole workspace with that toolchain, so the pin made cargo
refuse to build them. They now take the value from `[workspace.package]`.
`ruff_r2il` keeps its own pin: it is excluded from the workspace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
The fork builds with the 1.99.0 toolchain (rust-toolchain.toml, taken from
upstream), so the minimum supported Rust version now matches it:
`workspace.package.rust-version` goes from upstream's 1.97 to 1.99, and the
fork crates inherit it. `ruff_r2il` sits outside the workspace and pins the
same value by hand. CI's `cargo build (msrv)` job reads this field, so it now
builds with 1.99.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
@AdaWorldAPI AdaWorldAPI changed the title Merge upstream astral-sh/ruff main into the fork Replay the fork's commits onto upstream main (83a3afc3) Oct 14, 2026
Upstream's `.cargo/deny.toml` now lists every dependency allowed to run a build
script, and `cargo deny check bans` rejects the rest. Two fork crates bring one
each: `clang-sys` (via `clang`, used by `ruff_cpp_spo`'s libclang walker) and
`lib-ruby-parser` (used by `ruff_ruby_spo`). Both are added to the allowlist;
scripts/check-cargo-bans.py confirms each entry matches a dependency that has
a build script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJA9obVWqSQYNtXGnMGLd
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 14, 2026 21:10
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T21:11:29.750324Z a274a09 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@AdaWorldAPI
AdaWorldAPI merged commit 49e8695 into main Oct 14, 2026
9 of 10 checks passed
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.

2 participants