Add actors table and actor GraphQL API - #47
Conversation
3e7a1f0 to
9ce4e31
Compare
|
I have some comments related to actor schema. I see only followee/follower counters not actual follow relationship among actors. I don't think |
I haven't fully implemented the follow-related features yet since they weren't part of the current goal. I've kept the terminology as consistent as possible with the Hackers' Pub codebase. Personally, I also prefer the term "followee" because it provides a clear contrast to "follower." |
9ce4e31 to
237fe6e
Compare
| host: "test-instance.drfed.org", | ||
| }, | ||
| local: { | ||
| avatar: "avatar.png", |
There was a problem hiding this comment.
Maybe we don't need avatars and headers for users ? I don't think it's relevant to interop debugging
There was a problem hiding this comment.
may not activitypub.academy supports that. just wondering your thoughts
There was a problem hiding this comment.
Sometimes profile image updates aren't being sent between instances. We need to test for that, I think?
Apply the fixes from an AI-assisted code review of the create-actor branch (fedify-dev#47). - Mount the Fedify federation in front of the Yoga server. The srvx fetch handler now calls federation.fetch() and falls back to Yoga via onNotFound and onNotAcceptable, so the actor, WebFinger, inbox, and collection dispatchers are reachable over HTTP instead of only serving as URI builders. - Close the database client (PGlite or postgres.js) during shutdown before exiting the process. - Serialize genActors per instance by locking the local_instances row with SELECT ... FOR UPDATE before counting actors. This closes the race where two concurrent calls both passed the maxActors check under READ COMMITTED. The post-insert recount, tx.rollback(), the outer mutable state, and the string-matched catch block are gone; every error path now returns a result object directly from the transaction callback. - Derive the instance host from local_instances.slug and ctx.root rather than reading instances.host. - Generate one UUID per actor and reuse it for local_actors.id, actors.id, actors.localId, and the placeholder username, dropping the .returning() round trip on the local_actors insert. - Push the local-actor and host predicates into the Drizzle relational where clause in findLocalActor and mapHandle, replacing the full-row load plus JS filtering and the two-step instance/actor lookup. - Import hashSecret from auth/hash.ts in actor.test.ts and instance.test.ts instead of keeping duplicate copies. Provenance: the contributor asked Claude Code to review the branch against the pull request comments and the overall diff. Claude Code produced the review findings (unmounted federation routes, the maxActors race, the unclosed database client, the duplicated lookups, and the duplicated test helper) and drafted this commit message. The contributor implemented every change in this commit by hand and verified them with mise run check and the package test suites. Assisted-by: Claude Code:claude-fable-5-1
bfb2892 to
8a57495
Compare
Apply the fixes from an AI-assisted code review of the create-actor branch (fedify-dev#47). - Mount the Fedify federation in front of the Yoga server. The srvx fetch handler now calls federation.fetch() and falls back to Yoga via onNotFound and onNotAcceptable, so the actor, WebFinger, inbox, and collection dispatchers are reachable over HTTP instead of only serving as URI builders. - Close the database client (PGlite or postgres.js) during shutdown before exiting the process. - Serialize genActors per instance by locking the local_instances row with SELECT ... FOR UPDATE before counting actors. This closes the race where two concurrent calls both passed the maxActors check under READ COMMITTED. The post-insert recount, tx.rollback(), the outer mutable state, and the string-matched catch block are gone; every error path now returns a result object directly from the transaction callback. - Derive the instance host from local_instances.slug and ctx.root rather than reading instances.host. - Generate one UUID per actor and reuse it for local_actors.id, actors.id, actors.localId, and the placeholder username, dropping the .returning() round trip on the local_actors insert. - Push the local-actor and host predicates into the Drizzle relational where clause in findLocalActor and mapHandle, replacing the full-row load plus JS filtering and the two-step instance/actor lookup. - Import hashSecret from auth/hash.ts in actor.test.ts and instance.test.ts instead of keeping duplicate copies. Provenance: the contributor asked Claude Code to review the branch against the pull request comments and the overall diff. Claude Code produced the review findings (unmounted federation routes, the maxActors race, the unclosed database client, the duplicated lookups, and the duplicated test helper) and drafted this commit message. The contributor implemented every change in this commit by hand and verified them with mise run check and the package test suites. Assisted-by: Claude Code:claude-fable-5-1
8a57495 to
a961d0c
Compare
|
Conflict should be resolved first |
Prompts: Actor 를 위한 GraphQL 빌더를 추가했습니다. @packages/graphql/src/actor.test.ts 에 이를 위한 테스트를 생성해주세요. 파일 추가 직후 `mise run fmt` 로 라이선스를 추가하세요. 최소한 다음과 같은 테스트가 필요합니다. - 액터 생성 - 로컬 액터 조회 - 리모트 액터 조회 AI provenance: The contributor asked Codex to write tests for the new Actor GraphQL builder in packages/graphql/src/actor.test.ts, covering at least actor creation, local actor lookup, and remote actor lookup, and to run `mise run fmt` right after adding the file so the license header is inserted. Codex generated the whole test file: it reuses the existing withTestHarness helper to seed accounts, instances, and actors, and adds a Mutation.genActors test that creates local actors plus Actor query tests for a local actor and a remote actor. While making the tests pass, the genActors mutation query in actor.ts gained an inner join on local_instances so that actors are only generated for local instances. The human contributor reviewed the generated tests, ran the formatter, and ran the test suite locally before committing. Assisted-by: Codex:gpt-5.6-sol
Apply the same shape to actors that 09be000 applied to instances: - Move the remote_actors columns (iri, inboxUrl, outboxUrl, and the optional followers/followees/featured/profile/avatar/header URLs) into actors and drop the remote_actors table. iriUrl is renamed to iri and stays unique. - Drop the location enum column and the (id, location) composite FK/CHECK pairs. Local actors are identified by actors.localId, a nullable unique FK to local_actors.id with cascade delete, which inverts the previous local_actors.id -> actors.id dependency. local_actors keeps only id, avatar, and header. - Replace the actor migration. The old 20260803204536 migration created actors with the "location" type that the later 20260818 migration drops, so it could not apply on a fresh database and it was never on main. The new migration is regenerated from the current snapshot. - Store local actor URLs at creation time. genActors inserts local_actors first, then actors with the URLs expanded from the URI templates using instances.host, and joins local_instances via instances.localId. avatarUrl and headerUrl start as NULL. - Simplify the Actor GraphQL type to expose stored columns, keep handle as a field-level select on instance.host, and add uuid, created, and a nullable local: LocalActor relation. Replace the CreateActors node with Actor in CreateActorsSuccess.actors. - Fix the Instance.actors connection, whose resolveNode returned the instanceId instead of the actor. - Update the actor tests for the new seeds, fields, and assertions. AI provenance: Claude Code was asked to analyze the instance merge in 09be000 and write a plan applying the same approach to actors, covering the schema, migration, GraphQL API, and tests; it found that the branch no longer type-checked after the rebase and that the old actor migration depended on a dropped enum type. Codex implemented the changes from that plan. Claude Code then reviewed the diff against the plan, ran tsc for each package, and ran the test suite. The human contributor has read and reviewed both the plan and the implementation, and directly modified some of the designs. They validated the generated migration SQL and performed local checks and tests. Assisted-by: Claude Code:claude-fable-5 Assisted-by: Codex:gpt-5.6-sol
Replace the ad-hoc URI templates in the GraphQL package with Fedify's Federation object as the single source of truth for ActivityPub URIs. federation.ts now registers the actor, inbox, outbox, followers, following, and featured dispatchers, deriving each URL from the `Context` getters instead of string templates. The actor dispatcher scopes lookups to the requesting host so a multi-tenant DrFed instance never leaks one instance's actors under another's host, maps deleted actors to `Tombstone` objects, and resolves WebFinger handles via `mapHandle`. The inbox listener registers a catch-all logger so incoming activities are visible instead of silently discarded, since the data model has no follows/posts tables yet to persist them. actor.ts's `genActor` and the `handle` field now go through a Fedify `Context` created from `ServerContext.federation` instead of the old `uri-templates.ts` module, which is removed along with the `@fedify/uri-template` based templates. Adds `@fedify/vocab` as a direct dependency for the ActivityPub vocabulary classes used to build actor responses. Provenance: the contributor asked Claude Code to implement `buildFederation` in federation.ts using Fedify dispatcher setters (actor, inbox, outbox, followers, following, featured) and to replace the `uri-templates.ts`-based URI generation in actor.ts with Fedify's `Context` URI getters, then remove uri-templates.ts. Claude Code inspected the installed Fedify/vocab type declarations and the project's Drizzle schema to draft the dispatcher implementations, including the tombstone handling for deleted actors, the per-host actor scoping, and the inbox catch-all logger, refined after a follow-up automated review of the draft flagged missing inbox visibility and 404-vs-Tombstone handling for deleted actors. The contributor then asked Claude Code to hoist `findLocalActor` out of `buildFederation` into a module-level function that takes the database as an explicit argument, which was applied directly. The contributor verified the result by running `mise run check`, the package's `tsc --noEmit`, its test suite, and by exercising the dispatchers against a temporary PGlite-backed Federation instance (actor lookup, tombstone response for deleted actors, followers collection, and cross-host isolation). Assisted-by: Claude Code:claude-fable-5 Assisted-by: Claude Code:claude-sonnet-5
PostgreSQL access was split between two drivers: Drizzle used pg (node-postgres) while Fedify's PostgresKvStore used postgres (postgres-js), so a --postgres-url server opened two separate connection pools. Unify on postgres: - @drfed/models: migrate() now uses drizzle-orm/postgres-js. The PostgreSQL credentials mirror the PGlite shape: either a url with optional postgres.Options (a max: 1 client is created and closed after migrating) or an existing Sql client that is left open. - @drfed/drfed: the --postgres-url parser creates one postgres() client and shares it between Drizzle and PostgresKvStore through credentials.client. - Drop pg and @types/pg from both packages and the catalog; move postgres into the catalog. - Prune the orphaned pg-related entries from pnpm-lock.yaml, which pnpm did not remove on its own because drizzle-orm still lists pg as an optional peer. drizzle-orm/pg-core imports are the Drizzle dialect module, not the pg package, and remain. Verified with mise run check/build/test, a PGlite startup, and a PostgreSQL 17 container (migrations applied, GraphQL responding). AI assistance: Claude Code drafted the driver replacement, the credential type changes, the lockfile pruning, and this message; the result was reviewed and verified by a human. Assisted-by: Claude Code:claude-fable-5-1
createYogaServer() used to register the ActivityPub dispatchers on the Federation instance it received, so calling it twice with the same instance crashed with a RouterError from Fedify's duplicate-route check. The constraint was not documented on the option either. Move the registration into the Federation factory itself: - packages/graphql/src/federation.ts now exports buildFederation(), which registers every dispatcher and listener on a fresh Fedify FederationBuilder, and a default createFederation() that builds the Federation from that builder with the given options. - createYogaServer() takes the built Federation as its second positional argument and only stores it in the resolver context. The federation field is removed from YogaServerOptions, which is optional again. - @drfed/graphql exposes the ./federation subpath so that @drfed/drfed can build the Federation at startup. - The test harness builds a Federation with MemoryKvStore and exposes it; federation.test.ts checks the URI layout, that one builder yields independent instances, and that createYogaServer() no longer mutates the instance it is given. This commit was produced with AI assistance. The approach (moving the dispatcher registration to the Federation factory and using Fedify's FederationBuilder) and the second-positional-argument signature were chosen by the human author; the assistant wrote the code, tests, and documentation updates, and verified them with the mise check, build, and test tasks and a manual server start. Assisted-by: Claude Code:claude-fable-5-1
Apply the fixes from an AI-assisted code review of the create-actor branch (fedify-dev#47). - Mount the Fedify federation in front of the Yoga server. The srvx fetch handler now calls federation.fetch() and falls back to Yoga via onNotFound and onNotAcceptable, so the actor, WebFinger, inbox, and collection dispatchers are reachable over HTTP instead of only serving as URI builders. - Close the database client (PGlite or postgres.js) during shutdown before exiting the process. - Serialize genActors per instance by locking the local_instances row with SELECT ... FOR UPDATE before counting actors. This closes the race where two concurrent calls both passed the maxActors check under READ COMMITTED. The post-insert recount, tx.rollback(), the outer mutable state, and the string-matched catch block are gone; every error path now returns a result object directly from the transaction callback. - Derive the instance host from local_instances.slug and ctx.root rather than reading instances.host. - Generate one UUID per actor and reuse it for local_actors.id, actors.id, actors.localId, and the placeholder username, dropping the .returning() round trip on the local_actors insert. - Push the local-actor and host predicates into the Drizzle relational where clause in findLocalActor and mapHandle, replacing the full-row load plus JS filtering and the two-step instance/actor lookup. - Import hashSecret from auth/hash.ts in actor.test.ts and instance.test.ts instead of keeping duplicate copies. Provenance: the contributor asked Claude Code to review the branch against the pull request comments and the overall diff. Claude Code produced the review findings (unmounted federation routes, the maxActors race, the unclosed database client, the duplicated lookups, and the duplicated test helper) and drafted this commit message. The contributor implemented every change in this commit by hand and verified them with mise run check and the package test suites. Assisted-by: Claude Code:claude-fable-5-1
a961d0c to
d51f8c4
Compare
Code review objected to abbreviated identifiers. Rename the `Mutation.genActors` field to `Mutation.generateActors` and update the test's operation name, query constant, and assertions to match. The rename was applied by a subagent and reviewed by the assistant, which also caught the remaining `GenActors` operation name in the test. fedify-dev#47 (comment) Assisted-by: Claude Code:claude-fable-5-1
Resolved! |
Resolves #6.
Add
actorstable and actor GraphQL API to create and reads.To merge this PR, #44 must be preceded.
Before starting the server, please remove the existing
.pgdata/to clean up the DB.Assisted-by: Codex:gpt-5-6-sol to generate test and implement merging
remoteActorsintoactors.Assisted-by: Claude Code:claude-fable-5 to plan merging
remoteActorsintoactors.