diff --git a/docs/app/assets/icons/entire.svg b/docs/app/assets/icons/entire.svg
new file mode 100644
index 000000000..aeec67d4c
--- /dev/null
+++ b/docs/app/assets/icons/entire.svg
@@ -0,0 +1,3 @@
+
diff --git a/docs/content/4.providers/entire.md b/docs/content/4.providers/entire.md
new file mode 100644
index 000000000..b5a99d95a
--- /dev/null
+++ b/docs/content/4.providers/entire.md
@@ -0,0 +1,233 @@
+---
+title: Entire
+description: "Use Entire: repositories, file contents, commits, and trails as issues and pull requests, with their discussions, approvals and code reviews."
+icon: i-custom-entire
+---
+
+The Entire provider works with the repositories that [Entire](https://entire.com) hosts, which live under `et/` in Entire's URLs. Entire organises work into trails: a project trail describes a piece of work, and each repository the work touches gets a change, a branch with its own discussions, approvals and code reviews. The provider reads project trails as issues and changes as pull requests, and normalises both into the shared [data model](/concepts/data-model).
+
+
+
+::provider-header{provider="entire"}
+| Detail | Value |
+| --- | --- |
+| Import | `forges/entire` |
+| Factories | `entire()` |
+| Auth | `token` |
+::
+
+
+## Authentication
+
+Entire has no personal access tokens. Sign in with the [Entire CLI](https://docs.entire.com/) and run `entire auth token` to print a token for your account. The provider sends it as `Authorization: Bearer `:
+
+```ts
+import { entire } from 'forges/entire'
+
+const forge = entire({
+ auth: { type: 'token', token: process.env.ENTIRE_TOKEN! },
+})
+```
+
+The token expires after a few hours. For a long-running program, pass `token` as a function, and the provider calls it before every request:
+
+```ts
+import { execFileSync } from 'node:child_process'
+import { entire } from 'forges/entire'
+
+const forge = entire({
+ auth: { type: 'token', token: () => execFileSync('entire', ['auth', 'token'], { encoding: 'utf8' }).trim() },
+})
+```
+
+Entire has no anonymous reads, so every call needs a token.
+
+## Instance
+
+The `baseUrl` is Entire's control plane and defaults to `https://us.console.entire.io`. The control plane knows which processing cell holds each project and repository, such as `https://aws-eu-west-2.api.entire.io` for a project in the UK, and the provider sends every request about a project's repositories and trails to that cell. It looks up each cell once and keeps it for the life of the provider. An account with access to a repository but not to its project reaches the project's trails through the repository's cell.
+
+A repository ref's `owner` is the project and its `name` is the repository, so `https://entire.com/et/forges/forges-fixtures` is `{ owner: 'forges', name: 'forges-fixtures' }`. The provider reads only repositories that Entire hosts. A GitHub repository that Entire mirrors under `gh/` is a GitHub repository, so read it with the [GitHub provider](/providers/github).
+
+## Repositories and contents
+
+`repos.get()` reads a repository, `repos.list()` lists the repositories your account can reach, and `repos.byOwner()` lists the repositories of a project. A project is the owner, so a user's login names no repositories of its own. `repos.collaborators()` lists the accounts with access to a repository and their roles.
+
+`contents.getFile()`, `contents.tree()`, `contents.branches()`, `contents.tags()`, `contents.resolveRef()` and `contents.compare()` read the repository's git data, and so do `commits.list()` and `commits.get()`. `contents.compare()` compares `head` with the merge base of the two revisions, so its files are the changes that `head` adds.
+
+Entire has no labels, milestones, issue types or releases.
+
+## Trails
+
+A project trail is an issue. Its ref names the project rather than a repository, with a namespace ref as `repo`:
+
+```ts
+const issue = await forge.threads.get({
+ forge: 'entire',
+ instance: 'entire.com',
+ repo: { forge: 'entire', instance: 'entire.com', owner: 'forges', name: '', kind: 'namespace' },
+ kind: 'issue',
+ number: '1',
+})
+```
+
+`threads.list()` on a repository lists the project trails of the repository's project as issues and the repository's changes as pull requests. On a namespace ref, it lists the project trails alone. A project trail's `type`, such as `feature` or `bug`, is the thread's `issueType`.
+
+A change is a pull request, numbered within its repository. `threads.files()` and `threads.commits()` read the change's branch against its merge base. `threads.reviews()` lists the change's approvals and its code reviews. An approval has no comments of its own, and a code review's comments are its findings, with the file and line each one is about. `threads.reviewThreads()` reads each finding with the discussion under it.
+
+`threads.comments()` reads the messages of a trail's discussions, oldest first, with each comment's `thread` naming its discussion and whether it's resolved. The discussions of code review findings are left out, because `threads.reviewThreads()` reads those. `threads.events()` reads the trail's timeline: comments, approvals, code reviews, commits, and the trail opening and merging.
+
+A project trail's page is `https://entire.com/et//-/trails/`, and a change's page is `https://entire.com/et///trails/`. `urlFor()` builds both, and `parseUrl()` reads them back.
+
+## Writing trails
+
+`threads.create()` with `kind: 'issue'` opens a project trail. On a repository ref, the trail's scope is that repository, and on a namespace ref, it is the whole project. With `kind: 'pull_request'`, it opens a project trail with a change from `head` into `base`, and resolves with the change. Entire keeps a change's description on its project trail, so `body` is the trail's body. `threads.get()` reads a change's body from its trail, and `threads.update()` writes it there.
+
+`threads.update()`, `threads.close()`, `threads.reopen()` and `threads.setAssignees()` change a project trail or a change. Closing a project trail leaves its changes open. Pass assignees as actors, or as logins, which the provider sends as `@`.
+
+`threads.createComment()` starts a discussion with the comment as its first message. `threads.updateComment()` and `threads.deleteComment()` edit and delete a message, and read the trail's discussions to find the one that holds it.
+
+`threads.createReview()` with `event: 'approve'` or `event: 'request_changes'` submits an approval, which has a body but no inline comments. With `event: 'comment'`, it starts a code review and publishes each inline comment as a finding, with `body` as the review's summary. A review with a body and no comments publishes the body as a finding on the whole change. Entire has no pending reviews, and a finding is always on the head side of the diff. `threads.replyToReviewComment()` replies in the finding's discussion, and `threads.resolveReviewThread()` and `threads.unresolveReviewThread()` resolve and reopen that discussion.
+
+`threads.merge()` merges a change once its gates pass, with `method: 'merge'` only. Pass `sha`, and Entire refuses the merge with `BranchMovedError` if the head branch has moved. `threads.updateBranch()` merges the base into the change's branch, or rebases the branch onto the base with `method: 'rebase'`.
+
+`repos.addCollaborator()` grants an account the `read`, `write` or `admin` role on a repository. Entire grants access by account id, so pass the account as an actor.
+
+## Accounts
+
+`users.me()` reads the account the token belongs to. Entire has no way to read another account by login, and no sponsorship, follow feeds or repository events.
+
+
+## Capabilities
+
+
+::provider-capabilities{provider="entire"}
+| Capability | Support | Without credentials |
+| --- | --- | --- |
+| `experimental` | ✅ | ✅ |
+| `sources.stream` | ❌ | ❌ |
+| `repos.get` | ✅ | ❌ |
+| `users.get` | ❌ | ❌ |
+| `users.me` | ✅ | ❌ |
+| `users.sponsorable` | ❌ | ❌ |
+| `users.events` | ❌ | ❌ |
+| `users.block` | ❌ | ❌ |
+| `repos.list` | ✅ | ❌ |
+| `repos.byOwner` | ✅ | ❌ |
+| `repos.labels` | ∅ | ∅ |
+| `repos.createLabel` | ∅ | ∅ |
+| `repos.milestones` | ∅ | ∅ |
+| `repos.issueTypes` | ∅ | ∅ |
+| `repos.collaborators` | ✅ | ❌ |
+| `repos.contributors` | ❌ | ❌ |
+| `repos.contributorStats` | ❌ | ❌ |
+| `repos.templates` | ❌ | ❌ |
+| `repos.permissionFor` | ❌ | ❌ |
+| `repos.branchRules` | ❌ | ❌ |
+| `repos.addCollaborator` | ✅ | ❌ |
+| `repos.assignableUsers` | ❌ | ❌ |
+| `repos.reviewerCandidates` | ❌ | ❌ |
+| `repos.events` | ❌ | ❌ |
+| `orgs.teams` | ❌ | ❌ |
+| `orgs.teamMembers` | ❌ | ❌ |
+| `orgs.block` | ❌ | ❌ |
+| `threads.get` | issue, PR | ❌ |
+| `threads.list` | issue, PR | ❌ |
+| `threads.getMany` | ✅ | ❌ |
+| `threads.closingIssues` | ❌ | ❌ |
+| `threads.closingPullRequests` | ❌ | ❌ |
+| `threads.events` | issue, PR | ❌ |
+| `threads.comments` | issue, PR | ❌ |
+| `threads.getComment` | issue, PR | ❌ |
+| `threads.createComment` | issue, PR | ❌ |
+| `threads.updateComment` | issue, PR | ❌ |
+| `threads.deleteComment` | issue, PR | ❌ |
+| `threads.hideComment` | ∅ | ∅ |
+| `threads.reactions` | ∅ | ∅ |
+| `threads.react` | ∅ | ∅ |
+| `threads.create` | issue, PR | ❌ |
+| `threads.update` | issue, PR | ❌ |
+| `threads.close` | issue, PR | ❌ |
+| `threads.reopen` | issue, PR | ❌ |
+| `threads.lock` | ∅ | ∅ |
+| `threads.setLabels` | ∅ | ∅ |
+| `threads.addLabels` | ∅ | ∅ |
+| `threads.removeLabels` | ∅ | ∅ |
+| `threads.setMilestone` | ∅ | ∅ |
+| `threads.setIssueType` | ∅ | ∅ |
+| `threads.setAssignees` | issue, PR | ❌ |
+| `threads.requestReview` | ❌ | ❌ |
+| `threads.merge` | ✅ | ❌ |
+| `threads.updateBranch` | ✅ | ❌ |
+| `threads.transfer` | ∅ | ∅ |
+| `threads.markDuplicate` | ∅ | ∅ |
+| `threads.subscription` | ❌ | ❌ |
+| `threads.subscribe` | ❌ | ❌ |
+| `threads.checks` | ❌ | ❌ |
+| `threads.files` | ✅ | ❌ |
+| `threads.commits` | ✅ | ❌ |
+| `threads.reviews` | ✅ | ❌ |
+| `threads.getReview` | ✅ | ❌ |
+| `threads.createReview` | ✅ | ❌ |
+| `threads.submitReview` | ❌ | ❌ |
+| `threads.approve` | ✅ | ❌ |
+| `threads.replyToReviewComment` | ✅ | ❌ |
+| `threads.reviewThreads` | ✅ | ❌ |
+| `threads.resolveReviewThread` | ✅ | ❌ |
+| `notifications.list` | ❌ | ❌ |
+| `notifications.markRead` | ❌ | ❌ |
+| `notifications.markDone` | ❌ | ❌ |
+| `notifications.unsubscribe` | ❌ | ❌ |
+| `notifications.markAllRead` | ❌ | ❌ |
+| `notifications.markAllDone` | ❌ | ❌ |
+| `notifications.unreadCount` | ❌ | ❌ |
+| `installations.list` | ❌ | ❌ |
+| `checks.list` | ❌ | ❌ |
+| `checks.get` | ❌ | ❌ |
+| `checks.report` | ❌ | ❌ |
+| `checks.rerun` | ❌ | ❌ |
+| `ci.runs` | ❌ | ❌ |
+| `ci.getRun` | ❌ | ❌ |
+| `ci.jobs` | ❌ | ❌ |
+| `ci.getJob` | ❌ | ❌ |
+| `ci.log` | ❌ | ❌ |
+| `ci.rerunFailed` | ❌ | ❌ |
+| `contents.getFile` | ✅ | ❌ |
+| `contents.tree` | ✅ | ❌ |
+| `contents.branches` | ✅ | ❌ |
+| `contents.tags` | ✅ | ❌ |
+| `contents.resolveRef` | ✅ | ❌ |
+| `contents.compare` | ✅ | ❌ |
+| `contents.commitFiles` | ❌ | ❌ |
+| `commits.list` | ✅ | ❌ |
+| `commits.get` | ✅ | ❌ |
+| `commits.comments` | ❌ | ❌ |
+| `commits.getComment` | ❌ | ❌ |
+| `commits.createComment` | ❌ | ❌ |
+| `commits.updateComment` | ❌ | ❌ |
+| `commits.deleteComment` | ❌ | ❌ |
+| `commits.hideComment` | ❌ | ❌ |
+| `releases.list` | ∅ | ∅ |
+| `releases.get` | ∅ | ∅ |
+| `releases.latest` | ∅ | ∅ |
+| `releases.getByTag` | ∅ | ∅ |
+| `releases.downloadAsset` | ∅ | ∅ |
+| `releases.create` | ∅ | ∅ |
+| `releases.update` | ∅ | ∅ |
+| `webhooks.ingest` | ❌ | ❌ |
+| `webhooks.list` | ❌ | ❌ |
+| `webhooks.create` | ❌ | ❌ |
+| `webhooks.update` | ❌ | ❌ |
+| `webhooks.delete` | ❌ | ❌ |
+| `webhooks.rotateSecret` | ❌ | ❌ |
+| `webhooks.deliveries` | ❌ | ❌ |
+| `webhooks.redeliver` | ❌ | ❌ |
+| `search.threads` | ❌ | ❌ |
+| `search.repos` | ❌ | ❌ |
+| `search.commits` | ❌ | ❌ |
+| `search.code` | ❌ | ❌ |
+| `securityAlerts.list` | ❌ | ❌ |
+| `eventKinds` | native | native |
+| `authKinds` | `token` | `token` |
+| `limits` | unknown | unknown |
+::
+
diff --git a/docs/content/4.providers/index.md b/docs/content/4.providers/index.md
index 085955a6d..2bf47bf9c 100644
--- a/docs/content/4.providers/index.md
+++ b/docs/content/4.providers/index.md
@@ -26,6 +26,7 @@ A provider is what connects `forges` to one particular forge. It translates the
| [Tangled](/providers/tangled) | `forges/tangled` | `tangled()` |
| [Cloudflare Artifacts](/providers/cloudflare-artifacts) | `forges/cloudflare-artifacts` | `cloudflareArtifacts()` |
| [Sourcehut](/providers/sourcehut) | `forges/sourcehut` | `sourcehut()` |
+| [Entire](/providers/entire) | `forges/entire` | `entire()` |
::
diff --git a/docs/content/5.reference/11.capability-matrix.md b/docs/content/5.reference/11.capability-matrix.md
index 93f6dd875..78a5fb172 100644
--- a/docs/content/5.reference/11.capability-matrix.md
+++ b/docs/content/5.reference/11.capability-matrix.md
@@ -11,134 +11,134 @@ To learn what each level of support means, see [Capabilities](/concepts/capabili
::capability-matrix
-| Capability | GitHub | GitLab | Bitbucket | Forgejo | Gitea | Gitee | Azure DevOps | Cursor Origin | Pushin.eu | Tangled | Cloudflare Artifacts | Sourcehut |
-| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
-| `experimental` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| `sources.stream` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
-| `repos.get` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ✅ | ✅ | ✅ | ✅ |
-| `users.get` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
-| `users.me` | ✅ (only with `classic` or `fine-grained`) | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
-| `users.sponsorable` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `users.events` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
-| `users.block` | ✅ (only with `classic` or `fine-grained`) | ❌ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `repos.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ✅ | ✅ | ✅ | ✅ |
-| `repos.byOwner` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
-| `repos.labels` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | emulated |
-| `repos.createLabel` | ✅ | ✅ | ∅ | ✅ | ✅ | unverified | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
-| `repos.milestones` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.issueTypes` | ✅ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `repos.collaborators` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
-| `repos.contributors` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.contributorStats` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.templates` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `repos.permissionFor` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | emulated |
-| `repos.branchRules` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.addCollaborator` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
-| `repos.assignableUsers` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.reviewerCandidates` | emulated | emulated | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `repos.events` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `orgs.teams` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
-| `orgs.teamMembers` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
-| `orgs.block` | ✅ | ❌ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `threads.get` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR |
-| `threads.list` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR |
-| `threads.getMany` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ✅ | ✅ | ❌ | ✅ |
-| `threads.closingIssues` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `threads.closingPullRequests` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `threads.events` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue (emulated), PR (emulated) | issue, PR | ❌ | issue, PR |
-| `threads.comments` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue (experimental), PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR |
-| `threads.getComment` | issue, PR, discussion | issue, PR | PR (emulated) | issue, PR | issue, PR | issue (emulated), PR (emulated) | issue (experimental), PR | PR (unverified) | issue (emulated), PR (emulated) | issue (emulated), PR (emulated) | ❌ | issue (emulated), PR (emulated) |
-| `threads.createComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue (experimental), PR | PR (experimental) | issue, PR | issue, PR | ❌ | ❌ |
-| `threads.updateComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue (experimental), PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.deleteComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue (experimental), PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.hideComment` | issue, PR, discussion | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `threads.reactions` | issue, PR, discussion | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | ❌ |
-| `threads.react` | issue, PR, discussion | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ |
-| `threads.create` | issue, PR | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue, PR | PR (unverified) | issue, PR | issue, PR | ❌ | ❌ |
-| `threads.update` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.close` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (experimental) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.reopen` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (experimental) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.lock` | issue, PR, discussion | issue, PR | ∅ | ∅ | issue, PR | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `threads.setLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue, PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ |
-| `threads.addLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ |
-| `threads.removeLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ |
-| `threads.setMilestone` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
-| `threads.setIssueType` | issue | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `threads.setAssignees` | issue, PR | issue, PR | ❌ | issue, PR | issue, PR | issue (unverified) | issue | ❌ | ∅ | issue, PR | ❌ | ❌ |
-| `threads.requestReview` | PR | PR | PR | PR | PR | PR (unverified) | PR | PR (unverified) | ∅ | ∅ | ❌ | ❌ |
-| `threads.merge` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ∅ | ✅ | ❌ | ❌ |
-| `threads.updateBranch` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ |
-| `threads.transfer` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
-| `threads.markDuplicate` | ✅ | emulated | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
-| `threads.subscription` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | issue |
-| `threads.subscribe` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | ❌ |
-| `threads.checks` | PR | PR | PR | PR | PR | PR | PR (experimental) | PR (experimental) | ❌ | ∅ | ❌ | ❌ |
-| `threads.files` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | unverified | ❌ | ✅ | ❌ | ❌ |
-| `threads.commits` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ❌ |
-| `threads.reviews` | ✅ | emulated | emulated | ✅ | ✅ | ∅ | emulated | experimental | ✅ | ∅ | ❌ | ❌ |
-| `threads.getReview` | ✅ | emulated | emulated | ✅ | ✅ | ∅ | emulated | unverified | ✅ | ∅ | ❌ | ❌ |
-| `threads.createReview` | ✅ | emulated | emulated | ✅ | ✅ | unverified | emulated | unverified | ❌ | ∅ | ❌ | ❌ |
-| `threads.submitReview` | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `threads.approve` | ✅ | ✅ | emulated | ✅ | ✅ | unverified | emulated | unverified | ❌ | ∅ | ❌ | ❌ |
-| `threads.replyToReviewComment` | ✅ | ✅ | ❌ | ∅ | ✅ | ❌ | ❌ | ❌ | ✅ | ∅ | ❌ | ❌ |
-| `threads.reviewThreads` | ✅ | ✅ | ❌ | emulated | emulated | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `threads.resolveReviewThread` | ✅ | ✅ | ❌ | ∅ | ∅ | ❌ | ✅ | unverified | ✅ | ∅ | ❌ | ❌ |
-| `notifications.list` | ✅ (only with `classic`) | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | experimental | ❌ | ❌ |
-| `notifications.markRead` | ✅ (only with `classic`) | ❌ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ∅ | experimental | ❌ | ❌ |
-| `notifications.markDone` | ✅ (only with `classic`) | ✅ | ∅ | emulated | emulated | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
-| `notifications.unsubscribe` | ✅ (only with `classic`) | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
-| `notifications.markAllRead` | ✅ (only with `classic`) | ❌ | ∅ | ✅ | ✅ | unverified | ❌ | ∅ | ∅ | unverified | ❌ | ❌ |
-| `notifications.markAllDone` | ❌ | ✅ | ∅ | ❌ | ❌ | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
-| `notifications.unreadCount` | ❌ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ❌ | unverified | ❌ | ❌ |
-| `installations.list` | ✅ (only with `installation` or `app`) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | unverified (only with `app`) | ❌ | ❌ | ❌ | ❌ |
-| `checks.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ∅ | ❌ | ❌ |
-| `checks.get` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `checks.report` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `checks.rerun` | ✅ | ✅ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `ci.runs` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated |
-| `ci.getRun` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
-| `ci.jobs` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
-| `ci.getJob` | ✅ | ✅ | ❌ | emulated | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated |
-| `ci.log` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
-| `ci.rerunFailed` | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `contents.getFile` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ |
-| `contents.tree` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ |
-| `contents.branches` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ✅ |
-| `contents.tags` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ✅ |
-| `contents.resolveRef` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ |
-| `contents.compare` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ❌ |
-| `contents.commitFiles` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ |
-| `commits.get` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ |
-| `commits.comments` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.getComment` | ✅ | emulated | emulated | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.createComment` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.updateComment` | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.deleteComment` | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `commits.hideComment` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `releases.list` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated |
-| `releases.get` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated |
-| `releases.latest` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated |
-| `releases.getByTag` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated |
-| `releases.downloadAsset` | ✅ | ❌ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated |
-| `releases.create` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | ❌ |
-| `releases.update` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | ❌ |
-| `webhooks.ingest` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ∅ | ✅ | ❌ | ✅ |
-| `webhooks.list` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.create` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.update` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.delete` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.rotateSecret` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.deliveries` | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `webhooks.redeliver` | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ |
-| `search.threads` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `search.repos` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated |
-| `search.commits` | ✅ | experimental | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-| `search.code` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | experimental |
-| `securityAlerts.list` | dependency, code scanning, secret, advisory | dependency, code scanning, secret | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ |
-| `eventKinds` | native | heuristic | native | native | native | native | native | native | native | native | native | native |
-| `authKinds` | `token`, `app`, `anonymous` | `token`, `anonymous` | `token`, `basic`, `anonymous` | `token`, `anonymous` | `token`, `anonymous` | `token`, `anonymous` | `token`, `basic`, `anonymous` | `token`, `app`, `anonymous` | `token`, `anonymous` | `anonymous`, `app_password`, `oauth` | `token` | `token` |
-| `limits` | body 65536, comment 65536, label 50 | body 1048576, comment 1000000, label 255 | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown |
+| Capability | GitHub | GitLab | Bitbucket | Forgejo | Gitea | Gitee | Azure DevOps | Cursor Origin | Pushin.eu | Tangled | Cloudflare Artifacts | Sourcehut | Entire |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| `experimental` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
+| `sources.stream` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `repos.get` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `users.get` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
+| `users.me` | ✅ (only with `classic` or `fine-grained`) | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ |
+| `users.sponsorable` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `users.events` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `users.block` | ✅ (only with `classic` or `fine-grained`) | ❌ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `repos.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `repos.byOwner` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `repos.labels` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | emulated | ∅ |
+| `repos.createLabel` | ✅ | ✅ | ∅ | ✅ | ✅ | unverified | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ∅ |
+| `repos.milestones` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ |
+| `repos.issueTypes` | ✅ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ |
+| `repos.collaborators` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ |
+| `repos.contributors` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `repos.contributorStats` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `repos.templates` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `repos.permissionFor` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | emulated | ❌ |
+| `repos.branchRules` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `repos.addCollaborator` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
+| `repos.assignableUsers` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `repos.reviewerCandidates` | emulated | emulated | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `repos.events` | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `orgs.teams` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `orgs.teamMembers` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `orgs.block` | ✅ | ❌ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `threads.get` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR | issue, PR |
+| `threads.list` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR | issue, PR |
+| `threads.getMany` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ✅ | ✅ | ❌ | ✅ | ✅ |
+| `threads.closingIssues` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `threads.closingPullRequests` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `threads.events` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue, PR | PR (experimental) | issue (emulated), PR (emulated) | issue, PR | ❌ | issue, PR | issue, PR |
+| `threads.comments` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue, PR | issue (experimental), PR | PR (experimental) | issue, PR | issue, PR | ❌ | issue, PR | issue, PR |
+| `threads.getComment` | issue, PR, discussion | issue, PR | PR (emulated) | issue, PR | issue, PR | issue (emulated), PR (emulated) | issue (experimental), PR | PR (unverified) | issue (emulated), PR (emulated) | issue (emulated), PR (emulated) | ❌ | issue (emulated), PR (emulated) | issue, PR |
+| `threads.createComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue (experimental), PR | PR (experimental) | issue, PR | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.updateComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue (experimental), PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.deleteComment` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue (experimental), PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.hideComment` | issue, PR, discussion | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ |
+| `threads.reactions` | issue, PR, discussion | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | ❌ | ∅ |
+| `threads.react` | issue, PR, discussion | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ | ∅ |
+| `threads.create` | issue, PR | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue, PR | PR (unverified) | issue, PR | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.update` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.close` | issue, PR, discussion | issue, PR | PR | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (experimental) | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.reopen` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | issue (unverified), PR | issue, PR | PR (experimental) | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.lock` | issue, PR, discussion | issue, PR | ∅ | ∅ | issue, PR | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ |
+| `threads.setLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | issue, PR | PR (unverified) | ∅ | issue, PR | ❌ | ❌ | ∅ |
+| `threads.addLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ | ∅ |
+| `threads.removeLabels` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | issue (unverified), PR (unverified) | ❌ | ❌ | ∅ | issue, PR | ❌ | ❌ | ∅ |
+| `threads.setMilestone` | issue, PR | issue, PR | ∅ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ | ∅ |
+| `threads.setIssueType` | issue | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ | ∅ | ∅ | ❌ | ❌ | ∅ |
+| `threads.setAssignees` | issue, PR | issue, PR | ❌ | issue, PR | issue, PR | issue (unverified) | issue | ❌ | ∅ | issue, PR | ❌ | ❌ | issue, PR |
+| `threads.requestReview` | PR | PR | PR | PR | PR | PR (unverified) | PR | PR (unverified) | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `threads.merge` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | experimental | ∅ | ✅ | ❌ | ❌ | ✅ |
+| `threads.updateBranch` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ❌ | ❌ | ✅ |
+| `threads.transfer` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ | ∅ |
+| `threads.markDuplicate` | ✅ | emulated | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ | ∅ |
+| `threads.subscription` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | issue | ❌ |
+| `threads.subscribe` | issue, PR, discussion | issue, PR | ❌ | issue, PR | issue, PR | ❌ | ❌ | ❌ | ❌ | issue, PR | ❌ | ❌ | ❌ |
+| `threads.checks` | PR | PR | PR | PR | PR | PR | PR (experimental) | PR (experimental) | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `threads.files` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | unverified | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `threads.commits` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `threads.reviews` | ✅ | emulated | emulated | ✅ | ✅ | ∅ | emulated | experimental | ✅ | ∅ | ❌ | ❌ | ✅ |
+| `threads.getReview` | ✅ | emulated | emulated | ✅ | ✅ | ∅ | emulated | unverified | ✅ | ∅ | ❌ | ❌ | ✅ |
+| `threads.createReview` | ✅ | emulated | emulated | ✅ | ✅ | unverified | emulated | unverified | ❌ | ∅ | ❌ | ❌ | ✅ |
+| `threads.submitReview` | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `threads.approve` | ✅ | ✅ | emulated | ✅ | ✅ | unverified | emulated | unverified | ❌ | ∅ | ❌ | ❌ | ✅ |
+| `threads.replyToReviewComment` | ✅ | ✅ | ❌ | ∅ | ✅ | ❌ | ❌ | ❌ | ✅ | ∅ | ❌ | ❌ | ✅ |
+| `threads.reviewThreads` | ✅ | ✅ | ❌ | emulated | emulated | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ✅ |
+| `threads.resolveReviewThread` | ✅ | ✅ | ❌ | ∅ | ∅ | ❌ | ✅ | unverified | ✅ | ∅ | ❌ | ❌ | ✅ |
+| `notifications.list` | ✅ (only with `classic`) | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | experimental | ❌ | ❌ | ❌ |
+| `notifications.markRead` | ✅ (only with `classic`) | ❌ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ∅ | experimental | ❌ | ❌ | ❌ |
+| `notifications.markDone` | ✅ (only with `classic`) | ✅ | ∅ | emulated | emulated | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ | ❌ |
+| `notifications.unsubscribe` | ✅ (only with `classic`) | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ | ❌ |
+| `notifications.markAllRead` | ✅ (only with `classic`) | ❌ | ∅ | ✅ | ✅ | unverified | ❌ | ∅ | ∅ | unverified | ❌ | ❌ | ❌ |
+| `notifications.markAllDone` | ❌ | ✅ | ∅ | ❌ | ❌ | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ | ❌ |
+| `notifications.unreadCount` | ❌ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ❌ | unverified | ❌ | ❌ | ❌ |
+| `installations.list` | ✅ (only with `installation` or `app`) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | unverified (only with `app`) | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `checks.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `checks.get` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `checks.report` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `checks.rerun` | ✅ | ✅ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `ci.runs` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated | ❌ |
+| `ci.getRun` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| `ci.jobs` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| `ci.getJob` | ✅ | ✅ | ❌ | emulated | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated | ❌ |
+| `ci.log` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| `ci.rerunFailed` | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `contents.getFile` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `contents.tree` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `contents.branches` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ✅ | ✅ |
+| `contents.tags` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ✅ | ✅ |
+| `contents.resolveRef` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `contents.compare` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `contents.commitFiles` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.list` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `commits.get` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ✅ | ✅ | ✅ | ✅ |
+| `commits.comments` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.getComment` | ✅ | emulated | emulated | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.createComment` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.updateComment` | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.deleteComment` | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `commits.hideComment` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `releases.list` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated | ∅ |
+| `releases.get` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated | ∅ |
+| `releases.latest` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated | ∅ |
+| `releases.getByTag` | ✅ | ✅ | ∅ | ✅ | ✅ | ✅ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated | ∅ |
+| `releases.downloadAsset` | ✅ | ❌ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | emulated | ∅ |
+| `releases.create` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | ❌ | ∅ |
+| `releases.update` | ✅ | ✅ | ∅ | ✅ | ✅ | ❌ | ❌ | ∅ | ✅ | ∅ | ❌ | ❌ | ∅ |
+| `webhooks.ingest` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ∅ | ✅ | ❌ | ✅ | ❌ |
+| `webhooks.list` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.create` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.update` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.delete` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ❌ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.rotateSecret` | ✅ | ✅ | ✅ | ✅ | ✅ | unverified | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.deliveries` | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `webhooks.redeliver` | ✅ | ❌ | ∅ | ∅ | ∅ | ∅ | ∅ | ❌ | ∅ | ∅ | ❌ | ❌ | ❌ |
+| `search.threads` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `search.repos` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | emulated | ❌ |
+| `search.commits` | ✅ | experimental | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `search.code` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | experimental | ❌ |
+| `securityAlerts.list` | dependency, code scanning, secret, advisory | dependency, code scanning, secret | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ∅ | ❌ | ❌ | ❌ |
+| `eventKinds` | native | heuristic | native | native | native | native | native | native | native | native | native | native | native |
+| `authKinds` | `token`, `app`, `anonymous` | `token`, `anonymous` | `token`, `basic`, `anonymous` | `token`, `anonymous` | `token`, `anonymous` | `token`, `anonymous` | `token`, `basic`, `anonymous` | `token`, `app`, `anonymous` | `token`, `anonymous` | `anonymous`, `app_password`, `oauth` | `token` | `token` | `token` |
+| `limits` | body 65536, comment 65536, label 50 | body 1048576, comment 1000000, label 255 | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown | unknown |
Each cell shows the best support of any credential. Where support depends on the kind of credential, a cell names the credentials that have it, unless they are the forge's usual ones: `classic`, `fine-grained` or `installation` on GitHub and `token` or `installation` on Cursor Origin.
diff --git a/docs/content/5.reference/12.events.md b/docs/content/5.reference/12.events.md
index 0154fc42d..05b02963a 100644
--- a/docs/content/5.reference/12.events.md
+++ b/docs/content/5.reference/12.events.md
@@ -41,7 +41,7 @@ const labels = provider.webhooks.events.some(
| `issue_type` | `typed`, `untyped` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `other` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
-Pushin.eu and Cloudflare Artifacts receive no webhooks.
+Pushin.eu, Cloudflare Artifacts and Entire receive no webhooks.
GitLab reports `eventKinds: 'heuristic'`, so `kind` can be `other` for wording the provider doesn't recognise.
::
diff --git a/docs/content/5.reference/13.environment-variables.md b/docs/content/5.reference/13.environment-variables.md
index c1f1673a5..cf043c972 100644
--- a/docs/content/5.reference/13.environment-variables.md
+++ b/docs/content/5.reference/13.environment-variables.md
@@ -8,7 +8,7 @@ This page lists every variable that `providersFromEnv()` and `forgesFromEnv()` f
## Kinds
-`` is one of `GITHUB`, `GITLAB`, `BITBUCKET`, `FORGEJO`, `GITEA`, `GITEE`, `AZURE_DEVOPS`, `CURSOR_ORIGIN`, `TANGLED`, `PUSHIN`, `CLOUDFLARE_ARTIFACTS` and `SOURCEHUT`.
+`` is one of `GITHUB`, `GITLAB`, `BITBUCKET`, `FORGEJO`, `GITEA`, `GITEE`, `AZURE_DEVOPS`, `CURSOR_ORIGIN`, `TANGLED`, `PUSHIN`, `CLOUDFLARE_ARTIFACTS`, `SOURCEHUT` and `ENTIRE`.
## Fields
@@ -47,7 +47,7 @@ A set of variables creates a provider when it contains one of the credentials th
| Cursor Origin | `TOKEN`, or `APP_ID` and `PRIVATE_KEY` | `INSTALLATION_ID` with `APP_ID` and `PRIVATE_KEY` | Webhooks only with `ENABLED`. Skipped otherwise |
| Tangled | `IDENTIFIER` and `PASSWORD` | `PDS`, `API_URL` and `NOTIFICATIONS_URL` | Anonymous reads |
| Cloudflare Artifacts | `ACCOUNT_ID`, and `TOKEN` | None | Skipped, even with `ENABLED` |
-| Sourcehut | `TOKEN` | None | Skipped, even with `ENABLED` |
+| Sourcehut, Entire | `TOKEN` | None | Skipped, even with `ENABLED` |
::
diff --git a/docs/content/7.contributing/4.record-fixtures.md b/docs/content/7.contributing/4.record-fixtures.md
index 481122a17..5567326f7 100644
--- a/docs/content/7.contributing/4.record-fixtures.md
+++ b/docs/content/7.contributing/4.record-fixtures.md
@@ -109,10 +109,12 @@ A write recording needs:
- to record `orgs.block` and `users.block`, the login of an account outside the scratch repository's organization in `FIXTURE__BLOCKED_USER`. The bot blocks and unblocks it for the organization, which the bot must own, and for itself. On GitHub, the bot's token needs the `admin:org` and `user` scopes;
- on GitHub, [immutable releases](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases) turned on in the scratch repository, so that the recorder can check that GitHub refuses to retag a release.
-Before the steps run, the recorder closes threads that an earlier run left open, and creates three branches with one commit each through the forge's own API. Afterwards it deletes the branches, the label and the webhook, and every release and tag whose name starts with `forges-fixture-`. On Codeberg, which refuses issues and pull requests whose titles are too similar to ones the account opened in the last hour, the title of each thread ends with a random suffix. The recorder can write to GitHub, GitLab, Bitbucket, Forgejo, Gitea, Gitee, Azure DevOps, Pushin.eu and Tangled.
+Before the steps run, the recorder closes threads that an earlier run left open, and creates three branches with one commit each through the forge's own API. Afterwards it deletes the branches, the label and the webhook, and every release and tag whose name starts with `forges-fixture-`. On Codeberg, which refuses issues and pull requests whose titles are too similar to ones the account opened in the last hour, the title of each thread ends with a random suffix. The recorder can write to GitHub, GitLab, Bitbucket, Forgejo, Gitea, Gitee, Azure DevOps, Pushin.eu, Entire and Tangled.
Pushin.eu has no API for branches or tags, so the recorder pushes them over HTTPS as the bot. Its API can't close threads either, so the recorder closes those an earlier run left open by importing them again as closed.
+On Entire, the recorder also pushes the branches over HTTPS as the bot, straight to the storage node that the git endpoint redirects to. Entire's findings gate refuses to merge a change with a finding, so the recorder makes that gate non-blocking in the scratch repository before the steps run.
+
On Tangled, the recorder pushes the branches to the knot over HTTPS. It signs in as the bot with `TANGLED_BOT_IDENTIFIER` and `TANGLED_BOT_PASSWORD`, and `TANGLED_BOT_PDS` when the identifier is a handle on a PDS other than `bsky.social`. A reviewer's records would go into its own account, so the reviewer is a second bot, in `TANGLED_REVIEWER_IDENTIFIER`, `TANGLED_REVIEWER_PASSWORD` and `TANGLED_REVIEWER_PDS`, rather than your account. Before and after the steps, it deletes the issues and pull requests that earlier runs opened, the bot's comments, states, labels and reactions on those and on the pull request in `FIXTURE_TANGLED_SCRATCH_PULL`, an AT-URI, and every label whose name starts with `forges-fixture-`.
The recorder refuses, before sending it, any write outside the scratch and transfer repositories, apart from the bot account's own notifications and its blocks of the account in `FIXTURE__BLOCKED_USER`. On GitHub, a GraphQL mutation may only name ids that a response from those repositories returned. On Tangled, a record may only name the scratch repository, the bot's own records or the scratch pull request.
@@ -152,7 +154,7 @@ pnpm vitest run test/recorded.test.ts -u
## Record on a schedule
-The `fixtures` workflow records every forge each Monday: reads at 04:23 UTC and writes at 06:23 UTC. It opens or updates a pull request per kind, on the `fixtures/reads` or `fixtures/writes` branch, with a commit per forge whose recording changed, and runs CI on it. A forge whose responses didn't change gets no commit. To record now, run the workflow from the **Actions** tab, and choose the kind, the forges and the steps to record.
+The `fixtures` workflow records every forge except Entire each Monday: reads at 04:23 UTC and writes at 06:23 UTC. It opens or updates a pull request per kind, on the `fixtures/reads` or `fixtures/writes` branch, with a commit per forge whose recording changed, and runs CI on it. A forge whose responses didn't change gets no commit. To record now, run the workflow from the **Actions** tab, and choose the kind, the forges and the steps to record.
To record the fixtures of a pull request, name its branch in `ref`. The workflow records with the code on that branch, and opens a pull request into the branch from `fixtures/-`. Only branches of this repository can be recorded.
@@ -353,6 +355,16 @@ pnpm record-fixtures sourcehut
Sourcehut refuses reads without a token, so there is no anonymous recording. The token needs the read grants of `meta.sr.ht`, `git.sr.ht`, `todo.sr.ht`, `lists.sr.ht` and `builds.sr.ht`. To record other threads, set `FIXTURE_SOURCEHUT_REPO` and `FIXTURE_SOURCEHUT_ISSUE`, and for the patchset, `FIXTURE_SOURCEHUT_LIST` and `FIXTURE_SOURCEHUT_PATCHSET`, as patchsets belong to a mailing list rather than a repository. To record a self-hosted instance, set `SOURCEHUT_BASE_URL`.
+### Entire
+
+```sh
+ENTIRE_TOKEN=$(entire auth token) pnpm record-fixtures entire
+```
+
+Entire refuses reads without a token, so there is no anonymous recording. Its tokens expire after 8 hours, so the `fixtures` workflow doesn't record Entire. Record it locally with `node scripts/record-forge.ts entire --kind reads`. The reads go to `forges/forges-fixtures` by default. To record another repository, set `FIXTURE_ENTIRE_REPO`, and to record other trails, set `FIXTURE_ENTIRE_ISSUE` to a project trail number and `FIXTURE_ENTIRE_PULL` to a change number.
+
+To record writes, the bot owns a project with a public scratch repository, such as `forges-scratch/forges-fixtures` in `FIXTURE_ENTIRE_SCRATCH_REPO`, and signs in with its own `entire auth token` in `ENTIRE_BOT_TOKEN`. The reviewer has a grant on the repository but not on the project, so it finds the project's trails through the list of repositories it can read. The recorder leaves private repositories out of every list, so a private scratch repository would leave that list empty.
+
### Tangled
Recording Tangled needs no credentials, so without them the recording goes to `tangled.org-anonymous/`:
diff --git a/docs/shared/explorer.ts b/docs/shared/explorer.ts
index 72d3cf9e9..48af91ada 100644
--- a/docs/shared/explorer.ts
+++ b/docs/shared/explorer.ts
@@ -293,6 +293,18 @@ export const EXPLORER_FORGES: ExplorerForge[] = [
// number is the scdoc issue and a patch call reads the `scdoc` list, which has none.
sample: sample({ owner: '~sircmpwn', name: 'scdoc', number: '6', login: '~sircmpwn', sha: '24be30ef17d5ca0162f11b35258c8fa2413d556a', tag: '1.10.1', releaseId: '1.10.1', base: 'master', head: 'master', runId: '1' }),
},
+ {
+ name: 'Entire',
+ factory: 'entire',
+ module: 'entire',
+ load: () => import('forges/entire'),
+ forge: 'entire',
+ instance: 'entire.com',
+ credentials: [token('ENTIRE_TOKEN', false)],
+ blocked: 'Entire\'s processing cells don\'t allow requests from browsers, and Entire has no anonymous reads.',
+ signedIn: true,
+ sample: sample({ owner: 'forges', name: 'forges-fixtures', number: '1', login: 'danielroe', sha: 'c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5', tag: 'v0.1.0', base: 'main', head: 'feature/shout' }),
+ },
]
/** The refs a call takes, built from the form. */
diff --git a/package.json b/package.json
index 7eaaee18a..c60a01bd9 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"./bitbucket": "./src/bitbucket/index.ts",
"./cloudflare-artifacts": "./src/cloudflare-artifacts/index.ts",
"./cursor-origin": "./src/cursor-origin/index.ts",
+ "./entire": "./src/entire/index.ts",
"./env": "./src/env.ts",
"./fake": "./src/fake/index.ts",
"./forgejo": "./src/forgejo/index.ts",
@@ -71,6 +72,7 @@
"./bitbucket": "./dist/bitbucket/index.mjs",
"./cloudflare-artifacts": "./dist/cloudflare-artifacts/index.mjs",
"./cursor-origin": "./dist/cursor-origin/index.mjs",
+ "./entire": "./dist/entire/index.mjs",
"./env": "./dist/env.mjs",
"./fake": "./dist/fake/index.mjs",
"./forgejo": "./dist/forgejo/index.mjs",
diff --git a/scripts/api-docs/config.ts b/scripts/api-docs/config.ts
index 62fe1da72..2a803effc 100644
--- a/scripts/api-docs/config.ts
+++ b/scripts/api-docs/config.ts
@@ -105,6 +105,7 @@ export const PAGES: PageConfig[] = [
forge('Tangled', 'tangled'),
forge('Cloudflare Artifacts', 'cloudflare-artifacts'),
forge('Sourcehut', 'sourcehut'),
+ forge('Entire', 'entire'),
{ title: 'Forges registry', intro: 'A registry routes each ref to the provider it belongs to.', names: ['createForges', 'ProviderFactoryFunction'] },
{ title: 'Registry methods', members: { of: 'Forges' } },
{ title: 'Create from the environment', intro: 'Import from `forges/env`. The variable names are listed under [Environment variables](/reference/environment-variables).', entry: 'forges/env', names: [/./] },
diff --git a/scripts/capabilities.ts b/scripts/capabilities.ts
index 71278952d..2ca209b97 100644
--- a/scripts/capabilities.ts
+++ b/scripts/capabilities.ts
@@ -56,7 +56,7 @@ function withoutCredentials(provider: Forges.ForgeProvider): Forges.ForgeProvide
/** Providers as the matrix and provider pages show them, keyed by their `docs/content/4.providers/.md` page. */
export function matrixProviders(forges: typeof Forges): MatrixProvider[] {
- const { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } = forges
+ const { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, entire, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } = forges
const auth = { type: 'token', token: 'token' } as const
const entry = (slug: string, name: string, factory: (options: O) => Forges.ForgeProvider, options: O, credentials?: CredentialOptions[]): MatrixProvider => {
const exported = Object.keys(forges).find(key => (forges as Record)[key] === factory)!
@@ -97,6 +97,7 @@ export function matrixProviders(forges: typeof Forges): MatrixProvider[] {
entry('tangled', 'Tangled', tangled, { auth: { type: 'app_password', identifier: 'handle', password: 'password' }, notificationsUrl: 'https://notifications.example' }),
entry('cloudflare-artifacts', 'Cloudflare Artifacts', cloudflareArtifacts, { auth, accountId: 'acme' }),
entry('sourcehut', 'Sourcehut', sourcehut, { auth }),
+ entry('entire', 'Entire', entire, { auth }),
]
}
diff --git a/scripts/credential-steps.ts b/scripts/credential-steps.ts
index acb74d4a8..37923771b 100644
--- a/scripts/credential-steps.ts
+++ b/scripts/credential-steps.ts
@@ -230,6 +230,15 @@ export const credentialSteps: Record> =
},
],
},
+ 'entire': {
+ TOKEN: [
+ {
+ text: 'Sign in with the [Entire CLI](https://docs.entire.com/) and run `entire auth token` to print a token. It expires after a few hours, so a long-running program should pass `token` as a function that reads a fresh one.',
+ sources: ['https://docs.entire.com/', 'docs/content/4.providers/entire.md'],
+ verified: '2026-10-26',
+ },
+ ],
+ },
'tangled': {
'IDENTIFIER+PASSWORD': [
{
diff --git a/scripts/exercised.ts b/scripts/exercised.ts
index 03e1549da..a16cc3530 100644
--- a/scripts/exercised.ts
+++ b/scripts/exercised.ts
@@ -1,7 +1,7 @@
import type { ForgeCapabilities, ForgeProvider } from '../src/index.ts'
import type { VerbRun } from '../test/setup/verbs-directory.ts'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
-import { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } from '../src/index.ts'
+import { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, entire, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } from '../src/index.ts'
import { VERB_RUN } from '../test/setup/verbs-directory.ts'
import { withDeclared } from './capabilities.ts'
@@ -56,6 +56,7 @@ const checked: Array<[string, ForgeProvider]> = [
['pushin', pushin({ auth })],
['cloudflare-artifacts', cloudflareArtifacts({ auth, accountId: 'acme' })],
['sourcehut', sourcehut({ auth })],
+ ['entire', entire({ auth })],
['github:anonymous', github({})],
['gitlab:anonymous', gitlab({})],
['bitbucket:anonymous', bitbucket({})],
diff --git a/scripts/fixtures-environments.ts b/scripts/fixtures-environments.ts
index 75dd66f5f..6636bd7b0 100644
--- a/scripts/fixtures-environments.ts
+++ b/scripts/fixtures-environments.ts
@@ -10,6 +10,7 @@
* `--reviewer` (you, by default) to approve each run. Both deploy from
* `main` only. The variables of each forge are stored as one `FIXTURES_ENV`
* secret, in `.env` format.
+ * Forges that record only locally get no environment.
* `--forge`, which repeats, limits the run to the environments of those forges.
* `--dry-run` prints the environments and the names of their variables.
*/
@@ -31,6 +32,7 @@ const PREFIXES: Record = {
'tangled': ['TANGLED_', 'FIXTURE_TANGLED_'],
'cloudflare-artifacts': ['CLOUDFLARE_ARTIFACTS_', 'FIXTURE_CLOUDFLARE_ARTIFACTS_'],
'sourcehut': ['SOURCEHUT_', 'FIXTURE_SOURCEHUT_'],
+ 'entire': ['ENTIRE_', 'FIXTURE_ENTIRE_'],
}
/** Variables only write recordings and seeding use: the bot accounts and the Tangled reviewer. */
@@ -56,7 +58,7 @@ const unknown = values.forge?.filter(forge => !(forge in PLANS)) ?? []
if (unknown.length) {
throw new Error(`Unknown forge: ${unknown.join(', ')}. Choose from ${Object.keys(PLANS).join(', ')}`)
}
-const forges = Object.entries(PLANS).filter(([forge]) => !values.forge || values.forge.includes(forge))
+const forges = Object.entries(PLANS).filter(([forge, plan]) => !plan.local && (!values.forge || values.forge.includes(forge)))
const env = parseEnv(readFileSync(values['env-file'], 'utf8')) as Record
const dry = values['dry-run']
const reviewer = dry ? 0 : Number(gh(['api', values.reviewer ? `users/${values.reviewer}` : 'user', '--jq', '.id']).trim())
diff --git a/scripts/record-fixtures.ts b/scripts/record-fixtures.ts
index e5eab8cf6..e56b901a7 100644
--- a/scripts/record-fixtures.ts
+++ b/scripts/record-fixtures.ts
@@ -12,7 +12,7 @@ import process, { env, exit } from 'node:process'
import { fileURLToPath } from 'node:url'
import { parseArgs } from 'node:util'
import { ForgeApiError } from '../src/errors.ts'
-import { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } from '../src/index.ts'
+import { azureDevOps, bitbucket, cloudflareArtifacts, cursorOrigin, entire, forgejo, gitea, gitee, github, gitlab, pushin, sourcehut, tangled } from '../src/index.ts'
import { recordingFetch, signDelivery } from '../src/testing/index.ts'
import { recordedSteps, stepSlug } from '../test/recording/layout.ts'
import { redact } from '../test/recording/redact.ts'
@@ -27,10 +27,10 @@ import { guardedFetch } from './write-guard.ts'
import { writeHarness } from './write-harness.ts'
const { positionals: [target], values: flags } = parseArgs({ allowPositionals: true, options: { 'anonymous': { type: 'boolean', default: false }, 'app': { type: 'boolean', default: false }, 'writes': { type: 'boolean', default: false }, 'webhooks': { type: 'boolean', default: false }, 'seed': { type: 'boolean', default: false }, 'only': { type: 'string', multiple: true }, 'allow-fewer': { type: 'boolean', default: false }, 'defer-splice': { type: 'boolean', default: false } } })
-const TOKEN_VARIABLES: Record = { 'github': 'GITHUB_TOKEN', 'forgejo': 'CODEBERG_TOKEN', 'gitlab': 'GITLAB_TOKEN', 'bitbucket': 'BITBUCKET_TOKEN', 'cursor-origin': 'CURSOR_AUTH_TOKEN', 'gitee': 'GITEE_TOKEN', 'azure-devops': 'AZURE_DEVOPS_TOKEN', 'gitea': 'GITEA_TOKEN', 'pushin': 'PUSHIN_TOKEN', 'tangled': 'TANGLED_PASSWORD', 'cloudflare-artifacts': 'CLOUDFLARE_ARTIFACTS_TOKEN', 'sourcehut': 'SOURCEHUT_TOKEN' }
+const TOKEN_VARIABLES: Record = { 'github': 'GITHUB_TOKEN', 'forgejo': 'CODEBERG_TOKEN', 'gitlab': 'GITLAB_TOKEN', 'bitbucket': 'BITBUCKET_TOKEN', 'cursor-origin': 'CURSOR_AUTH_TOKEN', 'gitee': 'GITEE_TOKEN', 'azure-devops': 'AZURE_DEVOPS_TOKEN', 'gitea': 'GITEA_TOKEN', 'pushin': 'PUSHIN_TOKEN', 'tangled': 'TANGLED_PASSWORD', 'cloudflare-artifacts': 'CLOUDFLARE_ARTIFACTS_TOKEN', 'sourcehut': 'SOURCEHUT_TOKEN', 'entire': 'ENTIRE_TOKEN' }
if (!target || !(target in TOKEN_VARIABLES)) {
- console.error('Usage: pnpm record-fixtures [--anonymous | --app | --writes [--allow-fewer] [--defer-splice] | --webhooks | --seed] [--only ]...')
+ console.error('Usage: pnpm record-fixtures [--anonymous | --app | --writes [--allow-fewer] [--defer-splice] | --webhooks | --seed] [--only ]...')
exit(1)
}
@@ -519,6 +519,18 @@ function targetFor(credentials: Credentials, fetch: FetchLike): Target {
...setupRepos('SOURCEHUT', value => repoRef('sourcehut', provider.instance, value)),
} }
}
+ case 'entire': {
+ const provider = entire({ baseUrl: env.ENTIRE_BASE_URL, auth: tokenAuth!, fetch, timeout })
+ const [project = '', name = ''] = (env.FIXTURE_ENTIRE_REPO ?? 'forges/forges-fixtures').split('/')
+ const repo = { forge: 'entire', instance: provider.instance, owner: project, name }
+ return { provider, manifest: {
+ repo,
+ // Project trails stand in for issues, so the issue ref names the project rather than a repository.
+ issue: { forge: 'entire', instance: provider.instance, repo: { ...repo, name: '', kind: 'namespace' }, kind: 'issue', number: env.FIXTURE_ENTIRE_ISSUE ?? '1' },
+ pull: { forge: 'entire', instance: provider.instance, repo, kind: 'pull_request', number: env.FIXTURE_ENTIRE_PULL ?? '1' },
+ ...setupRepos('ENTIRE', slug => repoRef('entire', provider.instance, slug)),
+ } }
+ }
default: {
const provider = forgejo({ baseUrl: env.CODEBERG_BASE_URL, auth: tokenAuth, fetch, timeout })
const slug = env.FIXTURE_FORGEJO_REPO ?? (codeForgejo ? 'forgejo/runner' : 'forgejo/forgejo')
@@ -548,6 +560,7 @@ function authorizationFor(token: string): string {
case 'gitlab':
case 'bitbucket':
case 'sourcehut':
+ case 'entire':
return `Bearer ${token}`
case 'azure-devops':
return `Basic ${btoa(`:${token}`)}`
diff --git a/scripts/record-forge.ts b/scripts/record-forge.ts
index f13ad0c58..3ab520d80 100644
--- a/scripts/record-forge.ts
+++ b/scripts/record-forge.ts
@@ -11,8 +11,8 @@
* except with `--only`. `--defer-splice` passes on to write recordings, to
* leave their splicing to `scripts/splice-partial.ts`.
* `--summary` writes the outcome of each recording as Markdown. The exit code
- * is 1 when any recording failed. `--list` prints the forges that record
- * that kind, as a JSON array.
+ * is 1 when any recording failed. `--list` prints the forges that the
+ * `fixtures` workflow records of that kind, as a JSON array.
*/
import type { Buffer } from 'node:buffer'
import { spawn } from 'node:child_process'
@@ -29,8 +29,11 @@ interface Recording {
const CODE_FORGEJO = { CODEBERG_BASE_URL: 'https://code.forgejo.org' }
-/** The recordings of each forge, by kind. */
-export const PLANS: Record = {
+/**
+ * The recordings of each forge, by kind. A `local` forge records only on a
+ * maintainer's machine, as its credentials expire before the next scheduled run.
+ */
+export const PLANS: Record = {
'github': {
reads: [{ label: 'github.com', args: [] }, { label: 'anonymous', args: ['--anonymous'] }, { label: 'app', args: ['--app'] }],
writes: [{ label: 'writes', args: ['--writes'] }],
@@ -52,6 +55,7 @@ export const PLANS: Record
'pushin': { reads: [{ label: 'pushin.eu', args: [] }, { label: 'anonymous', args: ['--anonymous'] }], writes: [{ label: 'writes', args: ['--writes'] }] },
'cloudflare-artifacts': { reads: [{ label: 'artifacts.cloudflare.net', args: [] }], writes: [] },
'sourcehut': { reads: [{ label: 'sr.ht', args: [] }], writes: [] },
+ 'entire': { reads: [{ label: 'entire.com', args: [] }], writes: [{ label: 'writes', args: ['--writes'] }], local: true },
'tangled': {
reads: [{ label: 'tangled.org', args: [] }, { label: 'anonymous', args: ['--anonymous'] }],
writes: [{ label: 'writes', args: ['--writes'] }, { label: 'seed', args: ['--seed'] }],
@@ -82,7 +86,7 @@ function record(forge: string, recording: Recording, options: { envFile?: string
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const { positionals: [forge], values } = parseArgs({ allowPositionals: true, options: { 'kind': { type: 'string' }, 'list': { type: 'string' }, 'env-file': { type: 'string' }, 'only': { type: 'string', multiple: true, default: [] }, 'defer-splice': { type: 'boolean', default: false }, 'summary': { type: 'string' } } })
if (values.list === 'reads' || values.list === 'writes') {
- console.info(JSON.stringify(Object.keys(PLANS).filter(name => PLANS[name]![values.list as 'reads' | 'writes'].length)))
+ console.info(JSON.stringify(Object.keys(PLANS).filter(name => !PLANS[name]!.local && PLANS[name]![values.list as 'reads' | 'writes'].length)))
process.exit(0)
}
const kind = values.kind
diff --git a/scripts/recorded-coverage.ts b/scripts/recorded-coverage.ts
index 687d92d14..eefbc5fa7 100644
--- a/scripts/recorded-coverage.ts
+++ b/scripts/recorded-coverage.ts
@@ -32,6 +32,7 @@ const FORGES: Array<[string, string]> = [
['tangled', 'Tangled'],
['cloudflare-artifacts', 'Cloudflare Artifacts'],
['sourcehut', 'Sourcehut'],
+ ['entire', 'Entire'],
]
const { values: flags } = parseArgs({ options: { out: { type: 'string' } } })
diff --git a/scripts/write-harness.ts b/scripts/write-harness.ts
index c9be9c984..6542a298d 100644
--- a/scripts/write-harness.ts
+++ b/scripts/write-harness.ts
@@ -376,6 +376,83 @@ function pushin({ baseUrl, authorization, scratch }: HarnessOptions): WriteHarne
}
}
+/**
+ * Entire writes go to the processing cell that holds the scratch repository, and to the trails of its project,
+ * which the run's issues and pull requests are opened in. Git redirects each request to a storage node and drops
+ * the credential on the way, so branches are pushed to the node directly.
+ */
+function entire({ baseUrl, authorization, scratch }: HarnessOptions): WriteHarness {
+ const core = client(baseUrl, authorization)
+ const scope: string[] = []
+ const auth = extraHeader(`Authorization: ${authorization}`)
+ let cell: ReturnType | undefined
+ let repoId = ''
+ let projectId = ''
+ let remote = ''
+ let clone: string | undefined
+ const trails = `/trails/et/${enc(scratch.owner)}/${enc(scratch.name)}`
+ const projectTrails = `/et/${enc(scratch.owner)}/trails`
+ const checkout = async (base: string) => {
+ if (!clone) {
+ const response = await fetch(`${remote}/info/refs?service=git-upload-pack`, { headers: { authorization }, redirect: 'manual', signal: AbortSignal.timeout(30_000) })
+ const node = new URL(response.headers.get('location') ?? remote).origin
+ clone = mkdtempSync(join(tmpdir(), 'forges-entire-'))
+ git(clone, ['clone', '-q', '--depth', '1', '--branch', base, `${node}${new URL(remote).pathname}`, '.'], auth)
+ }
+ return clone
+ }
+ return {
+ scope,
+ // The control plane names a repository's cell in reply to a POST, which writes nothing.
+ allows: (url, body) => url === `${baseUrl}/repos/resolve`
+ && ((body as { repositories?: Array<{ fullName?: string }> } | undefined)?.repositories ?? []).every(item => item.fullName === `${scratch.owner}/${scratch.name}`),
+ async prepare() {
+ const { repos } = await core<{ repos: Array<{ id: string, full_name: string, permission?: string, placements?: Array<{ id: string, cell: string, apiBaseUrl?: string, mirror?: boolean }> }> }>(`/repos?filter=${enc(`et/${scratch.owner}/${scratch.name}`)}`)
+ const repo = repos.find(item => item.full_name === `${scratch.owner}/${scratch.name}`)
+ const placement = repo?.placements?.find(item => item.apiBaseUrl && !item.mirror)
+ if (!repo || !placement?.apiBaseUrl || repo.permission !== 'admin') {
+ throw new Error('The author credential does not administer the scratch repository')
+ }
+ repoId = placement.id
+ const api = `${placement.apiBaseUrl}/api/v1`
+ cell = client(api, authorization)
+ remote = `https://${placement.cell}.entire.io/et/${enc(scratch.owner)}/${enc(scratch.name)}`
+ scope.push(`${baseUrl}/repos/${repoId}`, `${api}/repos/${repoId}`, `${api}/git/repos/${repoId}`, `${api}${trails}`, `${api}${projectTrails}`)
+ // The run merges a change it left a finding on, which a blocking findings gate refuses.
+ const { gates } = await cell<{ gates: Array<{ id: string, gateType: string, blocking: boolean }> }>(`/repos/${repoId}/trails/gates`)
+ for (const gate of gates.filter(item => item.gateType === 'findings' && item.blocking)) {
+ await cell(`/repos/${repoId}/trails/gates/${gate.id}`, { method: 'PATCH', json: { blocking: false } })
+ }
+ const data = await cell<{ default_branch: string, owning_project_id: string }>(`/repos/${repoId}`)
+ projectId = data.owning_project_id
+ return data.default_branch
+ },
+ async createBranch(base, branch, path) {
+ const directory = await checkout(base)
+ git(directory, ['switch', '-q', '-C', branch, `origin/${base}`])
+ mkdirSync(dirname(join(directory, path)), { recursive: true })
+ writeFileSync(join(directory, path), fixtureContent(branch))
+ git(directory, ['add', path])
+ git(directory, ['commit', '-q', '-m', `test: add ${path}`])
+ git(directory, ['push', '-q', 'origin', branch], auth)
+ },
+ async cleanUp(branches) {
+ const call = cell!
+ const changes = await call<{ items: Array<{ number: number, title: string }> }>(`${trails}?status=open&per_page=100`).catch(() => ({ items: [] }))
+ const open = await call<{ items: Array<{ number: number, title: string }> }>(`/trails?projectId=${projectId}&status=open&pageSize=100`).catch(() => ({ items: [] }))
+ const heads = await call<{ branches: Array<{ name: string, commitSHA: string }> }>(`/git/repos/${repoId}/branches?pageSize=100`).catch(() => ({ branches: [] }))
+ await settle([
+ ...changes.items.filter(item => item.title.startsWith(FIXTURE_TITLE)).map(item => () => call(`${trails}/${item.number}`, { method: 'PATCH', json: { status: 'closed' } })),
+ ...open.items.filter(item => item.title.startsWith(FIXTURE_TITLE)).map(item => () => call(`${projectTrails}/${item.number}`, { method: 'PATCH', json: { status: 'closed' } })),
+ ...heads.branches.filter(item => branches.includes(item.name)).map(item => () => call(`/git/repos/${repoId}/branches/${item.name.split('/').map(enc).join('/')}?expectedSHA=${item.commitSHA}`, { method: 'DELETE' })),
+ ])
+ },
+ async removeCollaborator(_repo, id) {
+ await core(`/repos/${repoId}/grants/account/${enc(id)}`, { method: 'DELETE' })
+ },
+ }
+}
+
/**
* Tangled writes are records in the signed-in account's own repository on its
* PDS. A new record may only name the scratch repository, a record of that
@@ -598,6 +675,7 @@ const HARNESSES: Record WriteHarness> = {
'gitee': gitee,
'azure-devops': azureDevOps,
'pushin': pushin,
+ 'entire': entire,
}
/** The setup and teardown for `options.forge`, or `undefined` where write recordings are not supported. */
diff --git a/src/entire/index.ts b/src/entire/index.ts
new file mode 100644
index 000000000..fb7cb78ec
--- /dev/null
+++ b/src/entire/index.ts
@@ -0,0 +1,831 @@
+import type { ProviderContext, ProviderDefinition, ProviderFactoryFunction, ProviderSpec } from '../define.ts'
+import type { Fetcher } from '../fetch.ts'
+import type { Actor, BaseOptions, Branch, ChangedFile, Comment, CommentRef, Commit, CommitQuery, CommitRef, CompareOptions, Comparison, Cursor, FileOptions, ForgeEventInput, ForgeWarning, ListOptions, MergeOptions, MergeResult, Page, PageOptions, RepoQuery, RepoRef, RepoRole, ResolvedThreadRef, Review, ReviewComment, ReviewInput, ReviewRef, ReviewThread, Tag, Thread, ThreadInput, ThreadQuery, ThreadRef, ThreadUpdate, TreeEntry, TreeOptions, UpdateBranchOptions, UpdateBranchResult, User } from '../model.ts'
+import type { ForgeOptionsBase, TokenAuth, VerbScopes } from '../provider.ts'
+import type { ForgeVerb } from '../supports.ts'
+import type { WebLinks } from '../web.ts'
+import type { EntireApproval, EntireBranch, EntireChange, EntireCollaborator, EntireCommit, EntireCommitDetail, EntireCompareFile, EntireCompareStatus, EntireCoreRepo, EntireDiscussion, EntireDiscussionCreated, EntireMe, EntireMerged, EntireMessage, EntireProjectTrail, EntireRef, EntireRepo, EntireRepoResolution, EntireResolvedProject, EntireReviewComment, EntireReviewPublished, EntireReviewSnapshot, EntireTimelineItem, EntireTreeEntry } from './types.ts'
+import { isSha, toFileContent } from '../contents.ts'
+import { defineForgeProvider, perKind, verb } from '../define.ts'
+import { BranchMovedError, ForgeApiError, ForgeError, NotFoundError, UnsupportedOperationError } from '../errors.ts'
+import { eventTime } from '../events.ts'
+import { isNamespaceRef } from '../model.ts'
+import { getManyConcurrently, memoBy, phased, requireIssueOrPull, resolveToken, toDate, toFileStatus } from '../utils.ts'
+import { webUrlFor } from '../web.ts'
+import { approvalState, findingComment, flattenMessages, FORGE, identityActor, projectRef, pullRef, toApproval, toBranch, toChangedFile, toCodeReview, toCollaborator, toComment, toCommit, toCoreRepo, toIssue, toPull, toRepo, toReviewThread, toTreeEntry } from './normalise.ts'
+import { ENTIRE_WEB, HOST } from './web.ts'
+
+/**
+ * A bearer token for an Entire account, such as the one `entire auth token` prints. Login tokens
+ * expire, so pass `token` as a function to read a fresh one for every request.
+ */
+export type EntireAuth = TokenAuth
+
+/** Options for `entire()`. */
+export interface EntireOptions extends ForgeOptionsBase {
+ auth: EntireAuth
+ /**
+ * Control plane root. `/api/v1` is appended. Requests about a project go to the processing cell that the
+ * control plane names for it.
+ * @default https://us.console.entire.io
+ */
+ baseUrl?: string
+}
+
+interface Project {
+ id: string
+ cell: Fetcher
+}
+
+interface TokenPageShape {
+ items: string
+ next: string
+ size: string
+ token: string
+}
+
+const camelPage = (items: string): TokenPageShape => ({ items, next: 'nextPageToken', size: 'pageSize', token: 'pageToken' })
+const snakePage = (items: string): TokenPageShape => ({ items, next: 'next_cursor', size: 'per_page', token: 'cursor' })
+
+const bearer = (auth: EntireAuth) => async () => ({ authorization: `Bearer ${await resolveToken(auth)}` })
+
+function setupEntire({ options, instance, origin, fetcher, createFetcher, baseUrl }: ProviderContext): ProviderSpec & { web: WebLinks } {
+ const web = ENTIRE_WEB.links({ baseUrl, instance })
+ const authHeaders = bearer(options.auth)
+ const cellFetcher = memoBy(async (apiUrl: string) => createFetcher({ baseUrl: `${apiUrl.replace(/\/$/, '')}/api/v1`, authHeaders }))
+
+ const placed = (raw: Pick | undefined) => raw?.placements?.find(placement => placement.apiBaseUrl && !placement.mirror)
+
+ /** A project the account reads through one of its repositories, when it has no grant on the project itself. */
+ async function projectThroughRepo(name: string): Promise {
+ const { data } = await fetcher.json<{ repos: EntireCoreRepo[] }>('/repos', { query: { org: name, pageSize: 1 } })
+ const raw = data.repos[0]
+ const placement = placed(raw)
+ if (!raw || !placement?.apiBaseUrl) {
+ return undefined
+ }
+ const cell = await cellFetcher(placement.apiBaseUrl)
+ const { data: repo } = await cell.json(`/repos/${encodeURIComponent(placement.id)}`)
+ return repo.owning_project_id ? { id: repo.owning_project_id, cell } : undefined
+ }
+
+ const project = memoBy(async (name: string): Promise => {
+ try {
+ const { data } = await fetcher.json(`/projects/resolve/${HOST}/${encodeURIComponent(name)}`)
+ if (!data.project.apiUrl) {
+ throw new NotFoundError(`Project ${name} has no processing cell`, 404, '', origin)
+ }
+ return { id: data.project.id, cell: await cellFetcher(data.project.apiUrl) }
+ }
+ catch (error) {
+ const fallback = error instanceof ForgeApiError && error.status === 403 ? await projectThroughRepo(name) : undefined
+ if (!fallback) {
+ throw error
+ }
+ return fallback
+ }
+ })
+
+ const placement = memoBy(async (path: string): Promise<{ cell: Fetcher, id: string }> => {
+ const { data } = await fetcher.json<{ resolutions: EntireRepoResolution[] }>('/repos/resolve', { method: 'POST', json: { repositories: [{ fullName: path, provider: 'entire' }] } })
+ const found = placed(data.resolutions[0])
+ if (!found?.apiBaseUrl) {
+ throw new NotFoundError(`No repository ${path} on Entire`, 404, '', origin)
+ }
+ return { cell: await cellFetcher(found.apiBaseUrl), id: found.id }
+ })
+
+ async function located(repo: RepoRef): Promise<{ cell: Fetcher, id: string, path: string }> {
+ const { cell, id } = await placement(`${repo.owner}/${repo.name}`)
+ return { cell, id: encodeURIComponent(id), path: `/git/repos/${encodeURIComponent(id)}` }
+ }
+
+ const trailPath = (repo: RepoRef) => `/trails/${HOST}/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`
+ const projectTrailPath = (project: string) => `/${HOST}/${encodeURIComponent(project)}/trails`
+
+ async function tokenPage(f: Fetcher, path: string, shape: TokenPageShape, page: PageOptions & { query?: Record }, map: (raw: R) => T | undefined): Promise> {
+ const { data } = await f.json>(path, {
+ query: { ...page.query, [shape.size]: page.perPage, [shape.token]: page.cursor?.token },
+ signal: page.signal,
+ })
+ const raw = data[shape.items] as R[] | undefined
+ const next = data[shape.next]
+ const total = data.total_count
+ return {
+ items: (raw ?? []).flatMap(item => map(item) ?? []),
+ ...typeof total === 'number' ? { total } : {},
+ ...typeof next === 'string' && next ? { cursor: { token: next } } : {},
+ }
+ }
+
+ async function everything(f: Fetcher, path: string, shape: TokenPageShape, query: Record, signal?: AbortSignal): Promise {
+ const items: R[] = []
+ let cursor: Cursor | undefined
+ do {
+ const page: Page = await tokenPage(f, path, shape, { query, perPage: 100, cursor, signal }, (raw: R) => raw)
+ items.push(...page.items)
+ cursor = page.cursor
+ } while (cursor)
+ return items
+ }
+
+ function slice(items: T[], page: PageOptions): Page {
+ const offset = Number(page.cursor?.token ?? 0)
+ const size = page.perPage ?? 100
+ const next = offset + size
+ return { items: items.slice(offset, next), ...next < items.length ? { cursor: { token: String(next) } } : {} }
+ }
+
+ function requirePull(thread: ThreadRef, action: string): ResolvedThreadRef {
+ const ref = requireIssueOrPull(thread, origin, action)
+ if (ref.kind !== 'pull_request' || isNamespaceRef(ref.repo)) {
+ throw new UnsupportedOperationError(`Only changes can be ${action}`, origin)
+ }
+ return ref
+ }
+
+ async function change(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ const { cell } = await located(ref.repo)
+ return (await cell.json(`${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}`, { signal })).data
+ }
+
+ async function parentTrail(repo: RepoRef, raw: EntireChange, signal?: AbortSignal): Promise {
+ if (!raw.parent) {
+ return undefined
+ }
+ const { cell } = await located(repo)
+ return (await cell.json(`${projectTrailPath(raw.parent.project)}/${encodeURIComponent(raw.parent.id)}`, { signal })).data
+ }
+
+ async function get(thread: ThreadRef, opts?: BaseOptions): Promise {
+ const ref = requireIssueOrPull(thread, origin, 'read')
+ if (ref.kind === 'pull_request') {
+ const { repo } = requirePull(ref, 'read')
+ const raw = await change(ref, opts?.signal)
+ const parent = raw.body_document?.text_snapshot ? undefined : await parentTrail(repo, raw, opts?.signal)
+ return toPull(repo, raw, parent?.body)
+ }
+ const { cell } = await project(ref.repo.owner)
+ const { data } = await cell.json(`${projectTrailPath(ref.repo.owner)}/${encodeURIComponent(ref.number)}`, { signal: opts?.signal })
+ return toIssue(origin, ref.repo.owner, data)
+ }
+
+ async function issuesPage(repo: RepoRef, query: ThreadQuery, cursor?: Cursor): Promise> {
+ const { id, cell } = await project(repo.owner)
+ const status = query.state === 'all' ? undefined : query.state === 'closed' ? 'closed' : 'open'
+ return tokenPage(cell, '/trails', camelPage('items'), {
+ ...query,
+ cursor,
+ query: { projectId: id, status, author: query.author, assignee: query.assignee },
+ }, (raw: EntireProjectTrail) => toIssue(origin, repo.owner, raw))
+ }
+
+ async function pullsPage(repo: RepoRef, query: ThreadQuery, cursor?: Cursor): Promise> {
+ const { cell } = await located(repo)
+ const status = query.state === 'all' ? undefined : query.state === 'merged' ? 'merged' : query.state === 'closed' ? 'closed,merged' : 'open'
+ return tokenPage(cell, trailPath(repo), snakePage('items'), {
+ ...query,
+ cursor,
+ query: { status, author: query.author, assignee: query.assignee },
+ }, (raw: EntireChange) => toPull(repo, raw))
+ }
+
+ async function listPage(repo: RepoRef, query: ThreadQuery = {}): Promise> {
+ const warnings: ForgeWarning[] = query.labels?.length ? [{ code: 'filter_unsupported', message: 'Entire trails have no labels' }] : []
+ const namespace = isNamespaceRef(repo)
+ const withWarnings = (page: Page) => warnings.length ? { ...page, warnings: [...page.warnings ?? [], ...warnings] } : page
+ if (query.kind === 'discussion') {
+ return { items: [], warnings: [{ code: 'kind_unsupported', message: 'Entire has no discussions' }] }
+ }
+ if (query.kind === 'issue' || namespace) {
+ return query.state === 'merged' ? { items: [] } : withWarnings(await issuesPage(projectRef(origin, repo.owner), query, query.cursor))
+ }
+ if (query.kind === 'pull_request' || query.state === 'merged') {
+ return withWarnings(await pullsPage(repo, query, query.cursor))
+ }
+ return withWarnings(await phased([
+ cursor => issuesPage(projectRef(origin, repo.owner), query, cursor),
+ cursor => pullsPage(repo, query, cursor),
+ ], query.cursor))
+ }
+
+ async function discussionsAt(ref: ResolvedThreadRef): Promise<{ cell: Fetcher, path: string }> {
+ if (ref.kind === 'pull_request') {
+ return { cell: (await located(ref.repo)).cell, path: `${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}/discussions` }
+ }
+ return { cell: (await project(ref.repo.owner)).cell, path: `${projectTrailPath(ref.repo.owner)}/${encodeURIComponent(ref.number)}/discussions` }
+ }
+
+ async function discussions(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ const { cell, path } = await discussionsAt(ref)
+ if (ref.kind === 'pull_request') {
+ return everything(cell, path, snakePage('items'), { include_messages: true }, signal)
+ }
+ const list = await everything(cell, path, camelPage('items'), {}, signal)
+ return Promise.all(list.map(async (discussion) => {
+ const { data } = await cell.json<{ discussion: EntireDiscussion, messages: EntireDiscussion['messages'] }>(`${path}/${encodeURIComponent(discussion.id)}`, { signal })
+ return { ...data.discussion, messages: data.messages }
+ }))
+ }
+
+ async function comments(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ return (await discussions(ref, signal))
+ .filter(discussion => !discussion.review_comment_id)
+ .flatMap(discussion => flattenMessages(discussion.messages).map(message => toComment(ref, discussion, message)))
+ .sort((a, b) => (a.createdAt?.getTime() ?? 0) - (b.createdAt?.getTime() ?? 0))
+ }
+
+ /** The message `ref` names, with its discussion, or the finding it names. */
+ async function findComment(ref: CommentRef, signal?: AbortSignal): Promise<{ thread: ResolvedThreadRef, discussion: EntireDiscussion, message?: EntireMessage }> {
+ const thread = requireIssueOrPull(ref.thread, origin, 'read comments on')
+ for (const discussion of await discussions(thread, signal)) {
+ const message = flattenMessages(discussion.messages).find(item => item.id === ref.id)
+ if (message || discussion.review_comment?.id === ref.id) {
+ return { thread, discussion, message }
+ }
+ }
+ throw new NotFoundError(`No comment ${ref.id} on ${thread.kind === 'issue' ? 'trail' : 'change'} ${thread.number}`, 404, '', origin)
+ }
+
+ async function findings(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ const { cell } = await located(ref.repo)
+ return everything(cell, `${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}/reviews/comments`, snakePage('comments'), {}, signal)
+ }
+
+ async function approvals(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ const { cell } = await located(ref.repo)
+ return (await cell.json<{ approvals: EntireApproval[] }>(`${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}/approvals`, { signal })).data.approvals
+ }
+
+ async function codeReview(ref: ResolvedThreadRef, id: string, signal?: AbortSignal): Promise {
+ const { cell } = await located(ref.repo)
+ return (await cell.json(`${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}/reviews/${encodeURIComponent(id)}`, { signal })).data
+ }
+
+ async function reviews(ref: ResolvedThreadRef, signal?: AbortSignal): Promise {
+ const [approved, all] = await Promise.all([approvals(ref, signal), findings(ref, signal)])
+ const byReview = Map.groupBy(all.sort((a, b) => a.created_at.localeCompare(b.created_at)), finding => finding.review_id)
+ const summaries = await Promise.all([...byReview.keys()].map(async id => (await codeReview(ref, id, signal)).review.summary))
+ return [
+ ...approved.map(approval => toApproval(ref, approval)),
+ ...[...byReview].map(([id, items], index) => toCodeReview(ref, id, items, summaries[index])),
+ ].sort((a, b) => (a.submittedAt?.getTime() ?? 0) - (b.submittedAt?.getTime() ?? 0))
+ }
+
+ function toEvent(ref: ResolvedThreadRef, item: EntireTimelineItem, threads = new Map()): ForgeEventInput {
+ const actor = item.actor ? identityActor(origin, item.actor.identity, item.actor.login) : undefined
+ const who = actor?.login ?? 'someone'
+ const base = {
+ ...origin,
+ id: item.id,
+ kindRaw: item.kind,
+ ...eventTime(toDate(item.occurredAt ?? item.occurred_at), new Date(0)),
+ actor,
+ repo: ref.repo,
+ thread: ref,
+ source: 'poll' as const,
+ payload: item,
+ }
+ const commentId = item.commentId ?? item.comment_id
+ const discussion = threads.get(item.discussionId ?? item.discussion_id ?? '')
+ switch (item.kind) {
+ case 'comment': {
+ const body = item.body ?? flattenMessages(discussion?.messages).find(message => message.id === commentId)?.body
+ const finding = discussion?.review_comment
+ if (finding) {
+ const line = finding.location.end_line ?? finding.location.start_line
+ return { ...base, kind: 'review_comment', summary: `${who} commented on a review`, detail: { type: 'review_comment', ...commentId ? { comment: { ...origin, thread: ref, id: commentId } } : {}, ...body ? { body } : {}, ...finding.location.file_path ? { path: finding.location.file_path } : {}, ...line ? { line } : {} } }
+ }
+ return { ...base, kind: 'comment', summary: `${who} commented`, detail: { type: 'comment', ...commentId ? { comment: { ...origin, thread: ref, id: commentId } } : {}, ...body ? { body } : {} } }
+ }
+ case 'approval': {
+ const state = approvalState(item.approvalStatus ?? '')
+ return { ...base, kind: 'review', summary: `${who} ${state === 'approved' ? 'approved' : state === 'changes_requested' ? 'requested changes' : 'reviewed'}`, detail: { type: 'review', state, ...item.approvalStatus ? { stateRaw: item.approvalStatus } : {}, ...item.body ? { body: item.body } : {}, review: { ...origin, thread: ref, id: item.id.replace(/^trail-approval-/, '') } } }
+ }
+ case 'review_posted':
+ return { ...base, kind: 'review', summary: `${who} reviewed`, detail: { type: 'review', state: 'commented', stateRaw: item.kind, review: { ...origin, thread: ref, id: item.id.replace(/^trail-review-/, '') } } }
+ case 'commit':
+ return { ...base, kind: 'commit', summary: item.title ?? 'commit', ...item.commitSha ? { commit: { ...origin, repo: ref.repo, sha: item.commitSha } } : {} }
+ case 'trail_created':
+ return { ...base, kind: 'state_change', action: 'opened', summary: `${who} opened the trail`, detail: { type: 'state_change', state: 'open' } }
+ case 'trail_branch_merged':
+ return { ...base, kind: 'state_change', action: 'merged', summary: `${who} merged the change`, detail: { type: 'state_change', state: 'merged' } }
+ default:
+ return { ...base, kind: 'other', summary: item.title ?? item.kind }
+ }
+ }
+
+ async function eventsPage(thread: ThreadRef, list: ListOptions = {}): Promise> {
+ const ref = requireIssueOrPull(thread, origin, 'read events for')
+ if (ref.kind === 'pull_request') {
+ const { cell, id } = await located(requirePull(ref, 'read').repo)
+ const [page, all] = await Promise.all([
+ tokenPage(cell, `/repos/${id}/trails/${encodeURIComponent(ref.number)}/timeline`, camelPage('items'), list, (item: EntireTimelineItem) => item),
+ discussions(ref, list.signal),
+ ])
+ const threads = new Map(all.map(discussion => [discussion.id, discussion]))
+ return { ...page, items: page.items.map(item => toEvent(ref, item, threads)) }
+ }
+ const { cell } = await project(ref.repo.owner)
+ return tokenPage(cell, `${projectTrailPath(ref.repo.owner)}/${encodeURIComponent(ref.number)}/timeline`, snakePage('items'), list, (item: EntireTimelineItem) => toEvent(ref, item))
+ }
+
+ async function compare(repo: RepoRef, base: string, head: string, compareOptions: CompareOptions = {}): Promise {
+ const { cell, path } = await located(repo)
+ const signal = compareOptions.signal
+ const { data: status } = await cell.json(`${path}/compare/status`, { query: { base, head }, signal })
+ const from = status.mergeBaseSHA ?? base
+ const [files, commits] = await Promise.all([
+ compareOptions.files === false ? undefined : cell.json<{ files?: EntireCompareFile[] }>(`${path}/compare`, { query: { base: from, head }, signal }),
+ cell.json<{ commits: EntireCommit[] }>(`${path}/commits`, { query: { ref: head, notRef: from, pageSize: Math.min(compareOptions.commitLimit ?? 200, 200) }, signal }),
+ ])
+ return {
+ base,
+ head,
+ aheadBy: status.commitsAhead,
+ behindBy: status.commitsBehind,
+ commits: commits.data.commits.map(commit => toCommit(origin, repo, commit)),
+ files: (files?.data.files ?? []).map(toChangedFile),
+ ...status.mergeBaseSHA ? { mergeBaseSha: status.mergeBaseSHA } : {},
+ raw: { status, files: files?.data, commits: commits.data },
+ }
+ }
+
+ async function changeRange(thread: ThreadRef, signal?: AbortSignal): Promise<{ repo: RepoRef, base: string, head: string }> {
+ const ref = requirePull(thread, 'compared')
+ const raw = await change(ref, signal)
+ const head = raw.head_sha ?? raw.branch
+ if (raw.merge_base_sha) {
+ return { repo: ref.repo, base: raw.merge_base_sha, head }
+ }
+ const { cell, path } = await located(ref.repo)
+ const { data } = await cell.json(`${path}/compare/status`, { query: { base: raw.base, head }, signal })
+ return { repo: ref.repo, base: data.mergeBaseSHA ?? raw.base, head }
+ }
+
+ async function refsPage(repo: RepoRef, prefix: string, page: PageOptions): Promise> {
+ const { cell, path } = await located(repo)
+ return tokenPage(cell, `${path}/refs`, camelPage('refs'), { ...page, query: { prefix, peel: true, symrefs: true } }, (raw: EntireRef) => raw)
+ }
+
+ async function resolveRef(repo: RepoRef, ref: string, opts?: BaseOptions): Promise {
+ if (isSha(ref)) {
+ return ref
+ }
+ const { cell, path } = await located(repo)
+ const { data } = await cell.json<{ commits: EntireCommit[] }>(`${path}/commits`, { query: { ref, pageSize: 1 }, signal: opts?.signal })
+ const sha = data.commits[0]?.sha
+ if (!sha) {
+ throw new NotFoundError(`No revision ${ref} in ${repo.owner}/${repo.name}`, 404, '', origin)
+ }
+ return sha
+ }
+
+ function actorReference(actor: string | Actor): string {
+ if (typeof actor !== 'string') {
+ return `ulid:${actor.id}`
+ }
+ return actor.startsWith('ulid:') || actor.startsWith('@') ? actor : `@${actor}`
+ }
+
+ function refuseLabels(labels: string[] | undefined): void {
+ if (labels?.length) {
+ throw new UnsupportedOperationError('Entire trails have no labels', origin)
+ }
+ }
+
+ async function create(repo: RepoRef, input: ThreadInput, opts?: BaseOptions): Promise {
+ refuseLabels(input.labels)
+ const assignees = input.assignees?.map(actorReference)
+ if (input.kind === 'issue') {
+ const scoped = isNamespaceRef(repo) ? undefined : await located(repo)
+ const cell = scoped?.cell ?? (await project(repo.owner)).cell
+ const { data } = await cell.json(projectTrailPath(repo.owner), {
+ method: 'POST',
+ json: { title: input.title, body: input.body, assignees, repoIds: scoped && [decodeURIComponent(scoped.id)] },
+ signal: opts?.signal,
+ })
+ return toIssue(origin, repo.owner, data)
+ }
+ if (input.kind !== 'pull_request' || isNamespaceRef(repo)) {
+ throw new UnsupportedOperationError(`Entire cannot open a ${input.kind.replace('_', ' ')} here`, origin)
+ }
+ if (!input.head || !input.base) {
+ throw new TypeError('A change needs `head` and `base`')
+ }
+ if (input.head.includes(':')) {
+ throw new UnsupportedOperationError('Entire opens changes from branches of the same repository', origin)
+ }
+ const { cell, id } = await located(repo)
+ const repositoryId = decodeURIComponent(id)
+ const { data } = await cell.json(projectTrailPath(repo.owner), {
+ method: 'POST',
+ headers: { 'idempotency-key': crypto.randomUUID() },
+ json: {
+ title: input.title,
+ body: input.body,
+ repoIds: [repositoryId],
+ changes: [{ repositoryId, branchName: input.head, base: input.base, branchAction: 'link', title: input.title, assignees, readyForReview: !input.draft }],
+ },
+ signal: opts?.signal,
+ })
+ const number = data.changes?.find(item => item.repositoryId === repositoryId)?.number
+ if (number === undefined) {
+ throw new ForgeError(`Entire opened trail ${data.number} without a change in ${repo.owner}/${repo.name}`, origin)
+ }
+ return get(pullRef(repo, number), opts)
+ }
+
+ async function update(thread: ThreadRef, input: ThreadUpdate, opts?: BaseOptions): Promise {
+ const ref = requireIssueOrPull(thread, origin, 'update')
+ refuseLabels(input.labels)
+ const assignees = input.assignees?.map(actorReference)
+ const signal = opts?.signal
+ if (ref.kind === 'issue') {
+ const { cell } = await project(ref.repo.owner)
+ const { data } = await cell.json(`${projectTrailPath(ref.repo.owner)}/${encodeURIComponent(ref.number)}`, {
+ method: 'PATCH',
+ json: { title: input.title, body: input.body, status: input.state, assignees },
+ signal,
+ })
+ return toIssue(origin, ref.repo.owner, data)
+ }
+ const pull = requirePull(ref, 'updated')
+ const { cell } = await located(pull.repo)
+ if (input.title !== undefined || input.state !== undefined || assignees) {
+ await cell.json(`${trailPath(pull.repo)}/${encodeURIComponent(pull.number)}`, { method: 'PATCH', json: { title: input.title, status: input.state, assignees }, signal })
+ }
+ if (input.body !== undefined) {
+ const raw = await change(pull, signal)
+ if (!raw.parent) {
+ throw new UnsupportedOperationError(`Change ${pull.number} has no trail to hold its description`, origin)
+ }
+ await cell.json(`${projectTrailPath(raw.parent.project)}/${encodeURIComponent(raw.parent.id)}`, { method: 'PATCH', json: { body: input.body }, signal })
+ }
+ return get(pull, opts)
+ }
+
+ async function createComment(thread: ThreadRef, body: string, opts?: BaseOptions): Promise {
+ const ref = requireIssueOrPull(thread, origin, 'comment on')
+ const { cell, path } = await discussionsAt(ref)
+ const { data } = await cell.json(path, { method: 'POST', json: { body }, signal: opts?.signal })
+ return toComment(ref, data.discussion, data.message)
+ }
+
+ async function messagePath(ref: CommentRef, signal?: AbortSignal) {
+ const found = await findComment(ref, signal)
+ if (!found.message) {
+ throw new UnsupportedOperationError('Entire edits a finding through the code review that left it', origin)
+ }
+ const { cell, path } = await discussionsAt(found.thread)
+ return { ...found, message: found.message, cell, path: `${path}/${encodeURIComponent(found.discussion.id)}/messages/${encodeURIComponent(found.message.id)}` }
+ }
+
+ async function setResolved(thread: ThreadRef, id: string, resolved: boolean, opts?: BaseOptions): Promise {
+ const ref = requirePull(thread, 'reviewed')
+ const { cell, path } = await discussionsAt(ref)
+ await cell.json(`${path}/${encodeURIComponent(id)}`, { method: 'PATCH', json: { resolved }, signal: opts?.signal })
+ }
+
+ /** The text of lines `start` to `end` of `file` at the head of `ref`, which a line finding quotes. */
+ async function selectedText(ref: ResolvedThreadRef, file: string, start: number, end: number, signal?: AbortSignal): Promise {
+ const raw = await change(ref, signal)
+ const { cell, path } = await located(ref.repo)
+ const response = await cell.raw(`${path}/raw`, { query: { ref: raw.head_sha ?? raw.branch, path: file }, signal })
+ return (await response.text()).split('\n').slice(start - 1, end).join('\n')
+ }
+
+ async function createReview(thread: ThreadRef, input: ReviewInput, opts?: BaseOptions): Promise {
+ const ref = requirePull(thread, 'reviewed')
+ const signal = opts?.signal
+ const { cell } = await located(ref.repo)
+ const path = `${trailPath(ref.repo)}/${encodeURIComponent(ref.number)}`
+ if (input.event === 'approve' || input.event === 'request_changes') {
+ if (input.comments?.length) {
+ throw new UnsupportedOperationError('Entire approvals carry no inline comments', origin)
+ }
+ const { data } = await cell.json<{ approval: EntireApproval }>(`${path}/approvals`, { method: 'POST', json: { event: input.event, body: input.body }, signal })
+ return toApproval(ref, data.approval)
+ }
+ if (input.event !== 'comment') {
+ throw new UnsupportedOperationError('Entire has no pending reviews', origin)
+ }
+ const inline = input.comments ?? []
+ if (!inline.length && !input.body) {
+ throw new TypeError('A review needs a body or comments')
+ }
+ if (inline.some(comment => comment.side === 'left')) {
+ throw new UnsupportedOperationError('Entire anchors findings to the head of a change', origin)
+ }
+ const comments = inline.length
+ ? await Promise.all(inline.map(async (comment, index) => {
+ const end = comment.line
+ const start = comment.startLine ?? end
+ return {
+ client_id: String(index + 1),
+ body: comment.body,
+ location: start === undefined || end === undefined
+ ? { granularity: 'file', file_path: comment.path }
+ : { granularity: start === end ? 'line' : 'range', file_path: comment.path, start_line: start, end_line: end, selected_text: await selectedText(ref, comment.path, start, end, signal) },
+ }
+ }))
+ : [{ client_id: '1', body: input.body, location: { granularity: 'whole_change' } }]
+ const { data: started } = await cell.json<{ review_id: string }>(`${path}/reviews`, { method: 'POST', json: {}, signal })
+ const { data } = await cell.json(`${path}/reviews/${encodeURIComponent(started.review_id)}/comments`, {
+ method: 'POST',
+ json: { summary: input.body, comments },
+ signal,
+ })
+ const failed = data.results.find(result => result.error)
+ if (failed) {
+ throw new ForgeError(`Entire refused a review comment: ${failed.error!.message}`, origin)
+ }
+ return toCodeReview(ref, started.review_id, data.results.flatMap(result => result.comment ?? []), input.body)
+ }
+
+ async function getReview(reviewRef: ReviewRef, opts?: BaseOptions): Promise {
+ const ref = requirePull(reviewRef.thread, 'reviewed')
+ const approval = (await approvals(ref, opts?.signal)).find(item => item.id === reviewRef.id)
+ if (approval) {
+ return toApproval(ref, approval)
+ }
+ const { review, comments = [] } = await codeReview(ref, reviewRef.id, opts?.signal)
+ return toCodeReview(ref, review.id, comments, review.summary)
+ }
+
+ async function replyToReviewComment(thread: ThreadRef, comment: Pick, body: string, opts?: BaseOptions): Promise {
+ const ref = requirePull(thread, 'reviewed')
+ const discussion = comment.thread?.id ?? (comment.ref && (await findComment(comment.ref, opts?.signal)).discussion.id)
+ if (!discussion) {
+ throw new UnsupportedOperationError('Entire replies to a review comment in its discussion; pass the comment with its `thread` or `ref`', origin)
+ }
+ const { cell, path } = await discussionsAt(ref)
+ const { data } = await cell.json<{ message: EntireMessage }>(`${path}/${encodeURIComponent(discussion)}/messages`, { method: 'POST', json: { body }, signal: opts?.signal })
+ const reply = toComment(ref, { id: discussion, resolved: comment.thread?.resolved ?? false }, data.message)
+ return { ...reply, ...comment.inReplyTo ?? comment.ref?.id ? { inReplyTo: comment.inReplyTo ?? comment.ref?.id } : {} }
+ }
+
+ const moved = (error: unknown) => error instanceof ForgeApiError && (error.status === 412 || (error.status === 409 && /head_mismatch/.test(error.body)))
+ ? new BranchMovedError('The head branch no longer points at the expected sha', error.status, error.body, origin, { cause: error })
+ : error
+
+ async function merge(thread: ThreadRef, options: MergeOptions = {}): Promise {
+ const ref = requirePull(thread, 'merged')
+ if (options.method && options.method !== 'merge') {
+ throw new UnsupportedOperationError(`Entire has no ${options.method} merge`, origin)
+ }
+ if (options.whenChecksPass) {
+ throw new UnsupportedOperationError('Entire merges a change once its gates pass, with no queue', origin)
+ }
+ const { cell, id } = await located(ref.repo)
+ const path = `/repos/${id}/trails/${encodeURIComponent(ref.number)}`
+ const submit = () => cell.json(`${path}/merge`, { method: 'POST', json: { deleteBranch: false, expectedHeadSha: options.sha }, mapError: moved, signal: options.signal })
+ const { data } = await submit().catch(async (error: unknown) => {
+ if (!(error instanceof ForgeApiError) || error.status !== 422 || !/merge_gates_pending/.test(error.body)) {
+ throw error
+ }
+ await cell.json(`${path}/gates/recompute`, { method: 'POST', signal: options.signal })
+ return submit()
+ })
+ return { status: 'merged', ...data.mergeCommitSha ? { sha: data.mergeCommitSha } : {}, raw: data }
+ }
+
+ async function updateBranch(thread: ThreadRef, options: UpdateBranchOptions = {}): Promise {
+ const ref = requirePull(thread, 'updated')
+ const { cell, id, path } = await located(ref.repo)
+ const signal = options.signal
+ const raw = await change(ref, signal)
+ if (options.method === 'rebase') {
+ const response = await cell.raw(`${path}/rebases`, { method: 'POST', json: { branch: raw.branch, onto: raw.base, expectedBranchSHA: options.sha }, mapError: moved, signal })
+ const events = (await response.text()).split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string })
+ if (events.at(-1)?.type !== 'completed') {
+ throw new ForgeError(`Entire did not finish rebasing ${raw.branch}: ${JSON.stringify(events.at(-1))}`, origin)
+ }
+ return { status: 'updated', raw: events }
+ }
+ if (options.sha && raw.head_sha && raw.head_sha !== options.sha) {
+ throw new BranchMovedError(`${raw.branch} is at ${raw.head_sha}, not ${options.sha}`, 409, '', origin)
+ }
+ const { data } = await cell.json(`/repos/${id}/trails/${encodeURIComponent(ref.number)}/update-branch`, { method: 'POST', signal })
+ return { status: 'updated', raw: data }
+ }
+
+ const GRANT_ROLES: Partial> = { read: 'reader', write: 'writer', admin: 'admin' }
+
+ return {
+ traits: {
+ eventKinds: 'native',
+ authKinds: ['token'],
+ },
+ notApplicable: [
+ 'repos.labels',
+ 'repos.createLabel',
+ 'repos.milestones',
+ 'repos.issueTypes',
+ 'threads.setLabels',
+ 'threads.addLabels',
+ 'threads.removeLabels',
+ 'threads.setMilestone',
+ 'threads.setIssueType',
+ 'threads.reactions',
+ 'threads.react',
+ 'threads.transfer',
+ 'threads.markDuplicate',
+ 'threads.lock',
+ 'threads.hideComment',
+ 'releases.list',
+ 'releases.get',
+ 'releases.getByTag',
+ 'releases.latest',
+ 'releases.create',
+ 'releases.update',
+ 'releases.downloadAsset',
+ ],
+ users: {
+ me: verb(true, async (opts): Promise => {
+ const { data } = await fetcher.json('/me', { signal: opts?.signal })
+ return {
+ ...origin,
+ login: data.global.username ?? data.global.handle ?? data.global.accountId,
+ id: data.global.accountId,
+ ...data.global.displayName ? { name: data.global.displayName } : {},
+ ...data.global.avatarUrl ? { avatarUrl: data.global.avatarUrl } : {},
+ isBotHint: false,
+ ...data.regional?.bio ? { bio: data.regional.bio } : {},
+ ...data.regional?.location ? { location: data.regional.location } : {},
+ ...data.global.createdAt ? { createdAt: toDate(data.global.createdAt) } : {},
+ raw: data,
+ }
+ }),
+ },
+ repos: {
+ get: verb(true, async (repo, opts) => {
+ const { cell, id } = await located(repo)
+ return toRepo(origin, (await cell.json(`/repos/${id}`, { signal: opts?.signal })).data)
+ }),
+ listPage: verb(true, async (query: RepoQuery = {}) => tokenPage(fetcher, '/repos', camelPage('repos'), query, (raw: EntireCoreRepo) => toCoreRepo(origin, raw))),
+ byOwnerPage: verb(true, async (owner: RepoRef, query: RepoQuery = {}) => {
+ const { id } = await project(owner.owner)
+ const page = await tokenPage(fetcher, `/projects/${encodeURIComponent(id)}/repos`, camelPage('repos'), query, (raw: EntireCoreRepo) => toCoreRepo(origin, raw, owner.owner))
+ return query.visibility && query.visibility !== 'all' ? { ...page, items: page.items.filter(repo => repo.visibility === query.visibility) } : page
+ }),
+ collaboratorsPage: verb(true, async (repo, page: PageOptions = {}) => {
+ const { id } = await located(repo)
+ return tokenPage(fetcher, `/repos/${id}/collaborators`, camelPage('collaborators'), page, (raw: EntireCollaborator) => toCollaborator(origin, raw))
+ }),
+ addCollaborator: verb(true, async (repo, actor, role, opts) => {
+ const grant = GRANT_ROLES[role]
+ if (!grant) {
+ throw new UnsupportedOperationError(`Entire has no ${role} repository role`, origin)
+ }
+ if (typeof actor === 'string') {
+ throw new UnsupportedOperationError('Entire grants access to an account by its id; pass an actor', origin)
+ }
+ const { id } = await located(repo)
+ await fetcher.json(`/repos/${id}/grants`, { method: 'POST', json: { accountId: actor.id, role: grant }, signal: opts?.signal })
+ return { status: 'added' }
+ }),
+ },
+ threads: {
+ get: perKind({ issue: true, pull_request: true }, get),
+ listPage: perKind({ issue: true, pull_request: true }, listPage),
+ getMany: verb(true, (refs: ThreadRef[], opts) => getManyConcurrently(refs, get, opts)),
+ eventsPage: verb(true, eventsPage),
+ commentsPage: perKind({ issue: true, pull_request: true }, async (thread: ThreadRef, list: ListOptions = {}) => slice(await comments(requireIssueOrPull(thread, origin, 'read comments on'), list.signal), list)),
+ getComment: perKind({ issue: true, pull_request: true }, async (ref, opts) => {
+ const { thread, discussion, message } = await findComment(ref, opts?.signal)
+ return message ? toComment(thread, discussion, message) : findingComment(thread, discussion.review_comment!, discussion.resolved || discussion.review_comment!.status !== 'open')
+ }),
+ createComment: perKind({ issue: true, pull_request: true }, createComment),
+ updateComment: perKind({ issue: true, pull_request: true }, async (ref, body, opts) => {
+ const { thread, discussion, cell, path } = await messagePath(ref, opts?.signal)
+ const { data } = await cell.json<{ message: EntireMessage }>(path, { method: 'PATCH', json: { body }, signal: opts?.signal })
+ return toComment(thread, discussion, data.message)
+ }),
+ deleteComment: perKind({ issue: true, pull_request: true }, async (ref, opts) => {
+ const { cell, path } = await messagePath(ref, opts?.signal)
+ await cell.raw(path, { method: 'DELETE', signal: opts?.signal })
+ }),
+ create: perKind({ issue: true, pull_request: true }, create),
+ update: perKind({ issue: true, pull_request: true }, update),
+ close: perKind({ issue: true, pull_request: true }, (ref, opts) => update(ref, { state: 'closed' }, opts)),
+ reopen: perKind({ issue: true, pull_request: true }, (ref, opts) => update(ref, { state: 'open' }, opts)),
+ setAssignees: perKind({ issue: true, pull_request: true }, async (ref, assignees, opts) => (await update(ref, { assignees }, opts)).assignees),
+ merge: verb(true, merge),
+ updateBranch: verb(true, updateBranch),
+ getReview: verb(true, getReview),
+ createReview: verb(true, createReview),
+ replyToReviewComment: verb(true, replyToReviewComment),
+ reviewThreads: verb(true, {
+ resolveReviewThread: (ref, id, opts) => setResolved(ref, id, true, opts),
+ unresolveReviewThread: (ref, id, opts) => setResolved(ref, id, false, opts),
+ }),
+ reviewsPage: verb(true, async (thread: ThreadRef, page: PageOptions = {}) => slice(await reviews(requirePull(thread, 'reviewed'), page.signal), page)),
+ reviewThreadsPage: verb(true, async (thread: ThreadRef, page: PageOptions = {}): Promise> => {
+ const ref = requirePull(thread, 'reviewed')
+ const threads = (await discussions(ref, page.signal))
+ .filter((discussion): discussion is EntireDiscussion & { review_comment: EntireReviewComment } => Boolean(discussion.review_comment))
+ .map(discussion => toReviewThread(ref, discussion))
+ return slice(threads, page)
+ }),
+ filesPage: verb(true, async (thread: ThreadRef, page: PageOptions = {}): Promise> => {
+ const { repo, base, head } = await changeRange(thread, page.signal)
+ const { cell, path } = await located(repo)
+ const { data } = await cell.json<{ files?: EntireCompareFile[] }>(`${path}/compare`, { query: { base, head }, signal: page.signal })
+ return slice((data.files ?? []).map(toChangedFile), page)
+ }),
+ commitsPage: verb(true, async (thread: ThreadRef, page: PageOptions = {}): Promise> => {
+ const { repo, base, head } = await changeRange(thread, page.signal)
+ const { cell, path } = await located(repo)
+ return tokenPage(cell, `${path}/commits`, camelPage('commits'), { ...page, query: { ref: head, notRef: base } }, (raw: EntireCommit) => toCommit(origin, repo, raw))
+ }),
+ },
+ contents: {
+ getFile: verb(true, async (repo: RepoRef, filePath: string, fileOptions: FileOptions = {}) => {
+ const { cell, path } = await located(repo)
+ const at = fileOptions.ref ?? 'HEAD'
+ const response = await cell.raw(`${path}/raw`, { query: { ref: at, path: filePath }, signal: fileOptions.signal })
+ const bytes = new Uint8Array(await response.arrayBuffer())
+ const sha = response.headers.get('entire-sha') ?? undefined
+ const metadata = { path: filePath, ...sha ? { sha } : {}, size: bytes.byteLength, url: webUrlFor(web, { file: { repo, path: filePath, at } }) }
+ return toFileContent(bytes, metadata, fileOptions, origin)
+ }),
+ treePage: verb(true, async (repo: RepoRef, tree: TreeOptions = {}): Promise> => {
+ const { cell, path } = await located(repo)
+ const { data } = await cell.json<{ entries: EntireTreeEntry[], truncated?: boolean }>(`${path}/tree`, {
+ query: { ref: tree.ref ?? 'HEAD', path: tree.path || undefined, recursive: tree.recursive || undefined },
+ signal: tree.signal,
+ })
+ const page = slice(data.entries.map(entry => toTreeEntry(entry, tree.recursive ? '' : tree.path?.replace(/\/$/, '') ?? '')), tree)
+ return data.truncated ? { ...page, warnings: [{ code: 'tree_truncated', message: 'Entire truncated the tree' }] } : page
+ }),
+ branchesPage: verb(true, async (repo: RepoRef, page: PageOptions = {}): Promise> => {
+ const { cell, path } = await located(repo)
+ const [head, branches] = await Promise.all([
+ cell.json<{ refs: EntireRef[] }>(`${path}/refs`, { query: { prefix: 'HEAD', symrefs: true }, signal: page.signal }),
+ tokenPage(cell, `${path}/branches`, camelPage('branches'), page, (raw: EntireBranch) => raw),
+ ])
+ const defaultBranch = head.data.refs.find(ref => ref.name === 'HEAD')?.symrefTarget?.replace(/^refs\/heads\//, '')
+ return { ...branches, items: branches.items.map(raw => toBranch(raw, defaultBranch)) }
+ }),
+ tagsPage: verb(true, async (repo: RepoRef, page: PageOptions = {}): Promise> => {
+ const refs = await refsPage(repo, 'refs/tags/', page)
+ return { ...refs, items: refs.items.map(raw => ({ name: raw.name.replace(/^refs\/tags\//, ''), sha: raw.peeled ?? raw.oid, raw })) }
+ }),
+ resolveRef: verb(true, resolveRef),
+ compare: verb(true, compare),
+ },
+ commits: {
+ listPage: verb(true, async (repo: RepoRef, query: CommitQuery = {}): Promise> => {
+ const { cell, path } = await located(repo)
+ return tokenPage(cell, `${path}/commits`, camelPage('commits'), {
+ ...query,
+ query: { ref: query.ref ?? 'HEAD', path: query.path, author: query.author, since: query.since?.toISOString(), until: query.until?.toISOString(), includeStats: true },
+ }, (raw: EntireCommit) => toCommit(origin, repo, raw))
+ }),
+ get: verb(true, async ({ repo, sha }: CommitRef, opts?: BaseOptions) => {
+ const { cell, path, id } = await located(repo)
+ const [{ data: list }, { data: detail }] = await Promise.all([
+ cell.json<{ commits: EntireCommit[] }>(`${path}/commits`, { query: { ref: sha, pageSize: 1, includeStats: true }, signal: opts?.signal }),
+ cell.json(`/repos/${id}/commits/${encodeURIComponent(sha)}`, { signal: opts?.signal }),
+ ])
+ const commit = list.commits[0]
+ if (!commit) {
+ throw new NotFoundError(`No commit ${sha} in ${repo.owner}/${repo.name}`, 404, '', origin)
+ }
+ const files = (detail.commit.fileStats ?? []).map((file): ChangedFile => ({
+ path: file.filename,
+ ...file.previousFilename && file.previousFilename !== file.filename ? { previousPath: file.previousFilename } : {},
+ status: toFileStatus(file.status),
+ statusRaw: file.status,
+ ...file.additions !== undefined ? { additions: file.additions } : {},
+ ...file.deletions !== undefined ? { deletions: file.deletions } : {},
+ ...file.patch ? { patch: file.patch } : {},
+ }))
+ return toCommit(origin, repo, commit, files, detail)
+ }),
+ },
+ web,
+ webhooks: {},
+ scopes: entireScopesFor,
+ }
+}
+
+const ENTIRE: ProviderDefinition = {
+ forge: FORGE,
+ hasWeb: true,
+ experimental: true,
+ baseUrl: ENTIRE_WEB.baseUrl,
+ apiPath: ENTIRE_WEB.apiPath,
+ instance: ENTIRE_WEB.instance,
+ authHeaders: ({ options: { auth } }) => bearer(auth),
+ setup: setupEntire,
+}
+
+/** Creates an Entire provider for the repositories Entire hosts, which live under `et/`. */
+export const entire: ProviderFactoryFunction = /* @__PURE__ */ defineForgeProvider(ENTIRE)
+
+/** Scope requirements for an Entire operation. */
+function entireScopesFor(_verb: ForgeVerb): VerbScopes {
+ return { note: 'An Entire login token, which carries the scopes of the account that signed in' }
+}
diff --git a/src/entire/normalise.ts b/src/entire/normalise.ts
new file mode 100644
index 000000000..a7a270ead
--- /dev/null
+++ b/src/entire/normalise.ts
@@ -0,0 +1,363 @@
+import type { Actor, Branch, ChangedFile, Collaborator, Comment, Commit, CommitSignature, ForgeOrigin, Repo, RepoRef, RepoRole, ResolvedThreadRef, Review, ReviewComment, ReviewState, ReviewThread, Thread, TreeEntry } from '../model.ts'
+import type { EntireApproval, EntireAuthor, EntireBranch, EntireChange, EntireCollaborator, EntireCommit, EntireCompareFile, EntireCoreRepo, EntireDiscussion, EntireIdentity, EntireMessage, EntirePerson, EntireProjectTrail, EntireRepo, EntireReviewComment, EntireSignature, EntireTreeEntry } from './types.ts'
+import { namespaceRef } from '../model.ts'
+import { toDate, toFileStatus } from '../utils.ts'
+
+export const FORGE = 'entire'
+
+export function projectRef(origin: ForgeOrigin, project: string): RepoRef {
+ return namespaceRef(origin, project)
+}
+
+export function toRepoRef(origin: ForgeOrigin, project: string, name: string, id?: string): RepoRef {
+ return { forge: origin.forge, instance: origin.instance, owner: project, name, ...id ? { externalId: id } : {} }
+}
+
+function primaryHandle(identity: EntireIdentity | undefined): string | undefined {
+ const handles = identity?.handles ?? []
+ return (handles.find(handle => handle.is_primary) ?? handles[0])?.handle
+}
+
+/** An account from the identity Entire attaches to people, automations and agents. */
+export function identityActor(origin: ForgeOrigin, identity: EntireIdentity | undefined, login?: string): Actor | undefined {
+ const name = login ?? identity?.username ?? primaryHandle(identity) ?? identity?.automation?.slug ?? identity?.display_name ?? undefined
+ if (!name) {
+ return undefined
+ }
+ return {
+ forge: origin.forge,
+ instance: origin.instance,
+ login: name,
+ id: identity?.account_id ?? name,
+ ...identity?.display_name ? { name: identity.display_name } : {},
+ ...identity?.avatar_url ? { avatarUrl: identity.avatar_url } : {},
+ ...identity?.kind && identity.kind !== 'account' ? { typeRaw: identity.kind } : {},
+ isBotHint: identity?.kind === 'automation' || identity?.kind === 'agent' || Boolean(identity?.automation),
+ }
+}
+
+export function personActor(origin: ForgeOrigin, person: EntirePerson | EntireAuthor | undefined): Actor | undefined {
+ if (!person) {
+ return undefined
+ }
+ const actor = identityActor(origin, person.identity, person.login)
+ const id = 'accountId' in person ? person.accountId : 'id' in person ? person.id : undefined
+ return actor && id ? { ...actor, id } : actor
+}
+
+export function toRepo(origin: ForgeOrigin, raw: EntireRepo): Repo {
+ const [project = '', name = raw.name] = raw.full_name.split('/')
+ return {
+ ref: toRepoRef(origin, project, name, raw.id),
+ ...raw.description ? { description: raw.description } : {},
+ ...raw.default_branch ? { defaultBranch: raw.default_branch } : {},
+ visibility: raw.private ? 'private' : 'public',
+ isFork: false,
+ isArchived: raw.archived,
+ topics: [],
+ ...raw.updated_at ? { updatedAt: toDate(raw.updated_at) } : {},
+ ...raw.language ? { language: raw.language } : {},
+ ...raw.stargazers_count !== undefined ? { stars: raw.stargazers_count } : {},
+ ...raw.open_pr_count !== undefined ? { openPullCount: raw.open_pr_count } : {},
+ ...raw.permission ? { permissions: permissionsOf(raw.permission) } : {},
+ raw,
+ }
+}
+
+/** A repository as the control plane lists it, or `undefined` for a GitHub mirror. `project` names its project where `raw` does not. */
+export function toCoreRepo(origin: ForgeOrigin, raw: EntireCoreRepo, project?: string): Repo | undefined {
+ if (raw.provider && raw.provider !== 'entire') {
+ return undefined
+ }
+ const [owner = project ?? raw.org ?? '', name = raw.name] = (raw.full_name ?? '').split('/')
+ const permission = raw.permission ?? (raw.capabilities?.canManage ? 'admin' : raw.capabilities?.canPush ? 'write' : raw.capabilities?.canPull ? 'read' : undefined)
+ return {
+ ref: toRepoRef(origin, owner, name, raw.id),
+ visibility: raw.visibility === 'public' ? 'public' : 'private',
+ isFork: false,
+ isArchived: false,
+ topics: [],
+ ...raw.lastPushedAt ? { pushedAt: toDate(raw.lastPushedAt) } : {},
+ ...permission ? { permissions: permissionsOf(permission) } : {},
+ raw,
+ }
+}
+
+export function toRole(raw: string | undefined): RepoRole {
+ switch (raw) {
+ case 'owner':
+ case 'admin':
+ return 'admin'
+ case 'maintain':
+ case 'maintainer':
+ return 'maintain'
+ case 'write':
+ case 'writer':
+ return 'write'
+ case 'triage':
+ return 'triage'
+ case 'read':
+ case 'reader':
+ return 'read'
+ default:
+ return 'none'
+ }
+}
+
+function permissionsOf(raw: string): Repo['permissions'] {
+ const role = toRole(raw)
+ const rank = ['none', 'read', 'triage', 'write', 'maintain', 'admin'].indexOf(role)
+ return { admin: rank >= 5, maintain: rank >= 4, push: rank >= 3, triage: rank >= 2, pull: rank >= 1 }
+}
+
+export function toCollaborator(origin: ForgeOrigin, raw: EntireCollaborator): Collaborator {
+ return {
+ actor: { forge: origin.forge, instance: origin.instance, login: raw.username, id: raw.accountId, isBotHint: false },
+ role: toRole(raw.role),
+ roleRaw: raw.role,
+ raw,
+ }
+}
+
+export function toBranch(raw: EntireBranch, defaultBranch?: string): Branch {
+ return { name: raw.name, sha: raw.commitSHA, ...defaultBranch ? { isDefault: raw.name === defaultBranch } : {}, raw }
+}
+
+function toSignature(raw: EntireSignature | undefined): CommitSignature | undefined {
+ return raw ? { ...raw.name ? { name: raw.name } : {}, ...raw.email ? { email: raw.email } : {}, ...raw.date ? { date: toDate(raw.date) } : {} } : undefined
+}
+
+export function toCommit(origin: ForgeOrigin, repo: RepoRef, raw: EntireCommit, files?: ChangedFile[], detail?: unknown): Commit {
+ return {
+ ref: { forge: origin.forge, instance: origin.instance, repo, sha: raw.sha },
+ sha: raw.sha,
+ message: raw.message.replace(/\n$/, ''),
+ author: toSignature(raw.author),
+ committer: toSignature(raw.committer),
+ parents: raw.parents,
+ ...raw.stats ? { stats: { additions: raw.stats.additions, deletions: raw.stats.deletions, total: raw.stats.additions + raw.stats.deletions } } : {},
+ ...files ? { files } : {},
+ raw: detail === undefined ? raw : { commit: raw, detail },
+ }
+}
+
+export function toChangedFile(raw: EntireCompareFile): ChangedFile {
+ return {
+ path: raw.path,
+ ...raw.oldPath && raw.oldPath !== raw.path ? { previousPath: raw.oldPath } : {},
+ status: toFileStatus(raw.status),
+ statusRaw: raw.status,
+ ...raw.additions !== undefined ? { additions: raw.additions } : {},
+ ...raw.deletions !== undefined ? { deletions: raw.deletions } : {},
+ ...raw.binary ? { isBinary: true } : {},
+ }
+}
+
+export function toTreeEntry(raw: EntireTreeEntry, parent: string): TreeEntry {
+ const path = parent && !raw.path.startsWith(`${parent}/`) ? `${parent}/${raw.path}` : raw.path
+ return {
+ path,
+ type: raw.type === 'tree' ? 'directory' : raw.type === 'commit' ? 'submodule' : raw.mode === '120000' ? 'symlink' : 'file',
+ sha: raw.sha,
+ ...raw.type === 'blob' && raw.size !== undefined ? { size: raw.size } : {},
+ mode: raw.mode,
+ }
+}
+
+export function issueRef(origin: ForgeOrigin, project: string, number: number | string): ResolvedThreadRef {
+ return { forge: origin.forge, instance: origin.instance, repo: projectRef(origin, project), kind: 'issue', number: String(number) }
+}
+
+export function pullRef(repo: RepoRef, number: number | string): ResolvedThreadRef {
+ return { forge: repo.forge, instance: repo.instance, repo, kind: 'pull_request', number: String(number) }
+}
+
+/** A project trail, read as an issue. */
+export function toIssue(origin: ForgeOrigin, project: string, raw: EntireProjectTrail): Thread {
+ return {
+ ref: { ...issueRef(origin, project, raw.number), externalId: raw.id },
+ kind: 'issue',
+ title: raw.title,
+ ...raw.body ? { body: raw.body } : {},
+ state: raw.status === 'closed' ? 'closed' : 'open',
+ stateRaw: raw.status,
+ isDraft: false,
+ author: personActor(origin, raw.author),
+ assignees: (raw.assigneeProfiles ?? []).flatMap(person => personActor(origin, person) ?? []),
+ reviewers: [],
+ labels: [],
+ ...raw.url ? { url: raw.url } : {},
+ createdAt: toDate(raw.createdAt),
+ updatedAt: toDate(raw.metadataUpdatedAt),
+ ...raw.closedAt ? { closedAt: toDate(raw.closedAt) } : {},
+ lastActivityAt: toDate(raw.lastActivityAt),
+ ...raw.type ? { issueType: { id: raw.type, name: raw.type, raw: raw.type } } : {},
+ raw,
+ }
+}
+
+function reviewerState(raw: string | undefined): ReviewState {
+ switch (raw) {
+ case 'approved':
+ return 'approved'
+ case 'changes_requested':
+ return 'changes_requested'
+ case 'requested':
+ case 'pending':
+ return 'pending'
+ case 'dismissed':
+ return 'dismissed'
+ default:
+ return 'unknown'
+ }
+}
+
+/** A change, the branch of one repository on a trail, read as a pull request. Its description is its parent trail's body, `parentBody`, unless it has its own. */
+export function toPull(repo: RepoRef, raw: EntireChange, parentBody?: string): Thread {
+ const origin = { forge: repo.forge, instance: repo.instance }
+ const state = raw.status === 'merged' ? 'merged' : raw.status === 'closed' ? 'closed' : raw.status === 'open' ? 'open' : 'unknown'
+ const body = raw.body_document?.text_snapshot || parentBody
+ return {
+ ref: { ...pullRef(repo, raw.number), externalId: raw.id },
+ kind: 'pull_request',
+ title: raw.title,
+ ...body ? { body } : {},
+ state,
+ stateRaw: raw.status,
+ isDraft: raw.ready_for_review === false,
+ author: personActor(origin, raw.author),
+ assignees: (raw.assignee_identities ?? []).flatMap(assignee => identityActor(origin, assignee.identity, assignee.login) ?? []),
+ reviewers: (raw.reviewers ?? []).flatMap((reviewer) => {
+ const actor = identityActor(origin, reviewer.identity, reviewer.login)
+ const state = reviewer.state ?? reviewer.status
+ return actor ? [{ actor, state: reviewerState(state), ...state ? { stateRaw: state } : {} }] : []
+ }),
+ labels: [],
+ ...raw.url ? { url: raw.url } : {},
+ createdAt: toDate(raw.created_at),
+ updatedAt: toDate(raw.updated_at),
+ ...raw.merged_at ? { mergedAt: toDate(raw.merged_at), closedAt: toDate(raw.merged_at) } : {},
+ lastActivityAt: toDate(raw.last_activity_at),
+ ...raw.comment_count !== undefined ? { commentCount: raw.comment_count } : {},
+ branches: {
+ head: { ref: raw.branch || raw.original_branch || '', ...raw.head_sha ? { sha: raw.head_sha } : {}, repo },
+ base: { ref: raw.base, ...raw.base_sha ? { sha: raw.base_sha } : {} },
+ },
+ raw,
+ }
+}
+
+/** Every message of a discussion, replies after the message they answer. */
+export function flattenMessages(messages: EntireMessage[] = []): EntireMessage[] {
+ return messages.flatMap(message => [message, ...flattenMessages(message.replies)])
+}
+
+export function toComment(thread: ResolvedThreadRef, discussion: Pick, raw: EntireMessage): Comment {
+ return {
+ ref: { forge: thread.forge, instance: thread.instance, thread, id: raw.id },
+ body: raw.body,
+ author: identityActor(thread, raw.identity, raw.author),
+ createdAt: toDate(raw.created_at),
+ ...raw.updated_at ? { updatedAt: toDate(raw.updated_at) } : {},
+ thread: { id: discussion.id, resolved: discussion.resolved },
+ raw,
+ }
+}
+
+function findingAuthor(thread: ResolvedThreadRef, raw: EntireReviewComment): Actor | undefined {
+ const actor = identityActor(thread, raw.actor_identity?.kind === 'unknown' ? undefined : raw.actor_identity, undefined)
+ return actor ?? (raw.actor_display_name ? { forge: thread.forge, instance: thread.instance, login: raw.actor_display_name, id: raw.actor_id ?? raw.actor_display_name, isBotHint: false } : undefined)
+}
+
+function anchor(raw: EntireReviewComment): Pick {
+ const { file_path, end_line, start_line } = raw.location
+ const line = end_line ?? start_line
+ return {
+ ...file_path ? { path: file_path } : {},
+ ...line ? { line, side: 'right' as const } : {},
+ }
+}
+
+/** A finding: a review comment an agent or a person left on a change. */
+export function toFinding(thread: ResolvedThreadRef, raw: EntireReviewComment, resolved = raw.status !== 'open'): ReviewComment {
+ return {
+ ref: { forge: thread.forge, instance: thread.instance, thread, id: raw.id },
+ body: raw.body ?? '',
+ author: findingAuthor(thread, raw),
+ ...anchor(raw),
+ ...raw.discussion_id ? { thread: { id: raw.discussion_id, resolved } } : {},
+ createdAt: toDate(raw.created_at),
+ raw,
+ }
+}
+
+/** A finding, read as a comment. */
+export function findingComment(thread: ResolvedThreadRef, raw: EntireReviewComment, resolved = raw.status !== 'open'): Comment {
+ return {
+ ref: { forge: thread.forge, instance: thread.instance, thread, id: raw.id },
+ body: raw.body ?? '',
+ author: findingAuthor(thread, raw),
+ createdAt: toDate(raw.created_at),
+ ...raw.updated_at ? { updatedAt: toDate(raw.updated_at) } : {},
+ ...raw.discussion_id ? { thread: { id: raw.discussion_id, resolved } } : {},
+ raw,
+ }
+}
+
+/** A finding and the discussion under it, read as a review conversation. */
+export function toReviewThread(thread: ResolvedThreadRef, discussion: EntireDiscussion & { review_comment: EntireReviewComment }): ReviewThread {
+ const finding = discussion.review_comment
+ const resolved = discussion.resolved || finding.status !== 'open'
+ const { start_line, end_line } = finding.location
+ const replies = flattenMessages(discussion.messages).map((message): ReviewComment => ({
+ ref: { forge: thread.forge, instance: thread.instance, thread, id: message.id },
+ body: message.body,
+ author: identityActor(thread, message.identity, message.author),
+ ...anchor(finding),
+ inReplyTo: finding.id,
+ thread: { id: discussion.id, resolved },
+ createdAt: toDate(message.created_at),
+ raw: message,
+ }))
+ return {
+ id: discussion.id,
+ resolved,
+ outdated: finding.stale_outcome === 'stale',
+ ...anchor(finding),
+ ...start_line && end_line && start_line !== end_line ? { startLine: start_line } : {},
+ comments: [toFinding(thread, finding, resolved), ...replies],
+ raw: discussion,
+ }
+}
+
+export function approvalState(raw: string): ReviewState {
+ return raw === 'approved' || raw === 'approve' ? 'approved' : raw === 'changes_requested' || raw === 'request_changes' ? 'changes_requested' : raw === 'dismissed' ? 'dismissed' : 'unknown'
+}
+
+export function toApproval(thread: ResolvedThreadRef, raw: EntireApproval): Review {
+ return {
+ ref: { forge: thread.forge, instance: thread.instance, thread, id: raw.id },
+ author: { forge: thread.forge, instance: thread.instance, login: raw.author, id: raw.author, isBotHint: false },
+ state: approvalState(raw.event),
+ stateRaw: raw.event,
+ ...raw.body ? { body: raw.body } : {},
+ submittedAt: toDate(raw.created_at),
+ comments: false,
+ raw,
+ }
+}
+
+/** The findings of one code review, as a review that comments. */
+export function toCodeReview(thread: ResolvedThreadRef, id: string, findings: EntireReviewComment[], summary?: string | null): Review {
+ const [first] = findings
+ return {
+ ref: { forge: thread.forge, instance: thread.instance, thread, id },
+ author: first ? findingAuthor(thread, first) : undefined,
+ state: 'commented',
+ stateRaw: 'review',
+ ...summary ? { body: summary } : {},
+ submittedAt: first ? toDate(first.created_at) : undefined,
+ comments: findings.map(finding => toFinding(thread, finding)),
+ raw: findings,
+ }
+}
diff --git a/src/entire/types.ts b/src/entire/types.ts
new file mode 100644
index 000000000..fac0e1777
--- /dev/null
+++ b/src/entire/types.ts
@@ -0,0 +1,322 @@
+export interface EntireIdentityHandle {
+ provider: string
+ handle: string
+ is_primary?: boolean
+}
+
+export interface EntireIdentity {
+ kind: string
+ account_id?: string | null
+ display_name?: string | null
+ avatar_url?: string | null
+ username?: string
+ handles?: EntireIdentityHandle[]
+ automation?: { kind?: string, name?: string, slug?: string } | null
+}
+
+/** A person as project trails report them. */
+export interface EntirePerson {
+ accountId?: string
+ login: string
+ displayName?: string
+ avatarUrl?: string
+ username?: string
+ identity?: EntireIdentity
+}
+
+/** A person as repository trails report them. */
+export interface EntireAuthor {
+ id?: string
+ login: string
+ identity?: EntireIdentity
+}
+
+export interface EntireProject {
+ id: string
+ name: string
+ ownerType?: string
+ ownerName?: string
+ region?: string
+ createdAt?: string
+ primaryProcessingCell?: string
+ apiUrl?: string
+}
+
+export interface EntireResolvedProject {
+ project: EntireProject
+ reference?: { host: string, project: string }
+}
+
+/** A repository as the control plane lists it. */
+export interface EntireCoreRepo {
+ id: string
+ name: string
+ full_name?: string
+ org?: string
+ provider?: string
+ visibility?: string
+ permission?: string
+ path?: string
+ owningProjectId?: string
+ lastPushedAt?: string
+ placements?: Array<{ id: string, apiBaseUrl?: string, mirror?: boolean, status?: string }>
+ capabilities?: { canManage?: boolean, canPush?: boolean, canPull?: boolean }
+}
+
+/** Where the control plane places a repository. */
+export interface EntireRepoResolution {
+ requestedFullName: string
+ status: string
+ repoId?: string
+ placements?: EntireCoreRepo['placements']
+}
+
+/** A repository as a cell reports it. */
+export interface EntireRepo {
+ id: string
+ owning_project_id?: string
+ name: string
+ full_name: string
+ description?: string | null
+ private: boolean
+ archived: boolean
+ updated_at?: string
+ default_branch?: string
+ language?: string | null
+ stargazers_count?: number
+ permission?: string
+ trails_enabled?: boolean
+ open_pr_count?: number
+}
+
+export interface EntireCollaborator {
+ accountId: string
+ username: string
+ handle?: string
+ role: string
+}
+
+export interface EntireMe {
+ global: {
+ accountId: string
+ handle?: string
+ username?: string
+ displayName?: string
+ avatarUrl?: string
+ createdAt?: string
+ homeJurisdiction?: string
+ }
+ regional?: {
+ displayName?: string
+ bio?: string
+ location?: string
+ }
+}
+
+export interface EntireBranch {
+ name: string
+ commitSHA: string
+}
+
+export interface EntireRef {
+ name: string
+ oid: string
+ symrefTarget?: string
+ peeled?: string
+}
+
+export interface EntireSignature {
+ name?: string
+ email?: string
+ date?: string
+}
+
+export interface EntireCommit {
+ sha: string
+ parents: string[]
+ message: string
+ author?: EntireSignature
+ committer?: EntireSignature
+ stats?: { additions: number, deletions: number, filesChanged?: number, truncated?: boolean } | null
+}
+
+export interface EntireCommitDetail {
+ commit_sha: string
+ commit: {
+ commitSha: string
+ fileStats?: Array<{ filename: string, previousFilename?: string | null, status: string, additions?: number, deletions?: number, patch?: string | null }>
+ }
+}
+
+export interface EntireCompareStatus {
+ commitsAhead: number
+ commitsBehind: number
+ mergeBaseSHA?: string
+ resolvedBaseSHA?: string
+ resolvedHeadSHA?: string
+}
+
+export interface EntireCompareFile {
+ path: string
+ oldPath?: string
+ status: string
+ additions?: number
+ deletions?: number
+ binary?: boolean
+}
+
+export interface EntireTreeEntry {
+ path: string
+ mode: string
+ type: string
+ sha: string
+ size?: number
+}
+
+/** A project trail: what the provider reads as an issue. */
+export interface EntireProjectTrail {
+ id: string
+ projectId: string
+ number: number
+ url?: string
+ title: string
+ body?: string
+ status: 'open' | 'closed'
+ phase?: string
+ type?: string
+ priority?: string
+ author?: EntirePerson
+ assigneeProfiles?: EntirePerson[]
+ assignees?: string[]
+ repoIds?: string[]
+ createdAt?: string
+ metadataUpdatedAt?: string
+ closedAt?: string | null
+ lastActivityAt?: string
+ changes?: Array<{ id: string, repositoryId: string, repository: string, number: number, branch: string, base: string, status: string, title: string }>
+}
+
+/** A repository trail, which the trail docs call a change: what the provider reads as a pull request. */
+export interface EntireChange {
+ id: string
+ number: number
+ url?: string
+ title: string
+ status: string
+ branch: string
+ original_branch?: string
+ base: string
+ type?: string
+ priority?: string
+ author?: EntireAuthor
+ assignees?: string[]
+ assignee_identities?: Array<{ login: string, identity?: EntireIdentity }>
+ reviewers?: Array<{ login: string, state?: string, status?: string, identity?: EntireIdentity, actor_account_id?: string }>
+ ready_for_review?: boolean
+ comment_count?: number
+ created_at?: string
+ updated_at?: string
+ merged_at?: string | null
+ last_activity_at?: string
+ phase?: string | null
+ parent?: { id: string, number: number, host: string, project: string, url?: string }
+ head_sha?: string | null
+ base_sha?: string | null
+ merge_base_sha?: string | null
+ body_document?: { text_snapshot?: string } | null
+}
+
+export interface EntireDiscussionCreated {
+ discussion: EntireDiscussion
+ message: EntireMessage
+}
+
+/** A code review, with its summary and findings. */
+export interface EntireReviewSnapshot {
+ review: { id: string, summary?: string | null, actor_id?: string | null, started_at?: string }
+ comments?: EntireReviewComment[]
+}
+
+export interface EntireReviewPublished {
+ review?: EntireReviewSnapshot['review']
+ results: Array<{ client_id: string, status: string, comment?: EntireReviewComment, error?: { code: string, message: string } }>
+}
+
+export interface EntireMerged {
+ ok: boolean
+ mergeCommitSha?: string
+ branchDeleted?: boolean
+}
+
+export interface EntireMessage {
+ id: string
+ author: string
+ identity?: EntireIdentity
+ body: string
+ created_at: string
+ updated_at?: string
+ replies?: EntireMessage[]
+}
+
+export interface EntireReviewComment {
+ id: string
+ review_id: string
+ discussion_id?: string | null
+ actor_id?: string | null
+ actor_display_name?: string | null
+ actor_identity?: EntireIdentity
+ body?: string | null
+ severity?: string | null
+ status: string
+ stale_outcome?: string
+ created_at: string
+ updated_at?: string
+ location: {
+ granularity: string
+ file_path?: string | null
+ start_line?: number | null
+ end_line?: number | null
+ }
+}
+
+export interface EntireDiscussion {
+ id: string
+ trail_id: string
+ title?: string
+ kind: string
+ resolved: boolean
+ created_by?: string | null
+ created_by_identity?: EntireIdentity
+ created_at: string
+ updated_at?: string
+ message_count: number
+ messages?: EntireMessage[]
+ review_comment_id?: string | null
+ review_comment?: EntireReviewComment | null
+}
+
+export interface EntireApproval {
+ id: string
+ author: string
+ event: string
+ body?: string | null
+ commit_sha?: string
+ created_at: string
+}
+
+export interface EntireTimelineItem {
+ id: string
+ kind: string
+ actor?: { login: string, identity?: EntireIdentity } | null
+ occurredAt?: string
+ occurred_at?: string
+ commentId?: string
+ comment_id?: string
+ discussionId?: string
+ discussion_id?: string
+ approvalStatus?: string
+ body?: string
+ title?: string
+ commitSha?: string
+ findingCount?: number
+}
diff --git a/src/entire/web.ts b/src/entire/web.ts
new file mode 100644
index 000000000..7e14c24f3
--- /dev/null
+++ b/src/entire/web.ts
@@ -0,0 +1,61 @@
+import type { RepoRef } from '../model.ts'
+import type { WebDefinition, WebLinks } from '../web.ts'
+
+/** Entire's prefix for repositories it hosts, beside `gh` for GitHub mirrors. */
+export const HOST = 'et'
+
+const projectPath = (project: string) => `/${HOST}/${encodeURIComponent(project)}`
+const repoPath = (repo: RepoRef) => repo.kind === 'namespace' ? projectPath(repo.owner) : `${projectPath(repo.owner)}/${encodeURIComponent(repo.name)}`
+
+export const ENTIRE_WEB: WebDefinition<'entire'> = {
+ forge: 'entire',
+ baseUrl: 'https://us.console.entire.io',
+ apiPath: '/api/v1',
+ instance: host => host.endsWith('.console.entire.io') ? 'entire.com' : host,
+ links: ({ instance }) => entireWeb(instance === 'entire.com' ? 'https://entire.com' : `https://${instance}`),
+}
+
+/**
+ * A project trail is at `/et/{project}/-/trails/{number}`, and a change, the
+ * branch of one repository on a trail, at `/et/{project}/{repo}/trails/{number}`.
+ */
+function entireWeb(baseUrl: string): WebLinks {
+ return {
+ baseUrl,
+ repo: repo => repoPath(repo),
+ section: (repo, section) => {
+ if (section === 'issues') {
+ return `${projectPath(repo.owner)}/-/trails`
+ }
+ return section === 'pullRequests' && repo.kind !== 'namespace' ? `${repoPath(repo)}/trails` : undefined
+ },
+ thread: (ref) => {
+ if (!ref.number) {
+ return undefined
+ }
+ if (ref.kind === 'issue') {
+ return `${projectPath(ref.repo.owner)}/-/trails/${encodeURIComponent(ref.number)}`
+ }
+ return ref.kind === 'pull_request' && ref.repo.kind !== 'namespace' ? `${repoPath(ref.repo)}/trails/${encodeURIComponent(ref.number)}` : undefined
+ },
+ parse: (segments, _url, from) => {
+ const [host, project, name, section, number] = segments
+ if (host !== HOST || !project) {
+ return undefined
+ }
+ const namespace: RepoRef = { ...from, owner: project, name: '', kind: 'namespace' }
+ if (!name) {
+ return { repo: namespace }
+ }
+ if (name === '-') {
+ return section === 'trails' && number && /^\d+$/.test(number)
+ ? { repo: namespace, thread: { ...from, repo: namespace, kind: 'issue', number } }
+ : { repo: namespace }
+ }
+ const repo: RepoRef = { ...from, owner: project, name }
+ return section === 'trails' && number && /^\d+$/.test(number)
+ ? { repo, thread: { ...from, repo, kind: 'pull_request', number } }
+ : { repo }
+ },
+ }
+}
diff --git a/src/env.ts b/src/env.ts
index e02a594be..9eb48f15f 100644
--- a/src/env.ts
+++ b/src/env.ts
@@ -4,6 +4,7 @@ import { azureDevOps } from './azure-devops/index.ts'
import { bitbucket } from './bitbucket/index.ts'
import { cloudflareArtifacts } from './cloudflare-artifacts/index.ts'
import { cursorOrigin } from './cursor-origin/index.ts'
+import { entire } from './entire/index.ts'
import { forgejo } from './forgejo/index.ts'
import { gitea } from './gitea/index.ts'
import { gitee } from './gitee/index.ts'
@@ -14,7 +15,7 @@ import { pushin } from './pushin/index.ts'
import { sourcehut } from './sourcehut/index.ts'
import { tangled } from './tangled/index.ts'
-const KINDS = ['GITHUB', 'GITLAB', 'BITBUCKET', 'FORGEJO', 'GITEA', 'TANGLED', 'CURSOR_ORIGIN', 'GITEE', 'AZURE_DEVOPS', 'PUSHIN', 'CLOUDFLARE_ARTIFACTS', 'SOURCEHUT'] as const
+const KINDS = ['GITHUB', 'GITLAB', 'BITBUCKET', 'FORGEJO', 'GITEA', 'TANGLED', 'CURSOR_ORIGIN', 'GITEE', 'AZURE_DEVOPS', 'PUSHIN', 'CLOUDFLARE_ARTIFACTS', 'SOURCEHUT', 'ENTIRE'] as const
/** Field names, longest first so `INSTANCE_VERSION` is not read as `INSTANCE` with suffix `VERSION`. */
const FIELDS = [
@@ -168,6 +169,10 @@ function fromFields(kind: typeof KINDS[number], suffix: string, fields: Partial<
return isOn(fields.ENABLED)
? { ...entry, provider: cursorOrigin(base) }
: { ...entry, skipped: 'needs TOKEN, or APP_ID and PRIVATE_KEY, or ENABLED for webhooks only' }
+ case 'ENTIRE':
+ return fields.TOKEN
+ ? { ...entry, provider: entire({ ...base, auth: { type: 'token', token: fields.TOKEN } }) }
+ : { ...entry, skipped: 'needs TOKEN' }
case 'SOURCEHUT':
return fields.TOKEN
? { ...entry, provider: sourcehut({ ...base, ...fields.WEBHOOK_PUBLIC_KEY ? { webhookPublicKey: fields.WEBHOOK_PUBLIC_KEY } : {}, auth: { type: 'token', token: fields.TOKEN } }) }
diff --git a/src/index.ts b/src/index.ts
index 111199145..a0ed423d2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,6 +7,8 @@ export type { CloudflareArtifactsAuth, CloudflareArtifactsOptions } from './clou
export { cursorOrigin, ORIGIN_API_VERSION } from './cursor-origin/index.ts'
export type { CursorOriginAuth, CursorOriginOptions } from './cursor-origin/index.ts'
export type { ProviderFactoryFunction } from './define.ts'
+export { entire } from './entire/index.ts'
+export type { EntireAuth, EntireOptions } from './entire/index.ts'
export {
AuthenticationRequiredError,
BranchMovedError,
diff --git a/src/model.ts b/src/model.ts
index 91f538688..5633f2c61 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -12,7 +12,7 @@
export type ForgeKind = KnownForgeKind | (string & {})
/** The forges this package ships providers for. */
-export type KnownForgeKind = 'github' | 'gitlab' | 'bitbucket' | 'forgejo' | 'gitea' | 'tangled' | 'gitee' | 'azure-devops' | 'cursor-origin' | 'pushin' | 'cloudflare-artifacts' | 'sourcehut'
+export type KnownForgeKind = 'github' | 'gitlab' | 'bitbucket' | 'forgejo' | 'gitea' | 'tangled' | 'gitee' | 'azure-devops' | 'cursor-origin' | 'pushin' | 'cloudflare-artifacts' | 'sourcehut' | 'entire'
/**
* Capability flag. Every value other than `false` and `'not_applicable'` means the
diff --git a/src/schema/schemas.json b/src/schema/schemas.json
index 13129f8d3..4863437c3 100644
--- a/src/schema/schemas.json
+++ b/src/schema/schemas.json
@@ -2749,7 +2749,8 @@
"cursor-origin",
"pushin",
"cloudflare-artifacts",
- "sourcehut"
+ "sourcehut",
+ "entire"
],
"description": "The forges this package ships providers for.",
"x-forges-type": "KnownForgeKind"
diff --git a/src/web/index.ts b/src/web/index.ts
index e5cda5197..3c00e237c 100644
--- a/src/web/index.ts
+++ b/src/web/index.ts
@@ -6,6 +6,7 @@
import type { ForgeWeb, WebForges, WebOptions } from '../web.ts'
import { AZURE_DEVOPS_WEB } from '../azure-devops/web.ts'
import { BITBUCKET_WEB } from '../bitbucket/web.ts'
+import { ENTIRE_WEB } from '../entire/web.ts'
import { FORGEJO_WEB, GITEA_WEB } from '../forgejo/web.ts'
import { GITEE_WEB } from '../gitee/web.ts'
import { GITHUB_WEB } from '../github/web.ts'
@@ -22,6 +23,8 @@ export type { ForgeWeb, WebForges }
export const azureDevOps: (options?: WebOptions) => ForgeWeb<'azure-devops'> = /* @__PURE__ */ defineForgeWeb(AZURE_DEVOPS_WEB)
/** The web links of Bitbucket Cloud. */
export const bitbucket: (options?: WebOptions) => ForgeWeb<'bitbucket'> = /* @__PURE__ */ defineForgeWeb(BITBUCKET_WEB)
+/** The web links of Entire. */
+export const entire: (options?: WebOptions) => ForgeWeb<'entire'> = /* @__PURE__ */ defineForgeWeb(ENTIRE_WEB)
/** The web links of Codeberg or another Forgejo instance. */
export const forgejo: (options?: WebOptions) => ForgeWeb<'forgejo'> = /* @__PURE__ */ defineForgeWeb(FORGEJO_WEB)
/** The web links of gitea.com or another Gitea instance. */
diff --git a/test/fixtures/entire/recorded/entire.com-writes/manifest.json b/test/fixtures/entire/recorded/entire.com-writes/manifest.json
new file mode 100644
index 000000000..c962db391
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/manifest.json
@@ -0,0 +1,29 @@
+{
+ "baseUrl": "https://us.console.entire.io/api/v1",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "scratch": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "run": {
+ "id": "20261026210133",
+ "base": "main",
+ "branches": [
+ "forges-fixtures/20261026210133-1",
+ "forges-fixtures/20261026210133-2",
+ "forges-fixtures/20261026210133-3"
+ ],
+ "files": [
+ "forges-fixtures/20261026210133-1.md",
+ "forges-fixtures/20261026210133-2.md",
+ "forges-fixtures/20261026210133-3.md"
+ ]
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/01-post-01M5WYSQC41P69DTXEMZ0TE5YK-grants.json b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/01-post-01M5WYSQC41P69DTXEMZ0TE5YK-grants.json
new file mode 100644
index 000000000..905944f62
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/01-post-01M5WYSQC41P69DTXEMZ0TE5YK-grants.json
@@ -0,0 +1,20 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://us.console.entire.io/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK/grants",
+ "body": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "role": "writer"
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/GrantRepoAccessOutputBody.json",
+ "status": "granted"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/02-post-repos-resolve.json b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/02-post-repos-resolve.json
new file mode 100644
index 000000000..0fea65502
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/02-post-repos-resolve.json
@@ -0,0 +1,56 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://us.console.entire.io/api/v1/repos/resolve",
+ "body": {
+ "repositories": [
+ {
+ "fullName": "forges-scratch/forges-fixtures",
+ "provider": "entire"
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ResolveReposResponse.json",
+ "resolutions": [
+ {
+ "provider": "entire",
+ "requestedFullName": "forges-scratch/forges-fixtures",
+ "fullName": "forges-scratch/forges-fixtures",
+ "status": "ready",
+ "repoId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repoGroupId": "01M5WYSQC42H6K0NWNZ6FYTSRQ",
+ "placements": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "apiBaseUrl": "https://aws-eu-central-1.api.entire.io",
+ "clusterSlug": "aws-eu-central-1",
+ "mirror": false,
+ "status": "ready"
+ }
+ ],
+ "primaries": {
+ "git_data": "entire:01M5WYSQC41P69DTXEMZ0TE5YK",
+ "processing": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "data_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "processing_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/golden.json
new file mode 100644
index 000000000..490680bdd
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/golden.json
@@ -0,0 +1,3 @@
+{
+ "status": "added"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/step.json
new file mode 100644
index 000000000..b522b0e09
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/add-collaborator/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "add collaborator",
+ "recordedAt": "2026-10-26T21:00:32.537Z",
+ "needs": [
+ "reviewer"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/approve/01-post-N-approvals.json b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/01-post-N-approvals.json
new file mode 100644
index 000000000..59d2bd6e5
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/01-post-N-approvals.json
@@ -0,0 +1,138 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/approvals",
+ "body": {
+ "event": "approve"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailApprovalSubmitResponse.json",
+ "ok": true,
+ "approval": {
+ "id": "01M5X0APDPB2XFXGPA85JXFMA4",
+ "author": "danielroe",
+ "event": "approved",
+ "body": null,
+ "commit_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "created_at": "2026-10-26T21:02:13.430135692Z"
+ },
+ "gates": {
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "01M5X0APE62CD4GDEZHPF8PRZD",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:13.446381654Z",
+ "completed_at": "2026-10-26T21:02:13.446381654Z",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0ANSHBMKPX71GYC40D1GV",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "failed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 0,
+ "high": 0,
+ "medium": 0,
+ "unspecified": 1
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 1
+ },
+ "rationale": "Unresolved findings: 1 without a severity",
+ "created_at": "2026-10-26T21:02:12.7852Z",
+ "completed_at": "2026-10-26T21:02:12.7852Z",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A4D1WK6QWSP6JXWFERW1",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:54.977313Z",
+ "completed_at": "2026-10-26T21:01:54.977313Z",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ }
+ ],
+ "blocking_failures": [],
+ "mergeable": true,
+ "evaluated_at": "2026-10-26T21:02:13.446381654Z",
+ "gates_enabled": true
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/approve/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/golden.json
new file mode 100644
index 000000000..c02ad5121
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/golden.json
@@ -0,0 +1,31 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0APDPB2XFXGPA85JXFMA4"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "danielroe",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved",
+ "submittedAt": "",
+ "comments": false
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/approve/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/step.json
new file mode 100644
index 000000000..a8c6b4e35
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/approve/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "approve",
+ "recordedAt": "2026-10-26T21:02:12.955Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/author/01-get-v1-me.json b/test/fixtures/entire/recorded/entire.com-writes/steps/author/01-get-v1-me.json
new file mode 100644
index 000000000..73a7d84c1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/author/01-get-v1-me.json
@@ -0,0 +1,41 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/me"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/GetMeOutputBody.json",
+ "global": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "handle": "danielroe-bot",
+ "homeJurisdiction": "uk",
+ "createdAt": "2026-10-26T20:34:57.534531Z",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "providerUserId": "57801166"
+ }
+ ]
+ },
+ "regionalUnavailable": {
+ "error": "foreign_jurisdiction",
+ "jurisdiction": "uk",
+ "homeCoreUrl": "https://uk.auth.entire.io",
+ "message": "Your profile is hosted in the uk jurisdiction."
+ },
+ "auth": {
+ "provider": "github",
+ "providerUserId": "57801166"
+ },
+ "mode": "regional",
+ "jurisdiction": "us"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/author/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/author/golden.json
new file mode 100644
index 000000000..2d1091fe0
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/author/golden.json
@@ -0,0 +1,9 @@
+{
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false,
+ "createdAt": ""
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/author/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/author/step.json
new file mode 100644
index 000000000..3db091827
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/author/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "author",
+ "recordedAt": "2026-10-26T20:52:03.172Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/01-post-N-reviews.json b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/01-post-N-reviews.json
new file mode 100644
index 000000000..a5b9fd1f2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/01-post-N-reviews.json
@@ -0,0 +1,29 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews",
+ "body": {}
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/StartReviewResponseBody.json",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "base_sha": "80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "event_stream_url": "/api/v1/trails/et/forges-scratch/forges-fixtures/9/events",
+ "diff_url": "/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK/trails/9/reviews/01M5X0AGW04E508JH4BY5VE4V7/diff",
+ "files_url": "/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK/trails/9/reviews/01M5X0AGW04E508JH4BY5VE4V7/files",
+ "limits": {
+ "max_comments_per_batch": 100
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/02-post-01M5X0AGW04E508JH4BY5VE4V7-comments.json b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/02-post-01M5X0AGW04E508JH4BY5VE4V7-comments.json
new file mode 100644
index 000000000..93f3d351d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/02-post-01M5X0AGW04E508JH4BY5VE4V7-comments.json
@@ -0,0 +1,95 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/01M5X0AGW04E508JH4BY5VE4V7/comments",
+ "body": {
+ "summary": "A review comment from run 20261026210133.",
+ "comments": [
+ {
+ "client_id": "1",
+ "body": "A review comment from run 20261026210133.",
+ "location": {
+ "granularity": "whole_change"
+ }
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/ReviewCommentsBatchResponseBody.json",
+ "results": [
+ {
+ "client_id": "1",
+ "status": "created",
+ "comment": {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 0,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/golden.json
new file mode 100644
index 000000000..c911eef52
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/golden.json
@@ -0,0 +1,70 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "commented",
+ "stateRaw": "review",
+ "body": "A review comment from run 20261026210133.",
+ "submittedAt": "",
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD"
+ },
+ "body": "A review comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": false
+ },
+ "createdAt": ""
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/step.json
new file mode 100644
index 000000000..95ab0b231
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/comment-review/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "comment review",
+ "recordedAt": "2026-10-26T21:02:06.524Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/01-post-forges-scratch-trails.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/01-post-forges-scratch-trails.json
new file mode 100644
index 000000000..80aa561d7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/01-post-forges-scratch-trails.json
@@ -0,0 +1,69 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails",
+ "body": {
+ "title": "forges fixture 20261026210133: duplicate",
+ "body": "Opened by run 20261026210133.",
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ]
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:52.188419Z\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A1NVH0ZAA8B1J3CQ1CSV"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A1NVH0ZAA8B1J3CQ1CSV",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 14,
+ "url": "https://entire.com/et/forges-scratch/-/trails/14",
+ "title": "forges fixture 20261026210133: duplicate",
+ "body": "Opened by run 20261026210133.",
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:52.188Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:52.188Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:52.188Z"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/golden.json
new file mode 100644
index 000000000..2a051ae46
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/golden.json
@@ -0,0 +1,41 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "14",
+ "externalId": "01M5X0A1NVH0ZAA8B1J3CQ1CSV"
+ },
+ "kind": "issue",
+ "title": "forges fixture 20261026210133: duplicate",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/-/trails/14",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/step.json
new file mode 100644
index 000000000..3fd6b69ee
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-duplicate-issue/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "create duplicate issue",
+ "recordedAt": "2026-10-26T21:01:50.930Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/01-post-forges-scratch-trails.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/01-post-forges-scratch-trails.json
new file mode 100644
index 000000000..1ea49c77e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/01-post-forges-scratch-trails.json
@@ -0,0 +1,69 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails",
+ "body": {
+ "title": "forges fixture 20261026210133: issue",
+ "body": "Opened by run 20261026210133.",
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ]
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:44.774532Z\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 13,
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "title": "forges fixture 20261026210133: issue",
+ "body": "Opened by run 20261026210133.",
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:44.774Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:44.774Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:44.774Z"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/golden.json
new file mode 100644
index 000000000..56b163910
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/golden.json
@@ -0,0 +1,41 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "kind": "issue",
+ "title": "forges fixture 20261026210133: issue",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/step.json
new file mode 100644
index 000000000..6e54aa1db
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-issue/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "create issue",
+ "recordedAt": "2026-10-26T21:01:43.764Z",
+ "needs": [
+ "author"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
new file mode 100644
index 000000000..1337b01ba
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:54.892145Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 15,
+ "url": "https://entire.com/et/forges-scratch/-/trails/15",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A493RSBKDGYF0ZWVG8E9:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:54.892Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:54.892Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:54.892Z",
+ "changes": [
+ {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-1",
+ "originalBranch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "number": 9,
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "updatedAt": "2026-10-26T21:01:54.919Z",
+ "lastActivityAt": "2026-10-26T21:01:54.849Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..75dc5538d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/02-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:01:54.919Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:01:54.849Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "number": 15,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/15"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:01:55.274874765Z",
+ "completed_at": "2026-10-26T21:01:55.274874765Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A4D1WK6QWSP6JXWFERW1",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:54.977313Z",
+ "completed_at": "2026-10-26T21:01:54.977313Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/03-post-forges-scratch-trails.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/03-post-forges-scratch-trails.json
new file mode 100644
index 000000000..0bcf4b32a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/03-post-forges-scratch-trails.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails",
+ "body": {
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Opened by run 20261026210133.",
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "changes": [
+ {
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "branchName": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "branchAction": "link",
+ "title": "forges fixture 20261026210133: pull 1",
+ "readyForReview": true
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:54.892145Z\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 15,
+ "url": "https://entire.com/et/forges-scratch/-/trails/15",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Opened by run 20261026210133.",
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:54.892Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:54.892Z",
+ "changes": [
+ {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-1",
+ "originalBranch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "number": 9,
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "hasCodeChanges": false,
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "updatedAt": "2026-10-26T21:01:54.849Z",
+ "lastActivityAt": "2026-10-26T21:01:54.849Z",
+ "readiness": "unknown",
+ "mergeability": null
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/golden.json
new file mode 100644
index 000000000..6dc90adc1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/golden.json
@@ -0,0 +1,61 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-1",
+ "sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/step.json
new file mode 100644
index 000000000..3043cc78c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-pull/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "create pull",
+ "recordedAt": "2026-10-26T21:01:53.927Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/01-get-trails-01M5X0A57TTTYB6JDQBMBHG4V8.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/01-get-trails-01M5X0A57TTTYB6JDQBMBHG4V8.json
new file mode 100644
index 000000000..d13994e43
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/01-get-trails-01M5X0A57TTTYB6JDQBMBHG4V8.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A57TTTYB6JDQBMBHG4V8"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:55.894211Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A57TTTYB6JDQBMBHG4V8",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 16,
+ "url": "https://entire.com/et/forges-scratch/-/trails/16",
+ "title": "forges fixture 20261026210133: pull 2",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A57TTTYB6JDQBMBHG4V8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:55.894Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:55.830Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:55.894Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:55.894Z",
+ "changes": [
+ {
+ "id": "01M5X0A58D0Z486R9F4Y7JKPDN",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-2",
+ "originalBranch": "forges-fixtures/20261026210133-2",
+ "base": "main",
+ "number": 10,
+ "title": "forges fixture 20261026210133: pull 2",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:55.830Z",
+ "updatedAt": "2026-10-26T21:01:55.933Z",
+ "lastActivityAt": "2026-10-26T21:01:55.830Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..23a2514e7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/02-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/10"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A58D0Z486R9F4Y7JKPDN",
+ "number": 10,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/10",
+ "title": "forges fixture 20261026210133: pull 2",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-2",
+ "original_branch": "forges-fixtures/20261026210133-2",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:55.830Z",
+ "updated_at": "2026-10-26T21:01:55.933Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:01:55.830Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A57TTTYB6JDQBMBHG4V8",
+ "number": 16,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A57TTTYB6JDQBMBHG4V8",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/16"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A59VYSSJTW7ETN7R8D7J",
+ "document_key": "trail:01M5X0A58D0Z486R9F4Y7JKPDN:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:55.830Z",
+ "etag": "W/\"2026-10-26T21:01:55.830929Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:01:56.235232Z",
+ "completed_at": "2026-10-26T21:01:56.235232Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A5CJM66W2V438Q6C488K",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:55.986801Z",
+ "completed_at": "2026-10-26T21:01:55.986801Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/03-post-forges-scratch-trails.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/03-post-forges-scratch-trails.json
new file mode 100644
index 000000000..3459b276d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/03-post-forges-scratch-trails.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails",
+ "body": {
+ "title": "forges fixture 20261026210133: pull 2",
+ "body": "Opened by run 20261026210133.",
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "changes": [
+ {
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "branchName": "forges-fixtures/20261026210133-2",
+ "base": "main",
+ "branchAction": "link",
+ "title": "forges fixture 20261026210133: pull 2",
+ "readyForReview": true
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:55.894211Z\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A57TTTYB6JDQBMBHG4V8"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A57TTTYB6JDQBMBHG4V8",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 16,
+ "url": "https://entire.com/et/forges-scratch/-/trails/16",
+ "title": "forges fixture 20261026210133: pull 2",
+ "body": "Opened by run 20261026210133.",
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:55.830Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:55.894Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:55.894Z",
+ "changes": [
+ {
+ "id": "01M5X0A58D0Z486R9F4Y7JKPDN",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-2",
+ "originalBranch": "forges-fixtures/20261026210133-2",
+ "base": "main",
+ "number": 10,
+ "title": "forges fixture 20261026210133: pull 2",
+ "status": "open",
+ "hasCodeChanges": false,
+ "createdAt": "2026-10-26T21:01:55.830Z",
+ "updatedAt": "2026-10-26T21:01:55.830Z",
+ "lastActivityAt": "2026-10-26T21:01:55.830Z",
+ "readiness": "unknown",
+ "mergeability": null
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/golden.json
new file mode 100644
index 000000000..45ec9b65b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/golden.json
@@ -0,0 +1,61 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "10",
+ "externalId": "01M5X0A58D0Z486R9F4Y7JKPDN"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull 2",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/10",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-2",
+ "sha": "1728ca410ea20a6de3169562d0cf61d5432ae57d",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/step.json
new file mode 100644
index 000000000..ce66feab6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-second-pull/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "create second pull",
+ "recordedAt": "2026-10-26T21:01:55.599Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
new file mode 100644
index 000000000..08491c898
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:57.046039Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 17,
+ "url": "https://entire.com/et/forges-scratch/-/trails/17",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A6D5Q8H8VHFN7M7HTKRS:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:57.046Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:57.046Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:57.046Z",
+ "changes": [
+ {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-3",
+ "originalBranch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "number": 11,
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "updatedAt": "2026-10-26T21:01:57.061Z",
+ "lastActivityAt": "2026-10-26T21:01:57.028Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..a2a3732d4
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/02-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:01:57.061Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:01:57.028Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "number": 17,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/17"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:01:57.586575738Z",
+ "completed_at": "2026-10-26T21:01:57.586575738Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A6F5K3QMX8SRPSPC76YP",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:57.093243Z",
+ "completed_at": "2026-10-26T21:01:57.093243Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/03-post-forges-scratch-trails.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/03-post-forges-scratch-trails.json
new file mode 100644
index 000000000..3392bf064
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/03-post-forges-scratch-trails.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails",
+ "body": {
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "changes": [
+ {
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "branchName": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "branchAction": "link",
+ "title": "forges fixture 20261026210133: pull 3",
+ "readyForReview": true
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:57.046039Z\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 17,
+ "url": "https://entire.com/et/forges-scratch/-/trails/17",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:57.046Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:57.046Z",
+ "changes": [
+ {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-3",
+ "originalBranch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "number": 11,
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "hasCodeChanges": false,
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "updatedAt": "2026-10-26T21:01:57.028Z",
+ "lastActivityAt": "2026-10-26T21:01:57.028Z",
+ "readiness": "unknown",
+ "mergeability": null
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/golden.json
new file mode 100644
index 000000000..96022278c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/golden.json
@@ -0,0 +1,61 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "11",
+ "externalId": "01M5X0A6DAPK6DZNKVR2KEJNF8"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-3",
+ "sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/step.json
new file mode 100644
index 000000000..dc14c9b6e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/create-third-pull/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "create third pull",
+ "recordedAt": "2026-10-26T21:01:56.531Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/01-get-N-discussions.json
new file mode 100644
index 000000000..650005cae
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/01-get-N-discussions.json
@@ -0,0 +1,235 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A review comment from run 20261026210133.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "review_comment": {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 0,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": []
+ },
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "Conversation",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:02:01.374Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": []
+ },
+ {
+ "id": "01M5X0A9PBSMXT52PCMF5JCE0Y",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "updated_at": "2026-10-26T21:02:00.395Z",
+ "last_message_at": "2026-10-26T21:02:00.395Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A9PCYAK06HK5CXJRQR2P",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "5"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/golden.json
new file mode 100644
index 000000000..2a4e01e01
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/golden.json
@@ -0,0 +1,36 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD"
+ },
+ "body": "A review comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "updatedAt": "",
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/step.json
new file mode 100644
index 000000000..9629699c4
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "get review comment",
+ "recordedAt": "2026-10-26T21:02:08.977Z",
+ "needs": [
+ "comment review"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/01-get-N-approvals.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/01-get-N-approvals.json
new file mode 100644
index 000000000..a93f99e86
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/01-get-N-approvals.json
@@ -0,0 +1,25 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11/approvals"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailApprovalsResponse.json",
+ "approvals": [
+ {
+ "id": "01M5X0AFJEGJWS360MQ5YST9XY",
+ "author": "danielroe",
+ "event": "approved",
+ "body": null,
+ "commit_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "created_at": "2026-10-26T21:02:06.414872128Z"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/golden.json
new file mode 100644
index 000000000..41ac0ddef
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/golden.json
@@ -0,0 +1,31 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "11",
+ "externalId": "01M5X0A6DAPK6DZNKVR2KEJNF8"
+ },
+ "id": "01M5X0AFJEGJWS360MQ5YST9XY"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "danielroe",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved",
+ "submittedAt": "",
+ "comments": false
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/step.json
new file mode 100644
index 000000000..bdd834758
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/get-review/step.json
@@ -0,0 +1,8 @@
+{
+ "name": "get review",
+ "recordedAt": "2026-10-26T21:02:08.780Z",
+ "needs": [
+ "pending review",
+ "comment review"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/01-patch-trails-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/01-patch-trails-N.json
new file mode 100644
index 000000000..8141468dc
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/01-patch-trails-N.json
@@ -0,0 +1,129 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13",
+ "body": {
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ]
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:49.813331Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 13,
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "title": "forges fixture 20261026210133: issue",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X09TE6P0R8TR8WPCVX7H6Y:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:49.813Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "assigneeAccountIds": [
+ "01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:44.774Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:49.813Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:49.813Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/golden.json
new file mode 100644
index 000000000..4f87da032
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/golden.json
@@ -0,0 +1,10 @@
+[
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+]
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/step.json
new file mode 100644
index 000000000..e17731698
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-assign/step.json
@@ -0,0 +1,8 @@
+{
+ "name": "issue assign",
+ "recordedAt": "2026-10-26T21:01:48.904Z",
+ "needs": [
+ "create issue",
+ "author"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/01-patch-trails-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/01-patch-trails-N.json
new file mode 100644
index 000000000..98e1498b2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/01-patch-trails-N.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13",
+ "body": {
+ "status": "closed"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:52.804375Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 13,
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X09TE6P0R8TR8WPCVX7H6Y:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Edited by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Edited by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:52.804Z",
+ "etag": ""
+ },
+ "status": "closed",
+ "phase": "closed",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "assigneeAccountIds": [
+ "01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:44.774Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:52.804Z",
+ "closedAt": "2026-10-26T21:01:52.804Z",
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:52.804Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/golden.json
new file mode 100644
index 000000000..8c53f143e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/golden.json
@@ -0,0 +1,51 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "kind": "issue",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "state": "closed",
+ "stateRaw": "closed",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "createdAt": "",
+ "updatedAt": "",
+ "closedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/step.json
new file mode 100644
index 000000000..a01932ed7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-close/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue close",
+ "recordedAt": "2026-10-26T21:01:52.284Z",
+ "needs": [
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/01-get-et-forges-scratch.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/01-get-et-forges-scratch.json
new file mode 100644
index 000000000..9895c159c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/01-get-et-forges-scratch.json
@@ -0,0 +1,38 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/projects/resolve/et/forges-scratch"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ResolveProjectOutputBody.json",
+ "project": {
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "ownerType": "account",
+ "ownerId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "ownerName": "github:danielroe-bot",
+ "region": "eu",
+ "createdAt": "2026-10-26T20:35:26.151561Z",
+ "capabilities": {
+ "canDelete": true,
+ "canManageAccess": true,
+ "canCreateRepository": true,
+ "canManageTrails": true,
+ "canManagePlugins": true,
+ "canManageAgents": true
+ },
+ "primaryProcessingCell": "aws-eu-central-1",
+ "apiUrl": "https://aws-eu-central-1.api.entire.io"
+ },
+ "reference": {
+ "host": "et",
+ "project": "forges-scratch"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/02-post-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/02-post-N-discussions.json
new file mode 100644
index 000000000..0e3443e92
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/02-post-N-discussions.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions",
+ "body": {
+ "body": "A comment from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:1\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X09TE6P0R8TR8WPCVX7H6Y/discussions/01M5X09V1QW77SKERM3TA6VTXC"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionCreateResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:1\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "A comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:45.399Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "message": {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:1\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "A comment from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/golden.json
new file mode 100644
index 000000000..8d8662bc3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/golden.json
@@ -0,0 +1,35 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "id": "01M5X09V1T11B2MR912QBBWXHP"
+ },
+ "body": "A comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/step.json
new file mode 100644
index 000000000..fb7563c73
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue comment",
+ "recordedAt": "2026-10-26T21:01:44.859Z",
+ "needs": [
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/01-delete-messages-01M5X09V1T11B2MR912QBBWXHP.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/01-delete-messages-01M5X09V1T11B2MR912QBBWXHP.json
new file mode 100644
index 000000000..9f9c38c1d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/01-delete-messages-01M5X09V1T11B2MR912QBBWXHP.json
@@ -0,0 +1,10 @@
+{
+ "request": {
+ "method": "DELETE",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC/messages/01M5X09V1T11B2MR912QBBWXHP"
+ },
+ "response": {
+ "status": 204,
+ "headers": {}
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/02-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/02-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
new file mode 100644
index 000000000..c0c343150
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/02-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:2\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "3"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/03-get-discussions-01M5X09X6MW0ZY9BGSJQF9EP7F.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/03-get-discussions-01M5X09X6MW0ZY9BGSJQF9EP7F.json
new file mode 100644
index 000000000..b9510d269
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/03-get-discussions-01M5X09X6MW0ZY9BGSJQF9EP7F.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09X6MW0ZY9BGSJQF9EP7F"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09X6MW0ZY9BGSJQF9EP7F:1\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09X6MW0ZY9BGSJQF9EP7F:1\"",
+ "id": "01M5X09X6MW0ZY9BGSJQF9EP7F",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:47.604Z",
+ "updated_at": "2026-10-26T21:01:47.604Z",
+ "last_message_at": "2026-10-26T21:01:47.604Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5X09X6NVNBBBXF1YX5PRNPV:1\"",
+ "id": "01M5X09X6NVNBBBXF1YX5PRNPV",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:47.604Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ ],
+ "eventCursor": "3"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/04-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/04-get-N-discussions.json
new file mode 100644
index 000000000..6eaab1dbd
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/04-get-N-discussions.json
@@ -0,0 +1,133 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5X09X6MW0ZY9BGSJQF9EP7F:1\"",
+ "id": "01M5X09X6MW0ZY9BGSJQF9EP7F",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:47.604Z",
+ "updated_at": "2026-10-26T21:01:47.604Z",
+ "last_message_at": "2026-10-26T21:01:47.604Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "3"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/golden.json
new file mode 100644
index 000000000..19765bd50
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/golden.json
@@ -0,0 +1 @@
+null
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/step.json
new file mode 100644
index 000000000..fce7c4a12
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-delete-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue delete comment",
+ "recordedAt": "2026-10-26T21:01:47.687Z",
+ "needs": [
+ "issue comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
new file mode 100644
index 000000000..f61040301
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:1\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:1\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "A comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:45.399Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:1\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "A comment from run 20261026210133.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/02-get-N-discussions.json
new file mode 100644
index 000000000..35002c6ca
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/02-get-N-discussions.json
@@ -0,0 +1,76 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:1\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "A comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:45.399Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/03-patch-messages-01M5X09V1T11B2MR912QBBWXHP.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/03-patch-messages-01M5X09V1T11B2MR912QBBWXHP.json
new file mode 100644
index 000000000..3ecb64c8b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/03-patch-messages-01M5X09V1T11B2MR912QBBWXHP.json
@@ -0,0 +1,40 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC/messages/01M5X09V1T11B2MR912QBBWXHP",
+ "body": {
+ "body": "An edited comment from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"message:01M5X09V1T11B2MR912QBBWXHP:2\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionMessageResponseBody.json",
+ "message": {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:2\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/golden.json
new file mode 100644
index 000000000..bb7133927
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/golden.json
@@ -0,0 +1,35 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "id": "01M5X09V1T11B2MR912QBBWXHP"
+ },
+ "body": "An edited comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/step.json
new file mode 100644
index 000000000..bc41942f0
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-edit-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue edit comment",
+ "recordedAt": "2026-10-26T21:01:45.493Z",
+ "needs": [
+ "issue comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
new file mode 100644
index 000000000..5e0f3ab56
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:2\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/02-get-N-discussions.json
new file mode 100644
index 000000000..b03afc264
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/02-get-N-discussions.json
@@ -0,0 +1,76 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/golden.json
new file mode 100644
index 000000000..bb7133927
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/golden.json
@@ -0,0 +1,35 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "id": "01M5X09V1T11B2MR912QBBWXHP"
+ },
+ "body": "An edited comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/step.json
new file mode 100644
index 000000000..1b3842cf3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-get-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue get comment",
+ "recordedAt": "2026-10-26T21:01:46.502Z",
+ "needs": [
+ "issue comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/01-patch-trails-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/01-patch-trails-N.json
new file mode 100644
index 000000000..94d15a2e0
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/01-patch-trails-N.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13",
+ "body": {
+ "status": "open"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:53.8364Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 13,
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X09TE6P0R8TR8WPCVX7H6Y:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Edited by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Edited by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:53.836Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "assigneeAccountIds": [
+ "01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:44.774Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:53.836Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:53.836Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/golden.json
new file mode 100644
index 000000000..4f2d42c71
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/golden.json
@@ -0,0 +1,50 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "kind": "issue",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/step.json
new file mode 100644
index 000000000..00f1daa82
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-reopen/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue reopen",
+ "recordedAt": "2026-10-26T21:01:52.889Z",
+ "needs": [
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/01-patch-trails-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/01-patch-trails-N.json
new file mode 100644
index 000000000..ac007a9de
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/01-patch-trails-N.json
@@ -0,0 +1,128 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13",
+ "body": {
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:50.840469Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 13,
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X09TE6P0R8TR8WPCVX7H6Y:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Edited by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Edited by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:50.840Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "assigneeAccountIds": [
+ "01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:44.774Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:50.840Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:01:50.840Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/golden.json
new file mode 100644
index 000000000..4f2d42c71
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/golden.json
@@ -0,0 +1,50 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "kind": "issue",
+ "title": "forges fixture 20261026210133: issue, edited",
+ "body": "Edited by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/-/trails/13",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/step.json
new file mode 100644
index 000000000..ee8526e1b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-update/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue update",
+ "recordedAt": "2026-10-26T21:01:49.902Z",
+ "needs": [
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
new file mode 100644
index 000000000..5e0f3ab56
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/01-get-discussions-01M5X09V1QW77SKERM3TA6VTXC.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions/01M5X09V1QW77SKERM3TA6VTXC"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5X09V1T11B2MR912QBBWXHP:2\"",
+ "id": "01M5X09V1T11B2MR912QBBWXHP",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/02-get-N-discussions.json
new file mode 100644
index 000000000..b03afc264
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/02-get-N-discussions.json
@@ -0,0 +1,76 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5X09V1QW77SKERM3TA6VTXC:2\"",
+ "id": "01M5X09V1QW77SKERM3TA6VTXC",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:45.399Z",
+ "updated_at": "2026-10-26T21:01:46.409Z",
+ "last_message_at": "2026-10-26T21:01:45.399Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/03-post-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/03-post-N-discussions.json
new file mode 100644
index 000000000..d15d7c983
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/03-post-N-discussions.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions",
+ "body": {
+ "body": "An upserted comment from run 20261026210133.\n\n"
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X09X6MW0ZY9BGSJQF9EP7F:1\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X09TE6P0R8TR8WPCVX7H6Y/discussions/01M5X09X6MW0ZY9BGSJQF9EP7F"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionCreateResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X09X6MW0ZY9BGSJQF9EP7F:1\"",
+ "id": "01M5X09X6MW0ZY9BGSJQF9EP7F",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:47.604Z",
+ "updated_at": "2026-10-26T21:01:47.604Z",
+ "last_message_at": "2026-10-26T21:01:47.604Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "message": {
+ "etag": "\"message:01M5X09X6NVNBBBXF1YX5PRNPV:1\"",
+ "id": "01M5X09X6NVNBBBXF1YX5PRNPV",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:47.604Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/golden.json
new file mode 100644
index 000000000..c4c4160f9
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/golden.json
@@ -0,0 +1,38 @@
+{
+ "comment": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "id": "01M5X09X6NVNBBBXF1YX5PRNPV"
+ },
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X09X6MW0ZY9BGSJQF9EP7F",
+ "resolved": false
+ }
+ },
+ "created": true
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/step.json
new file mode 100644
index 000000000..3b694f681
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/issue-upsert-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue upsert comment",
+ "recordedAt": "2026-10-26T21:01:46.990Z",
+ "needs": [
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/01-get-repos-01M5WYSQC41P69DTXEMZ0TE5YK.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/01-get-repos-01M5WYSQC41P69DTXEMZ0TE5YK.json
new file mode 100644
index 000000000..1b66e5639
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/01-get-repos-01M5WYSQC41P69DTXEMZ0TE5YK.json
@@ -0,0 +1,42 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/RepoListItem.json",
+ "owning_project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "github_id": 0,
+ "name": "forges-fixtures",
+ "full_name": "forges-scratch/forges-fixtures",
+ "description": null,
+ "html_url": null,
+ "stargazers_count": 0,
+ "language": null,
+ "private": false,
+ "archived": false,
+ "updated_at": "2026-10-26T21:02:17.328Z",
+ "default_branch": "main",
+ "permission": "write",
+ "capabilities": {
+ "data_role": "write",
+ "can_manage_settings": false,
+ "can_manage_access": false
+ },
+ "checkpoint_count": 0,
+ "viewer_can_see_checkpoint_data": true,
+ "runners_enabled": false,
+ "trails_enabled": true,
+ "auto_run_on_push": false,
+ "public_sharing_enabled": false,
+ "open_pr_count": 0,
+ "repo_group_id": "01M5WYSQC42H6K0NWNZ6FYTSRQ"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/02-get-et-forges-scratch.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/02-get-et-forges-scratch.json
new file mode 100644
index 000000000..0cdc30069
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/02-get-et-forges-scratch.json
@@ -0,0 +1,18 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/projects/resolve/et/forges-scratch"
+ },
+ "response": {
+ "status": 403,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ErrorModel.json",
+ "title": "Forbidden",
+ "status": 403,
+ "detail": "project is not accessible"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/03-get-v1-repos.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/03-get-v1-repos.json
new file mode 100644
index 000000000..a9b7aa158
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/03-get-v1-repos.json
@@ -0,0 +1,56 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/repos?org=forges-scratch&pageSize=1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ListReposOutputBody.json",
+ "repos": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repoGroupId": "01M5WYSQC42H6K0NWNZ6FYTSRQ",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "clusterSlug": "aws-eu-central-1",
+ "name": "forges-fixtures",
+ "full_name": "forges-scratch/forges-fixtures",
+ "org": "forges-scratch",
+ "provider": "entire",
+ "visibility": "public",
+ "permission": "write",
+ "placements": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "apiBaseUrl": "https://aws-eu-central-1.api.entire.io",
+ "clusterSlug": "aws-eu-central-1",
+ "mirror": false,
+ "status": "ready"
+ }
+ ],
+ "primaries": {
+ "git_data": "entire:01M5WYSQC41P69DTXEMZ0TE5YK",
+ "processing": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "data_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "processing_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ }
+ },
+ "lastPushedAt": "2026-10-26T21:02:14.258085Z"
+ }
+ ],
+ "truncated": false,
+ "candidatesIncomplete": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/04-post-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/04-post-N-discussions.json
new file mode 100644
index 000000000..41957919a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/04-post-N-discussions.json
@@ -0,0 +1,98 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/13/discussions",
+ "body": {
+ "body": "@danielroe-bot a mention from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5X0AXP2TZKAGWA9N0KWXVZQ:1\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X09TE6P0R8TR8WPCVX7H6Y/discussions/01M5X0AXP2TZKAGWA9N0KWXVZQ"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/DiscussionCreateResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5X0AXP2TZKAGWA9N0KWXVZQ:1\"",
+ "id": "01M5X0AXP2TZKAGWA9N0KWXVZQ",
+ "trail_id": "01M5X09TE6P0R8TR8WPCVX7H6Y",
+ "title": "@danielroe-bot a mention from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:20.866Z",
+ "updated_at": "2026-10-26T21:02:20.866Z",
+ "last_message_at": "2026-10-26T21:02:20.866Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "message": {
+ "etag": "\"message:01M5X0AXP4NWPTC707S4SNYQYA:1\"",
+ "id": "01M5X0AXP4NWPTC707S4SNYQYA",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:20.866Z",
+ "body": "@danielroe-bot a mention from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/golden.json
new file mode 100644
index 000000000..caa7d2d32
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/golden.json
@@ -0,0 +1,36 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "13",
+ "externalId": "01M5X09TE6P0R8TR8WPCVX7H6Y"
+ },
+ "id": "01M5X0AXP4NWPTC707S4SNYQYA"
+ },
+ "body": "@danielroe-bot a mention from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0AXP2TZKAGWA9N0KWXVZQ",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/step.json
new file mode 100644
index 000000000..d2423eca6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/mention-author/step.json
@@ -0,0 +1,8 @@
+{
+ "name": "mention author",
+ "recordedAt": "2026-10-26T21:02:18.553Z",
+ "needs": [
+ "author",
+ "create issue"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/merge/01-post-N-merge.json b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/01-post-N-merge.json
new file mode 100644
index 000000000..554325def
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/01-post-N-merge.json
@@ -0,0 +1,22 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK/trails/9/merge",
+ "body": {
+ "deleteBranch": false
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailMergeSubmitResponse.json",
+ "ok": true,
+ "mergeCommitSha": "",
+ "branchDeleted": false,
+ "branchKeptReason": "not_requested"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/merge/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/golden.json
new file mode 100644
index 000000000..38ac4b869
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/golden.json
@@ -0,0 +1,3 @@
+{
+ "status": "merged"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/merge/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/step.json
new file mode 100644
index 000000000..5a0cdfbd5
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/merge/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "merge",
+ "recordedAt": "2026-10-26T21:02:13.541Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/01-post-N-approvals.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/01-post-N-approvals.json
new file mode 100644
index 000000000..9a8a5dc38
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/01-post-N-approvals.json
@@ -0,0 +1,122 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11/approvals",
+ "body": {
+ "event": "approve"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailApprovalSubmitResponse.json",
+ "ok": true,
+ "approval": {
+ "id": "01M5X0AFJEGJWS360MQ5YST9XY",
+ "author": "danielroe",
+ "event": "approved",
+ "body": null,
+ "commit_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "created_at": "2026-10-26T21:02:06.414872128Z"
+ },
+ "gates": {
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "01M5X0AFK10B04M0VR1DS05YNN",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:06.433525857Z",
+ "completed_at": "2026-10-26T21:02:06.433525857Z",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A6F5K3QMX8SRPSPC76YP",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:57.093243Z",
+ "completed_at": "2026-10-26T21:01:57.093243Z",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ }
+ ],
+ "blocking_failures": [],
+ "mergeable": true,
+ "evaluated_at": "2026-10-26T21:02:06.433525857Z",
+ "gates_enabled": true
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/02-post-repos-resolve.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/02-post-repos-resolve.json
new file mode 100644
index 000000000..0fea65502
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/02-post-repos-resolve.json
@@ -0,0 +1,56 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://us.console.entire.io/api/v1/repos/resolve",
+ "body": {
+ "repositories": [
+ {
+ "fullName": "forges-scratch/forges-fixtures",
+ "provider": "entire"
+ }
+ ]
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ResolveReposResponse.json",
+ "resolutions": [
+ {
+ "provider": "entire",
+ "requestedFullName": "forges-scratch/forges-fixtures",
+ "fullName": "forges-scratch/forges-fixtures",
+ "status": "ready",
+ "repoId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repoGroupId": "01M5WYSQC42H6K0NWNZ6FYTSRQ",
+ "placements": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "apiBaseUrl": "https://aws-eu-central-1.api.entire.io",
+ "clusterSlug": "aws-eu-central-1",
+ "mirror": false,
+ "status": "ready"
+ }
+ ],
+ "primaries": {
+ "git_data": "entire:01M5WYSQC41P69DTXEMZ0TE5YK",
+ "processing": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "data_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "processing_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/golden.json
new file mode 100644
index 000000000..41ac0ddef
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/golden.json
@@ -0,0 +1,31 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "11",
+ "externalId": "01M5X0A6DAPK6DZNKVR2KEJNF8"
+ },
+ "id": "01M5X0AFJEGJWS360MQ5YST9XY"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "danielroe",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved",
+ "submittedAt": "",
+ "comments": false
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/step.json
new file mode 100644
index 000000000..9750c8e43
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pending-review/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pending review",
+ "recordedAt": "2026-10-26T21:02:04.927Z",
+ "needs": [
+ "create third pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
new file mode 100644
index 000000000..7ac6db04f
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:54.892145Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 15,
+ "url": "https://entire.com/et/forges-scratch/-/trails/15",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A493RSBKDGYF0ZWVG8E9:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:54.892Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:54.892Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:02:01.374Z",
+ "changes": [
+ {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-1",
+ "originalBranch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "number": 9,
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "updatedAt": "2026-10-26T21:02:02.357Z",
+ "lastActivityAt": "2026-10-26T21:02:01.374Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..cbb7a74fa
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/02-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 1,
+ "unresolved_count": 1,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:02:02.357Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:01.374Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "number": 15,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/15"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:02:02.590144769Z",
+ "completed_at": "2026-10-26T21:02:02.590144769Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A4D1WK6QWSP6JXWFERW1",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:54.977313Z",
+ "completed_at": "2026-10-26T21:01:54.977313Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/03-patch-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/03-patch-forges-fixtures-N.json
new file mode 100644
index 000000000..569c79853
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/03-patch-forges-fixtures-N.json
@@ -0,0 +1,101 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9",
+ "body": {
+ "assignees": [
+ "ulid:01M5WYRRVZNA1TXNET6KE4Q3WX"
+ ]
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:02.357222Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailUpdateResponseBody.json",
+ "trail": {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull 1",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 1,
+ "unresolved_count": 1,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:02:02.357Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:01.374Z",
+ "phase": "reviewing",
+ "checkpoint_count": 0,
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/golden.json
new file mode 100644
index 000000000..4f87da032
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/golden.json
@@ -0,0 +1,10 @@
+[
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+]
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/step.json
new file mode 100644
index 000000000..b87b1db02
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-assign/step.json
@@ -0,0 +1,8 @@
+{
+ "name": "pull assign",
+ "recordedAt": "2026-10-26T21:02:01.457Z",
+ "needs": [
+ "create pull",
+ "author"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
new file mode 100644
index 000000000..5ab00f717
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
@@ -0,0 +1,122 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:57.046039Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 17,
+ "url": "https://entire.com/et/forges-scratch/-/trails/17",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A6D5Q8H8VHFN7M7HTKRS:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:57.046Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "complete",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:57.046Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:02:15.260Z",
+ "changes": [
+ {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-3",
+ "originalBranch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "number": 11,
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "closed",
+ "hasCodeChanges": false,
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "updatedAt": "2026-10-26T21:02:15.260Z",
+ "lastActivityAt": "2026-10-26T21:02:15.260Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "none",
+ "mergeability": null
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..8ac2e001f
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/02-get-forges-fixtures-N.json
@@ -0,0 +1,270 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "closed",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": false,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:15.260Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:15.260Z",
+ "phase": null,
+ "parent": {
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "number": 17,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/17"
+ },
+ "branch_state": "unknown",
+ "code_changes": "not_applicable",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "merge": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 1,
+ "comparison_status": "available",
+ "conflict_status": "unknown",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "01M5X0AFK10B04M0VR1DS05YNN",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:06.433525Z",
+ "completed_at": "2026-10-26T21:02:06.433525Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T21:02:15.279935Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0AQA2B9ZDQMTB47V2M5V2",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 1,
+ "compare_status": "diverged",
+ "comparison_status": "available"
+ },
+ "rationale": "1 commit behind",
+ "created_at": "2026-10-26T21:02:14.338494Z",
+ "completed_at": "2026-10-26T21:02:14.338494Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "snapshot",
+ "merge_base_sha": "80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2",
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/03-patch-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/03-patch-forges-fixtures-N.json
new file mode 100644
index 000000000..144602b33
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/03-patch-forges-fixtures-N.json
@@ -0,0 +1,118 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11",
+ "body": {
+ "status": "closed"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:15.260166Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailUpdateResponseBody.json",
+ "trail": {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "closed",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": false,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:15.260Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:15.260Z",
+ "phase": null,
+ "checkpoint_count": 0,
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "merge_base_sha": "80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2",
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/golden.json
new file mode 100644
index 000000000..7a20f5038
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/golden.json
@@ -0,0 +1,75 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "11",
+ "externalId": "01M5X0A6DAPK6DZNKVR2KEJNF8"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "state": "closed",
+ "stateRaw": "closed",
+ "isDraft": true,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-3",
+ "sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/step.json
new file mode 100644
index 000000000..5ef17e345
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-close/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull close",
+ "recordedAt": "2026-10-26T21:02:14.364Z",
+ "needs": [
+ "create third pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/01-post-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/01-post-N-discussions.json
new file mode 100644
index 000000000..704c7c732
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/01-post-N-discussions.json
@@ -0,0 +1,95 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions",
+ "body": {
+ "body": "A comment from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0A7TQB4NGZHFD3MVSJQTE"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionCreateResponseBody.json",
+ "discussion": {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:01:58.487Z",
+ "last_message_at": "2026-10-26T21:01:58.487Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "message": {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "A comment from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/golden.json
new file mode 100644
index 000000000..5024353cb
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/golden.json
@@ -0,0 +1,34 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ"
+ },
+ "body": "A comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/step.json
new file mode 100644
index 000000000..95f451d70
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull comment",
+ "recordedAt": "2026-10-26T21:01:58.239Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/01-delete-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/01-delete-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json
new file mode 100644
index 000000000..2abf3e49d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/01-delete-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json
@@ -0,0 +1,10 @@
+{
+ "request": {
+ "method": "DELETE",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0A7TQB4NGZHFD3MVSJQTE/messages/01M5X0A7TRJFRP22WZ8RWZZ0NQ"
+ },
+ "response": {
+ "status": 204,
+ "headers": {}
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/02-get-N-discussions.json
new file mode 100644
index 000000000..784fa9d3c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/02-get-N-discussions.json
@@ -0,0 +1,174 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0A9PBSMXT52PCMF5JCE0Y",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "updated_at": "2026-10-26T21:02:00.395Z",
+ "last_message_at": "2026-10-26T21:02:00.395Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A9PCYAK06HK5CXJRQR2P",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:01:59.356Z",
+ "last_message_at": "2026-10-26T21:01:58.487Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "3"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/golden.json
new file mode 100644
index 000000000..19765bd50
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/golden.json
@@ -0,0 +1 @@
+null
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/step.json
new file mode 100644
index 000000000..343b9a53f
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-delete-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull delete comment",
+ "recordedAt": "2026-10-26T21:02:00.476Z",
+ "needs": [
+ "pull comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/01-get-N-discussions.json
new file mode 100644
index 000000000..72ac0bb26
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/01-get-N-discussions.json
@@ -0,0 +1,96 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:01:58.487Z",
+ "last_message_at": "2026-10-26T21:01:58.487Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "A comment from run 20261026210133.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/02-patch-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/02-patch-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json
new file mode 100644
index 000000000..974908017
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/02-patch-messages-01M5X0A7TRJFRP22WZ8RWZZ0NQ.json
@@ -0,0 +1,38 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0A7TQB4NGZHFD3MVSJQTE/messages/01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "body": {
+ "body": "An edited comment from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionMessageResponseBody.json",
+ "message": {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/golden.json
new file mode 100644
index 000000000..cb706a473
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/golden.json
@@ -0,0 +1,34 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ"
+ },
+ "body": "An edited comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/step.json
new file mode 100644
index 000000000..a3692a990
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-edit-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull edit comment",
+ "recordedAt": "2026-10-26T21:01:58.574Z",
+ "needs": [
+ "pull comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/01-get-N-discussions.json
new file mode 100644
index 000000000..c445ba59b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/01-get-N-discussions.json
@@ -0,0 +1,96 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:01:59.356Z",
+ "last_message_at": "2026-10-26T21:01:58.487Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/golden.json
new file mode 100644
index 000000000..cb706a473
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/golden.json
@@ -0,0 +1,34 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ"
+ },
+ "body": "An edited comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/step.json
new file mode 100644
index 000000000..fac38ace3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-get-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull get comment",
+ "recordedAt": "2026-10-26T21:01:59.456Z",
+ "needs": [
+ "pull comment"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
new file mode 100644
index 000000000..ba5e638a8
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/01-get-trails-01M5X0A6D5Q8H8VHFN7M7HTKRS.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:01:57.046039Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 17,
+ "url": "https://entire.com/et/forges-scratch/-/trails/17",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A6D5Q8H8VHFN7M7HTKRS:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Opened by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Opened by run 20261026210133.",
+ "updated_at": "2026-10-26T21:01:57.046Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "metadataUpdatedAt": "2026-10-26T21:01:57.046Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:02:16.190Z",
+ "changes": [
+ {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-3",
+ "originalBranch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "number": 11,
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:57.028Z",
+ "updatedAt": "2026-10-26T21:02:16.213Z",
+ "lastActivityAt": "2026-10-26T21:02:16.190Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "unknown",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..2cbdde5b5
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/02-get-forges-fixtures-N.json
@@ -0,0 +1,262 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:16.213Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:16.190Z",
+ "phase": "building",
+ "parent": {
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "number": 17,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/17"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "unavailable",
+ "reason": "not_ready_for_review"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "blocked",
+ "reason": "comparison_unavailable"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "unknown",
+ "conflict_status": "clean",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:16.456969574Z",
+ "completed_at": "2026-10-26T21:02:16.456969574Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.166595Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/03-patch-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/03-patch-forges-fixtures-N.json
new file mode 100644
index 000000000..27f8235c7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/03-patch-forges-fixtures-N.json
@@ -0,0 +1,118 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11",
+ "body": {
+ "status": "open"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:16.213122Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailUpdateResponseBody.json",
+ "trail": {
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:16.213Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:16.190Z",
+ "phase": "building",
+ "checkpoint_count": 0,
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/golden.json
new file mode 100644
index 000000000..e33068638
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/golden.json
@@ -0,0 +1,75 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "11",
+ "externalId": "01M5X0A6DAPK6DZNKVR2KEJNF8"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull 3",
+ "body": "Opened by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": true,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-3",
+ "sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/step.json
new file mode 100644
index 000000000..d6ab4efc3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-reopen/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull reopen",
+ "recordedAt": "2026-10-26T21:02:15.772Z",
+ "needs": [
+ "create third pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/01-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/01-get-forges-fixtures-N.json
new file mode 100644
index 000000000..5c126f020
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/01-get-forges-fixtures-N.json
@@ -0,0 +1,262 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:16.213Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:16.190Z",
+ "phase": "building",
+ "parent": {
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "number": 17,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/17"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "unavailable",
+ "reason": "not_ready_for_review"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "blocked",
+ "reason": "comparison_unavailable"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "unknown",
+ "conflict_status": "clean",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:17.073853636Z",
+ "completed_at": "2026-10-26T21:02:17.073853636Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "pending",
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.166595Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/02-post-01M5WYSQC41P69DTXEMZ0TE5YK-rebases.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/02-post-01M5WYSQC41P69DTXEMZ0TE5YK-rebases.json
new file mode 100644
index 000000000..0e78a5113
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/02-post-01M5WYSQC41P69DTXEMZ0TE5YK-rebases.json
@@ -0,0 +1,16 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/git/repos/01M5WYSQC41P69DTXEMZ0TE5YK/rebases",
+ "body": {
+ "branch": "forges-fixtures/20261026210133-3",
+ "onto": "main",
+ "expectedBranchSHA": "f483a62c7214e4cead9ef1df09a663324a4825ae"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {},
+ "body": "{\"type\":\"started\",\"mergeBaseSHA\":\"80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2\",\"commitsToApply\":[\"f483a62c7214e4cead9ef1df09a663324a4825ae\"],\"ontoTipSHA\":\"9f5e93477cecca4f94f1888d8680ab55c75fccba\"}\n{\"type\":\"applied\",\"originalSHA\":\"f483a62c7214e4cead9ef1df09a663324a4825ae\",\"newSHA\":\"553f4166982b4a17d17dfd37ec253da9440d01ab\"}\n{\"type\":\"completed\",\"newBranchSHA\":\"553f4166982b4a17d17dfd37ec253da9440d01ab\",\"mapping\":[{\"originalSHA\":\"f483a62c7214e4cead9ef1df09a663324a4825ae\",\"newSHA\":\"553f4166982b4a17d17dfd37ec253da9440d01ab\",\"dropped\":false}]}\n"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/golden.json
new file mode 100644
index 000000000..705afed38
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/golden.json
@@ -0,0 +1,3 @@
+{
+ "status": "updated"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/step.json
new file mode 100644
index 000000000..1ea1d5cf9
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch-by-rebase/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull update branch by rebase",
+ "recordedAt": "2026-10-26T21:02:16.756Z",
+ "needs": [
+ "create third pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/01-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/01-get-forges-fixtures-N.json
new file mode 100644
index 000000000..0480b308d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/01-get-forges-fixtures-N.json
@@ -0,0 +1,286 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/11"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A6DAPK6DZNKVR2KEJNF8",
+ "number": 11,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/11",
+ "title": "forges fixture 20261026210133: pull 3",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-3",
+ "original_branch": "forges-fixtures/20261026210133-3",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": true,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T21:01:57.028Z",
+ "updated_at": "2026-10-26T21:02:17.316Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:17.318Z",
+ "phase": "building",
+ "parent": {
+ "id": "01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "number": 17,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A6D5Q8H8VHFN7M7HTKRS",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/17"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A6DQ168SD36K14JNJPYW",
+ "document_key": "trail:01M5X0A6DAPK6DZNKVR2KEJNF8:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:57.028Z",
+ "etag": "W/\"2026-10-26T21:01:57.02813Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "unavailable",
+ "reason": "not_ready_for_review"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "enabled",
+ "reason": null
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "bypass_not_required"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": true,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "f483a62c7214e4cead9ef1df09a663324a4825ae",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T21:02:17.480094293Z",
+ "completed_at": "2026-10-26T21:02:17.480094293Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0AT98BGQN9K7BC7RM57B2",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "passed",
+ "head_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 0,
+ "high": 0,
+ "medium": 0,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T21:02:17.384974Z",
+ "completed_at": "2026-10-26T21:02:17.384974Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0ATAH24JW8AN09P13R793",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:02:17.425879Z",
+ "completed_at": "2026-10-26T21:02:17.425879Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "553f4166982b4a17d17dfd37ec253da9440d01ab"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/02-post-N-update-branch.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/02-post-N-update-branch.json
new file mode 100644
index 000000000..28b30c922
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/02-post-N-update-branch.json
@@ -0,0 +1,16 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/repos/01M5WYSQC41P69DTXEMZ0TE5YK/trails/11/update-branch"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailUpdateBranchResponse.json",
+ "mergeCommitSha": ""
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/golden.json
new file mode 100644
index 000000000..705afed38
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/golden.json
@@ -0,0 +1,3 @@
+{
+ "status": "updated"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/step.json
new file mode 100644
index 000000000..2ec93ead3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update-branch/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull update branch",
+ "recordedAt": "2026-10-26T21:02:17.363Z",
+ "needs": [
+ "create third pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
new file mode 100644
index 000000000..a3e8772a7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/01-get-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
@@ -0,0 +1,127 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:04.373663Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 15,
+ "url": "https://entire.com/et/forges-scratch/-/trails/15",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Edited by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A493RSBKDGYF0ZWVG8E9:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Edited by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Edited by run 20261026210133.",
+ "updated_at": "2026-10-26T21:02:04.373Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "metadataUpdatedAt": "2026-10-26T21:02:04.373Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:02:04.373Z",
+ "changes": [
+ {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-1",
+ "originalBranch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "number": 9,
+ "title": "forges fixture 20261026210133: pull, edited",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "updatedAt": "2026-10-26T21:02:03.358Z",
+ "lastActivityAt": "2026-10-26T21:02:03.358Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..60f018da1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/02-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull, edited",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 1,
+ "unresolved_count": 1,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:02:03.358Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:03.358Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "number": 15,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/15"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:02:03.593036408Z",
+ "completed_at": "2026-10-26T21:02:03.593036408Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A4D1WK6QWSP6JXWFERW1",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:54.977313Z",
+ "completed_at": "2026-10-26T21:01:54.977313Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/03-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/03-get-forges-fixtures-N.json
new file mode 100644
index 000000000..47abd9cfb
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/03-get-forges-fixtures-N.json
@@ -0,0 +1,243 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull, edited",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 1,
+ "unresolved_count": 1,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:02:03.358Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:03.358Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "number": 15,
+ "project_id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "host": "et",
+ "project": "forges-scratch",
+ "path": "/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9",
+ "jurisdiction": "eu",
+ "primary_processing_cell": "aws-eu-central-1",
+ "url": "https://entire.com/et/forges-scratch/-/trails/15"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "blocked",
+ "reason": "self_approval_not_allowed"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_gates_required"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "branch_up_to_date"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "clean",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "gates": [
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2HAMXBN2SZ9TP2N3SN",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "approved": 0,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": false,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 0
+ },
+ "rationale": "No reviewers have approved this trail",
+ "created_at": "2026-10-26T21:02:04.612485905Z",
+ "completed_at": "2026-10-26T21:02:04.612485905Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WZ6B2M8PSY34EM5YJGG6ZR",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": false,
+ "status": "pending",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": null,
+ "rationale": "Gate has not been evaluated yet",
+ "created_at": "2026-10-26T20:42:22.164428Z",
+ "completed_at": null,
+ "state": "not_run",
+ "outcome": null,
+ "stale_reason": null,
+ "evaluated_at_sha": null,
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5X0A4D1WK6QWSP6JXWFERW1",
+ "gate_definition_id": "01M5WZ6B2PC2X2BE023RX01WEY",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T21:01:54.977313Z",
+ "completed_at": "2026-10-26T21:01:54.977313Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/04-patch-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/04-patch-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
new file mode 100644
index 000000000..9877408a2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/04-patch-trails-01M5X0A493RSBKDGYF0ZWVG8E9.json
@@ -0,0 +1,130 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/et/forges-scratch/trails/01M5X0A493RSBKDGYF0ZWVG8E9",
+ "body": {
+ "body": "Edited by run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:04.373663Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-central-1",
+ "id": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "name": "forges-scratch",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges-scratch"
+ },
+ "jurisdiction": "eu",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "displayName": "",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5X0A493RSBKDGYF0ZWVG8E9",
+ "projectId": "01M5WYSMPPYRBB13ZB20H7NK2Q",
+ "number": 15,
+ "url": "https://entire.com/et/forges-scratch/-/trails/15",
+ "title": "forges fixture 20261026210133: pull 1",
+ "body": "Edited by run 20261026210133.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5X0A493RSBKDGYF0ZWVG8E9:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Edited by run 20261026210133."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Edited by run 20261026210133.",
+ "updated_at": "2026-10-26T21:02:04.373Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WYSQC41P69DTXEMZ0TE5YK"
+ ],
+ "authorAccountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "metadataUpdatedAt": "2026-10-26T21:02:04.373Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T21:02:04.373Z",
+ "changes": [
+ {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repositoryId": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repository": "forges-fixtures",
+ "branch": "forges-fixtures/20261026210133-1",
+ "originalBranch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "number": 9,
+ "title": "forges fixture 20261026210133: pull, edited",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T21:01:54.849Z",
+ "updatedAt": "2026-10-26T21:02:03.358Z",
+ "lastActivityAt": "2026-10-26T21:02:03.358Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/05-patch-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/05-patch-forges-fixtures-N.json
new file mode 100644
index 000000000..9232554d8
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/05-patch-forges-fixtures-N.json
@@ -0,0 +1,99 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9",
+ "body": {
+ "title": "forges fixture 20261026210133: pull, edited"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T21:02:03.358195Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailUpdateResponseBody.json",
+ "trail": {
+ "id": "01M5X0A49M70NAMJX878FD2QD1",
+ "number": 9,
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "title": "forges fixture 20261026210133: pull, edited",
+ "status": "open",
+ "branch": "forges-fixtures/20261026210133-1",
+ "original_branch": "forges-fixtures/20261026210133-1",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe-bot"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 1,
+ "unresolved_count": 1,
+ "created_at": "2026-10-26T21:01:54.849Z",
+ "updated_at": "2026-10-26T21:02:03.358Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T21:02:03.358Z",
+ "phase": "reviewing",
+ "checkpoint_count": 0,
+ "body_document": {
+ "id": "01M5X0A4AGRFN6FB08AYTPCF6X",
+ "document_key": "trail:01M5X0A49M70NAMJX878FD2QD1:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T21:01:54.849Z",
+ "etag": "W/\"2026-10-26T21:01:54.849162Z\""
+ },
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/golden.json
new file mode 100644
index 000000000..e2b9df736
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/golden.json
@@ -0,0 +1,61 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "kind": "pull_request",
+ "title": "forges fixture 20261026210133: pull, edited",
+ "body": "Edited by run 20261026210133.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges-scratch/forges-fixtures/trails/9",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 1,
+ "branches": {
+ "head": {
+ "ref": "forges-fixtures/20261026210133-1",
+ "sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/step.json
new file mode 100644
index 000000000..ac01fe78e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-update/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull update",
+ "recordedAt": "2026-10-26T21:02:02.874Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/01-get-N-discussions.json
new file mode 100644
index 000000000..c445ba59b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/01-get-N-discussions.json
@@ -0,0 +1,96 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An edited comment from run 20261026210133.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:01:59.356Z",
+ "last_message_at": "2026-10-26T21:01:58.487Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A7TRJFRP22WZ8RWZZ0NQ",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "body": "An edited comment from run 20261026210133.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "2"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/02-post-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/02-post-N-discussions.json
new file mode 100644
index 000000000..eafd34056
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/02-post-N-discussions.json
@@ -0,0 +1,95 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions",
+ "body": {
+ "body": "An upserted comment from run 20261026210133.\n\n"
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0A9PBSMXT52PCMF5JCE0Y"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionCreateResponseBody.json",
+ "discussion": {
+ "id": "01M5X0A9PBSMXT52PCMF5JCE0Y",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "updated_at": "2026-10-26T21:02:00.395Z",
+ "last_message_at": "2026-10-26T21:02:00.395Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "message": {
+ "id": "01M5X0A9PCYAK06HK5CXJRQR2P",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/golden.json
new file mode 100644
index 000000000..095862b95
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/golden.json
@@ -0,0 +1,37 @@
+{
+ "comment": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0A9PCYAK06HK5CXJRQR2P"
+ },
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0A9PBSMXT52PCMF5JCE0Y",
+ "resolved": false
+ }
+ },
+ "created": true
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/step.json
new file mode 100644
index 000000000..c37698e9c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/pull-upsert-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull upsert comment",
+ "recordedAt": "2026-10-26T21:01:59.686Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/01-get-N-approvals.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/01-get-N-approvals.json
new file mode 100644
index 000000000..f58b19751
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/01-get-N-approvals.json
@@ -0,0 +1,16 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/approvals"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailApprovalsResponse.json",
+ "approvals": []
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json
new file mode 100644
index 000000000..232cd2ce3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json
@@ -0,0 +1,108 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/ReviewSnapshotResponseBody.json",
+ "review": {
+ "id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "summary": null,
+ "started_at": "2026-10-26T21:02:07.744Z",
+ "source_execution_id": null
+ },
+ "code_version": {
+ "id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "base_ref": null,
+ "head_ref": null,
+ "base_sha": "80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "captured_at": "2026-10-26T21:02:07.734Z"
+ },
+ "counts": {
+ "open": 1,
+ "resolved": 0,
+ "dismissed": 0,
+ "stale": 0,
+ "total": 1
+ },
+ "comments": [
+ {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 0,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ },
+ "suggested_changes": [],
+ "outgoing_links": []
+ }
+ ],
+ "event_cursor": "6"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/03-get-reviews-comments.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/03-get-reviews-comments.json
new file mode 100644
index 000000000..97a4fe38a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/03-get-reviews-comments.json
@@ -0,0 +1,80 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/comments?per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/ReviewCommentsPage.json",
+ "comments": [
+ {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 0,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ ],
+ "event_cursor": "6"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/04-post-01M5X0AHS4JAQ93WBG0CC9JN8M-messages.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/04-post-01M5X0AHS4JAQ93WBG0CC9JN8M-messages.json
new file mode 100644
index 000000000..7aab5ab57
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/04-post-01M5X0AHS4JAQ93WBG0CC9JN8M-messages.json
@@ -0,0 +1,39 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0AHS4JAQ93WBG0CC9JN8M/messages",
+ "body": {
+ "body": "A reply from run 20261026210133."
+ }
+ },
+ "response": {
+ "status": 201,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "location": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0AHS4JAQ93WBG0CC9JN8M/messages/01M5X0AK7JFP7KMT95DH7TEQ5S"
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionMessageResponseBody.json",
+ "message": {
+ "id": "01M5X0AK7JFP7KMT95DH7TEQ5S",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:10.162Z",
+ "body": "A reply from run 20261026210133.",
+ "replies": []
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/golden.json
new file mode 100644
index 000000000..66c7aec3a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/golden.json
@@ -0,0 +1,110 @@
+{
+ "reviews": {
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "commented",
+ "stateRaw": "review",
+ "submittedAt": "",
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD"
+ },
+ "body": "A review comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": false
+ },
+ "createdAt": ""
+ }
+ ]
+ }
+ ]
+ },
+ "reply": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AK7JFP7KMT95DH7TEQ5S"
+ },
+ "body": "A reply from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": false
+ },
+ "inReplyTo": "01M5X0AHS47CKYTKRY42WT1YYD"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/step.json
new file mode 100644
index 000000000..f16dc822e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reply-to-review-comment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "reply to review comment",
+ "recordedAt": "2026-10-26T21:02:09.180Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/01-get-N-approvals.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/01-get-N-approvals.json
new file mode 100644
index 000000000..f58b19751
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/01-get-N-approvals.json
@@ -0,0 +1,16 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/approvals"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailApprovalsResponse.json",
+ "approvals": []
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json
new file mode 100644
index 000000000..242753c41
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/02-get-reviews-01M5X0AGW04E508JH4BY5VE4V7.json
@@ -0,0 +1,108 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/ReviewSnapshotResponseBody.json",
+ "review": {
+ "id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "summary": null,
+ "started_at": "2026-10-26T21:02:07.744Z",
+ "source_execution_id": null
+ },
+ "code_version": {
+ "id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "base_ref": null,
+ "head_ref": null,
+ "base_sha": "80c3e7d4c9f5e16f4e11cf7737f571c513d14ac2",
+ "head_sha": "9f5e93477cecca4f94f1888d8680ab55c75fccba",
+ "captured_at": "2026-10-26T21:02:07.734Z"
+ },
+ "counts": {
+ "open": 1,
+ "resolved": 0,
+ "dismissed": 0,
+ "stale": 0,
+ "total": 1
+ },
+ "comments": [
+ {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 1,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ },
+ "suggested_changes": [],
+ "outgoing_links": []
+ }
+ ],
+ "event_cursor": "6"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/03-get-reviews-comments.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/03-get-reviews-comments.json
new file mode 100644
index 000000000..a8c118749
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/03-get-reviews-comments.json
@@ -0,0 +1,80 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/reviews/comments?per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/ReviewCommentsPage.json",
+ "comments": [
+ {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 1,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:08.674Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ ],
+ "event_cursor": "6"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/04-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/04-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json
new file mode 100644
index 000000000..50e492e33
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/04-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json
@@ -0,0 +1,147 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "body": {
+ "resolved": true
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionUpdateResponseBody.json",
+ "discussion": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A review comment from run 20261026210133.",
+ "kind": "code_review",
+ "resolved": true,
+ "resolved_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "resolved_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "resolved_at": "2026-10-26T21:02:11.209Z",
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:11.209Z",
+ "last_message_at": "2026-10-26T21:02:10.162Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "review_comment": {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 1,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "resolved",
+ "status_reason": null,
+ "status_actor_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "status_actor_display_name": "danielroe-bot",
+ "status_actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "status_changed_at": "2026-10-26T21:02:11.209Z",
+ "status_source": "discussion_resolve",
+ "status_actor_kind": "human",
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:11.209Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/golden.json
new file mode 100644
index 000000000..b1e7091e0
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/golden.json
@@ -0,0 +1,76 @@
+{
+ "reviews": {
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AGW04E508JH4BY5VE4V7"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "commented",
+ "stateRaw": "review",
+ "submittedAt": "",
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD"
+ },
+ "body": "A review comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": false
+ },
+ "createdAt": ""
+ }
+ ]
+ }
+ ]
+ },
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/step.json
new file mode 100644
index 000000000..ebdbb7cf8
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/resolve-review-thread/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "resolve review thread",
+ "recordedAt": "2026-10-26T21:02:10.255Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/01-get-N-discussions.json
new file mode 100644
index 000000000..8342a1eb6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/01-get-N-discussions.json
@@ -0,0 +1,286 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A review comment from run 20261026210133.",
+ "kind": "code_review",
+ "resolved": true,
+ "resolved_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "resolved_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "resolved_at": "2026-10-26T21:02:11.209Z",
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:11.209Z",
+ "last_message_at": "2026-10-26T21:02:10.162Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "review_comment": {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 1,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "resolved",
+ "status_reason": null,
+ "status_actor_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "status_actor_display_name": "danielroe-bot",
+ "status_actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "status_changed_at": "2026-10-26T21:02:11.209Z",
+ "status_source": "discussion_resolve",
+ "status_actor_kind": "human",
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:11.209Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": [
+ {
+ "id": "01M5X0AK7JFP7KMT95DH7TEQ5S",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:10.162Z",
+ "body": "A reply from run 20261026210133.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5X0A7TQB4NGZHFD3MVSJQTE",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "Conversation",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:01:58.487Z",
+ "updated_at": "2026-10-26T21:02:01.374Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": []
+ },
+ {
+ "id": "01M5X0A9PBSMXT52PCMF5JCE0Y",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "An upserted comment from run 20261026210133. ",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "updated_at": "2026-10-26T21:02:00.395Z",
+ "last_message_at": "2026-10-26T21:02:00.395Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5X0A9PCYAK06HK5CXJRQR2P",
+ "author": "danielroe-bot",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:00.395Z",
+ "body": "An upserted comment from run 20261026210133.\n\n",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "6"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/golden.json
new file mode 100644
index 000000000..ddb438707
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/golden.json
@@ -0,0 +1,81 @@
+{
+ "items": [
+ {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": true,
+ "outdated": false,
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD"
+ },
+ "body": "A review comment from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": true
+ },
+ "createdAt": ""
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "9",
+ "externalId": "01M5X0A49M70NAMJX878FD2QD1"
+ },
+ "id": "01M5X0AK7JFP7KMT95DH7TEQ5S"
+ },
+ "body": "A reply from run 20261026210133.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe-bot",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "isBotHint": false
+ },
+ "inReplyTo": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "thread": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "resolved": true
+ },
+ "createdAt": ""
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/step.json
new file mode 100644
index 000000000..92e0e4257
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/review-threads/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "review threads",
+ "recordedAt": "2026-10-26T21:02:11.299Z",
+ "needs": [
+ "create pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/01-get-v1-me.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/01-get-v1-me.json
new file mode 100644
index 000000000..e8c185f22
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/01-get-v1-me.json
@@ -0,0 +1,44 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/me"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/GetMeOutputBody.json",
+ "global": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "handle": "danielroe",
+ "username": "danielroe",
+ "homeJurisdiction": "uk",
+ "createdAt": "2026-10-26T18:48:45.073805Z",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "displayName": "Daniel Roe",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "providerUserId": "28706372",
+ "email": "redacted@example.invalid"
+ }
+ ]
+ },
+ "regionalUnavailable": {
+ "error": "foreign_jurisdiction",
+ "jurisdiction": "uk",
+ "homeCoreUrl": "https://uk.auth.entire.io",
+ "message": "Your profile is hosted in the uk jurisdiction."
+ },
+ "auth": {
+ "provider": "github",
+ "providerUserId": "28706372"
+ },
+ "mode": "regional",
+ "jurisdiction": "us"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/golden.json
new file mode 100644
index 000000000..2bced59d2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/golden.json
@@ -0,0 +1,10 @@
+{
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false,
+ "createdAt": "2026-10-26T18:48:45.073Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/step.json
new file mode 100644
index 000000000..0bb8c737a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/reviewer/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "reviewer",
+ "recordedAt": "2026-10-26T20:52:03.496Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/01-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/01-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json
new file mode 100644
index 000000000..068ed5692
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/01-patch-discussions-01M5X0AHS4JAQ93WBG0CC9JN8M.json
@@ -0,0 +1,135 @@
+{
+ "request": {
+ "method": "PATCH",
+ "url": "https://aws-eu-central-1.api.entire.io/api/v1/trails/et/forges-scratch/forges-fixtures/9/discussions/01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "body": {
+ "resolved": false
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-central-1.api.entire.io/api/v1/schemas/TrailDiscussionUpdateResponseBody.json",
+ "discussion": {
+ "id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "title": "A review comment from run 20261026210133.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:12.756Z",
+ "last_message_at": "2026-10-26T21:02:10.162Z",
+ "last_message_author": "danielroe-bot",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe-bot",
+ "actor_account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "display_name": null,
+ "avatar_url": "https://avatars.githubusercontent.com/u/57801166?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe-bot",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "review_comment": {
+ "discussion_id": "01M5X0AHS4JAQ93WBG0CC9JN8M",
+ "discussion_message_count": 1,
+ "id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "trail_id": "01M5X0A49M70NAMJX878FD2QD1",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "review_id": "01M5X0AGW04E508JH4BY5VE4V7",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "A review comment from run 20261026210133.",
+ "severity": null,
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "1",
+ "client_id_hash": "876dd2d2f894bbd709b9c4adf233e9cbe880d2c74674e9987f97a2d6c6e31faa",
+ "created_at": "2026-10-26T21:02:08.674Z",
+ "updated_at": "2026-10-26T21:02:12.756Z",
+ "location": {
+ "id": "01M5X0AHS4172XDD0412MR744W",
+ "review_comment_id": "01M5X0AHS47CKYTKRY42WT1YYD",
+ "code_version_id": "01M5X0AGVPHGGQD3ZADR9BX2KV",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/golden.json b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/golden.json
new file mode 100644
index 000000000..19765bd50
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/golden.json
@@ -0,0 +1 @@
+null
diff --git a/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/step.json b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/step.json
new file mode 100644
index 000000000..5b938d60a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com-writes/steps/unresolve-review-thread/step.json
@@ -0,0 +1,8 @@
+{
+ "name": "unresolve review thread",
+ "recordedAt": "2026-10-26T21:02:11.505Z",
+ "needs": [
+ "create pull",
+ "resolve review thread"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/manifest.json b/test/fixtures/entire/recorded/entire.com/manifest.json
new file mode 100644
index 000000000..8c1d72a87
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/manifest.json
@@ -0,0 +1,40 @@
+{
+ "baseUrl": "https://us.console.entire.io/api/v1",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "issue": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "pull": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "scratch": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/branches/01-get-01M5WS0P331VQ3AARB26Z9DGV7-branches.json b/test/fixtures/entire/recorded/entire.com/steps/branches/01-get-01M5WS0P331VQ3AARB26Z9DGV7-branches.json
new file mode 100644
index 000000000..3eed2b53b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/branches/01-get-01M5WS0P331VQ3AARB26Z9DGV7-branches.json
@@ -0,0 +1,29 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/branches?pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListBranchesResponse.json",
+ "branches": [
+ {
+ "name": "feature/shout",
+ "commitSHA": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ {
+ "name": "fix/typo",
+ "commitSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
+ },
+ {
+ "name": "main",
+ "commitSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/branches/02-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json b/test/fixtures/entire/recorded/entire.com/steps/branches/02-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json
new file mode 100644
index 000000000..47fc7932b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/branches/02-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json
@@ -0,0 +1,22 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/refs?prefix=HEAD&symrefs=true"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListRefsResponse.json",
+ "refs": [
+ {
+ "name": "HEAD",
+ "oid": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "symrefTarget": "refs/heads/main"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/branches/golden.json b/test/fixtures/entire/recorded/entire.com/steps/branches/golden.json
new file mode 100644
index 000000000..2684e627c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/branches/golden.json
@@ -0,0 +1,19 @@
+{
+ "items": [
+ {
+ "name": "feature/shout",
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "isDefault": false
+ },
+ {
+ "name": "fix/typo",
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "isDefault": false
+ },
+ {
+ "name": "main",
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "isDefault": true
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/branches/step.json b/test/fixtures/entire/recorded/entire.com/steps/branches/step.json
new file mode 100644
index 000000000..0fcb070b7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/branches/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "branches",
+ "recordedAt": "2026-10-26T19:57:22.982Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/collaborators/01-get-01M5WS0P331VQ3AARB26Z9DGV7-collaborators.json b/test/fixtures/entire/recorded/entire.com/steps/collaborators/01-get-01M5WS0P331VQ3AARB26Z9DGV7-collaborators.json
new file mode 100644
index 000000000..bd1888b6b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/collaborators/01-get-01M5WS0P331VQ3AARB26Z9DGV7-collaborators.json
@@ -0,0 +1,28 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/repos/01M5WS0P331VQ3AARB26Z9DGV7/collaborators?pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ListRepoCollaboratorsOutputBody.json",
+ "collaborators": [
+ {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "username": "danielroe",
+ "handle": "github:danielroe",
+ "role": "admin"
+ },
+ {
+ "accountId": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "handle": "github:danielroe-bot",
+ "role": "admin"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/collaborators/golden.json b/test/fixtures/entire/recorded/entire.com/steps/collaborators/golden.json
new file mode 100644
index 000000000..21c8b1c86
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/collaborators/golden.json
@@ -0,0 +1,25 @@
+{
+ "items": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "isBotHint": false
+ },
+ "role": "admin",
+ "roleRaw": "admin"
+ },
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "01M5WYRRVZNA1TXNET6KE4Q3WX",
+ "isBotHint": false
+ },
+ "role": "admin",
+ "roleRaw": "admin"
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/collaborators/step.json b/test/fixtures/entire/recorded/entire.com/steps/collaborators/step.json
new file mode 100644
index 000000000..dbcb25272
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/collaborators/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "collaborators",
+ "recordedAt": "2026-10-26T21:02:26.415Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commit/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/commit/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..c45b6604f
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commit/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,41 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=9ec20061f17f35212247320f525ec51d3e00c41a&pageSize=1&includeStats=true"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ],
+ "message": "docs: mention shouting\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ },
+ "stats": {
+ "additions": 2,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ }
+ ],
+ "nextPageToken": "eyJmIjpbImM4M2I2NGQ1MDMyYWUwZWU4OWVjYmFlZWE3MjczYjIyOTFiNGQyZTUiXX0"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commit/02-get-commits-SHA.json b/test/fixtures/entire/recorded/entire.com/steps/commit/02-get-commits-SHA.json
new file mode 100644
index 000000000..fdb43b2ef
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commit/02-get-commits-SHA.json
@@ -0,0 +1,50 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits/9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CommitDetailOutputBody.json",
+ "commit_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "branches": [],
+ "default_branch": "main",
+ "suggested_branch": null,
+ "commit": {
+ "commitSha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "commitMessage": "docs: mention shouting",
+ "commitSubject": "docs: mention shouting",
+ "commitBody": null,
+ "commitAuthor": "forges fixtures",
+ "commitAuthorUsername": null,
+ "commitDate": "2026-10-02T10:01:00.000Z",
+ "additions": 2,
+ "deletions": 0,
+ "filesChanged": 1,
+ "filesTouched": [
+ "README.md"
+ ],
+ "fileStats": [
+ {
+ "filename": "README.md",
+ "status": "modified",
+ "additions": 2,
+ "deletions": 0,
+ "patch": "diff --git a/README.md b/README.md\nindex a38a42678a31fbc0dd0be8e507f33c82c9fd12e0..3a191c52c3aba859eed62de00fe26038570e0c92 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,3 +1,5 @@\n # forges-fixtures\n \n A repository for the Entire recordings of [forges](https://forges.link).\n+\n+Pass `--shout` to greet loudly.\n"
+ }
+ ],
+ "url": null,
+ "checkpoints": [],
+ "trailNumber": 1,
+ "trailNumbers": [
+ 1
+ ]
+ },
+ "repo_full_name": "forges/forges-fixtures"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commit/golden.json b/test/fixtures/entire/recorded/entire.com/steps/commit/golden.json
new file mode 100644
index 000000000..6953ae27b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commit/golden.json
@@ -0,0 +1,43 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "message": "docs: mention shouting",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ],
+ "stats": {
+ "additions": 2,
+ "deletions": 0,
+ "total": 2
+ },
+ "files": [
+ {
+ "path": "README.md",
+ "status": "modified",
+ "statusRaw": "modified",
+ "additions": 2,
+ "deletions": 0,
+ "patch": "diff --git a/README.md b/README.md\nindex a38a42678a31fbc0dd0be8e507f33c82c9fd12e0..3a191c52c3aba859eed62de00fe26038570e0c92 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,3 +1,5 @@\n # forges-fixtures\n \n A repository for the Entire recordings of [forges](https://forges.link).\n+\n+Pass `--shout` to greet loudly.\n"
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commit/step.json b/test/fixtures/entire/recorded/entire.com/steps/commit/step.json
new file mode 100644
index 000000000..eb71b147c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commit/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "commit",
+ "recordedAt": "2026-10-26T19:57:23.749Z",
+ "needs": [
+ "pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..c759cc60d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,87 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=HEAD&includeStats=true&pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "docs: say what forges is\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "stats": {
+ "additions": 1,
+ "deletions": 1,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "parents": [
+ "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ ],
+ "message": "feat: add a command line entry\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ },
+ "stats": {
+ "additions": 3,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb",
+ "parents": [
+ "8344a38e82f7cf7fd830a450b7de4a382ac0de1f"
+ ],
+ "message": "chore: add a licence\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00Z"
+ },
+ "stats": {
+ "additions": 21,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ }
+ ],
+ "nextPageToken": "eyJmIjpbIjgzNDRhMzhlODJmN2NmN2ZkODMwYTQ1MGI3ZGU0YTM4MmFjMGRlMWYiXX0"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commits/golden.json b/test/fixtures/entire/recorded/entire.com/steps/commits/golden.json
new file mode 100644
index 000000000..a21125171
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commits/golden.json
@@ -0,0 +1,106 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
+ },
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "message": "docs: say what forges is",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00.000Z"
+ },
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "stats": {
+ "additions": 1,
+ "deletions": 1,
+ "total": 2
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2"
+ },
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "message": "feat: add a command line entry",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00.000Z"
+ },
+ "parents": [
+ "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ ],
+ "stats": {
+ "additions": 3,
+ "deletions": 0,
+ "total": 3
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ },
+ "sha": "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb",
+ "message": "chore: add a licence",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00.000Z"
+ },
+ "parents": [
+ "8344a38e82f7cf7fd830a450b7de4a382ac0de1f"
+ ],
+ "stats": {
+ "additions": 21,
+ "deletions": 0,
+ "total": 21
+ }
+ }
+ ],
+ "cursor": {
+ "token": "eyJmIjpbIjgzNDRhMzhlODJmN2NmN2ZkODMwYTQ1MGI3ZGU0YTM4MmFjMGRlMWYiXX0"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/commits/step.json b/test/fixtures/entire/recorded/entire.com/steps/commits/step.json
new file mode 100644
index 000000000..cd5544b2c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/commits/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "commits",
+ "recordedAt": "2026-10-26T19:57:23.580Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare-limited/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..146d4e348
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,133 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=main&includeStats=true&pageSize=5"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "docs: say what forges is\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "stats": {
+ "additions": 1,
+ "deletions": 1,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "parents": [
+ "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ ],
+ "message": "feat: add a command line entry\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ },
+ "stats": {
+ "additions": 3,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb",
+ "parents": [
+ "8344a38e82f7cf7fd830a450b7de4a382ac0de1f"
+ ],
+ "message": "chore: add a licence\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:03:00Z"
+ },
+ "stats": {
+ "additions": 21,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "8344a38e82f7cf7fd830a450b7de4a382ac0de1f",
+ "parents": [
+ "fc6d8c9fb93b340e7d064fee866d49d70073ff34"
+ ],
+ "message": "docs: describe the fixture\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:02:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:02:00Z"
+ },
+ "stats": {
+ "additions": 2,
+ "deletions": 0,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ },
+ {
+ "sha": "fc6d8c9fb93b340e7d064fee866d49d70073ff34",
+ "parents": [
+ "3e43c73635a4b6242ec9be1b632d929bec20a909"
+ ],
+ "message": "fix: greet the world when no name is given\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:01:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:01:00Z"
+ },
+ "stats": {
+ "additions": 1,
+ "deletions": 1,
+ "filesChanged": 1,
+ "truncated": false
+ }
+ }
+ ],
+ "nextPageToken": "eyJmIjpbIjNlNDNjNzM2MzVhNGI2MjQyZWM5YmUxYjYzMmQ5MjliZWMyMGE5MDkiXX0"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare-limited/02-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/02-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..afff5ce8c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/02-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,52 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=main¬Ref=fc6d8c9fb93b340e7d064fee866d49d70073ff34&pageSize=2"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "docs: say what forges is\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ }
+ },
+ {
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "parents": [
+ "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ ],
+ "message": "feat: add a command line entry\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00Z"
+ }
+ }
+ ],
+ "nextPageToken": "eyJmIjpbImRmNWMyYzU0MGJkOTdlYjU0YWU0MzY5ZTdhMjRkZGE2YzU1MWE5ZmIiXX0"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare-limited/03-get-compare-status.json b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/03-get-compare-status.json
new file mode 100644
index 000000000..1400a22b6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/03-get-compare-status.json
@@ -0,0 +1,21 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare/status?base=fc6d8c9fb93b340e7d064fee866d49d70073ff34&head=main"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareStatusResponse.json",
+ "commitsAhead": 4,
+ "commitsBehind": 0,
+ "mergeBaseSHA": "fc6d8c9fb93b340e7d064fee866d49d70073ff34",
+ "resolvedBaseSHA": "fc6d8c9fb93b340e7d064fee866d49d70073ff34",
+ "resolvedHeadSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare-limited/golden.json b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/golden.json
new file mode 100644
index 000000000..9a7d2cb76
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/golden.json
@@ -0,0 +1,66 @@
+{
+ "base": "fc6d8c9fb93b340e7d064fee866d49d70073ff34",
+ "head": "main",
+ "aheadBy": 4,
+ "behindBy": 0,
+ "commits": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
+ },
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "message": "docs: say what forges is",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00.000Z"
+ },
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ]
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2"
+ },
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "message": "feat: add a command line entry",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-01T10:04:00.000Z"
+ },
+ "parents": [
+ "df5c2c540bd97eb54ae4369e7a24dda6c551a9fb"
+ ]
+ }
+ ],
+ "files": [],
+ "mergeBaseSha": "fc6d8c9fb93b340e7d064fee866d49d70073ff34"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare-limited/step.json b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/step.json
new file mode 100644
index 000000000..9120ccd13
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare-limited/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "compare limited",
+ "recordedAt": "2026-10-26T19:57:24.349Z",
+ "needs": [
+ "repo"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/compare/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..c0f0622e1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,51 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=9ec20061f17f35212247320f525ec51d3e00c41a¬Ref=8127b4c88662985c819a6b3cabecde203907c3e2&pageSize=200"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ],
+ "message": "docs: mention shouting\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ }
+ },
+ {
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "feat: shout greetings\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00Z"
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare/02-get-compare-status.json b/test/fixtures/entire/recorded/entire.com/steps/compare/02-get-compare-status.json
new file mode 100644
index 000000000..5fd5279e3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare/02-get-compare-status.json
@@ -0,0 +1,21 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare/status?base=main&head=9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareStatusResponse.json",
+ "commitsAhead": 2,
+ "commitsBehind": 1,
+ "mergeBaseSHA": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "resolvedBaseSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "resolvedHeadSHA": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare/03-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json b/test/fixtures/entire/recorded/entire.com/steps/compare/03-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json
new file mode 100644
index 000000000..b019208a7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare/03-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json
@@ -0,0 +1,34 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare?base=8127b4c88662985c819a6b3cabecde203907c3e2&head=9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareResponse.json",
+ "files": [
+ {
+ "path": "README.md",
+ "status": "modified",
+ "additions": 2,
+ "deletions": 0,
+ "binary": false,
+ "tooLarge": false
+ },
+ {
+ "path": "src/greet.ts",
+ "status": "modified",
+ "additions": 3,
+ "deletions": 2,
+ "binary": false,
+ "tooLarge": false
+ }
+ ],
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare/golden.json b/test/fixtures/entire/recorded/entire.com/steps/compare/golden.json
new file mode 100644
index 000000000..ef34fec3e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare/golden.json
@@ -0,0 +1,81 @@
+{
+ "base": "main",
+ "head": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "aheadBy": 2,
+ "behindBy": 1,
+ "commits": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "message": "docs: mention shouting",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ]
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ },
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5",
+ "message": "feat: shout greetings",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00.000Z"
+ },
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ]
+ }
+ ],
+ "files": [
+ {
+ "path": "README.md",
+ "status": "modified",
+ "statusRaw": "modified",
+ "additions": 2,
+ "deletions": 0
+ },
+ {
+ "path": "src/greet.ts",
+ "status": "modified",
+ "statusRaw": "modified",
+ "additions": 3,
+ "deletions": 2
+ }
+ ],
+ "mergeBaseSha": "8127b4c88662985c819a6b3cabecde203907c3e2"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/compare/step.json b/test/fixtures/entire/recorded/entire.com/steps/compare/step.json
new file mode 100644
index 000000000..ff4ae5b38
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/compare/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "compare",
+ "recordedAt": "2026-10-26T19:57:23.972Z",
+ "needs": [
+ "pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-comments/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json
new file mode 100644
index 000000000..10287973b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1/discussions/01M5WVPAJVPM8Y1B7N1EVF8ZPV"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\"",
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "trail_id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "title": "French first, then German.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "updated_at": "2026-10-26T19:41:11.643Z",
+ "last_message_at": "2026-10-26T19:41:11.643Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5WVPAKKTQ8B5ZX1HK69FDM0:1\"",
+ "id": "01M5WVPAKKTQ8B5ZX1HK69FDM0",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "body": "French first, then German.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-comments/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/02-get-N-discussions.json
new file mode 100644
index 000000000..82816830e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/02-get-N-discussions.json
@@ -0,0 +1,76 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\"",
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "trail_id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "title": "French first, then German.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "updated_at": "2026-10-26T19:41:11.643Z",
+ "last_message_at": "2026-10-26T19:41:11.643Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-comments/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/golden.json
new file mode 100644
index 000000000..ad9de9e54
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/golden.json
@@ -0,0 +1,39 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "id": "01M5WVPAKKTQ8B5ZX1HK69FDM0"
+ },
+ "body": "French first, then German.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "resolved": false
+ }
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-comments/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/step.json
new file mode 100644
index 000000000..ce2f6eb90
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-comments/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "issue comments",
+ "recordedAt": "2026-10-26T19:57:19.690Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-events/01-get-N-timeline.json b/test/fixtures/entire/recorded/entire.com/steps/issue-events/01-get-N-timeline.json
new file mode 100644
index 000000000..d05d67fe9
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-events/01-get-N-timeline.json
@@ -0,0 +1,62 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1/timeline?per_page=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ProjectTrailTimelineResponse.json",
+ "items": [
+ {
+ "id": "trail-message-01M5WVPAKKTQ8B5ZX1HK69FDM0",
+ "kind": "comment",
+ "actor": {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "occurred_at": "2026-10-26T19:41:11.643Z",
+ "comment_id": "01M5WVPAKKTQ8B5ZX1HK69FDM0",
+ "discussion_id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "is_reply": false
+ },
+ {
+ "id": "trail-created-1",
+ "kind": "trail_created",
+ "actor": {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "occurred_at": "2026-10-26T19:40:26.099Z"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-events/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue-events/golden.json
new file mode 100644
index 000000000..a5f4d710e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-events/golden.json
@@ -0,0 +1,109 @@
+{
+ "items": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "trail-message-01M5WVPAKKTQ8B5ZX1HK69FDM0",
+ "kindRaw": "comment",
+ "occurredAt": "",
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "source": "poll",
+ "kind": "comment",
+ "summary": "danielroe commented",
+ "detail": {
+ "type": "comment",
+ "comment": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "id": "01M5WVPAKKTQ8B5ZX1HK69FDM0"
+ }
+ },
+ "action": "created"
+ },
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "trail-created-1",
+ "kindRaw": "trail_created",
+ "occurredAt": "",
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "source": "poll",
+ "kind": "state_change",
+ "action": "opened",
+ "summary": "danielroe opened the trail",
+ "detail": {
+ "type": "state_change",
+ "state": "open"
+ }
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-events/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue-events/step.json
new file mode 100644
index 000000000..a493b2a45
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-events/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "issue events",
+ "recordedAt": "2026-10-26T19:57:19.368Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json
new file mode 100644
index 000000000..10287973b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/01-get-discussions-01M5WVPAJVPM8Y1B7N1EVF8ZPV.json
@@ -0,0 +1,97 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1/discussions/01M5WVPAJVPM8Y1B7N1EVF8ZPV"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/DiscussionDetailResponseBody.json",
+ "discussion": {
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\"",
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "trail_id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "title": "French first, then German.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "updated_at": "2026-10-26T19:41:11.643Z",
+ "last_message_at": "2026-10-26T19:41:11.643Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ },
+ "messages": [
+ {
+ "etag": "\"message:01M5WVPAKKTQ8B5ZX1HK69FDM0:1\"",
+ "id": "01M5WVPAKKTQ8B5ZX1HK69FDM0",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "body": "French first, then German.",
+ "replies": []
+ }
+ ],
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/02-get-N-discussions.json
new file mode 100644
index 000000000..82816830e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/02-get-N-discussions.json
@@ -0,0 +1,76 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1/discussions?pageSize=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/DiscussionsListResponseBody.json",
+ "items": [
+ {
+ "etag": "\"discussion:01M5WVPAJVPM8Y1B7N1EVF8ZPV:1\"",
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "trail_id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "title": "French first, then German.",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:11.643Z",
+ "updated_at": "2026-10-26T19:41:11.643Z",
+ "last_message_at": "2026-10-26T19:41:11.643Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null
+ }
+ ],
+ "nextPageToken": null,
+ "eventCursor": "1"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/golden.json
new file mode 100644
index 000000000..101f064a4
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/golden.json
@@ -0,0 +1,35 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "id": "01M5WVPAKKTQ8B5ZX1HK69FDM0"
+ },
+ "body": "French first, then German.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVPAJVPM8Y1B7N1EVF8ZPV",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/step.json
new file mode 100644
index 000000000..5af624216
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getcomment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue getComment",
+ "recordedAt": "2026-10-26T19:57:20.213Z",
+ "needs": [
+ "issue comments"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/01-get-trails-N.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/01-get-trails-N.json
new file mode 100644
index 000000000..456d17d80
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/01-get-trails-N.json
@@ -0,0 +1,157 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T19:40:26.099783Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-west-2",
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges"
+ },
+ "jurisdiction": "uk",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 1,
+ "url": "https://entire.com/et/forges/-/trails/1",
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5WVMY3H5EDDRN67H0AXA3H0:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "It would be nice to greet people in their own language."
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "[ ] French"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "[ ] German"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "It would be nice to greet people in their own language.\n[ ] French\n[ ] German",
+ "updated_at": "2026-10-26T19:40:26.099Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "feature",
+ "priority": "low",
+ "assignees": [
+ "ulid:01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "assigneeAccountIds": [
+ "01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:26.099Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:26.099Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:41:11.643Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/golden.json
new file mode 100644
index 000000000..ce7ed5162
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/golden.json
@@ -0,0 +1,70 @@
+[
+ {
+ "ok": true,
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1"
+ },
+ "thread": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1",
+ "externalId": "01M5WVMY3H5EDDRN67H0AXA3H0"
+ },
+ "kind": "issue",
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges/-/trails/1",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "feature",
+ "name": "feature"
+ }
+ }
+ }
+]
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/step.json
new file mode 100644
index 000000000..85ab1dfbc
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-getmany/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "issue getMany",
+ "recordedAt": "2026-10-26T19:57:18.977Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-list/01-get-v1-trails.json b/test/fixtures/entire/recorded/entire.com/steps/issue-list/01-get-v1-trails.json
new file mode 100644
index 000000000..063355f94
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-list/01-get-v1-trails.json
@@ -0,0 +1,238 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails?projectId=01M5WS0JJNP4V9AW20MZ3MKF5M&status=open&pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ProjectTrailsListResponse.json",
+ "capabilities": {
+ "canManageTrails": true
+ },
+ "items": [
+ {
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 2,
+ "title": "Shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "feature",
+ "priority": "medium",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:44.561Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "isPossiblyPartial": true,
+ "groupKey": "",
+ "readiness": "in_review",
+ "changes": [
+ {
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repositoryId": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "repository": "forges-fixtures",
+ "branch": "feature/shout",
+ "originalBranch": "feature/shout",
+ "base": "main",
+ "number": 1,
+ "title": "feat: shout greetings",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "updatedAt": "2026-10-26T19:42:13.290Z",
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ]
+ },
+ {
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 3,
+ "title": "Say what forges is",
+ "body": "",
+ "status": "open",
+ "phase": "complete",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:45.752Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:45.813Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:41:42.414Z",
+ "isPossiblyPartial": true,
+ "groupKey": "",
+ "readiness": "none",
+ "changes": []
+ },
+ {
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 1,
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "feature",
+ "priority": "low",
+ "assignees": [
+ "ulid:01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "assigneeAccountIds": [
+ "01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:26.099Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:26.099Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:41:11.643Z",
+ "isPossiblyPartial": true,
+ "groupKey": "",
+ "readiness": "none",
+ "changes": []
+ }
+ ],
+ "groups": [
+ {
+ "rank": 0,
+ "key": "",
+ "label": "All trails",
+ "totalCount": 3,
+ "itemCount": 3
+ }
+ ],
+ "groupTotals": [
+ {
+ "rank": 0,
+ "key": "",
+ "label": "All trails",
+ "totalCount": 3
+ }
+ ],
+ "groupBy": "none",
+ "jurisdiction": "uk",
+ "nextPageToken": null
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-list/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue-list/golden.json
new file mode 100644
index 000000000..f5cefbda1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-list/golden.json
@@ -0,0 +1,136 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "2",
+ "externalId": "01M5WVNFY1D7AHX0KJW2ANZP14"
+ },
+ "kind": "issue",
+ "title": "Shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [],
+ "reviewers": [],
+ "labels": [],
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "feature",
+ "name": "feature"
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "3",
+ "externalId": "01M5WVNH9VCQPGCDV2QJVA6K0Y"
+ },
+ "kind": "issue",
+ "title": "Say what forges is",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [],
+ "reviewers": [],
+ "labels": [],
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "task",
+ "name": "task"
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1",
+ "externalId": "01M5WVMY3H5EDDRN67H0AXA3H0"
+ },
+ "kind": "issue",
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "issueType": {
+ "id": "feature",
+ "name": "feature"
+ }
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue-list/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue-list/step.json
new file mode 100644
index 000000000..3a320e9b9
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue-list/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "issue list",
+ "recordedAt": "2026-10-26T19:57:18.761Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue/01-get-trails-N.json b/test/fixtures/entire/recorded/entire.com/steps/issue/01-get-trails-N.json
new file mode 100644
index 000000000..456d17d80
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue/01-get-trails-N.json
@@ -0,0 +1,157 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T19:40:26.099783Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-west-2",
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges"
+ },
+ "jurisdiction": "uk",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assigneeProfiles": [
+ {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "id": "01M5WVMY3H5EDDRN67H0AXA3H0",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 1,
+ "url": "https://entire.com/et/forges/-/trails/1",
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5WVMY3H5EDDRN67H0AXA3H0:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "It would be nice to greet people in their own language."
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "[ ] French"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "[ ] German"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "It would be nice to greet people in their own language.\n[ ] French\n[ ] German",
+ "updated_at": "2026-10-26T19:40:26.099Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "planning",
+ "metadata": {},
+ "type": "feature",
+ "priority": "low",
+ "assignees": [
+ "ulid:01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "assigneeAccountIds": [
+ "01M5WRP9RJ4GVR2PA5PCNYP1FC"
+ ],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:26.099Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:26.099Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:41:11.643Z",
+ "changes": [],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue/02-get-et-forges.json b/test/fixtures/entire/recorded/entire.com/steps/issue/02-get-et-forges.json
new file mode 100644
index 000000000..860a633ec
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue/02-get-et-forges.json
@@ -0,0 +1,38 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/projects/resolve/et/forges"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ResolveProjectOutputBody.json",
+ "project": {
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "ownerType": "account",
+ "ownerId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "ownerName": "github:danielroe",
+ "region": "uk",
+ "createdAt": "2026-10-26T18:54:22.183414Z",
+ "capabilities": {
+ "canDelete": true,
+ "canManageAccess": true,
+ "canCreateRepository": true,
+ "canManageTrails": true,
+ "canManagePlugins": true,
+ "canManageAgents": true
+ },
+ "primaryProcessingCell": "aws-eu-west-2",
+ "apiUrl": "https://aws-eu-west-2.api.entire.io"
+ },
+ "reference": {
+ "host": "et",
+ "project": "forges"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue/golden.json b/test/fixtures/entire/recorded/entire.com/steps/issue/golden.json
new file mode 100644
index 000000000..4256a8804
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue/golden.json
@@ -0,0 +1,52 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "",
+ "kind": "namespace"
+ },
+ "kind": "issue",
+ "number": "1",
+ "externalId": "01M5WVMY3H5EDDRN67H0AXA3H0"
+ },
+ "kind": "issue",
+ "title": "Greet in other languages",
+ "body": "It would be nice to greet people in their own language.\n\n- [ ] French\n- [ ] German",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [],
+ "labels": [],
+ "url": "https://entire.com/et/forges/-/trails/1",
+ "createdAt": "2026-10-26T19:40:26.099Z",
+ "updatedAt": "2026-10-26T19:40:26.099Z",
+ "lastActivityAt": "2026-10-26T19:41:11.643Z",
+ "issueType": {
+ "id": "feature",
+ "name": "feature"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/issue/step.json b/test/fixtures/entire/recorded/entire.com/steps/issue/step.json
new file mode 100644
index 000000000..daf6bd80a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/issue/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "issue",
+ "recordedAt": "2026-10-26T21:02:33.232Z",
+ "needs": [
+ "pull"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/me/01-get-v1-me.json b/test/fixtures/entire/recorded/entire.com/steps/me/01-get-v1-me.json
new file mode 100644
index 000000000..e8c185f22
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/me/01-get-v1-me.json
@@ -0,0 +1,44 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/me"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/GetMeOutputBody.json",
+ "global": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "handle": "danielroe",
+ "username": "danielroe",
+ "homeJurisdiction": "uk",
+ "createdAt": "2026-10-26T18:48:45.073805Z",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "displayName": "Daniel Roe",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "providerUserId": "28706372",
+ "email": "redacted@example.invalid"
+ }
+ ]
+ },
+ "regionalUnavailable": {
+ "error": "foreign_jurisdiction",
+ "jurisdiction": "uk",
+ "homeCoreUrl": "https://uk.auth.entire.io",
+ "message": "Your profile is hosted in the uk jurisdiction."
+ },
+ "auth": {
+ "provider": "github",
+ "providerUserId": "28706372"
+ },
+ "mode": "regional",
+ "jurisdiction": "us"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/me/golden.json b/test/fixtures/entire/recorded/entire.com/steps/me/golden.json
new file mode 100644
index 000000000..2bced59d2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/me/golden.json
@@ -0,0 +1,10 @@
+{
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false,
+ "createdAt": "2026-10-26T18:48:45.073Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/me/step.json b/test/fixtures/entire/recorded/entire.com/steps/me/step.json
new file mode 100644
index 000000000..cfc1738da
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/me/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "me",
+ "recordedAt": "2026-10-26T19:57:20.718Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/merged-pull/01-get-trails-01M5WVNH9VCQPGCDV2QJVA6K0Y.json b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/01-get-trails-01M5WVNH9VCQPGCDV2QJVA6K0Y.json
new file mode 100644
index 000000000..8c6a4495e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/01-get-trails-01M5WVNH9VCQPGCDV2QJVA6K0Y.json
@@ -0,0 +1,116 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/01M5WVNH9VCQPGCDV2QJVA6K0Y"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T19:40:45.813788Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-west-2",
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges"
+ },
+ "jurisdiction": "uk",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 3,
+ "url": "https://entire.com/et/forges/-/trails/3",
+ "title": "Say what forges is",
+ "body": "",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5WVNH9VCQPGCDV2QJVA6K0Y:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:45.813Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "complete",
+ "metadata": {},
+ "type": "task",
+ "priority": "none",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:45.752Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:45.813Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:41:42.414Z",
+ "changes": [
+ {
+ "id": "01M5WVNHA7JVWPZP7W0SWDQKYY",
+ "repositoryId": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "repository": "forges-fixtures",
+ "branch": "fix/typo",
+ "originalBranch": "fix/typo",
+ "base": "main",
+ "number": 2,
+ "title": "docs: say what forges is",
+ "status": "merged",
+ "hasCodeChanges": false,
+ "createdAt": "2026-10-26T19:40:45.752Z",
+ "updatedAt": "2026-10-26T19:41:42.414Z",
+ "lastActivityAt": "2026-10-26T19:41:42.414Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "none",
+ "mergeability": null
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/merged-pull/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..62dda7129
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/02-get-forges-fixtures-N.json
@@ -0,0 +1,286 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/2"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5WVNHA7JVWPZP7W0SWDQKYY",
+ "number": 2,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/2",
+ "title": "docs: say what forges is",
+ "status": "merged",
+ "branch": "fix/typo",
+ "original_branch": "fix/typo",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": false,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T19:40:45.752Z",
+ "updated_at": "2026-10-26T19:41:42.414Z",
+ "merged_at": "2026-10-26T19:41:42.414Z",
+ "last_activity_at": "2026-10-26T19:41:42.414Z",
+ "phase": null,
+ "parent": {
+ "id": "01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "number": 3,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/3"
+ },
+ "branch_state": "unknown",
+ "code_changes": "not_applicable",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5WVNHBT9SNFF17Q2VTA66AJ",
+ "document_key": "trail:01M5WVNHA7JVWPZP7W0SWDQKYY:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:45.752Z",
+ "etag": "W/\"2026-10-26T19:40:45.752733Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "merge": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_open"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "branch_exists"
+ },
+ "delete_branch": {
+ "state": "enabled",
+ "reason": null
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": true,
+ "behind_by": 0,
+ "comparison_status": "available",
+ "conflict_status": "unknown",
+ "head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "approved",
+ "reviewed_head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "01M5WVPPCKAK64REFZ7Z59APSB",
+ "gate_definition_id": "01M5WTJP4MSACMEJ1KK4FGP2B1",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "value": {
+ "approved": 1,
+ "changes_requested": false,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": true,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "1 approval(s) recorded",
+ "created_at": "2026-10-26T19:41:23.731872Z",
+ "completed_at": "2026-10-26T19:41:23.731872Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVPXW16SZKPVENFYE9DYFK",
+ "gate_definition_id": "01M5WTJP5MDYCYNF3Z6BWN7NZ0",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 0,
+ "high": 0,
+ "medium": 0,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T19:41:31.393501Z",
+ "completed_at": "2026-10-26T19:41:31.393501Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVNHERD57DMBSKD0MMREKD",
+ "gate_definition_id": "01M5WTJP5QR0FQ7TWGE7Y3QCJC",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "value": {
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ },
+ "behind_by": 0,
+ "compare_status": "ahead",
+ "comparison_status": "available"
+ },
+ "rationale": "Up to date",
+ "created_at": "2026-10-26T19:40:45.912404Z",
+ "completed_at": "2026-10-26T19:40:45.912404Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "snapshot",
+ "merge_base_sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "base_sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "head_sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/merged-pull/03-get-forges-forges-fixtures.json b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/03-get-forges-forges-fixtures.json
new file mode 100644
index 000000000..b18a769eb
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/03-get-forges-forges-fixtures.json
@@ -0,0 +1,108 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures?status=merged&per_page=10"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailsListResponse.json",
+ "items": [
+ {
+ "id": "01M5WVNHA7JVWPZP7W0SWDQKYY",
+ "number": 2,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/2",
+ "title": "docs: say what forges is",
+ "status": "merged",
+ "branch": "fix/typo",
+ "original_branch": "fix/typo",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "approved",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": false,
+ "has_code_changes": false,
+ "comment_count": 0,
+ "unresolved_count": 0,
+ "created_at": "2026-10-26T19:40:45.752Z",
+ "updated_at": "2026-10-26T19:41:42.414Z",
+ "merged_at": "2026-10-26T19:41:42.414Z",
+ "last_activity_at": "2026-10-26T19:41:42.414Z",
+ "phase": null,
+ "parent": {
+ "id": "01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "number": 3,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNH9VCQPGCDV2QJVA6K0Y",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/3"
+ }
+ }
+ ],
+ "total_count": 1
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/merged-pull/golden.json b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/golden.json
new file mode 100644
index 000000000..efadfaa40
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/golden.json
@@ -0,0 +1,158 @@
+{
+ "listed": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "2",
+ "externalId": "01M5WVNHA7JVWPZP7W0SWDQKYY"
+ },
+ "kind": "pull_request",
+ "title": "docs: say what forges is",
+ "state": "merged",
+ "stateRaw": "merged",
+ "isDraft": true,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/2",
+ "createdAt": "2026-10-26T19:40:45.752Z",
+ "updatedAt": "2026-10-26T19:41:42.414Z",
+ "mergedAt": "2026-10-26T19:41:42.414Z",
+ "closedAt": "2026-10-26T19:41:42.414Z",
+ "lastActivityAt": "2026-10-26T19:41:42.414Z",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "fix/typo",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+ },
+ "read": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "2",
+ "externalId": "01M5WVNHA7JVWPZP7W0SWDQKYY"
+ },
+ "kind": "pull_request",
+ "title": "docs: say what forges is",
+ "state": "merged",
+ "stateRaw": "merged",
+ "isDraft": true,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "approved",
+ "stateRaw": "approved"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/2",
+ "createdAt": "2026-10-26T19:40:45.752Z",
+ "updatedAt": "2026-10-26T19:41:42.414Z",
+ "mergedAt": "2026-10-26T19:41:42.414Z",
+ "closedAt": "2026-10-26T19:41:42.414Z",
+ "lastActivityAt": "2026-10-26T19:41:42.414Z",
+ "commentCount": 0,
+ "branches": {
+ "head": {
+ "ref": "fix/typo",
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main",
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2"
+ }
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/merged-pull/step.json b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/step.json
new file mode 100644
index 000000000..a99f9242e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/merged-pull/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "merged pull",
+ "recordedAt": "2026-10-26T21:02:32.217Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/own-repos/01-get-v1-repos.json b/test/fixtures/entire/recorded/entire.com/steps/own-repos/01-get-v1-repos.json
new file mode 100644
index 000000000..713d5be2e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/own-repos/01-get-v1-repos.json
@@ -0,0 +1,56 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/repos?pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ListReposOutputBody.json",
+ "repos": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "repoGroupId": "01M5WYSQC42H6K0NWNZ6FYTSRQ",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "clusterSlug": "aws-eu-central-1",
+ "name": "forges-fixtures",
+ "full_name": "forges-scratch/forges-fixtures",
+ "org": "forges-scratch",
+ "provider": "entire",
+ "visibility": "public",
+ "permission": "write",
+ "placements": [
+ {
+ "id": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "jurisdiction": "eu",
+ "cell": "aws-eu-central-1",
+ "apiBaseUrl": "https://aws-eu-central-1.api.entire.io",
+ "clusterSlug": "aws-eu-central-1",
+ "mirror": false,
+ "status": "ready"
+ }
+ ],
+ "primaries": {
+ "git_data": "entire:01M5WYSQC41P69DTXEMZ0TE5YK",
+ "processing": "01M5WYSQC41P69DTXEMZ0TE5YK",
+ "data_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "processing_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ }
+ },
+ "lastPushedAt": "2026-10-26T21:02:24.149427Z"
+ }
+ ],
+ "truncated": false,
+ "candidatesIncomplete": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/own-repos/golden.json b/test/fixtures/entire/recorded/entire.com/steps/own-repos/golden.json
new file mode 100644
index 000000000..930f26755
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/own-repos/golden.json
@@ -0,0 +1,25 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges-scratch",
+ "name": "forges-fixtures",
+ "externalId": "01M5WYSQC41P69DTXEMZ0TE5YK"
+ },
+ "visibility": "public",
+ "isFork": false,
+ "isArchived": false,
+ "topics": [],
+ "pushedAt": "",
+ "permissions": {
+ "admin": false,
+ "maintain": false,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/own-repos/step.json b/test/fixtures/entire/recorded/entire.com/steps/own-repos/step.json
new file mode 100644
index 000000000..6fe2f18f2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/own-repos/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "own repos",
+ "recordedAt": "2026-10-26T21:02:36.097Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/owner-repos/01-get-01M5WS0JJNP4V9AW20MZ3MKF5M-repos.json b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/01-get-01M5WS0JJNP4V9AW20MZ3MKF5M-repos.json
new file mode 100644
index 000000000..a15ef5fb7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/01-get-01M5WS0JJNP4V9AW20MZ3MKF5M-repos.json
@@ -0,0 +1,16 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://us.console.entire.io/api/v1/projects/01M5WS0JJNP4V9AW20MZ3MKF5M/repos?pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ListProjectReposOutputBody.json",
+ "repos": []
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/owner-repos/golden.json b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/golden.json
new file mode 100644
index 000000000..2feb21015
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/golden.json
@@ -0,0 +1,3 @@
+{
+ "items": []
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/owner-repos/step.json b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/step.json
new file mode 100644
index 000000000..0fa78203c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/owner-repos/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "owner repos",
+ "recordedAt": "2026-10-26T20:01:21.788Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-comments/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/01-get-N-discussions.json
new file mode 100644
index 000000000..0e3836d99
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/01-get-N-discussions.json
@@ -0,0 +1,433 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "last_message_at": "2026-10-26T19:42:13.290Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "review_comment": {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": [
+ {
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:13.290Z",
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "No tests cover the `shout` option.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "review_comment": {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": []
+ },
+ {
+ "id": "01M5WVP9QKVVQWQQWRFW4K3T94",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Naming",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "updated_at": "2026-10-26T19:41:10.771Z",
+ "last_message_at": "2026-10-26T19:41:10.771Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP9QQB7HMVGA0SRF76KED",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "body": "`shout` or `loud`? I lean towards `shout`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Should `shout` also add an exclamation mark?",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "updated_at": "2026-10-26T19:41:09.989Z",
+ "last_message_at": "2026-10-26T19:41:09.989Z",
+ "last_message_author": "danielroe",
+ "message_count": 2,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "body": "Should `shout` also add an exclamation mark?",
+ "replies": []
+ },
+ {
+ "id": "01M5WVP8Z53BEF7ZA2WFQ2RQAX",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.989Z",
+ "body": "I think it reads fine without one.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "4"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-comments/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/golden.json
new file mode 100644
index 000000000..9f58cfcec
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/golden.json
@@ -0,0 +1,106 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ"
+ },
+ "body": "Should `shout` also add an exclamation mark?",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "resolved": false
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVP8Z53BEF7ZA2WFQ2RQAX"
+ },
+ "body": "I think it reads fine without one.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "resolved": false
+ }
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVP9QQB7HMVGA0SRF76KED"
+ },
+ "body": "`shout` or `loud`? I lean towards `shout`.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVP9QKVVQWQQWRFW4K3T94",
+ "resolved": false
+ }
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-comments/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/step.json
new file mode 100644
index 000000000..8aa7fe729
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-comments/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull comments",
+ "recordedAt": "2026-10-26T19:57:14.913Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..5e4e836d7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,51 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=9ec20061f17f35212247320f525ec51d3e00c41a¬Ref=8127b4c88662985c819a6b3cabecde203907c3e2&pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ],
+ "message": "docs: mention shouting\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00Z"
+ }
+ },
+ {
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "feat: shout greetings\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00Z"
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-commits/02-get-compare-status.json b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/02-get-compare-status.json
new file mode 100644
index 000000000..5fd5279e3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/02-get-compare-status.json
@@ -0,0 +1,21 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare/status?base=main&head=9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareStatusResponse.json",
+ "commitsAhead": 2,
+ "commitsBehind": 1,
+ "mergeBaseSHA": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "resolvedBaseSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "resolvedHeadSHA": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-commits/03-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/03-get-forges-fixtures-N.json
new file mode 100644
index 000000000..06fea9967
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/03-get-forges-fixtures-N.json
@@ -0,0 +1,287 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "number": 1,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "title": "feat: shout greetings",
+ "status": "open",
+ "branch": "feature/shout",
+ "original_branch": "feature/shout",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "changes_requested",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 4,
+ "unresolved_count": 4,
+ "created_at": "2026-10-26T19:40:44.349Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T19:42:13.290Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "number": 2,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/2"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5WVNG4RWC7X4X51BHP5Q5M2",
+ "document_key": "trail:01M5WVNG0FK8S3JBBFYZGK1VFD:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:44.349Z",
+ "etag": "W/\"2026-10-26T19:40:44.349281Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "enabled",
+ "reason": null
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_conflict"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "merge_conflict"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 1,
+ "comparison_status": "available",
+ "conflict_status": "conflicting",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "changes_requested",
+ "reviewed_head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP4MSACMEJ1KK4FGP2B1",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "approved": 0,
+ "changes_requested": true,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": true,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "Changes have been requested",
+ "created_at": "2026-10-26T21:02:31.570655543Z",
+ "completed_at": "2026-10-26T21:02:31.570655543Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVQW5BHQ3CSVZJTM3595D5",
+ "gate_definition_id": "01M5WTJP5MDYCYNF3Z6BWN7NZ0",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 1,
+ "high": 0,
+ "medium": 1,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T19:42:02.411692Z",
+ "completed_at": "2026-10-26T19:42:02.411692Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP5QR0FQ7TWGE7Y3QCJC",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "comparison_status": "available",
+ "behind_by": 1,
+ "compare_status": "diverged",
+ "merge_status": "conflicting",
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ }
+ },
+ "rationale": "Merge conflicts",
+ "created_at": "2026-10-26T19:41:42.616528Z",
+ "completed_at": null,
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-commits/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/golden.json
new file mode 100644
index 000000000..265331712
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/golden.json
@@ -0,0 +1,60 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "message": "docs: mention shouting",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:01:00.000Z"
+ },
+ "parents": [
+ "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ ]
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5"
+ },
+ "sha": "c83b64d5032ae0ee89ecbaeea7273b2291b4d2e5",
+ "message": "feat: shout greetings",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00.000Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T10:00:00.000Z"
+ },
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ]
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-commits/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/step.json
new file mode 100644
index 000000000..01015e368
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-commits/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull commits",
+ "recordedAt": "2026-10-26T21:02:31.439Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-events/01-get-N-timeline.json b/test/fixtures/entire/recorded/entire.com/steps/pull-events/01-get-N-timeline.json
new file mode 100644
index 000000000..0bdd9e4fd
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-events/01-get-N-timeline.json
@@ -0,0 +1,90 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/repos/01M5WS0P331VQ3AARB26Z9DGV7/trails/1/timeline?pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailTimelineResponse.json",
+ "items": [
+ {
+ "id": "trail-message-01M5WVR6SAXG323XBRAH7G0Q3V",
+ "kind": "comment",
+ "actor": {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "occurredAt": "2026-10-26T19:42:13.290Z",
+ "commentId": "01M5WVR6SAXG323XBRAH7G0Q3V",
+ "discussionId": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "isReply": false
+ },
+ {
+ "id": "trail-review-01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "kind": "review_posted",
+ "actor": {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "occurredAt": "2026-10-26T19:42:02.333Z",
+ "findingCount": 2,
+ "commentId": "01M5WVQW2ZTEEA9V72FF81J46Q"
+ },
+ {
+ "id": "trail-approval-01M5WVPQ2KX1QCY97NEWDQWTB8",
+ "kind": "approval",
+ "actor": {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "occurredAt": "2026-10-26T19:41:24.435Z",
+ "approvalStatus": "changes_requested",
+ "body": "Please add a test for shouting."
+ }
+ ],
+ "nextPageToken": "eyJvY2N1cnJlZEF0IjoiMjAyNi0xMC0yNlQxOTo0MToyNC40MzU0MTQwNDdaIiwiaWQiOiJ0cmFpbC1hcHByb3ZhbC0wMU01V1ZQUTJLWDFRQ1k5N05FV0RRV1RCOCJ9",
+ "commitCount": 2,
+ "incomplete": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-events/02-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/pull-events/02-get-N-discussions.json
new file mode 100644
index 000000000..0e3836d99
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-events/02-get-N-discussions.json
@@ -0,0 +1,433 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "last_message_at": "2026-10-26T19:42:13.290Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "review_comment": {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": [
+ {
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:13.290Z",
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "No tests cover the `shout` option.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "review_comment": {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": []
+ },
+ {
+ "id": "01M5WVP9QKVVQWQQWRFW4K3T94",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Naming",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "updated_at": "2026-10-26T19:41:10.771Z",
+ "last_message_at": "2026-10-26T19:41:10.771Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP9QQB7HMVGA0SRF76KED",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "body": "`shout` or `loud`? I lean towards `shout`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Should `shout` also add an exclamation mark?",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "updated_at": "2026-10-26T19:41:09.989Z",
+ "last_message_at": "2026-10-26T19:41:09.989Z",
+ "last_message_author": "danielroe",
+ "message_count": 2,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "body": "Should `shout` also add an exclamation mark?",
+ "replies": []
+ },
+ {
+ "id": "01M5WVP8Z53BEF7ZA2WFQ2RQAX",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.989Z",
+ "body": "I think it reads fine without one.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "4"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-events/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-events/golden.json
new file mode 100644
index 000000000..45808066d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-events/golden.json
@@ -0,0 +1,189 @@
+{
+ "items": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "trail-message-01M5WVR6SAXG323XBRAH7G0Q3V",
+ "kindRaw": "comment",
+ "occurredAt": "",
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "source": "poll",
+ "kind": "review_comment",
+ "summary": "danielroe commented on a review",
+ "detail": {
+ "type": "review_comment",
+ "comment": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V"
+ },
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "path": "src/greet.ts",
+ "line": 3
+ },
+ "action": "created"
+ },
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "trail-review-01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "kindRaw": "review_posted",
+ "occurredAt": "",
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "source": "poll",
+ "kind": "review",
+ "summary": "danielroe reviewed",
+ "detail": {
+ "type": "review",
+ "state": "commented",
+ "stateRaw": "review_posted",
+ "review": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQNQSS4DQNQB5Z00WA4V7"
+ }
+ },
+ "action": "submitted"
+ },
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "id": "trail-approval-01M5WVPQ2KX1QCY97NEWDQWTB8",
+ "kindRaw": "approval",
+ "occurredAt": "",
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "source": "poll",
+ "kind": "review",
+ "summary": "danielroe requested changes",
+ "detail": {
+ "type": "review",
+ "state": "changes_requested",
+ "stateRaw": "changes_requested",
+ "body": "Please add a test for shouting.",
+ "review": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVPQ2KX1QCY97NEWDQWTB8"
+ }
+ },
+ "action": "submitted"
+ }
+ ],
+ "cursor": {
+ "token": "eyJvY2N1cnJlZEF0IjoiMjAyNi0xMC0yNlQxOTo0MToyNC40MzU0MTQwNDdaIiwiaWQiOiJ0cmFpbC1hcHByb3ZhbC0wMU01V1ZQUTJLWDFRQ1k5N05FV0RRV1RCOCJ9"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-events/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-events/step.json
new file mode 100644
index 000000000..72293a438
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-events/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull events",
+ "recordedAt": "2026-10-26T19:58:25.584Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-files/01-get-compare-status.json b/test/fixtures/entire/recorded/entire.com/steps/pull-files/01-get-compare-status.json
new file mode 100644
index 000000000..5fd5279e3
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-files/01-get-compare-status.json
@@ -0,0 +1,21 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare/status?base=main&head=9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareStatusResponse.json",
+ "commitsAhead": 2,
+ "commitsBehind": 1,
+ "mergeBaseSHA": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "resolvedBaseSHA": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "resolvedHeadSHA": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-files/02-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json b/test/fixtures/entire/recorded/entire.com/steps/pull-files/02-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json
new file mode 100644
index 000000000..b019208a7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-files/02-get-01M5WS0P331VQ3AARB26Z9DGV7-compare.json
@@ -0,0 +1,34 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/compare?base=8127b4c88662985c819a6b3cabecde203907c3e2&head=9ec20061f17f35212247320f525ec51d3e00c41a"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/CompareResponse.json",
+ "files": [
+ {
+ "path": "README.md",
+ "status": "modified",
+ "additions": 2,
+ "deletions": 0,
+ "binary": false,
+ "tooLarge": false
+ },
+ {
+ "path": "src/greet.ts",
+ "status": "modified",
+ "additions": 3,
+ "deletions": 2,
+ "binary": false,
+ "tooLarge": false
+ }
+ ],
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-files/03-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com/steps/pull-files/03-get-forges-fixtures-N.json
new file mode 100644
index 000000000..76527be0d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-files/03-get-forges-fixtures-N.json
@@ -0,0 +1,287 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "number": 1,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "title": "feat: shout greetings",
+ "status": "open",
+ "branch": "feature/shout",
+ "original_branch": "feature/shout",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "changes_requested",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 4,
+ "unresolved_count": 4,
+ "created_at": "2026-10-26T19:40:44.349Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T19:42:13.290Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "number": 2,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/2"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5WVNG4RWC7X4X51BHP5Q5M2",
+ "document_key": "trail:01M5WVNG0FK8S3JBBFYZGK1VFD:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:44.349Z",
+ "etag": "W/\"2026-10-26T19:40:44.349281Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "enabled",
+ "reason": null
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_conflict"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "merge_conflict"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 1,
+ "comparison_status": "available",
+ "conflict_status": "conflicting",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "changes_requested",
+ "reviewed_head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP4MSACMEJ1KK4FGP2B1",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "approved": 0,
+ "changes_requested": true,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": true,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "Changes have been requested",
+ "created_at": "2026-10-26T21:02:30.955307204Z",
+ "completed_at": "2026-10-26T21:02:30.955307204Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVQW5BHQ3CSVZJTM3595D5",
+ "gate_definition_id": "01M5WTJP5MDYCYNF3Z6BWN7NZ0",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 1,
+ "high": 0,
+ "medium": 1,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T19:42:02.411692Z",
+ "completed_at": "2026-10-26T19:42:02.411692Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP5QR0FQ7TWGE7Y3QCJC",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "comparison_status": "available",
+ "behind_by": 1,
+ "compare_status": "diverged",
+ "merge_status": "conflicting",
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ }
+ },
+ "rationale": "Merge conflicts",
+ "created_at": "2026-10-26T19:41:42.616528Z",
+ "completed_at": null,
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-files/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-files/golden.json
new file mode 100644
index 000000000..b33638686
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-files/golden.json
@@ -0,0 +1,18 @@
+{
+ "items": [
+ {
+ "path": "README.md",
+ "status": "modified",
+ "statusRaw": "modified",
+ "additions": 2,
+ "deletions": 0
+ },
+ {
+ "path": "src/greet.ts",
+ "status": "modified",
+ "statusRaw": "modified",
+ "additions": 3,
+ "deletions": 2
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-files/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-files/step.json
new file mode 100644
index 000000000..7fef45b3c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-files/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull files",
+ "recordedAt": "2026-10-26T21:02:30.597Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/01-get-N-discussions.json
new file mode 100644
index 000000000..0e3836d99
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/01-get-N-discussions.json
@@ -0,0 +1,433 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "last_message_at": "2026-10-26T19:42:13.290Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "review_comment": {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": [
+ {
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:13.290Z",
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "No tests cover the `shout` option.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "review_comment": {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": []
+ },
+ {
+ "id": "01M5WVP9QKVVQWQQWRFW4K3T94",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Naming",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "updated_at": "2026-10-26T19:41:10.771Z",
+ "last_message_at": "2026-10-26T19:41:10.771Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP9QQB7HMVGA0SRF76KED",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "body": "`shout` or `loud`? I lean towards `shout`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Should `shout` also add an exclamation mark?",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "updated_at": "2026-10-26T19:41:09.989Z",
+ "last_message_at": "2026-10-26T19:41:09.989Z",
+ "last_message_author": "danielroe",
+ "message_count": 2,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "body": "Should `shout` also add an exclamation mark?",
+ "replies": []
+ },
+ {
+ "id": "01M5WVP8Z53BEF7ZA2WFQ2RQAX",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.989Z",
+ "body": "I think it reads fine without one.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "4"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/golden.json
new file mode 100644
index 000000000..6725b792a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/golden.json
@@ -0,0 +1,34 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ"
+ },
+ "body": "Should `shout` also add an exclamation mark?",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "createdAt": "",
+ "thread": {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "resolved": false
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/step.json
new file mode 100644
index 000000000..cba5322c4
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getcomment/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull getComment",
+ "recordedAt": "2026-10-26T19:57:15.120Z",
+ "needs": [
+ "pull comments"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json
new file mode 100644
index 000000000..1dfbfb50b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json
@@ -0,0 +1,153 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T19:40:44.561257Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-west-2",
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges"
+ },
+ "jurisdiction": "uk",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 2,
+ "url": "https://entire.com/et/forges/-/trails/2",
+ "title": "Shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5WVNFY1D7AHX0KJW2ANZP14:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Adds a "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "shout"
+ },
+ {
+ "type": "text",
+ "text": " option to "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "greet()"
+ },
+ {
+ "type": "text",
+ "text": "."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Adds a shout option to greet().",
+ "updated_at": "2026-10-26T19:40:44.561Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "feature",
+ "priority": "medium",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:44.561Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "changes": [
+ {
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repositoryId": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "repository": "forges-fixtures",
+ "branch": "feature/shout",
+ "originalBranch": "feature/shout",
+ "base": "main",
+ "number": 1,
+ "title": "feat: shout greetings",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "updatedAt": "2026-10-26T19:42:13.290Z",
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..a5eb6b48d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/02-get-forges-fixtures-N.json
@@ -0,0 +1,287 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "number": 1,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "title": "feat: shout greetings",
+ "status": "open",
+ "branch": "feature/shout",
+ "original_branch": "feature/shout",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "changes_requested",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 4,
+ "unresolved_count": 4,
+ "created_at": "2026-10-26T19:40:44.349Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T19:42:13.290Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "number": 2,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/2"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5WVNG4RWC7X4X51BHP5Q5M2",
+ "document_key": "trail:01M5WVNG0FK8S3JBBFYZGK1VFD:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:44.349Z",
+ "etag": "W/\"2026-10-26T19:40:44.349281Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "enabled",
+ "reason": null
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_conflict"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "merge_conflict"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 1,
+ "comparison_status": "available",
+ "conflict_status": "conflicting",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "changes_requested",
+ "reviewed_head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP4MSACMEJ1KK4FGP2B1",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "approved": 0,
+ "changes_requested": true,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": true,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "Changes have been requested",
+ "created_at": "2026-10-26T21:02:28.033199186Z",
+ "completed_at": "2026-10-26T21:02:28.033199186Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVQW5BHQ3CSVZJTM3595D5",
+ "gate_definition_id": "01M5WTJP5MDYCYNF3Z6BWN7NZ0",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 1,
+ "high": 0,
+ "medium": 1,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T19:42:02.411692Z",
+ "completed_at": "2026-10-26T19:42:02.411692Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP5QR0FQ7TWGE7Y3QCJC",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "comparison_status": "available",
+ "behind_by": 1,
+ "compare_status": "diverged",
+ "merge_status": "conflicting",
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ }
+ },
+ "rationale": "Merge conflicts",
+ "created_at": "2026-10-26T19:41:42.616528Z",
+ "completed_at": null,
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/golden.json
new file mode 100644
index 000000000..26f5d44e2
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/golden.json
@@ -0,0 +1,94 @@
+[
+ {
+ "ok": true,
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "thread": {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1",
+ "externalId": "01M5WVNG0FK8S3JBBFYZGK1VFD"
+ },
+ "kind": "pull_request",
+ "title": "feat: shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "changes_requested",
+ "stateRaw": "changes_requested"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "updatedAt": "2026-10-26T19:42:13.290Z",
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "commentCount": 4,
+ "branches": {
+ "head": {
+ "ref": "feature/shout",
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+ }
+ }
+]
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/step.json
new file mode 100644
index 000000000..45e9f3763
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getmany/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull getMany",
+ "recordedAt": "2026-10-26T21:02:27.854Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/01-get-N-approvals.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/01-get-N-approvals.json
new file mode 100644
index 000000000..c9882402d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/01-get-N-approvals.json
@@ -0,0 +1,25 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/approvals"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailApprovalsResponse.json",
+ "approvals": [
+ {
+ "id": "01M5WVPQ2KX1QCY97NEWDQWTB8",
+ "author": "danielroe",
+ "event": "changes_requested",
+ "body": "Please add a test for shouting.",
+ "commit_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "created_at": "2026-10-26T19:41:24.435414047Z"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/golden.json
new file mode 100644
index 000000000..2d26fe527
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/golden.json
@@ -0,0 +1,31 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVPQ2KX1QCY97NEWDQWTB8"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "danielroe",
+ "isBotHint": false
+ },
+ "state": "changes_requested",
+ "stateRaw": "changes_requested",
+ "body": "Please add a test for shouting.",
+ "submittedAt": "2026-10-26T19:41:24.435Z",
+ "comments": false
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/step.json
new file mode 100644
index 000000000..bc2f1ef1c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-getreview/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "pull getReview",
+ "recordedAt": "2026-10-26T21:02:30.137Z",
+ "needs": [
+ "pull reviews"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-list/01-get-forges-forges-fixtures.json b/test/fixtures/entire/recorded/entire.com/steps/pull-list/01-get-forges-forges-fixtures.json
new file mode 100644
index 000000000..9034fff80
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-list/01-get-forges-forges-fixtures.json
@@ -0,0 +1,108 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures?status=open&per_page=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailsListResponse.json",
+ "items": [
+ {
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "number": 1,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "title": "feat: shout greetings",
+ "status": "open",
+ "branch": "feature/shout",
+ "original_branch": "feature/shout",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "changes_requested",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 4,
+ "unresolved_count": 4,
+ "created_at": "2026-10-26T19:40:44.349Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T19:42:13.290Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "number": 2,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/2"
+ }
+ }
+ ],
+ "total_count": 1
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-list/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-list/golden.json
new file mode 100644
index 000000000..592ce42cb
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-list/golden.json
@@ -0,0 +1,80 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1",
+ "externalId": "01M5WVNG0FK8S3JBBFYZGK1VFD"
+ },
+ "kind": "pull_request",
+ "title": "feat: shout greetings",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "changes_requested",
+ "stateRaw": "changes_requested"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "createdAt": "",
+ "updatedAt": "",
+ "lastActivityAt": "",
+ "commentCount": 4,
+ "branches": {
+ "head": {
+ "ref": "feature/shout",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+ }
+ ],
+ "total": 1
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-list/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-list/step.json
new file mode 100644
index 000000000..892d09ffa
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-list/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull list",
+ "recordedAt": "2026-10-26T19:57:13.944Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/01-get-N-discussions.json b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/01-get-N-discussions.json
new file mode 100644
index 000000000..0e3836d99
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/01-get-N-discussions.json
@@ -0,0 +1,433 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/discussions?include_messages=true&per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDiscussionsListResponseBody.json",
+ "items": [
+ {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "last_message_at": "2026-10-26T19:42:13.290Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "review_comment": {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": [
+ {
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:13.290Z",
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "No tests cover the `shout` option.",
+ "kind": "code_review",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "last_message_at": null,
+ "last_message_author": null,
+ "message_count": 0,
+ "participants": [],
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "review_comment": {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ "messages": []
+ },
+ {
+ "id": "01M5WVP9QKVVQWQQWRFW4K3T94",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Naming",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "updated_at": "2026-10-26T19:41:10.771Z",
+ "last_message_at": "2026-10-26T19:41:10.771Z",
+ "last_message_author": "danielroe",
+ "message_count": 1,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP9QQB7HMVGA0SRF76KED",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:10.771Z",
+ "body": "`shout` or `loud`? I lean towards `shout`.",
+ "replies": []
+ }
+ ]
+ },
+ {
+ "id": "01M5WVP84KN3H6SFHM77XGN8WE",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "title": "Should `shout` also add an exclamation mark?",
+ "kind": "discussion",
+ "resolved": false,
+ "resolved_by": null,
+ "resolved_by_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "resolved_at": null,
+ "created_by": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "created_by_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "updated_at": "2026-10-26T19:41:09.989Z",
+ "last_message_at": "2026-10-26T19:41:09.989Z",
+ "last_message_author": "danielroe",
+ "message_count": 2,
+ "participants": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "review_comment_id": null,
+ "review_comment": null,
+ "messages": [
+ {
+ "id": "01M5WVP8552KBGTR9NZCXY91QQ",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.139Z",
+ "body": "Should `shout` also add an exclamation mark?",
+ "replies": []
+ },
+ {
+ "id": "01M5WVP8Z53BEF7ZA2WFQ2RQAX",
+ "author": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "created_at": "2026-10-26T19:41:09.989Z",
+ "body": "I think it reads fine without one.",
+ "replies": []
+ }
+ ]
+ }
+ ],
+ "event_cursor": "4"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/golden.json
new file mode 100644
index 000000000..77d38bc9e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/golden.json
@@ -0,0 +1,130 @@
+{
+ "items": [
+ {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "resolved": false,
+ "outdated": false,
+ "path": "src/greet.ts",
+ "line": 3,
+ "side": "right",
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q"
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "path": "src/greet.ts",
+ "line": 3,
+ "side": "right",
+ "thread": {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "resolved": false
+ },
+ "createdAt": ""
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVR6SAXG323XBRAH7G0Q3V"
+ },
+ "body": "Good catch, switching to `toLocaleUpperCase()`.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "path": "src/greet.ts",
+ "line": 3,
+ "side": "right",
+ "inReplyTo": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "thread": {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "resolved": false
+ },
+ "createdAt": ""
+ }
+ ]
+ },
+ {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "resolved": false,
+ "outdated": false,
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQW423TPZGYYS4025JBCT"
+ },
+ "body": "No tests cover the `shout` option.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "resolved": false
+ },
+ "createdAt": ""
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/step.json
new file mode 100644
index 000000000..5698c2adf
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-review-threads/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull review threads",
+ "recordedAt": "2026-10-26T19:57:16.349Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/01-get-N-approvals.json b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/01-get-N-approvals.json
new file mode 100644
index 000000000..c9882402d
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/01-get-N-approvals.json
@@ -0,0 +1,25 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/approvals"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailApprovalsResponse.json",
+ "approvals": [
+ {
+ "id": "01M5WVPQ2KX1QCY97NEWDQWTB8",
+ "author": "danielroe",
+ "event": "changes_requested",
+ "body": "Please add a test for shouting.",
+ "commit_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "created_at": "2026-10-26T19:41:24.435414047Z"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/02-get-reviews-01M5WVQNQSS4DQNQB5Z00WA4V7.json b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/02-get-reviews-01M5WVQNQSS4DQNQB5Z00WA4V7.json
new file mode 100644
index 000000000..6c4a30a2c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/02-get-reviews-01M5WVQNQSS4DQNQB5Z00WA4V7.json
@@ -0,0 +1,172 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/reviews/01M5WVQNQSS4DQNQB5Z00WA4V7"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ReviewSnapshotResponseBody.json",
+ "review": {
+ "id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "summary": null,
+ "started_at": "2026-10-26T19:41:55.833Z",
+ "source_execution_id": null
+ },
+ "code_version": {
+ "id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "base_ref": null,
+ "head_ref": null,
+ "base_sha": "8127b4c88662985c819a6b3cabecde203907c3e2",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "captured_at": "2026-10-26T19:41:55.824Z"
+ },
+ "counts": {
+ "open": 2,
+ "resolved": 0,
+ "dismissed": 0,
+ "stale": 0,
+ "total": 2
+ },
+ "comments": [
+ {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ },
+ "suggested_changes": [],
+ "outgoing_links": []
+ },
+ {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ },
+ "suggested_changes": [],
+ "outgoing_links": []
+ }
+ ],
+ "event_cursor": "11"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/03-get-reviews-comments.json b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/03-get-reviews-comments.json
new file mode 100644
index 000000000..edb17befd
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/03-get-reviews-comments.json
@@ -0,0 +1,142 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1/reviews/comments?per_page=100"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ReviewCommentsPage.json",
+ "comments": [
+ {
+ "discussion_id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "discussion_message_count": 0,
+ "id": "01M5WVQW423TPZGYYS4025JBCT",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "No tests cover the `shout` option.",
+ "severity": "medium",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "whole",
+ "client_id_hash": "a00f6d308f3eb66bbf8828200158393d01575a3bfc9f750bacf1b11ed8d9430a",
+ "created_at": "2026-10-26T19:42:02.368Z",
+ "updated_at": "2026-10-26T19:42:02.368Z",
+ "location": {
+ "id": "01M5WVQW42ER5GPTS4JM719TDV",
+ "review_comment_id": "01M5WVQW423TPZGYYS4025JBCT",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "whole_change",
+ "file_path": null,
+ "start_line": null,
+ "start_column": null,
+ "end_line": null,
+ "end_column": null,
+ "selected_text": null,
+ "nearby_text": null,
+ "language": null
+ }
+ },
+ {
+ "discussion_id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "discussion_message_count": 1,
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "trail_id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "review_id": "01M5WVQNQSS4DQNQB5Z00WA4V7",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "actor_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "actor_display_name": "danielroe",
+ "actor_identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "severity": "low",
+ "confidence": null,
+ "status": "open",
+ "status_reason": null,
+ "status_actor_id": null,
+ "status_actor_display_name": null,
+ "status_actor_identity": {
+ "kind": "unknown",
+ "account_id": null,
+ "display_name": null,
+ "avatar_url": null,
+ "handles": []
+ },
+ "status_changed_at": null,
+ "status_source": null,
+ "status_actor_kind": null,
+ "stale_outcome": "current",
+ "stale_checked_at": null,
+ "stale_checked_code_version_id": null,
+ "client_id": "shout-case",
+ "client_id_hash": "ea05e251d04f3b2e1dbf616445b14d9faba65c96f60b955c61a748fb186cd26d",
+ "created_at": "2026-10-26T19:42:02.333Z",
+ "updated_at": "2026-10-26T19:42:02.333Z",
+ "location": {
+ "id": "01M5WVQW2Z3NCP4Y284CQ5DE3Y",
+ "review_comment_id": "01M5WVQW2ZTEEA9V72FF81J46Q",
+ "code_version_id": "01M5WVQNQG83V6VQT17G0M6DBJ",
+ "granularity": "line",
+ "file_path": "src/greet.ts",
+ "start_line": 3,
+ "start_column": null,
+ "end_line": 3,
+ "end_column": null,
+ "selected_text": " return options.shout ? greeting.toUpperCase() : greeting",
+ "nearby_text": null,
+ "language": null
+ }
+ }
+ ],
+ "event_cursor": "11"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/golden.json
new file mode 100644
index 000000000..797e8a62e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/golden.json
@@ -0,0 +1,139 @@
+{
+ "items": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVPQ2KX1QCY97NEWDQWTB8"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "danielroe",
+ "isBotHint": false
+ },
+ "state": "changes_requested",
+ "stateRaw": "changes_requested",
+ "body": "Please add a test for shouting.",
+ "submittedAt": "2026-10-26T19:41:24.435Z",
+ "comments": false
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQNQSS4DQNQB5Z00WA4V7"
+ },
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "commented",
+ "stateRaw": "review",
+ "submittedAt": "2026-10-26T19:42:02.333Z",
+ "comments": [
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQW2ZTEEA9V72FF81J46Q"
+ },
+ "body": "`toUpperCase()` ignores the locale, so Turkish names shout incorrectly. Consider `toLocaleUpperCase()`.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "path": "src/greet.ts",
+ "line": 3,
+ "side": "right",
+ "thread": {
+ "id": "01M5WVQW2ZPDDH5X0C5SWM3KVM",
+ "resolved": false
+ },
+ "createdAt": "2026-10-26T19:42:02.333Z"
+ },
+ {
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "thread": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1"
+ },
+ "id": "01M5WVQW423TPZGYYS4025JBCT"
+ },
+ "body": "No tests cover the `shout` option.",
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "thread": {
+ "id": "01M5WVQW42CZ9N82B1QV6B6GTP",
+ "resolved": false
+ },
+ "createdAt": "2026-10-26T19:42:02.368Z"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/step.json
new file mode 100644
index 000000000..d2b2d9944
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull-reviews/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull reviews",
+ "recordedAt": "2026-10-26T21:02:29.586Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json b/test/fixtures/entire/recorded/entire.com/steps/pull/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json
new file mode 100644
index 000000000..1dfbfb50b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull/01-get-trails-01M5WVNFY1D7AHX0KJW2ANZP14.json
@@ -0,0 +1,153 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\"",
+ "etag": "W/\"2026-10-26T19:40:44.561257Z\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailResponse.json",
+ "capabilities": {
+ "canEdit": true,
+ "canManageScope": true
+ },
+ "project": {
+ "primaryProcessingCell": "aws-eu-west-2",
+ "id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "name": "forges",
+ "provider": "entire",
+ "reference": {
+ "forge": "et",
+ "project": "forges"
+ },
+ "jurisdiction": "uk",
+ "capabilities": {
+ "canManageTrails": true
+ }
+ },
+ "author": {
+ "accountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "displayName": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "projectId": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "number": 2,
+ "url": "https://entire.com/et/forges/-/trails/2",
+ "title": "Shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "bodyDocument": {
+ "id": "",
+ "document_key": "project-trail:01M5WVNFY1D7AHX0KJW2ANZP14:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Adds a "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "shout"
+ },
+ {
+ "type": "text",
+ "text": " option to "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "greet()"
+ },
+ {
+ "type": "text",
+ "text": "."
+ }
+ ]
+ }
+ ]
+ },
+ "text_snapshot": "Adds a shout option to greet().",
+ "updated_at": "2026-10-26T19:40:44.561Z",
+ "etag": ""
+ },
+ "status": "open",
+ "phase": "building",
+ "metadata": {},
+ "type": "feature",
+ "priority": "medium",
+ "assignees": [],
+ "assigneeAccountIds": [],
+ "repoIds": [
+ "01M5WS0P331VQ3AARB26Z9DGV7"
+ ],
+ "authorAccountId": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "metadataUpdatedAt": "2026-10-26T19:40:44.561Z",
+ "closedAt": null,
+ "lastVisitedAt": null,
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "changes": [
+ {
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "repositoryId": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "repository": "forges-fixtures",
+ "branch": "feature/shout",
+ "originalBranch": "feature/shout",
+ "base": "main",
+ "number": 1,
+ "title": "feat: shout greetings",
+ "status": "open",
+ "hasCodeChanges": true,
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "updatedAt": "2026-10-26T19:42:13.290Z",
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0
+ },
+ "readiness": "in_review",
+ "mergeability": {
+ "conflict_status": "unknown",
+ "head_sha": "",
+ "refreshed_at": null,
+ "forge_merge_block": null
+ }
+ }
+ ],
+ "isPossiblyPartial": true
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull/02-get-forges-fixtures-N.json b/test/fixtures/entire/recorded/entire.com/steps/pull/02-get-forges-fixtures-N.json
new file mode 100644
index 000000000..e93c190c8
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull/02-get-forges-fixtures-N.json
@@ -0,0 +1,287 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/trails/et/forges/forges-fixtures/1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/TrailDetailResponse.json",
+ "id": "01M5WVNG0FK8S3JBBFYZGK1VFD",
+ "number": 1,
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "title": "feat: shout greetings",
+ "status": "open",
+ "branch": "feature/shout",
+ "original_branch": "feature/shout",
+ "base": "main",
+ "type": "task",
+ "priority": "none",
+ "author": {
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ },
+ "assignees": [
+ "danielroe"
+ ],
+ "assignee_identities": [
+ {
+ "login": "danielroe",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "status": "changes_requested",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "identity": {
+ "kind": "account",
+ "account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "display_name": "Daniel Roe",
+ "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "handles": [
+ {
+ "provider": "github",
+ "handle": "danielroe",
+ "is_primary": true
+ }
+ ]
+ }
+ }
+ ],
+ "ready_for_review": true,
+ "has_code_changes": true,
+ "comment_count": 4,
+ "unresolved_count": 4,
+ "created_at": "2026-10-26T19:40:44.349Z",
+ "updated_at": "2026-10-26T19:42:13.290Z",
+ "merged_at": null,
+ "last_activity_at": "2026-10-26T19:42:13.290Z",
+ "phase": "reviewing",
+ "parent": {
+ "id": "01M5WVNFY1D7AHX0KJW2ANZP14",
+ "number": 2,
+ "project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "host": "et",
+ "project": "forges",
+ "path": "/api/v1/et/forges/trails/01M5WVNFY1D7AHX0KJW2ANZP14",
+ "jurisdiction": "uk",
+ "primary_processing_cell": "aws-eu-west-2",
+ "url": "https://entire.com/et/forges/-/trails/2"
+ },
+ "branch_state": "present",
+ "code_changes": "present",
+ "lifecycle_sync": "settled",
+ "body_document": {
+ "id": "01M5WVNG4RWC7X4X51BHP5Q5M2",
+ "document_key": "trail:01M5WVNG0FK8S3JBBFYZGK1VFD:body",
+ "schema_version": 1,
+ "content_json": {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph"
+ }
+ ]
+ },
+ "text_snapshot": "",
+ "updated_at": "2026-10-26T19:40:44.349Z",
+ "etag": "W/\"2026-10-26T19:40:44.349281Z\""
+ },
+ "monitors": [],
+ "agents": [],
+ "runners": [],
+ "actions": {
+ "approve": {
+ "state": "enabled",
+ "reason": null
+ },
+ "create_ci_pr": {
+ "state": "unavailable",
+ "reason": "not_applicable"
+ },
+ "merge": {
+ "state": "blocked",
+ "reason": "merge_conflict"
+ },
+ "merge_with_bypass": {
+ "state": "unavailable",
+ "reason": "merge_gates_bypass_forbidden"
+ },
+ "update_branch": {
+ "state": "unavailable",
+ "reason": "merge_conflict"
+ },
+ "restore_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ },
+ "delete_branch": {
+ "state": "unavailable",
+ "reason": "trail_not_merged"
+ }
+ },
+ "mergeability": {
+ "delete_branch_on_merge": false,
+ "bypass_policy": "nobody",
+ "mergeable": false,
+ "behind_by": 1,
+ "comparison_status": "available",
+ "conflict_status": "conflicting",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "gates": [
+ {
+ "reviewers": [
+ {
+ "login": "danielroe",
+ "actor_account_id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "state": "changes_requested",
+ "reviewed_head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "reason": null
+ }
+ ],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP4MSACMEJ1KK4FGP2B1",
+ "gate_key": "approvals",
+ "gate_type": "approvals",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "approved": 0,
+ "changes_requested": true,
+ "config": {
+ "version": 1,
+ "min_reviewers": 1,
+ "allow_self_approval": true,
+ "dismiss_stale_approvals_on_push": false
+ },
+ "required": 1,
+ "self_excluded": false,
+ "stale_dismissed": 0,
+ "total": 1
+ },
+ "rationale": "Changes have been requested",
+ "created_at": "2026-10-26T21:02:27.139303741Z",
+ "completed_at": "2026-10-26T21:02:27.139303741Z",
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "01M5WVQW5BHQ3CSVZJTM3595D5",
+ "gate_definition_id": "01M5WTJP5MDYCYNF3Z6BWN7NZ0",
+ "gate_key": "findings",
+ "gate_type": "findings",
+ "blocking": true,
+ "status": "passed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "config": {
+ "version": 1,
+ "agent_findings_min_severity": "high"
+ },
+ "reviewed_head": false,
+ "agent_findings": {
+ "low": 1,
+ "high": 0,
+ "medium": 1,
+ "unspecified": 0
+ },
+ "review_in_flight": false,
+ "reviewer_configured": false,
+ "review_grace_expired": false,
+ "agent_blocking_findings": 0
+ },
+ "rationale": "No blocking findings",
+ "created_at": "2026-10-26T19:42:02.411692Z",
+ "completed_at": "2026-10-26T19:42:02.411692Z",
+ "state": "evaluated",
+ "outcome": "passed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": 0
+ },
+ {
+ "reviewers": [],
+ "runner_ids": [],
+ "id": "",
+ "gate_definition_id": "01M5WTJP5QR0FQ7TWGE7Y3QCJC",
+ "gate_key": "up_to_date",
+ "gate_type": "up_to_date",
+ "blocking": true,
+ "status": "failed",
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "value": {
+ "comparison_status": "available",
+ "behind_by": 1,
+ "compare_status": "diverged",
+ "merge_status": "conflicting",
+ "config": {
+ "version": 1,
+ "allow_behind_if_mergeable": false
+ }
+ },
+ "rationale": "Merge conflicts",
+ "created_at": "2026-10-26T19:41:42.616528Z",
+ "completed_at": null,
+ "state": "evaluated",
+ "outcome": "failed",
+ "stale_reason": null,
+ "evaluated_at_sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "finding_count": null
+ }
+ ],
+ "checks": {
+ "availability": "not_applicable",
+ "runs": []
+ },
+ "forge_merge_block": null
+ },
+ "deployments": {
+ "availability": "available",
+ "ready": 0,
+ "total": 0,
+ "items": []
+ },
+ "config_source": "live",
+ "merge_base_sha": null,
+ "base_sha": null,
+ "head_sha": "9ec20061f17f35212247320f525ec51d3e00c41a"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull/golden.json b/test/fixtures/entire/recorded/entire.com/steps/pull/golden.json
new file mode 100644
index 000000000..9f77e9465
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull/golden.json
@@ -0,0 +1,77 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ },
+ "kind": "pull_request",
+ "number": "1",
+ "externalId": "01M5WVNG0FK8S3JBBFYZGK1VFD"
+ },
+ "kind": "pull_request",
+ "title": "feat: shout greetings",
+ "body": "Adds a `shout` option to `greet()`.",
+ "state": "open",
+ "stateRaw": "open",
+ "isDraft": false,
+ "author": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "assignees": [
+ {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ }
+ ],
+ "reviewers": [
+ {
+ "actor": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "login": "danielroe",
+ "id": "01M5WRP9RJ4GVR2PA5PCNYP1FC",
+ "name": "Daniel Roe",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/28706372?v=4",
+ "isBotHint": false
+ },
+ "state": "changes_requested",
+ "stateRaw": "changes_requested"
+ }
+ ],
+ "labels": [],
+ "url": "https://entire.com/et/forges/forges-fixtures/trails/1",
+ "createdAt": "2026-10-26T19:40:44.349Z",
+ "updatedAt": "2026-10-26T19:42:13.290Z",
+ "lastActivityAt": "2026-10-26T19:42:13.290Z",
+ "commentCount": 4,
+ "branches": {
+ "head": {
+ "ref": "feature/shout",
+ "sha": "9ec20061f17f35212247320f525ec51d3e00c41a",
+ "repo": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures"
+ }
+ },
+ "base": {
+ "ref": "main"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/pull/step.json b/test/fixtures/entire/recorded/entire.com/steps/pull/step.json
new file mode 100644
index 000000000..4236a57a7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/pull/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "pull",
+ "recordedAt": "2026-10-26T21:02:26.761Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
new file mode 100644
index 000000000..cb287450c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
@@ -0,0 +1,11 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/raw?ref=HEAD&path=README.md"
+ },
+ "response": {
+ "status": 200,
+ "headers": {},
+ "body": "# forges-fixtures\n\nA repository for the Entire recordings of [forges](https://forges.link), a forge client.\n"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/golden.json b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/golden.json
new file mode 100644
index 000000000..f80fc04a6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/golden.json
@@ -0,0 +1,6 @@
+{
+ "path": "README.md",
+ "size": 108,
+ "content": "# forges-fixtures\n\nA repository for the Entire recordings of [forges](https://forges.link), a forge client.\n",
+ "encoding": "utf-8"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/step.json b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/step.json
new file mode 100644
index 000000000..705a46788
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme-without-metadata/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "readme without metadata",
+ "recordedAt": "2026-10-26T19:57:22.098Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json b/test/fixtures/entire/recorded/entire.com/steps/readme/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
new file mode 100644
index 000000000..cb287450c
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
@@ -0,0 +1,11 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/raw?ref=HEAD&path=README.md"
+ },
+ "response": {
+ "status": 200,
+ "headers": {},
+ "body": "# forges-fixtures\n\nA repository for the Entire recordings of [forges](https://forges.link), a forge client.\n"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme/golden.json b/test/fixtures/entire/recorded/entire.com/steps/readme/golden.json
new file mode 100644
index 000000000..f80fc04a6
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme/golden.json
@@ -0,0 +1,6 @@
+{
+ "path": "README.md",
+ "size": 108,
+ "content": "# forges-fixtures\n\nA repository for the Entire recordings of [forges](https://forges.link), a forge client.\n",
+ "encoding": "utf-8"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/readme/step.json b/test/fixtures/entire/recorded/entire.com/steps/readme/step.json
new file mode 100644
index 000000000..df00a3868
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/readme/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "readme",
+ "recordedAt": "2026-10-26T19:57:21.909Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo-licence/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json
new file mode 100644
index 000000000..44aab1e74
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json
@@ -0,0 +1,42 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/repos/01M5WS0P331VQ3AARB26Z9DGV7"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/RepoListItem.json",
+ "owning_project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "github_id": 0,
+ "name": "forges-fixtures",
+ "full_name": "forges/forges-fixtures",
+ "description": null,
+ "html_url": null,
+ "stargazers_count": 0,
+ "language": null,
+ "private": true,
+ "archived": false,
+ "updated_at": "2026-10-26T19:41:42.564Z",
+ "default_branch": "main",
+ "permission": "admin",
+ "capabilities": {
+ "data_role": "write",
+ "can_manage_settings": true,
+ "can_manage_access": true
+ },
+ "checkpoint_count": 0,
+ "viewer_can_see_checkpoint_data": true,
+ "runners_enabled": false,
+ "trails_enabled": true,
+ "auto_run_on_push": false,
+ "public_sharing_enabled": false,
+ "open_pr_count": 0,
+ "repo_group_id": "01M5WS0P33VDWTEFK1HZ5QDEPA"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo-licence/golden.json b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/golden.json
new file mode 100644
index 000000000..e067fb171
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/golden.json
@@ -0,0 +1,24 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures",
+ "externalId": "01M5WS0P331VQ3AARB26Z9DGV7"
+ },
+ "defaultBranch": "main",
+ "visibility": "private",
+ "isFork": false,
+ "isArchived": false,
+ "topics": [],
+ "updatedAt": "",
+ "stars": 0,
+ "openPullCount": 0,
+ "permissions": {
+ "admin": true,
+ "maintain": true,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo-licence/step.json b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/step.json
new file mode 100644
index 000000000..b142cdf5e
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo-licence/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "repo licence",
+ "recordedAt": "2026-10-26T19:57:13.055Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json b/test/fixtures/entire/recorded/entire.com/steps/repo/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json
new file mode 100644
index 000000000..44aab1e74
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo/01-get-repos-01M5WS0P331VQ3AARB26Z9DGV7.json
@@ -0,0 +1,42 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/repos/01M5WS0P331VQ3AARB26Z9DGV7"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/RepoListItem.json",
+ "owning_project_id": "01M5WS0JJNP4V9AW20MZ3MKF5M",
+ "id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "github_id": 0,
+ "name": "forges-fixtures",
+ "full_name": "forges/forges-fixtures",
+ "description": null,
+ "html_url": null,
+ "stargazers_count": 0,
+ "language": null,
+ "private": true,
+ "archived": false,
+ "updated_at": "2026-10-26T19:41:42.564Z",
+ "default_branch": "main",
+ "permission": "admin",
+ "capabilities": {
+ "data_role": "write",
+ "can_manage_settings": true,
+ "can_manage_access": true
+ },
+ "checkpoint_count": 0,
+ "viewer_can_see_checkpoint_data": true,
+ "runners_enabled": false,
+ "trails_enabled": true,
+ "auto_run_on_push": false,
+ "public_sharing_enabled": false,
+ "open_pr_count": 0,
+ "repo_group_id": "01M5WS0P33VDWTEFK1HZ5QDEPA"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo/02-post-repos-resolve.json b/test/fixtures/entire/recorded/entire.com/steps/repo/02-post-repos-resolve.json
new file mode 100644
index 000000000..1d83a6321
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo/02-post-repos-resolve.json
@@ -0,0 +1,48 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://us.console.entire.io/api/v1/repos/resolve"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://us.console.entire.io/api/v1/schemas/ResolveReposResponse.json",
+ "resolutions": [
+ {
+ "provider": "entire",
+ "requestedFullName": "forges/forges-fixtures",
+ "fullName": "forges/forges-fixtures",
+ "status": "ready",
+ "repoId": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "repoGroupId": "01M5WS0P33VDWTEFK1HZ5QDEPA",
+ "placements": [
+ {
+ "id": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "jurisdiction": "uk",
+ "cell": "aws-eu-west-2",
+ "apiBaseUrl": "https://aws-eu-west-2.api.entire.io",
+ "clusterSlug": "aws-eu-west-2",
+ "mirror": false,
+ "status": "ready"
+ }
+ ],
+ "primaries": {
+ "git_data": "entire:01M5WS0P331VQ3AARB26Z9DGV7",
+ "processing": "01M5WS0P331VQ3AARB26Z9DGV7",
+ "data_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7"
+ },
+ "processing_primary": {
+ "forge": "entire",
+ "repo_id": "01M5WS0P331VQ3AARB26Z9DGV7"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo/golden.json b/test/fixtures/entire/recorded/entire.com/steps/repo/golden.json
new file mode 100644
index 000000000..eb84ea979
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo/golden.json
@@ -0,0 +1,24 @@
+{
+ "ref": {
+ "forge": "entire",
+ "instance": "entire.com",
+ "owner": "forges",
+ "name": "forges-fixtures",
+ "externalId": "01M5WS0P331VQ3AARB26Z9DGV7"
+ },
+ "defaultBranch": "main",
+ "visibility": "private",
+ "isFork": false,
+ "isArchived": false,
+ "topics": [],
+ "updatedAt": "2026-10-26T19:41:42.564Z",
+ "stars": 0,
+ "openPullCount": 0,
+ "permissions": {
+ "admin": true,
+ "maintain": true,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/repo/step.json b/test/fixtures/entire/recorded/entire.com/steps/repo/step.json
new file mode 100644
index 000000000..254fc7494
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/repo/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "repo",
+ "recordedAt": "2026-10-26T21:02:24.670Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
new file mode 100644
index 000000000..1997b17c1
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/01-get-01M5WS0P331VQ3AARB26Z9DGV7-commits.json
@@ -0,0 +1,35 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/commits?ref=main&pageSize=1"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListCommitsResponse.json",
+ "commits": [
+ {
+ "sha": "dd07002e23c86b5e68d53ebe9949e1bf5549c8ca",
+ "parents": [
+ "8127b4c88662985c819a6b3cabecde203907c3e2"
+ ],
+ "message": "docs: say what forges is\n",
+ "author": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ },
+ "committer": {
+ "name": "forges fixtures",
+ "email": "redacted@example.invalid",
+ "date": "2026-10-02T11:00:00Z"
+ }
+ }
+ ],
+ "nextPageToken": "eyJmIjpbIjgxMjdiNGM4ODY2Mjk4NWM4MTlhNmIzY2FiZWNkZTIwMzkwN2MzZTIiXX0"
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/golden.json b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/golden.json
new file mode 100644
index 000000000..fae5a61ad
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/golden.json
@@ -0,0 +1 @@
+"dd07002e23c86b5e68d53ebe9949e1bf5549c8ca"
diff --git a/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/step.json b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/step.json
new file mode 100644
index 000000000..48e0732aa
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/resolve-default-branch/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "resolve default branch",
+ "recordedAt": "2026-10-26T19:57:23.390Z",
+ "needs": [
+ "repo"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tags/01-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json b/test/fixtures/entire/recorded/entire.com/steps/tags/01-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json
new file mode 100644
index 000000000..56c9c3ad7
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tags/01-get-01M5WS0P331VQ3AARB26Z9DGV7-refs.json
@@ -0,0 +1,26 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/refs?prefix=refs%2Ftags%2F&peel=true&symrefs=true&pageSize=3"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/ListRefsResponse.json",
+ "refs": [
+ {
+ "name": "refs/tags/v0.0.1",
+ "oid": "fc6d8c9fb93b340e7d064fee866d49d70073ff34"
+ },
+ {
+ "name": "refs/tags/v0.1.0",
+ "oid": "5637ee18aa488d037aabeea7ce5981ba2037b50c",
+ "peeled": "8127b4c88662985c819a6b3cabecde203907c3e2"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tags/golden.json b/test/fixtures/entire/recorded/entire.com/steps/tags/golden.json
new file mode 100644
index 000000000..1e2534193
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tags/golden.json
@@ -0,0 +1,12 @@
+{
+ "items": [
+ {
+ "name": "v0.0.1",
+ "sha": "fc6d8c9fb93b340e7d064fee866d49d70073ff34"
+ },
+ {
+ "name": "v0.1.0",
+ "sha": "8127b4c88662985c819a6b3cabecde203907c3e2"
+ }
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tags/step.json b/test/fixtures/entire/recorded/entire.com/steps/tags/step.json
new file mode 100644
index 000000000..32f54b4cf
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tags/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "tags",
+ "recordedAt": "2026-10-26T19:57:23.212Z"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree-file/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json b/test/fixtures/entire/recorded/entire.com/steps/tree-file/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
new file mode 100644
index 000000000..3d39b7d2a
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree-file/01-get-01M5WS0P331VQ3AARB26Z9DGV7-raw.json
@@ -0,0 +1,11 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/raw?ref=HEAD&path=LICENCE"
+ },
+ "response": {
+ "status": 200,
+ "headers": {},
+ "body": "MIT License\n\nCopyright (c) 2026 forges fixtures\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree-file/golden.json b/test/fixtures/entire/recorded/entire.com/steps/tree-file/golden.json
new file mode 100644
index 000000000..db284e859
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree-file/golden.json
@@ -0,0 +1,6 @@
+{
+ "path": "LICENCE",
+ "size": 1072,
+ "content": "MIT License\n\nCopyright (c) 2026 forges fixtures\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
+ "encoding": "utf-8"
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree-file/step.json b/test/fixtures/entire/recorded/entire.com/steps/tree-file/step.json
new file mode 100644
index 000000000..78cd3807b
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree-file/step.json
@@ -0,0 +1,7 @@
+{
+ "name": "tree file",
+ "recordedAt": "2026-10-26T19:57:22.772Z",
+ "needs": [
+ "tree"
+ ]
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree/01-get-01M5WS0P331VQ3AARB26Z9DGV7-tree.json b/test/fixtures/entire/recorded/entire.com/steps/tree/01-get-01M5WS0P331VQ3AARB26Z9DGV7-tree.json
new file mode 100644
index 000000000..ee95b4058
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree/01-get-01M5WS0P331VQ3AARB26Z9DGV7-tree.json
@@ -0,0 +1,54 @@
+{
+ "request": {
+ "method": "GET",
+ "url": "https://aws-eu-west-2.api.entire.io/api/v1/git/repos/01M5WS0P331VQ3AARB26Z9DGV7/tree?ref=HEAD"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "link": "; rel=\"describedBy\""
+ },
+ "body": {
+ "$schema": "https://aws-eu-west-2.api.entire.io/api/v1/schemas/GetTreeResponse.json",
+ "sha": "f6bd8f24e749f053439a5c5adc275b42762e04e1",
+ "entries": [
+ {
+ "path": "LICENCE",
+ "mode": "100644",
+ "type": "blob",
+ "sha": "5ce04d65bd2730e802c0ecd64a3d375bd0e24181",
+ "size": 1072
+ },
+ {
+ "path": "README.md",
+ "mode": "100644",
+ "type": "blob",
+ "sha": "5e0c7c77ed6919d484c7bf5956816ad63dd2e5ed",
+ "size": 108
+ },
+ {
+ "path": "cli.ts",
+ "mode": "100644",
+ "type": "blob",
+ "sha": "d5da9c7b1a41ace3ab5cedf0176e9c3ad0dbe592",
+ "size": 82
+ },
+ {
+ "path": "package.json",
+ "mode": "100644",
+ "type": "blob",
+ "sha": "674b2088dda09d12850a9c65ac84ff90b9730b6b",
+ "size": 71
+ },
+ {
+ "path": "src",
+ "mode": "040000",
+ "type": "tree",
+ "sha": "88b193b2013af32600d786477091f6b73d83482a",
+ "size": 0
+ }
+ ],
+ "truncated": false
+ }
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree/golden.json b/test/fixtures/entire/recorded/entire.com/steps/tree/golden.json
new file mode 100644
index 000000000..39c639708
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree/golden.json
@@ -0,0 +1,28 @@
+{
+ "items": [
+ {
+ "path": "LICENCE",
+ "type": "file",
+ "sha": "5ce04d65bd2730e802c0ecd64a3d375bd0e24181",
+ "size": 1072,
+ "mode": "100644"
+ },
+ {
+ "path": "README.md",
+ "type": "file",
+ "sha": "5e0c7c77ed6919d484c7bf5956816ad63dd2e5ed",
+ "size": 108,
+ "mode": "100644"
+ },
+ {
+ "path": "cli.ts",
+ "type": "file",
+ "sha": "d5da9c7b1a41ace3ab5cedf0176e9c3ad0dbe592",
+ "size": 82,
+ "mode": "100644"
+ }
+ ],
+ "cursor": {
+ "token": "3"
+ }
+}
diff --git a/test/fixtures/entire/recorded/entire.com/steps/tree/step.json b/test/fixtures/entire/recorded/entire.com/steps/tree/step.json
new file mode 100644
index 000000000..acffc27dc
--- /dev/null
+++ b/test/fixtures/entire/recorded/entire.com/steps/tree/step.json
@@ -0,0 +1,4 @@
+{
+ "name": "tree",
+ "recordedAt": "2026-10-26T19:57:22.603Z"
+}
diff --git a/test/recorded.test.ts b/test/recorded.test.ts
index 10a7c2f2a..2ac06c3af 100644
--- a/test/recorded.test.ts
+++ b/test/recorded.test.ts
@@ -40,7 +40,7 @@ const REASONS: NotificationReason[] = [
/** The forges, by display name, on which a recording reads each listing again with its first page's `etag`. */
const conditionalListings = new Map>()
-const FORGE_NAMES: Record = { 'github': 'GitHub', 'gitlab': 'GitLab', 'bitbucket': 'Bitbucket', 'forgejo': 'Forgejo', 'gitea': 'Gitea', 'gitee': 'Gitee', 'azure-devops': 'Azure DevOps', 'cursor-origin': 'Cursor Origin', 'pushin': 'Pushin.eu', 'tangled': 'Tangled', 'cloudflare-artifacts': 'Cloudflare Artifacts', 'sourcehut': 'Sourcehut' }
+const FORGE_NAMES: Record = { 'github': 'GitHub', 'gitlab': 'GitLab', 'bitbucket': 'Bitbucket', 'forgejo': 'Forgejo', 'gitea': 'Gitea', 'gitee': 'Gitee', 'azure-devops': 'Azure DevOps', 'cursor-origin': 'Cursor Origin', 'pushin': 'Pushin.eu', 'tangled': 'Tangled', 'cloudflare-artifacts': 'Cloudflare Artifacts', 'sourcehut': 'Sourcehut', 'entire': 'Entire' }
function sameRequest(a: string, b: string): boolean {
const [first, second] = [new URL(a), new URL(b)]
diff --git a/test/recording/replay.ts b/test/recording/replay.ts
index 779149204..20aa59730 100644
--- a/test/recording/replay.ts
+++ b/test/recording/replay.ts
@@ -5,6 +5,7 @@ import { generateKeyPairSync } from 'node:crypto'
import { azureDevOps } from '../../src/azure-devops/index.ts'
import { bitbucket } from '../../src/bitbucket/index.ts'
import { cloudflareArtifacts } from '../../src/cloudflare-artifacts/index.ts'
+import { entire } from '../../src/entire/index.ts'
import { forgejo } from '../../src/forgejo/index.ts'
import { gitea } from '../../src/gitea/index.ts'
import { gitee } from '../../src/gitee/index.ts'
@@ -32,6 +33,7 @@ export const REPLAYED_PROVIDERS: Array<{ name: string, create: Create }> = [
{ name: 'cloudflare-artifacts', create: (fetch, manifest) => cloudflareArtifacts({ auth: token, accountId: manifest.repo.instance.split('.')[0]!, baseUrl: manifest.baseUrl, fetch }) },
{ name: 'pushin', create: (fetch, manifest) => pushin({ auth: authFor(manifest), baseUrl: manifest.baseUrl, fetch }) },
{ name: 'sourcehut', create: (fetch, manifest) => sourcehut({ auth: authFor(manifest) ?? token, baseUrl: manifest.baseUrl, fetch }) },
+ { name: 'entire', create: (fetch, manifest) => entire({ auth: token, baseUrl: manifest.baseUrl, fetch }) },
{ name: 'gitee', create: (fetch, manifest) => gitee({ auth: authFor(manifest), fetch }) },
{ name: 'azure-devops', create: (fetch, manifest) => azureDevOps({ auth: authFor(manifest), organization: manifest.repo.owner.split('/')[0]!, fetch }) },
{
diff --git a/test/unit/auth.test.ts b/test/unit/auth.test.ts
index e38f943d1..b12c9bee4 100644
--- a/test/unit/auth.test.ts
+++ b/test/unit/auth.test.ts
@@ -5,6 +5,7 @@ import { azureDevOps } from '../../src/azure-devops/index.ts'
import { bitbucket } from '../../src/bitbucket/index.ts'
import { cloudflareArtifacts } from '../../src/cloudflare-artifacts/index.ts'
import { cursorOrigin } from '../../src/cursor-origin/index.ts'
+import { entire } from '../../src/entire/index.ts'
import { forgejo } from '../../src/forgejo/index.ts'
import { gitea } from '../../src/gitea/index.ts'
import { gitee } from '../../src/gitee/index.ts'
@@ -27,6 +28,7 @@ const FACTORIES: Record = {
'pushin': (token, fetch) => pushin({ auth: { type: 'token', token }, fetch }),
'cloudflare-artifacts': (token, fetch) => cloudflareArtifacts({ auth: { type: 'token', token }, accountId: 'acme', fetch }),
'sourcehut': (token, fetch) => sourcehut({ auth: { type: 'token', token }, fetch }),
+ 'entire': (token, fetch) => entire({ auth: { type: 'token', token }, fetch }),
}
describe('token auth', () => {
diff --git a/test/unit/entire.test.ts b/test/unit/entire.test.ts
new file mode 100644
index 000000000..f43c2003a
--- /dev/null
+++ b/test/unit/entire.test.ts
@@ -0,0 +1,66 @@
+import { describe, expect, it } from 'vitest'
+import { entire } from '../../src/entire/index.ts'
+import { BranchMovedError, UnsupportedOperationError } from '../../src/errors.ts'
+import { fixtureFetch } from '../../src/testing/index.ts'
+
+const origin = { forge: 'entire', instance: 'entire.com' } as const
+const repo = { ...origin, owner: 'acme', name: 'widgets' }
+const pull = { ...origin, repo, kind: 'pull_request', number: '3' } as const
+const core = 'https://us.console.entire.io/api/v1'
+const cell = 'https://aws-eu-west-2.api.entire.io/api/v1'
+const change = `${cell}/trails/et/acme/widgets/3`
+const resolved = { status: 200, body: { resolutions: [{ requestedFullName: 'acme/widgets', status: 'resolved', placements: [{ id: 'repo1', apiBaseUrl: 'https://aws-eu-west-2.api.entire.io' }] }] } }
+
+function provider(overrides: Parameters[1]) {
+ const fixtures = fixtureFetch([], { [`POST ${core}/repos/resolve`]: resolved, ...overrides })
+ return { ...fixtures, forge: entire({ auth: { type: 'token', token: 't' }, fetch: fixtures.fetch }) }
+}
+
+describe('entire', () => {
+ it('quotes the commented lines of the change head in a line finding', async () => {
+ const finding = { id: 'f1', review_id: 'r1', status: 'open', created_at: '2026-01-01T00:00:00Z', body: 'Shout less', location: { granularity: 'range', file_path: 'src/a.ts', start_line: 2, end_line: 3 } }
+ const { forge, calls } = provider({
+ [`GET ${change}`]: { status: 200, body: { id: 'c1', number: 3, title: 'Shout', status: 'open', branch: 'feat', base: 'main', head_sha: 'abc' } },
+ [`GET ${cell}/git/repos/repo1/raw?ref=abc&path=src%2Fa.ts`]: { status: 200, body: 'one\ntwo\nthree\nfour\n' },
+ [`POST ${change}/reviews`]: { status: 201, body: { review_id: 'r1' } },
+ [`POST ${change}/reviews/r1/comments`]: { status: 201, body: { results: [{ client_id: '1', status: 'created', comment: finding }] } },
+ })
+
+ const review = await forge.threads.createReview(pull, { event: 'comment', body: 'Summary', comments: [{ path: 'src/a.ts', startLine: 2, line: 3, body: 'Shout less' }] })
+
+ expect(JSON.parse(calls.find(call => call.url.endsWith('/reviews/r1/comments'))!.body!)).toEqual({
+ summary: 'Summary',
+ comments: [{ client_id: '1', body: 'Shout less', location: { granularity: 'range', file_path: 'src/a.ts', start_line: 2, end_line: 3, selected_text: 'two\nthree' } }],
+ })
+ expect(review).toMatchObject({ state: 'commented', body: 'Summary' })
+ })
+
+ it('recomputes pending gates once before merging again', async () => {
+ const merge = { method: 'POST', url: `${cell}/repos/repo1/trails/3/merge` }
+ const { fetch, calls } = fixtureFetch([
+ { handAuthored: true, request: merge, response: { status: 422, body: { code: 'merge_gates_pending' } } },
+ { handAuthored: true, request: merge, response: { status: 200, body: { ok: true, mergeCommitSha: 'def' } } },
+ ], {
+ [`POST ${core}/repos/resolve`]: resolved,
+ [`POST ${cell}/repos/repo1/trails/3/gates/recompute`]: { status: 200, body: {} },
+ }, { sequential: true })
+ const forge = entire({ auth: { type: 'token', token: 't' }, fetch })
+
+ expect(await forge.threads.merge(pull, { sha: 'abc' })).toMatchObject({ status: 'merged', sha: 'def' })
+ expect(calls.map(call => call.url.replace(cell, ''))).toEqual([`${core}/repos/resolve`, '/repos/repo1/trails/3/merge', '/repos/repo1/trails/3/gates/recompute', '/repos/repo1/trails/3/merge'])
+ })
+
+ it.each([412, 409])('reads a %i from a merge at a stale sha as a moved branch', async (status) => {
+ const { forge } = provider({
+ [`POST ${cell}/repos/repo1/trails/3/merge`]: { status, body: { code: 'head_mismatch' } },
+ })
+ await expect(forge.threads.merge(pull, { sha: 'abc' })).rejects.toBeInstanceOf(BranchMovedError)
+ })
+
+ it('refuses merge methods and labels that Entire has no equivalent for', async () => {
+ const { forge, calls } = provider({})
+ await expect(forge.threads.merge(pull, { method: 'squash' })).rejects.toBeInstanceOf(UnsupportedOperationError)
+ await expect(forge.threads.create(repo, { kind: 'issue', title: 'A', labels: ['bug'] })).rejects.toBeInstanceOf(UnsupportedOperationError)
+ expect(calls).toEqual([])
+ })
+})
diff --git a/test/unit/web-entry.test.ts b/test/unit/web-entry.test.ts
index 31ca21abe..90e90a60c 100644
--- a/test/unit/web-entry.test.ts
+++ b/test/unit/web-entry.test.ts
@@ -19,6 +19,7 @@ const cases: Array<[keyof typeof web & keyof typeof providers, WebOptions, strin
['bitbucket', {}, ['https://bitbucket.org/acme/widgets/pull-requests/42']],
['azureDevOps', {}, ['https://dev.azure.com/contoso/Widgets/_git/widgets/pullrequest/42']],
['sourcehut', {}, [`https://git.sr.ht/~acme/widgets/commit/${sha}`]],
+ ['entire', {}, ['https://entire.com/et/acme/widgets/trails/2', 'https://entire.com/et/acme/-/trails/1']],
['tangled', {}, ['https://tangled.sh/@acme.dev/core/issues/4', 'https://tangled.sh/@acme.dev/core/compare/main...feat']],
]
diff --git a/tsdown.config.ts b/tsdown.config.ts
index dbae5d908..d626c23d0 100644
--- a/tsdown.config.ts
+++ b/tsdown.config.ts
@@ -3,7 +3,7 @@ import { defineConfig } from 'tsdown'
import { standaloneSchema } from './scripts/generate-schemas.ts'
export default defineConfig({
- entry: ['src/index.ts', 'src/github/index.ts', 'src/gitlab/index.ts', 'src/bitbucket/index.ts', 'src/forgejo/index.ts', 'src/gitea/index.ts', 'src/tangled/index.ts', 'src/cursor-origin/index.ts', 'src/gitee/index.ts', 'src/pushin/index.ts', 'src/azure-devops/index.ts', 'src/cloudflare-artifacts/index.ts', 'src/sourcehut/index.ts', 'src/env.ts', 'src/helpers.ts', 'src/kit.ts', 'src/schema/index.ts', 'src/testing/index.ts', 'src/fake/index.ts', 'src/web/index.ts'],
+ entry: ['src/index.ts', 'src/github/index.ts', 'src/gitlab/index.ts', 'src/bitbucket/index.ts', 'src/forgejo/index.ts', 'src/gitea/index.ts', 'src/tangled/index.ts', 'src/cursor-origin/index.ts', 'src/gitee/index.ts', 'src/pushin/index.ts', 'src/azure-devops/index.ts', 'src/cloudflare-artifacts/index.ts', 'src/sourcehut/index.ts', 'src/entire/index.ts', 'src/env.ts', 'src/helpers.ts', 'src/kit.ts', 'src/schema/index.ts', 'src/testing/index.ts', 'src/fake/index.ts', 'src/web/index.ts'],
dts: { generator: 'oxc' },
exports: {
devExports: true,