Skip to content

[HYPER-536] introduce entityFollow lexicon for non DID follows - #236

Merged
Ashex merged 5 commits into
mainfrom
feat/lex-entityFollow
Aug 3, 2026
Merged

[HYPER-536] introduce entityFollow lexicon for non DID follows #236
Ashex merged 5 commits into
mainfrom
feat/lex-entityFollow

Conversation

@Ashex

@Ashex Ashex commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

We need a social graph for following things that are not accounts — most immediately, individual records. The existing app.certified.graph.follow cannot serve this: its subject is constrained to format: "did", and a strongRef pins a specific version via CID, so the reference would break (or dangle semantically) every time the followed record is updated.

Resolves HYPER-536.

Design

  • Weak reference by AT-URI, no CID. The subject references a record by its AT-URI (at://did/collection/rkey), which identifies the record's identity rather than one snapshot of its contents. Updates change a record's CID, not its URI, so the follow tracks the living record. This mirrors upstream precedent: app.bsky.graph.follow uses a weak ref (bare DID) for evolving relationships, while app.bsky.feed.like uses a strongRef because a like is about that version of a post. Following is inherently a relationship with something that evolves — the weak form is semantically correct, not a workaround. URIs must use the DID form (not handles, which can change or be reassigned).

  • New lexicon rather than updating follow. ATProto lexicon evolution rules forbid changing constraints on published fields, and a DID is not a valid at-uri — there is no non-breaking edit to follow that could accept both. Dropping the format constraint would silently weaken validation for every existing consumer (AppView indexes, filters, client hooks), and follow is already published to npm and the ATProto network. A new lexicon is purely additive: minor semver bump, existing records and indexers untouched.

  • entityFollow with an open-union subject, not recordFollow with a plain string. The lexicon's charter is "follows for anything that isn't an account," so the subject is an open union with a single variant today (#recordSubject). This is a deliberate one-way door: a plain at-uri string could never be evolved into a union later without a breaking change, whereas new union variants (URLs, on-chain addresses, etc.) are purely additive. The small ceremony cost — clients wrap the URI in { "$type": "...#recordSubject", "uri": ... } —
    buys permanent extensibility.

  • DIDs are explicitly out of scope. Account follows belong to app.certified.graph.follow. Keeping the two collections mutually exclusive means the indexer/gql never has to merge two record types to compute a single relationship, and there is exactly one way to follow an account.

  • tid record key. An AT-URI is not a valid rkey (contains /), and deriving rkeys from subjects can't be enforced by schema validation anyway — the indexer must deduplicate regardless. tid matches every other atproto graph record (follows, likes, reposts), preserves creation-order scans, and would reuse the duplicate-ignoring logic the indexer should already have for follows.

Notes

  • Open-union permissiveness: @atproto/lexicon passes subjects with an unrecognized $type without validating their shape — this is standard open-union behavior and is what makes future variants non-breaking. Consumers (AppView/indexer) must treat unknown subject variants as skip-not-trust. A test documents this behavior explicitly.
  • Deleted targets dangle by design (same as likes on deleted posts); resolution failures are the AppView/Indexer's responsibility.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added support for following non-account entities, including specific records.
    • Record references remain valid across updates without requiring a content identifier.
    • Added optional mediation references and cryptographic signatures.
    • Enabled authorized creation of entity-follow records.
  • Documentation
    • Added schema references, relationship diagrams, and TypeScript examples.
    • Clarified that account follows continue using the existing account-follow feature.
  • Tests
    • Added validation coverage for valid, invalid, and incomplete entity-follow records.

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7b3d44b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hypercerts-org/lexicon Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c3fbddbb-fe35-49ee-a97a-8ab9e7a5e595

📥 Commits

Reviewing files that changed from the base of the PR and between 5439b36 and 7b3d44b.

📒 Files selected for processing (8)
  • .agents/skills/building-with-hypercerts-lexicons/SKILL.md
  • .changeset/add-graph-entity-follow.md
  • README.md
  • SCHEMAS.md
  • lexicons/app/certified/authWrite.json
  • lexicons/app/certified/defs.json
  • lexicons/app/certified/graph/entityFollow.json
  • tests/validate-graph-entity-follow.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • SCHEMAS.md
  • .changeset/add-graph-entity-follow.md
  • README.md

📝 Walkthrough

Walkthrough

Adds app.certified.graph.entityFollow for following non-account records by AT-URI. The change includes schema definitions, validation tests, ERD updates, documentation examples, permission wiring, and minor-release metadata.

Changes

Non-account entity follows

Layer / File(s) Summary
Lexicon schema and relationship model
lexicons/app/certified/defs.json, lexicons/app/certified/graph/entityFollow.json, lexicons/app/certified/authWrite.json, SCHEMAS.md, ERD.puml
Defines the entityFollow record, the recordSubject AT-URI definition, optional references, writable collection, and non-account follow relationship.
Validation behavior coverage
tests/validate-graph-entity-follow.test.ts
Covers valid records, optional fields, required fields, subject shapes, open-union behavior, datetime validation, and type requirements.
Documentation and release metadata
README.md, .agents/skills/.../SKILL.md, .changeset/add-graph-entity-follow.md
Documents the lexicon, relationship map, TypeScript usage, non-account subject semantics, and minor release note.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

Suggested reviewers: aspiers

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: introducing the entityFollow lexicon for non-DID follows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Lexicon Documentation Sync ✅ Passed The new JSON lexicon, README entry/example, SCHEMAS fields, authWrite listing, and ERD entityFollow relationship are consistent; recordSubject is documented and referenced from defs.
Lexicons Styleguide Compliance ✅ Passed Changed lexicons use lowerCamelCase names, documented definitions and fields, appropriate formats, a tid key, and an open union; the checker reports zero errors.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lex-entityFollow

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ERD.puml`:
- Around line 209-213: Add the optional signatures field to the ERD dataclass
for app.certified.graph.entityFollow within the SHOW_FIELDS conditional,
alongside subject, via?, and createdAt, so all non-facet fields are rendered.
- Around line 216-223: Remove the non-lexicon entityFollowSubject definition and
its related edge from the ERD. Update entityFollow to represent the followed
subject with a subject union annotation, or replace it with an entity backed by
a matching lexicon JSON; ensure every remaining entity corresponds to a lexicon
definition.

In `@lexicons/app/certified/graph/entityFollow.json`:
- Around line 41-44: Update the recordSubject.uri schema in entityFollow to
require a complete DID-based record AT-URI, including did:<authority>,
collection, and rkey segments. Add AppView/publisher validation and tests
covering handle-only and partial URIs, or explicitly relax the documented
DID-form contract if full validation cannot be enforced.

In `@tests/validate-graph-entity-follow.test.ts`:
- Around line 85-96: Extend the recordSubject validation tests near the existing
missing-uri case with a negative case that supplies a non-AT URI such as an
HTTPS URL. Call validate with the same AppCertifiedGraphEntityFollow schema and
assert result.success is false, preserving the existing missing-uri coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fb890bef-dffa-41a7-b793-25e037cf8249

📥 Commits

Reviewing files that changed from the base of the PR and between 456c55c and 5439b36.

📒 Files selected for processing (7)
  • .agents/skills/building-with-hypercerts-lexicons/SKILL.md
  • .changeset/add-graph-entity-follow.md
  • ERD.puml
  • README.md
  • SCHEMAS.md
  • lexicons/app/certified/graph/entityFollow.json
  • tests/validate-graph-entity-follow.test.ts

Comment thread ERD.puml
Comment thread ERD.puml
Comment thread lexicons/app/certified/graph/entityFollow.json Outdated
Comment thread tests/validate-graph-entity-follow.test.ts
Ashex added 3 commits July 23, 2026 00:38
The at-uri format constraint cannot enforce DID-form or full record
URIs at the schema level, so the recordSubject.uri description now
states explicitly what schema validation enforces vs. what writers
must supply and consumers must verify. Adds a negative test covering
a non-AT URI subject.

Co-Authored-By: Rommie Code (Claude Fable 5)
Main introduced namespace permission sets that must enumerate every
record collection; without this entry the new collection would not be
grantable via the published set.

Co-Authored-By: Rommie Code (Claude Fable 5)
Comment thread lexicons/app/certified/graph/entityFollow.json Outdated

@aspiers aspiers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM as mentioned earlier

@aspiers aspiers changed the title introduce entityFollow lexicon for non DID follows [HYPER-536] introduce entityFollow lexicon for non DID follows Jul 28, 2026
@Ashex
Ashex merged commit bcae750 into main Aug 3, 2026
9 checks passed
@Ashex
Ashex deleted the feat/lex-entityFollow branch August 3, 2026 11:37
@hypercerts-release-bot hypercerts-release-bot Bot mentioned this pull request Aug 3, 2026
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