fix: Body-ledger @annotation reification + update-clause annotation minting#1383
Open
bplatz wants to merge 4 commits into
Open
fix: Body-ledger @annotation reification + update-clause annotation minting#1383bplatz wants to merge 4 commits into
bplatz wants to merge 4 commits into
Conversation
…eify @annotation reification was silently dropped when the target ledger was supplied in the request body (vs the URL path). The annotation lowering pass has its own envelope detector (is_envelope) which only tolerated @context/@graph/opts, so a stray top-level `ledger` key misclassified the document as a data node-map, skipped @graph during lowering, and let @annotation expand into a dangling literal predicate with no reification. Three independent stages each hard-coded their own notion of "reserved top-level transactor key" and had drifted apart: - strip_opts_for_expansion (jsonld) stripped only opts/txn-meta - is_envelope / is_transaction_wrapper (edge_annotations) each carried a different ad-hoc key list Introduce a single canonical RESERVED_TXN_KEYS / CLAUSE_KEYS in parse/mod and source all four sites from it. Also strip the routing/dataset keys (ledger, from, ...) before expansion so the single-object body form no longer leaks them as stray predicates, and lift reserved keys out of the node in attach_siblings' rewrap path (where lowering otherwise buries a body-form `ledger` inside @graph, out of the strip's reach). Tests: two unit sync-guards that fail if any detector drifts from the canonical set, plus integration regressions for the envelope and single-object body-ledger forms.
Inline @annotation in an UPDATE insert/upsert clause silently no-opped the entire clause: attach_siblings flushed the synthesized f:reifies* nodes onto the top-level wrapper, rewrapping the whole {where, insert, ...} document into {@graph: [...]} so parse_update then found no insert clause at all. Scope the sibling accumulator to each clause: take it empty before recursing, attach whatever that clause synthesized back onto the clause's own value, and restore the outer accumulator. next_anon_id stays shared so blank-node ids don't collide across clauses. No parser change is needed — the resulting {@graph: [base, ...siblings]} clause value flows through the same parse_expanded_triples_with_ctx path the insert/upsert envelope already uses.
Add integration coverage for minting annotations through an UPDATE insert clause now that per-clause sibling scoping makes it work: - constant template gated by WHERE - WHERE-bound subject/object (one annotation per solution, no blank-node collision) - anonymous (blank-node) annotation - combined delete-clause retract + insert-clause mint in one transaction Document the WHERE/INSERT mint form in the concepts doc (the surface was already claimed but had no worked example) and record the per-clause sibling-attachment invariant in the design doc so the top-level-flush no-op isn't reintroduced. Scope notes for reviewers: an `upsert` clause inside an update is not a parse_update feature (only where/delete/insert/values), and inline named-graph annotation *queries* remain outside the read-side correctness envelope — both are pre-existing and orthogonal to this change.
A coverage audit of the annotation surface found the behavior complete but several capabilities unpinned by tests. Lock in the high-value ones, all verified working: - update insert clause: explicit-@id annotation - update insert clause: parallel annotations (repeated nodes) - update insert clause: literal-valued (typed) edge - editing an ANONYMOUS annotation's metadata by binding its subject through the inline @annotation WHERE form, then delete/insert by that variable — the only route to mutate an annotation with no user-assigned @id - selector delete (no @id) retracting an anonymous annotation No functional holes were found in add/update/delete across anonymous vs explicit-@id, ref vs literal objects, single vs parallel, default graph.
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.
Summary
Fixes silent
@annotationdata-model corruption and unblocks minting annotations through update transactions. Three related issues, surfaced from a body-ledger bug report and a follow-on audit:Body-ledger annotation drop (reported bug). Supplying the target ledger in the request body instead of the URL path silently dropped
@annotationreification — the annotation expanded into a dangling literal predicate with no reification bundle and no error. The annotation-lowering pass had its own envelope detector (is_envelope) that only tolerated@context/@graph/opts, so a stray top-levelledgerkey made it misclassify the envelope as a data node and skip@graph.Reserved-key drift. Four independent stages each hard-coded their own notion of "reserved top-level transactor key" and had diverged. Consolidated into a single canonical
RESERVED_TXN_KEYS/CLAUSE_KEYSinparse/mod.rs, sourced by all four sites, with unit sync-guards that fail if any detector drifts. This also fixed a latent single-object-form leak (a body-formledgerleaking as a stray predicate).Update-clause annotations no-opped. Inline
@annotationin an UPDATEinsertclause silently dropped the entire clause: synthesizedf:reifies*sibling nodes were flushed onto the top-level wrapper, rewrapping{where, insert, …}into{@graph: […]}soparse_updatethen found no clauses. Fixed by scoping sibling attachment per clause.Changes
parse/mod.rs— canonicalRESERVED_TXN_KEYS/CLAUSE_KEYS.parse/jsonld.rs—strip_opts_for_expansionstrips the full reserved set (fixes single-object leak).parse/edge_annotations.rs—is_envelope/is_transaction_wrappersource the shared set;attach_siblingsscopes synthesized siblings per clause and lifts reserved keys out of the rewrapped node.Coverage
An audit of the annotation surface (operation × surface × value-type × identity × cardinality) found no functional holes in the JSON-LD add/update/delete lifecycle. New tests pin the high-value, previously-unpinned capabilities:
@id, parallel, literal/typed@annotationWHERE bindingScope boundaries (pre-existing, unchanged)
@reifiesminting is deferred;upsertis not aparse_updateclause.Validation
fluree-db-transact(263) ·grp_miscedge annotations (78) ·grp_transact(159) ·grp_query_sparql(233) all green. Clippy clean on changed targets;cargo fmt --allapplied.