Skip to content

Fix causal anchor schema validation#643

Open
flyingrobots wants to merge 4 commits into
mainfrom
feature/causal-anchors
Open

Fix causal anchor schema validation#643
flyingrobots wants to merge 4 commits into
mainfrom
feature/causal-anchors

Conversation

@flyingrobots

Copy link
Copy Markdown
Owner

Summary

This resolves the Code Lawyer follow-up findings for Echo causal anchors.

  • Adds schema_version to causal anchor requests and facts so the Rust value contract matches the documented shape.
  • Binds schema version into anchor digest/id computation.
  • Rejects roots that appear in both retained authority roots and materialization roots.
  • Documents the retained/materialization root disjointness rule.

Validation

  • cargo fmt --all -- --check
  • ./scripts/check_spdx.sh
  • cargo test -p warp-core --test causal_anchor_tests
  • cargo test -p warp-core --lib
  • pnpm exec markdownlint-cli2 docs/topics/CausalAnchors.md docs/README.md
  • git diff --check

Local result: causal anchor tests 9 passed; warp-core lib tests 565 passed; markdownlint 0 errors.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@flyingrobots, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f5840ac0-5106-4255-a8d7-a5c15760463e

📥 Commits

Reviewing files that changed from the base of the PR and between 5c968a4 and debc7b5.

📒 Files selected for processing (5)
  • crates/warp-core/src/causal_anchor.rs
  • crates/warp-core/src/lib.rs
  • crates/warp-core/tests/causal_anchor_tests.rs
  • docs/README.md
  • docs/topics/CausalAnchors.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/causal-anchors

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.

Copy link
Copy Markdown
Owner Author

@codex review please

Code Lawyer resolution summary:

Severity Finding Resolution
P2 Causal anchor docs included schemaVersion, but the Rust fact did not. schema_version is now part of requests/facts and is bound into anchor digest/id computation.
P2 A root could be both retained authority and materialization. Cross-set duplicate roots are rejected and the disjointness rule is documented.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: debc7b5c07

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +269 to +270
if retained_roots.is_empty() {
return Err(CausalAnchorError::EmptyRetainedRoots);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require an authority/evidence retained root

retained_roots.is_empty() only rejects an empty vector, so a request whose retained set contains only CasObject { role: Materialization } or a GraphFact index is admitted and digested as a recovery anchor. That violates the stated invariant in this type (EmptyRetainedRoots says the anchor must retain at least one authority or evidence root) and lets projection/cache artifacts become the sole durable basis; validate the canonical retained set's roles before computing the digest.

Useful? React with 👍 / 👎.

Comment on lines +288 to +289
let anchor_digest = compute_anchor_digest(
request.schema_version,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unsupported causal anchor schema versions

request.schema_version is passed straight into the digest and then stored on the fact, so a caller can mint schema 0 or 2 anchors using the v1 struct and v1 digest domain. If schema changes later, old code will already have admitted facts claiming that future schema without applying its validation or canonicalization rules; compare this field with CAUSAL_ANCHOR_SCHEMA_VERSION and return a validation error for unsupported versions.

Useful? React with 👍 / 👎.

Comment on lines +282 to +284
if retained_roots
.iter()
.any(|root| materialization_roots.binary_search(root).is_ok())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare cross-set root identities without roles

This binary_search compares the full CausalAnchorRoot, including its role, so the same underlying root can still be admitted in both sets if the role differs, e.g. a retained GraphFact { id, role: Evidence } and a materialization GraphFact { id, role: Index }. That violates the new disjointness rule documented for retained/materialization roots and allows one root id to be treated as both retained evidence and projection material; compare by root kind and id rather than the entire role-bearing value.

Useful? React with 👍 / 👎.

Comment on lines +267 to +268
let retained_roots =
canonicalize_roots(request.retained_roots, CausalAnchorRootSet::Retained)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate application root identity fields

Only the top-level anchor subject is checked for empty strings before roots are canonicalized and digested, so an AppSubjectRoot with an empty app_id, subject_kind, or id is accepted as retained authority/evidence. In that scenario the anchor can claim to retain an application root that cannot be scoped or resolved by the appId/subjectKind/id contract shape, so root identity fields need the same non-empty validation as the anchor subject.

Useful? React with 👍 / 👎.

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.

1 participant