fix: fetch_by_subject returns every source under the subject, reducing nothing - #287
Open
thedavidmeister wants to merge 4 commits into
Open
fix: fetch_by_subject returns every source under the subject, reducing nothing#287thedavidmeister wants to merge 4 commits into
thedavidmeister wants to merge 4 commits into
Conversation
fetch_by_subject took decoded_items[0] of metabytes[0], a positional pick out of indexer-ordered rows, with nothing tying the source to the subject. It now collects every DotrainSourceV1 under the subject and returns one only when they all agree: Ok(None) for none, Ok(Some) for agreement, Err(AmbiguousSubject) when they differ. The result is a function of the set of metas, not their order. Verification against the subject is deliberately absent: the dotrain subject derivation is unsettled between keccak(cbor item) and keccak(content) (#158, #219). Agreement is checkable without it, because both derivations make the subject a function of the content. MetasBySubject gains orderBy: id, orderDirection: asc so the 100-row window the subgraph returns is a function of subgraph state rather than indexer-defined. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 22 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
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. Comment |
Reduce nothing. Nothing requires the sources under one subject to agree: subject is "the entity the metadata is about" and both directions of that relation are 1:N, so plurality is ordinary valid data, and any rule that picks one of them decides for the caller which axis wins and destroys the rest. fetch_by_subject still scans every meta the subject carries and every item in each, but now returns all the DotrainSourceV1s it finds, in the order scanned. Result<Option<Self>> could not express that, so the signature is Result<Vec<Self>>; an empty vec is a subject carrying no dotrain source. Error::AmbiguousSubject and its Display arm go with the rule that raised them; error/mod.rs returns to its state on main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"every DotrainSourceV1 under the subject" is every one in the first page the subgraph returns: metaV1S defaults to first: 100 and this client does not paginate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #215. Not
Closes— see "What this does not do" below.The defect
DotrainSourceV1::fetch_by_subjectreturneddecoded_items[0]ofmetabytes[0]— a positional pick out of whatever rows the indexer happened tohand back, ignoring every other row and every other item. The issue names two
consequences:
MetasBySubjectcarried noorderBy, so which row is "first" isindexer-implementation-defined.
The change
fetch_by_subjectwalks every meta the subject carries and every item in each,and returns every
DotrainSourceV1it finds. It reduces nothing.Result<Option<Self>, Error>->Result<Vec<Self>, Error>.MetasBySubjectgainsorderBy: id, orderDirection: asc.Why it reduces nothing
Nothing says the sources under one subject must agree.
IMetaV1_2documentssubjectas the entity the metadata is about — context specific, MAY be anaddress, MAY be anything else. Every cardinality in that relation is 1:N: one
sender emits many metas about many subjects, and one subject carries many metas
from many senders. Plurality under a subject is ordinary valid data and needs no
bad actor to produce it.
Any reduction rule — first row, newest row, agreement-or-error, dedup — picks an
axis and decides for the caller which one matters, and destroys information the
caller may want. So this function reports what is there; the choice belongs to
whoever asked.
The first revision of this PR returned
Ok(Some)only when every source foundagreed and
Err(AmbiguousSubject)when they did not. That is a reduction rule,and it fails for the reason above: it turns ordinary valid data into an error.
It is gone, along with
Error::AmbiguousSubjectand itsDisplayarm —crates/cli/src/error/mod.rsis byte-identical tomainagain.The return shape
Result<Vec<Self>, Error>, in scan order: rows in the order the query pins,items within a blob in the order they were encoded.
Vec, notOption.Optioncannot hold more than one, which is what forcedthe choice in the first place.
[]is the oldOk(None)and[x]the oldOk(Some(x)), so no case a caller could previously see has disappeared.subject, and how many there are is the caller's to read.
caller can sort by whatever it wants, and can only do so if what it receives
is in a stated order rather than one this function invented.
HashSetor a map: keying by content dedups, keying by sender collapsesone sender's repeated emissions. Both are the reduction under another name.
payload is not UTF-8, is an
Err, not a silently shorter vec. The onlyOk(vec![])is "the subject carries no dotrain source".Two limits of the shape, stated rather than hidden:
caller cannot filter by emitter, which is exactly the kind of choice this PR
is trying to hand back. That is not a reduction chosen here: the client drops
it first,
MetaboardSubgraphClient::get_metabytes_by_subjectreturningVec<Vec<u8>>and nothing else. Surfacing it is a change to a second publicAPI and is not in scope.
metaV1Sdefaults tofirst: 100and this client does not paginate, so for asubject carrying more than 100 metas the vec is every source in the first
window, not every source. That gap is neither created nor closed here; the
orderBypin at least makes which window a function of subgraph staterather than of indexer implementation. The doc comment says so.
MetaV1.idis now board address ++ zero-padded counter (#227, fixed onmainand merged into this branch), so
orderBy: id ascgroups rows by metaboard andruns chronologically within each.
Callers
There are none to fix outside the tests: a GitHub code search for
fetch_by_subject org:rainlanguagereturns exactly this one file. Downstreamconsumers of the published crate see a signature change, with
[]and[x]standing in for the old
NoneandSome(x).What this does not do — why
Refs, notClosesViolated property 2 is verification of the source against the subject, and it is
not implemented. It cannot be settled from inside this PR:
DotrainSourceV1lookup be a hash of the content atall?
IMetaV1_2says a subject "MAY be the address of the emitting contract… OR anything else", and
LibDescribedByMeta.emitForDescribedAddressin thissame repo emits under an address subject. A content-hash check would reject
that shape outright.
derives the dotrain subject two different ways —
generate_dotrain_source_emit_tx_dataemits underkeccak256(cbor(item)),while
DotrainSourceV1::hash()and theDotrainSourceEmitData.subjectdocsay
keccak256(content)— and both are explicitly left unadjudicated there.So a subject carrying an attacker-emitted source still returns it, now
alongside every other source rather than instead of them. That half of #215
stays open, and the collection point this PR adds is where the filter goes once
the convention is ruled.
Relationship to #252 (issue #159)
#252 is open against the same function and also makes it scan every meta and
item, leaving "which meta wins" to this issue. The answer here is that none of
them wins and all are returned, so the two PRs conflict textually in this
function; the resolution is this file's version. Two deliberate differences:
#252 recurses into nested
RainMetaDocumentV1payloads and this does not (anddoes not remove that recursion if #252 lands first — #226 tracks the unbounded
recursion in the sibling extractor); and #252 keeps first-item-wins inside one
blob, which is the rule this PR removes.
Tests
Re-pinned:
test_fetch_by_subject_found/_not_found— same fixtures, asserting aone-element and an empty vec.
test_fetch_by_subject_wrong_magic->test_fetch_by_subject_only_other_meta_types_yields_nothing. This wasErr(InvalidMetaMagic)whenever the first item happened to be another metatype; the NatSpec already promised found/not-found.
test_fetch_by_subject_takes_first_decoded_item->test_fetch_by_subject_returns_every_item_in_one_meta: two different sourcesin one blob, both returned in encoding order.
New:
test_fetch_by_subject_returns_divergent_metas_in_row_order— the issue'srepro, two metas holding two different sources, run in both row orders. Both
sources come back, and the vec follows the rows: divergence is an expected
result, not an error, and nothing is reordered.
test_fetch_by_subject_keeps_duplicate_sources— the same source twice comesback twice.
test_fetch_by_subject_ignores_other_meta_types_alongside— a non-dotrainmeta ahead of the dotrain one no longer errors and is not counted.
test_get_metabytes_by_subject_successgainsorderBy: idandorderDirection: ascto its existing wire-shape pins.QA
All runs under
nix develop -c, on the merge oforigin/maininto this branch.Green:
cargo test -p rain-metadata --lib source_v1::tests18 passed 0 failed;cargo test -p rain-metadata --lib error::7 passed 0 failed;cargo test -p rain-metaboard-subgraph --lib12 passed 0 failed;cargo clippy --workspace --all-targets -- -D warningsclean;cargo fmt --all -- --checkclean. Thefull suite was NOT run locally — this box is shared by many agents; CI runs it.
Discriminating tests: the pre-PR implementation cannot be restored under this
signature, so the discriminators are recorded as mutations of the shipped
code, including one that restores the old first-wins selection on top of the
new signature (M7).
Mutations applied to
fetch_by_subject, 9 applied / 9 killed / 0 survivors:for meta_bytes in metabytes->.into_iter().take(1)— killed by..._returns_divergent_metas_in_row_order,..._keeps_duplicate_sources,..._ignores_other_meta_types_alongside.for item in cbor_decode(&meta_bytes)?->.into_iter().take(1)— killedby
..._returns_every_item_in_one_meta.if item.magic != KnownMagic::DotrainSourceV1->if false— killed by..._only_other_meta_types_yields_nothing,..._ignores_other_meta_types_alongside.sources.dedup_by(|a, b| a.0 == b.0)inserted before the return — killed by..._keeps_duplicate_sources.sources.sort_by(|a, b| a.0.cmp(&b.0))inserted before the return — killedby
..._returns_divergent_metas_in_row_order.Err(Empty(_)) => Ok(vec![])arm deleted — killed by..._not_found.Ok(sources)->Ok(sources.into_iter().take(1).collect()), the oldfirst-wins rule on the new signature — killed by
..._returns_every_item_in_one_meta,..._returns_divergent_metas_in_row_order,..._keeps_duplicate_sources.orderDirection: asc->desc— killed bytest_get_metabytes_by_subject_success.orderBy: id, orderDirection: ascdropped — killed by the same.Oracle:
IMetaV1_2's definition ofsubjectand the 1:N cardinality itimplies, plus the issue, not the implementation. Expected order-independence
of the set and order-faithfulness of the vec are both asserted by running
one fixture in both row orders. Wire-shape strings are the GraphQL the
subgraph schema defines (
MetaV1_orderBy.id,OrderDirection.asc), pinnedthe way the sibling
where: {metaHash: $metahash}pin already is.Category check: the issue asks for (A) deterministic ordering on
MetasBySubjectand (B) verification of the returned source against thesubject. A is covered twice over — the result no longer depends on row order
for what it contains, and the
orderBy/orderDirectionthe issue names areadded. B is NOT covered, deliberately, for the reasons above. Hence
Refs.