Repository navigation
Conversation
…ed contract Amend RFC 0030 with a public graph diff over two read targets, a branch review form against the merge base, `branch merge-base`, and a read-only `branch merge --dry-run`. Keep `commit changes` as the cause-carrying unit, retire the table-shaped `ChangeSet`, and reshape C6 around the per-commit schema content storage format 14 retains. Adds D0 evidence gates, D1-D3 phases and a decision log recording how each premise was checked.
ragnorc
left a comment
There was a problem hiding this comment.
Recommendation: request changes to the RFC before accepting this amendment. Two contract issues are marked inline. This is a COMMENT review because the authenticated account is the PR author.
This PR gives branch reviewers and history readers a public way to ask what changed between two graph states. It compares exact endpoint images, returns net entity changes, and keeps the per-commit feed for ordered replay with a cause. It also proposes a merge-base read, a read-only merge preview, and later support for additive schema changes. These are design-stage surfaces; this PR changes no runtime code.
Blocking: the merge-base-to-feed recipe fails when the selected base is outside the source's first-parent chain. Also, the branch-form authorization rule omits the named target: its two diff endpoints are the base and source, which can both be readable while the target is forbidden. Both issues need contract corrections, not another storage primitive.
The core design reduces long-term liability if implementation follows §8.6: remove the old table-shaped diff API and its separate update rule, and share type pairing and typed image comparison. The existing linear stamp path really does classify an existing touched id as an update without comparing values. Typed comparison is the appropriate shared owner.
The tradeoff is explicit: arbitrary endpoint comparison can scan large changed tables even when the result is small. Exact adjacent-transaction proofs can reduce that work. The new endpoints and token kind add API and compatibility obligations, but no new durable history store. Five more change readers should reuse this comparator and capture path. Five separate classifiers or preview evaluators would reverse the benefit. Merge preview also trades retention protection for a read-only operation: concurrent cleanup may cause a typed gap. Current merge preparation creates input tags; candidate validation precedes detached publication. The preview must reuse that evaluator while supplying the specified read-only capture.
I checked the open-PR direction. Archived endpoint schemas fit #963 and stable property binding in #959. Future typed edge endpoints in #884 must participate in logical images. The finer access views in #951 will also need to govern before/after images. These are future integration duties, not runtime defects introduced here. Optional: qualify the GQ exclusion against #670, which already proposes a paged read envelope; see the third inline comment.
The substrate check used pinned Lance 11.0.0, source ab6b5bbe46009ed78746b444df8db59a8bc5d842, with the relevant complete format, transaction, schema, row-lineage, versioning, branch/tag, and read/write documentation. Its insert/update delta scans use version stamps; those stamps alone do not establish logical image changes. OmniGraph's candidate proof checks exact pins, transaction ancestry, and operation provenance. Lance 13 adds spilled row lineage and related changes. Those require a future adapter audit, not a change to the graph-level diff contract.
Validation:
- Reviewed head:
10db8e9367fe05dc1d8f567fc57420c046120a0a. Before the amendment: merge base47dfe7b4a00c1d6405bc076dc6f4f549ae6fc06c. GitHub's current target was5e530377dce385a7e1f0d0ee797b4ef18dd76b54. - Local:
/tmp/review-916-docs/bin/python scripts/check-docs.pypassed, 255 Markdown files.bash scripts/check-agents-md.shpassed, 54 links / 50 docs.typos docs/rfcs/0030-cdc-time-travel.md docs/rfcs/README.mdandgit diff --check 5e530377dce385a7e1f0d0ee797b4ef18dd76b54...HEADpassed. - Local pristine-head baseline:
cargo test -p omnigraph-engine --locked --test integration changes::change_feed_merge_commit_is_first_parent_relative_with_merged_parent_on_cause -- --exact --nocapturepassed: 1 test. - I temporarily extended that same fixture to resolve the real merge base, prove it equals the merged parent, and poll after it. The same command passed 1 test on each before/head commit, with the expected
ChangeCursorRejected:start commit is not on this branch's first-parent chain. Both runs usedRUSTUP_TOOLCHAIN=1.97.1,CARGO_TARGET_DIR=/tmp/review-928/target,RUSTFLAGS='--cfg tokio_unstable --cfg tokio_unstable',RUST_MIN_STACK=16777216, and an emptyRUSTC_WRAPPER. All temporary test edits were restored; the checkout is clean at the reviewed head. - GQT can create and merge branches, but cannot currently call the feed or assert this merge-base/feed relationship. The existing Rust owner is therefore the appropriate test. These runs prove the RFC counterexample; they are not a failing-before/passing-after runtime fix. No runtime fix exists in this PR.
- Exact-head GitHub evidence: CI Gate and GQ Logic Tests succeeded. Test Workspace was skipped; I do not count it as passing coverage.
Limits: the new APIs, authorization checks, schema-crossing format, and dry run are not implemented, so their conformance gates remain unproved. I did not run an object-store cleanup race or a performance campaign. The authorization finding is a gap in the proposed contract, not a reproduced vulnerability in a shipped route.
| [merge guide](../user/branching/merge.md) documents. It reads lineage only. It | ||
| also lets a caller replay a branch's own commits through the feed with | ||
| `--start after:<base>`. |
There was a problem hiding this comment.
[P2] Limit the feed recipe to a base on the first-parent chain
A merge base can be a second-parent ancestor. For example, fork feature, write on both it and main, then merge main into feature. The base for feature → main is now main's imported head, but feature's first-parent chain skips that commit. changes poll --branch feature --start after:<base> therefore returns ChangeCursorRejected, as required by feed.rs. I confirmed the same shape with the real CommitGraph::merge_base and feed APIs in the existing merge/feed test, on both the pre-amendment commit and this head. Please restrict this recipe to bases on the selected feed chain, explain that replay still includes imported changes in merge blocks, and use branch diff for net changes from an arbitrary DAG merge base. Add this shape to D0 and retain the small assertion in the existing Rust owner. Changing the feed to walk both parents would change its replay contract and would not solve this by a documentation correction.
There was a problem hiding this comment.
Fixed in 5169758. §8.4 now limits --start after:<base> to a base on the source's first-parent chain. A merge commit in that range still carries what it imported (§3.2). Once the target has been merged into the source, the base is off that chain and the feed refuses it (§5.3); branch diff gives the net change from any base. The feed still walks first parents only, and D0 gains this shape.
In #966 (3bb0775), the user guide's recipe is corrected the same way. change_feed_merge_commit_is_first_parent_relative_with_merged_parent_on_cause now asserts the shape you reproduced:
- the merge base of
mainintosideequals the merge block's merged parent; main's feed refusesafter:<base>withChangeCursorRejected(first-parent chain);- the branch diff from that base reports only
main's own update.
| Authorization is `read` on the branch each endpoint resolves to. A commit | ||
| endpoint authorizes against its authored branch, or against the recorded | ||
| target of a proposal or snapshot, and a forbidden endpoint is reported as not | ||
| found, as the commit diff does. |
There was a problem hiding this comment.
[P2] Authorize both named branches before branch comparison
For the branch form, §8.2 defines the endpoints as the merge base and source head. Neither need resolve to the named target branch. An actor allowed to read main and feature, but not private-target, can satisfy both endpoint checks when all three share a base on main. Comparing feature against private-target would then reveal whether that target has incorporated feature: the base and result change even though the actor cannot read the target's history. The current commit-list route requires read access to the requested branch. Please require read on both SOURCE and TARGET for branch diff and branch merge-base before exposing lineage/results, as this draft already does for dry-run. Keep the authored-branch rule for explicitly supplied commit endpoints. Add an authorization gate where both diff endpoints are readable but the requested target is denied, including continuation requests. This closes the missing input scope without adding a new permission action.
There was a problem hiding this comment.
Fixed in 5169758. §8.2 now requires read on both named branches for the branch form and the merge base, on every request including a continuation, before lineage is read. The authored-branch rule stays for the two-point form's commit endpoints, and D0 gains the gate.
The implementation in #966 already authorized source and target before calling the engine (authorize_branch_reads), but its test denied only the source. #966 (3bb0775) adds policy_branch_review_needs_read_on_the_target. An actor who reads main and feature but not private gets 200 for feature against main. It gets 403 for feature against private, on the first page, on a continuation with a token minted by an actor who reads all three, and on merge-base. Both comparisons have the same endpoint commits.
| These commands are not GQ statements, because their results are paged change | ||
| records rather than query rows. |
There was a problem hiding this comment.
[P3] Make the GQ exclusion a scoped rollout choice
Optional: link this choice to #670 and say whether GQ exposure is deferred or rejected. That draft explicitly carries commit changes and changes poll as entity-change rows plus an optional ReadOutput.page object (its “User and operational behavior” section). Paged change records therefore do not, by themselves, explain the exclusion. D1 can still ship through the existing CLI/HTTP paths, but a scoped deferral avoids deciding incompatible long-term interfaces in two RFCs.
…a branch diff Address the review of the graph diff amendment. - A merge base is a valid feed start only on the source's first-parent chain. After the target was merged into the source, the base is the imported target commit and the feed refuses it; point at branch diff for the net change and add the shape to the D0 gates. - The branch diff and merge base require read on both named branches, continuations included: their endpoints, the base and the source head, can both be readable while the target is not. Add the D0 gate. - Defer GQ exposure of the diff surfaces to RFC 0056 rather than reject it.
ragnorc
left a comment
There was a problem hiding this comment.
Recommendation: approve this RFC amendment, with one optional wording correction inline. The two earlier blocking contract issues are addressed. This is a COMMENT review because the authenticated account is the PR author.
This PR proposes a public way to compare two graph states and review a branch before merging it. It compares exact endpoint images and returns net entity changes. The existing feed keeps its separate job: replay each commit in first-parent order, with its cause. The amendment also proposes a merge-base read, a read-only merge preview, and later support for additive schema changes. These remain design-stage at this head; no runtime code changes here.
The revised contract fixes the causes of the earlier findings:
- §8.2 requires read access to both named branches before reading lineage, including continuation requests. Readable base/source endpoints do not imply a readable target. This uses the existing permission action and closes the missing input scope.
- §8.4 makes first-parent membership the feed-start condition. That matches the feed's actual check. Net comparison remains separate from replay, so there is no need to change feed traversal or add another history store.
- GQ exposure is now explicitly deferred to #670, which owns the future result envelope.
Tradeoffs and liability: removing the old table-shaped diff and sharing type pairing and typed image comparison should reduce maintenance. The old stamp path can still label equal-value rewrites as updates. One shared comparator removes that competing definition. New routes and a token kind add compatibility duties, but no new durable authority. Five similar readers should reuse those owners. Separate classifiers or preview evaluators would erase this benefit. Arbitrary endpoint diffs can scan whole changed tables; small output does not imply small work. A preview without retention tags can fail if cleanup reclaims an input.
I considered the other open PRs. #966 contains the target-denial test for the first page, continuation, and merge base, plus the merge-base/feed test extension. Its handlers authorize both names before calling the engine. This is code inspection of 3bb077542571eba1a038d7fe40cf389406f4b3b7, not local execution or approval of that implementation. It also explicitly amends this RFC to omit preview counts, since pointer adoption does not compute a row delta. That is a separate tradeoff for its review. #968 works on side resolution and page cost within the same contract. Archived schemas fit #963; actor views in #951 and typed endpoints in #884 remain integration duties for image access and equality.
The dependency lock is unchanged: Lance 11.0.0, source ab6b5bbe46009ed78746b444df8db59a8bc5d842. I reused the prior complete upstream documentation audit and rechecked the pinned delta implementation. Its version predicates select physical candidates; they do not replace logical image comparison. Lance 13's spilled row lineage requires an adapter audit on upgrade. It does not change the graph-level definition.
Validation:
- Before this revision:
10db8e9367fe05dc1d8f567fc57420c046120a0a. Reviewed head:5169758a661cf21b090085e00afe0e4ab590ae66. The revision changes only the RFC. - Local pristine-head baseline: 1 test passed. I then temporarily extended the existing merge/feed fixture. The same probe passed 1 test on each before/head commit. It proves that an off-chain base is refused and that a later reverse merge can make the new base an accepted start. This supports the inline qualification. Both passes validate existing behavior; they are not failing-before/passing-after proof of a runtime fix.
env RUSTUP_TOOLCHAIN=1.97.1 CARGO_TARGET_DIR=/tmp/review-928/target \
RUSTFLAGS='--cfg tokio_unstable --cfg tokio_unstable' \
RUST_MIN_STACK=16777216 RUSTC_WRAPPER= \
rustup run 1.97.1 cargo test --locked -p omnigraph-engine \
--test integration -j3 \
changes::change_feed_merge_commit_is_first_parent_relative_with_merged_parent_on_cause \
-- --exact --nocapture- GQT at this head cannot call the feed or assert this merge-base/start relationship. The existing Rust integration owner is appropriate. All temporary test edits were restored; the isolated checkout is clean at the reviewed head.
- Local
/tmp/review-916-docs/bin/python scripts/check-docs.pypassed: 255 Markdown files.bash scripts/check-agents-md.sh,typos docs/rfcs/0030-cdc-time-travel.md docs/rfcs/README.md, andgit diff --check 47dfe7b4a00c1d6405bc076dc6f4f549ae6fc06c...HEADpassed. - Exact-head CI Gate succeeded. Test Workspace was skipped. The successful GQT ordinary job also explicitly skipped tests because no GQT input changed; it is not runtime coverage.
Limits: this follow-up does not qualify the proposed APIs, cloud cleanup races, or performance claims. Those remain implementation evidence gates. No blocking defect remains in the revised design contract; the inline note narrows an example that is currently stated too broadly.
| replay is the source's history, not only its own edits. Once the target has | ||
| been merged into the source, the base is the target commit that merge imported, | ||
| which the source's first-parent chain does not hold, and the feed refuses it as |
There was a problem hiding this comment.
[P3] State the off-chain merge base as a case, not a permanent rule
Optional: qualify “Once the target has been merged into the source” here and in D0/§15. The base can later return to the source's first-parent chain. In the existing fixture, merge side into main, then merge main back into side. For review from main to side, the new base equals main's head, and changes poll after it succeeds with an empty, caught-up result. I reproduced this on both the previous and current RFC heads. The precise condition is the one this paragraph already opens with: whether the selected base is on the source's first-parent chain. Describe the one-way divergent merge as an example instead of implying that any past target-to-source merge permanently prevents the recipe. No feed behavior change is needed.
There was a problem hiding this comment.
Done in 32c3078. §8.4, the D0 gate and the §15 entry now state the condition: a base off the source's first-parent chain is refused as a start. A merge of the target into a diverged source is given as one example, and a later merge back can bring the base onto the chain again. #966 carries the same correction in the user guide (46f9c96).
A base off the source's first-parent chain is refused as a feed start. A merge of the target into a diverged source is one way to get there, and a later merge back can bring the base onto the chain again, so the RFC no longer says any past target-to-source merge prevents the recipe.
What & why
This amends RFC 0030 with a public graph diff. Today OmniGraph can report what one commit changed (
commit changes) and stream those per-commit changes (changes poll), but no public surface answers either of these:main? This is the question branch review needs.The net diff that exists (
diff_between/diff_commits) is embedded-Rust only and returns the old table-shapedChangeSet. On pre-detached history it also reports a row rewritten with equal values as an update.The amendment adds the following design-stage surfaces (§8), in the shipped entity shape:
diff --from <REV> --to <REV>GET /graphs/{id}/diffbranch diff <SOURCE> [--into <TARGET>]GET /graphs/{id}/branches/diffbranch merge-base <SOURCE> [--into <TARGET>]GET /graphs/{id}/branches/merge-basebranch merge --dry-runPOST /branches/mergewithdry_run: trueOther changes in the amendment:
commit changesstays as the cause-carrying unit the feed delivers. The graph diff never carries a cause.ChangeSet,EntityChangeandChangeStats.Backing issue / RFC
Checklist
Local verification
python3 scripts/check-docs.py: Documentation OK (255 Markdown files checked)typos docs/rfcs/0030-cdc-time-travel.md: cleanbash scripts/check-agents-md.sh: OKpython3 scripts/check-pr-title.py --title "…": PASS47dfe7b4and the 0.13.0 release, run against a scratch graph. The results are recorded in §15. They include the fork-point feed, a conflicting merge leaving no tags, the schema-apply feed stop, andcommit changeson an--as-commitproposal before and after the target moves.Notes for reviewers
commit changesalongsidediff. I chose two commands with fixed output shapes, mirroringgit showandgit diff, over a singlediff --commit|--branch|--from/--towhose output shape would change with the flag. That single-command design is the main alternative to weigh.--as-commitproposal stops being readable once its target moves ("expired and awaits cleanup"), and that expiry is by design. So a proposal-based review lasts only until the target's next commit, which is whybranch diffand the dry run compute their answer fresh each time.