@volter/twin-github 0.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +88 -36
  2. package/client/github-mirror.css +277 -319
  3. package/client/github-mirror.d.ts +418 -0
  4. package/client/github-mirror.js +485 -0
  5. package/client/github-mirror.tsx +153 -357
  6. package/client/pulls-rest.ts +159 -0
  7. package/client/pulls-workspace.tsx +841 -0
  8. package/dist/client/github-mirror.bundle.js +239 -0
  9. package/dist/client/github-mirror.css +916 -0
  10. package/dist/client/github-mirror.d.ts +418 -0
  11. package/dist/client/github-mirror.js +485 -0
  12. package/dist/client/github-mirror.tsx +1315 -0
  13. package/dist/client/pulls-rest.d.ts +42 -0
  14. package/dist/client/pulls-rest.js +140 -0
  15. package/dist/client/pulls-rest.ts +159 -0
  16. package/dist/client/pulls-workspace.bundle.js +22 -0
  17. package/dist/client/pulls-workspace.d.ts +114 -0
  18. package/dist/client/pulls-workspace.js +418 -0
  19. package/dist/client/pulls-workspace.tsx +841 -0
  20. package/dist/src/cli.d.ts +2 -0
  21. package/dist/src/cli.js +40 -0
  22. package/dist/src/generated/graphql-sdl.gen.json +1 -0
  23. package/dist/src/generated/graphql.gen.json +1 -0
  24. package/dist/src/generated/surface.gen.json +1 -0
  25. package/dist/src/generated/ui.gen.json +1 -0
  26. package/dist/src/github-budget.d.ts +69 -0
  27. package/dist/src/github-budget.js +172 -0
  28. package/dist/src/github-capabilities.d.ts +5 -0
  29. package/dist/src/github-capabilities.js +4468 -0
  30. package/dist/src/github-conformance.d.ts +43 -0
  31. package/dist/src/github-conformance.js +76 -0
  32. package/dist/src/github-connector.d.ts +307 -0
  33. package/dist/src/github-connector.js +1398 -0
  34. package/dist/src/github-events.d.ts +41 -0
  35. package/dist/src/github-events.js +230 -0
  36. package/dist/src/github-git-http.d.ts +49 -0
  37. package/dist/src/github-git-http.js +185 -0
  38. package/dist/src/github-git-plane.d.ts +114 -0
  39. package/dist/src/github-git-plane.js +407 -0
  40. package/dist/src/github-mirror-state.d.ts +2 -0
  41. package/dist/src/github-mirror-state.js +335 -0
  42. package/dist/src/github-mirror-ui.d.ts +20 -0
  43. package/dist/src/github-mirror-ui.js +101 -0
  44. package/dist/src/github-server.d.ts +14 -0
  45. package/dist/src/github-server.js +240 -0
  46. package/dist/src/github-shared.d.ts +12 -0
  47. package/dist/src/github-shared.js +21 -0
  48. package/dist/src/github-twin.d.ts +1411 -0
  49. package/dist/src/github-twin.js +4084 -0
  50. package/dist/src/github-ui-conformance.d.ts +4 -0
  51. package/dist/src/github-ui-conformance.js +105 -0
  52. package/dist/src/github-ui-structure.d.ts +18 -0
  53. package/dist/src/github-ui-structure.js +251 -0
  54. package/dist/src/graphql-wire.d.ts +22 -0
  55. package/dist/src/graphql-wire.js +89 -0
  56. package/dist/src/index.d.ts +17 -0
  57. package/dist/src/index.js +101 -0
  58. package/dist/src/manifest.d.ts +8 -0
  59. package/dist/src/manifest.js +597 -0
  60. package/dist/src/npm-registry.d.ts +7 -0
  61. package/dist/src/npm-registry.js +47 -0
  62. package/dist/src/screens/app-installation.d.ts +3 -0
  63. package/dist/src/screens/app-installation.js +166 -0
  64. package/dist/src/screens/app-manifest.d.ts +3 -0
  65. package/dist/src/screens/app-manifest.js +81 -0
  66. package/dist/src/screens/oauth.d.ts +15 -0
  67. package/dist/src/screens/oauth.js +257 -0
  68. package/dist/src/screens/session.d.ts +8 -0
  69. package/dist/src/screens/session.js +159 -0
  70. package/dist/src/semantics/actions.d.ts +2 -0
  71. package/dist/src/semantics/actions.js +413 -0
  72. package/dist/src/semantics/activity.d.ts +4 -0
  73. package/dist/src/semantics/activity.js +161 -0
  74. package/dist/src/semantics/apps.d.ts +2 -0
  75. package/dist/src/semantics/apps.js +144 -0
  76. package/dist/src/semantics/branches.d.ts +2 -0
  77. package/dist/src/semantics/branches.js +136 -0
  78. package/dist/src/semantics/checks.d.ts +2 -0
  79. package/dist/src/semantics/checks.js +176 -0
  80. package/dist/src/semantics/code-scanning-upload.d.ts +2 -0
  81. package/dist/src/semantics/code-scanning-upload.js +97 -0
  82. package/dist/src/semantics/codespaces.d.ts +2 -0
  83. package/dist/src/semantics/codespaces.js +58 -0
  84. package/dist/src/semantics/commits.d.ts +2 -0
  85. package/dist/src/semantics/commits.js +109 -0
  86. package/dist/src/semantics/contents.d.ts +2 -0
  87. package/dist/src/semantics/contents.js +131 -0
  88. package/dist/src/semantics/deployments.d.ts +2 -0
  89. package/dist/src/semantics/deployments.js +127 -0
  90. package/dist/src/semantics/gists.d.ts +2 -0
  91. package/dist/src/semantics/gists.js +86 -0
  92. package/dist/src/semantics/git.d.ts +2 -0
  93. package/dist/src/semantics/git.js +235 -0
  94. package/dist/src/semantics/graphql.d.ts +7 -0
  95. package/dist/src/semantics/graphql.js +512 -0
  96. package/dist/src/semantics/index.d.ts +5 -0
  97. package/dist/src/semantics/index.js +62 -0
  98. package/dist/src/semantics/issues.d.ts +2 -0
  99. package/dist/src/semantics/issues.js +456 -0
  100. package/dist/src/semantics/keys.d.ts +2 -0
  101. package/dist/src/semantics/keys.js +66 -0
  102. package/dist/src/semantics/labels.d.ts +2 -0
  103. package/dist/src/semantics/labels.js +100 -0
  104. package/dist/src/semantics/meta.d.ts +12 -0
  105. package/dist/src/semantics/meta.js +144 -0
  106. package/dist/src/semantics/notifications.d.ts +4 -0
  107. package/dist/src/semantics/notifications.js +62 -0
  108. package/dist/src/semantics/orgs.d.ts +2 -0
  109. package/dist/src/semantics/orgs.js +420 -0
  110. package/dist/src/semantics/packages.d.ts +2 -0
  111. package/dist/src/semantics/packages.js +55 -0
  112. package/dist/src/semantics/pages.d.ts +2 -0
  113. package/dist/src/semantics/pages.js +176 -0
  114. package/dist/src/semantics/projects.d.ts +2 -0
  115. package/dist/src/semantics/projects.js +239 -0
  116. package/dist/src/semantics/pulls.d.ts +2 -0
  117. package/dist/src/semantics/pulls.js +462 -0
  118. package/dist/src/semantics/push-reactions.d.ts +101 -0
  119. package/dist/src/semantics/push-reactions.js +513 -0
  120. package/dist/src/semantics/releases.d.ts +19 -0
  121. package/dist/src/semantics/releases.js +231 -0
  122. package/dist/src/semantics/repo-invitations.d.ts +2 -0
  123. package/dist/src/semantics/repo-invitations.js +98 -0
  124. package/dist/src/semantics/repos.d.ts +17 -0
  125. package/dist/src/semantics/repos.js +390 -0
  126. package/dist/src/semantics/rulesets.d.ts +2 -0
  127. package/dist/src/semantics/rulesets.js +111 -0
  128. package/dist/src/semantics/search.d.ts +2 -0
  129. package/dist/src/semantics/search.js +84 -0
  130. package/dist/src/semantics/security.d.ts +2 -0
  131. package/dist/src/semantics/security.js +177 -0
  132. package/dist/src/semantics/shared.d.ts +61 -0
  133. package/dist/src/semantics/shared.js +151 -0
  134. package/dist/src/semantics/users.d.ts +2 -0
  135. package/dist/src/semantics/users.js +255 -0
  136. package/dist/src/semantics/webhooks.d.ts +2 -0
  137. package/dist/src/semantics/webhooks.js +107 -0
  138. package/dist/test-fixtures/github-a11y-reference.pr-list.SOURCE.md +56 -0
  139. package/dist/test-fixtures/github-a11y-reference.pr-list.json +1447 -0
  140. package/dist/test-fixtures/github-comment-schema.SOURCE.md +11 -0
  141. package/dist/test-fixtures/github-comment-schema.json +702 -0
  142. package/dist/test-fixtures/github-known-deviations.json +94 -0
  143. package/dist/test-fixtures/github-openapi-operations.SOURCE.md +76 -0
  144. package/dist/test-fixtures/github-openapi-operations.json +362 -0
  145. package/dist/test-fixtures/github-pull-schema.SOURCE.md +37 -0
  146. package/dist/test-fixtures/github-pull-schema.json +3601 -0
  147. package/dist/test-fixtures/github-review-schema.SOURCE.md +12 -0
  148. package/dist/test-fixtures/github-review-schema.json +236 -0
  149. package/package.json +20 -11
  150. package/src/cli.ts +7 -6
  151. package/src/generated/graphql-sdl.gen.json +1 -0
  152. package/src/generated/graphql.gen.json +1 -0
  153. package/src/generated/surface.gen.json +1 -0
  154. package/src/generated/ui.gen.json +1 -0
  155. package/src/github-a11y-snapshot.uitest.ts +5 -5
  156. package/src/github-budget.ts +4 -4
  157. package/src/github-capabilities.ts +1993 -556
  158. package/src/github-conformance.ts +12 -7
  159. package/src/github-connector.ts +108 -96
  160. package/src/github-events.ts +223 -95
  161. package/src/github-git-http.ts +47 -83
  162. package/src/github-git-plane.ts +247 -385
  163. package/src/github-journey.uitest.ts +28 -44
  164. package/src/github-mirror-state.ts +23 -61
  165. package/src/github-mirror-ui.ts +16 -10
  166. package/src/github-server.ts +158 -60
  167. package/src/github-shared.ts +1 -1
  168. package/src/github-twin.ts +1525 -4494
  169. package/src/github-ui-conformance.ts +7 -8
  170. package/src/github-ui-structure.ts +19 -18
  171. package/src/graphql-wire.ts +115 -0
  172. package/src/index.ts +47 -7
  173. package/src/manifest.ts +605 -0
  174. package/src/npm-registry.ts +43 -0
  175. package/src/screens/app-installation.tsx +217 -0
  176. package/src/screens/app-manifest.tsx +97 -0
  177. package/src/screens/oauth.tsx +258 -0
  178. package/src/screens/session.tsx +186 -0
  179. package/src/semantics/actions.ts +395 -0
  180. package/src/semantics/activity.ts +171 -0
  181. package/src/semantics/apps.ts +133 -0
  182. package/src/semantics/branches.ts +133 -0
  183. package/src/semantics/checks.ts +163 -0
  184. package/src/semantics/code-scanning-upload.ts +92 -0
  185. package/src/semantics/codespaces.ts +58 -0
  186. package/src/semantics/commits.ts +109 -0
  187. package/src/semantics/contents.ts +112 -0
  188. package/src/semantics/deployments.ts +116 -0
  189. package/src/semantics/gists.ts +85 -0
  190. package/src/semantics/git.ts +226 -0
  191. package/src/semantics/graphql.ts +505 -0
  192. package/src/semantics/index.ts +68 -0
  193. package/src/semantics/issues.ts +434 -0
  194. package/src/semantics/keys.ts +66 -0
  195. package/src/semantics/labels.ts +91 -0
  196. package/src/semantics/meta.ts +139 -0
  197. package/src/semantics/notifications.ts +58 -0
  198. package/src/semantics/orgs.ts +383 -0
  199. package/src/semantics/packages.ts +59 -0
  200. package/src/semantics/pages.ts +154 -0
  201. package/src/semantics/projects.ts +246 -0
  202. package/src/semantics/pulls.ts +421 -0
  203. package/src/semantics/push-reactions.ts +471 -0
  204. package/src/semantics/releases.ts +213 -0
  205. package/src/semantics/repo-invitations.ts +112 -0
  206. package/src/semantics/repos.ts +373 -0
  207. package/src/semantics/rulesets.ts +107 -0
  208. package/src/semantics/search.ts +83 -0
  209. package/src/semantics/security.ts +153 -0
  210. package/src/semantics/shared.ts +181 -0
  211. package/src/semantics/users.ts +252 -0
  212. package/src/semantics/webhooks.ts +101 -0
  213. package/test-fixtures/github-known-deviations.json +7 -8
  214. package/test-fixtures/github-openapi-operations.json +46 -227
  215. package/src/github-graphql.ts +0 -398
@@ -0,0 +1,605 @@
1
+ // GitHub's manifest: the vendor facts its published description does not carry, and the resources
2
+ // the derived core serves (docs/contributing/architecture.md, "Protocol 3"). The surfaces are
3
+ // generated (./generated/surface.gen.json for REST, ./generated/graphql.gen.json for GraphQL, from
4
+ // ../spec by scripts/derive-pack.ts).
5
+ import type { DerivedManifest } from '@volter/world-core';
6
+ import { callerLogin, GITHUB_WORLD_USER } from './github-twin.ts';
7
+
8
+ /** Path parameters whose values GitHub lets hold slashes, which its OpenAPI description does not say:
9
+ * a git ref (`heads/feature/x`, https://docs.github.com/rest/git/refs#get-a-reference), a file path
10
+ * (https://docs.github.com/rest/repos/contents#get-repository-content), a release tag, a branch name
11
+ * (https://docs.github.com/rest/branches/branches#get-a-branch), a label name, and a compare range
12
+ * (`base...head` where either is a branch). The surface's `spanning` list (github-server.ts). */
13
+ export const spanning = ['ref', 'path', 'tag', 'branch', 'name', 'basehead'];
14
+
15
+ /** A resource that lives in a repository: named by `{owner}/{repo}` and stored with that value in its
16
+ * `repository` field. Its `key`, where one is declared, is the subject id the twin mints for it from the
17
+ * path (locally created subjects; an observed one keeps the id it was observed under). The core serves
18
+ * none of these operations: the repository itself is stored under a sequence id (`repo:<n>`), so the
19
+ * core's parent check, which looks the parent up by subject id, can never find `{owner}/{repo}`; and
20
+ * every stored shape is the twin's (githubState folds and renames), not the answer. Handlers serve them. */
21
+ // a repository is stored as repo:<n> with its owner and name, and addressed by both
22
+ const inRepository = { params: ['owner', 'repo'], value: '{owner}/{repo}', field: 'repository', resource: 'full-repository', where: { owner: '{owner}', name: '{repo}' } };
23
+
24
+ export const manifest: DerivedManifest = {
25
+ vendor: 'github',
26
+ service: 'github',
27
+ // GitHub reads a body as JSON whatever its content type says (curl's default is a form type)
28
+ body: { json: 'always' },
29
+ ids: { template: '{n}' },
30
+ time: 'iso',
31
+ error: { message: '{message}', documentation_url: 'https://docs.github.com/rest', status: '{statusText}' },
32
+ readOnly: { status: 403, message: 'twin is read-only (mirror mode); start in simulator/fork to write' },
33
+ notFound: { status: 404, message: 'Not Found' },
34
+ list: {
35
+ style: 'envelope',
36
+ envelope: '{data}',
37
+ limit: { param: 'per_page', default: 30, max: 100 },
38
+ page: { param: 'page', link: true },
39
+ },
40
+ deleted: null,
41
+ // a token minted at the twin's user door names its user; the World's shared token names no one
42
+ identity: (authorization, root) => callerLogin((authorization ?? '').replace(/^(?:bearer|token)\s+/i, '').trim() || undefined, root) ?? GITHUB_WORLD_USER,
43
+ // Operations on a declared resource the core must not claim: GitHub's stored subjects are the twin's
44
+ // own shape (a sequence id such as `sshkey:3`, fields folded by githubState), not the vendor's, so
45
+ // the core reproduces none of them; each is a handler, or unserved (the vendor's Not Found).
46
+ unmodeled: [
47
+ 'actions/delete-environment-variable',
48
+ 'actions/delete-workflow-run',
49
+ 'actions/disable-workflow',
50
+ 'actions/enable-workflow',
51
+ 'actions/get-environment-variable',
52
+ 'actions/get-workflow-run-attempt',
53
+ 'actions/list-environment-variables',
54
+ 'actions/list-jobs-for-workflow-run-attempt',
55
+ 'actions/list-repo-organization-secrets',
56
+ 'actions/list-repo-organization-variables',
57
+ 'actions/list-workflow-run-artifacts',
58
+ 'activity/mark-thread-as-done',
59
+ 'agents/delete-org-secret',
60
+ 'agents/delete-org-variable',
61
+ 'agents/delete-repo-secret',
62
+ 'agents/delete-repo-variable',
63
+ 'agents/get-org-secret',
64
+ 'agents/get-org-variable',
65
+ 'agents/get-repo-secret',
66
+ 'agents/get-repo-variable',
67
+ 'agents/list-org-secrets',
68
+ 'agents/list-org-variables',
69
+ 'agents/list-repo-organization-secrets',
70
+ 'agents/list-repo-organization-variables',
71
+ 'agents/list-repo-secrets',
72
+ 'agents/list-repo-variables',
73
+ 'apps/delete-installation',
74
+ 'apps/get-org-installation',
75
+ 'apps/get-repo-installation',
76
+ 'apps/get-user-installation',
77
+ 'apps/list-installations-for-authenticated-user',
78
+ 'codespaces/create-with-repo-for-authenticated-user',
79
+ 'codespaces/get-codespaces-for-user-in-org',
80
+ 'codespaces/list-in-organization',
81
+ 'codespaces/list-in-repository-for-authenticated-user',
82
+ 'codespaces/update-for-authenticated-user',
83
+ 'gists/list-for-user',
84
+ 'gists/list-forks',
85
+ 'gists/list-public',
86
+ 'gists/list-starred',
87
+ 'git/list-matching-refs',
88
+ 'issues/add-blocked-by-dependency',
89
+ 'issues/delete-milestone',
90
+ 'issues/get-parent',
91
+ 'issues/list',
92
+ 'issues/list-comments-for-repo',
93
+ 'issues/list-dependencies-blocked-by',
94
+ 'issues/list-dependencies-blocking',
95
+ 'issues/list-for-authenticated-user',
96
+ 'issues/list-for-org',
97
+ 'issues/list-labels-for-milestone',
98
+ 'issues/remove-dependency-blocked-by',
99
+ 'orgs/delete',
100
+ 'orgs/get-membership-for-authenticated-user',
101
+ 'orgs/list-app-installations',
102
+ 'orgs/list-invitation-teams',
103
+ 'orgs/list-memberships-for-authenticated-user',
104
+ 'orgs/update-membership-for-authenticated-user',
105
+ 'packages/delete-package-for-org',
106
+ 'packages/delete-package-for-user',
107
+ 'packages/delete-package-version-for-org',
108
+ 'packages/delete-package-version-for-user',
109
+ 'packages/get-all-package-versions-for-package-owned-by-org',
110
+ 'packages/get-all-package-versions-for-package-owned-by-user',
111
+ 'packages/get-package-for-organization',
112
+ 'packages/get-package-for-user',
113
+ 'packages/get-package-version-for-organization',
114
+ 'packages/get-package-version-for-user',
115
+ 'packages/list-docker-migration-conflicting-packages-for-authenticated-user',
116
+ 'packages/list-docker-migration-conflicting-packages-for-organization',
117
+ 'packages/list-docker-migration-conflicting-packages-for-user',
118
+ 'packages/list-packages-for-organization',
119
+ 'packages/list-packages-for-user',
120
+ 'projects/add-field-for-org',
121
+ 'projects/add-field-for-user',
122
+ 'projects/delete-item-for-org',
123
+ 'projects/delete-item-for-user',
124
+ 'projects/get-field-for-org',
125
+ 'projects/get-field-for-user',
126
+ 'projects/get-for-user',
127
+ 'projects/get-org-item',
128
+ 'projects/get-user-item',
129
+ 'projects/list-fields-for-org',
130
+ 'projects/list-fields-for-user',
131
+ 'projects/list-for-user',
132
+ 'projects/list-items-for-org',
133
+ 'projects/list-items-for-user',
134
+ 'projects/list-view-items-for-org',
135
+ 'projects/list-view-items-for-user',
136
+ 'projects/update-item-for-org',
137
+ 'projects/update-item-for-user',
138
+ 'pulls/delete-pending-review',
139
+ 'pulls/list-review-comments-for-repo',
140
+ 'reactions/create-for-commit-comment',
141
+ 'reactions/create-for-release',
142
+ 'reactions/list-for-commit-comment',
143
+ 'reactions/list-for-release',
144
+ 'repos/add-team-access-restrictions',
145
+ 'repos/create-org-ruleset',
146
+ 'repos/delete-deployment',
147
+ 'repos/delete-org-ruleset',
148
+ 'repos/delete-release-asset',
149
+ 'repos/get-access-restrictions',
150
+ 'repos/get-org-rule-suite',
151
+ 'repos/get-org-ruleset',
152
+ 'repos/get-org-rulesets',
153
+ 'repos/get-teams-with-access-to-protected-branch',
154
+ 'repos/list-issue-types',
155
+ 'repos/list-teams',
156
+ 'repos/update-org-ruleset',
157
+ 'repos/update-release-asset',
158
+ 'teams/check-permissions-for-repo-legacy',
159
+ 'teams/delete-legacy',
160
+ 'teams/get-legacy',
161
+ 'teams/list-child-in-org',
162
+ 'teams/list-child-legacy',
163
+ 'teams/list-for-authenticated-user',
164
+ 'teams/remove-repo-legacy',
165
+ 'teams/update-legacy',
166
+ 'users/list-gpg-keys-for-user',
167
+ ],
168
+ // Every declared resource's served operations are handlers (semantics/); the declarations name the
169
+ // stored type and rule on the spec's state candidates.
170
+ // The screens applications and people reach (docs/contributing/architecture.md, "Screens"). Demand is the
171
+ // ladder's 85 applications: the code that sends a user to the page.
172
+ screens: [
173
+ {
174
+ id: 'sign-in', kind: 'flow', host: 'github.com', path: '/login', status: 'done',
175
+ demand: 'every github.com page a person reaches: each asks who is signed in', controls: ['Username or email address', 'Password', 'Sign in'],
176
+ source: 'https://docs.github.com/authentication/keeping-your-account-and-data-secure/about-authentication-to-github',
177
+ },
178
+ {
179
+ id: 'two-factor', kind: 'flow', host: 'github.com', path: '/sessions/two-factor/app', status: 'done',
180
+ demand: 'every person with two-factor authentication signing in, as GitHub requires of contributors', controls: ['Authentication code', 'Verify'],
181
+ source: 'https://docs.github.com/authentication/securing-your-account-with-two-factor-authentication-2fa/accessing-github-using-two-factor-authentication',
182
+ },
183
+ {
184
+ id: 'oauth-consent', kind: 'flow', host: 'github.com', path: '/login/oauth/authorize', status: 'done',
185
+ demand: '35 of 85 applications sign users in with GitHub', controls: ['Authorize {login}', 'Cancel'],
186
+ source: 'https://docs.github.com/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps',
187
+ },
188
+ {
189
+ id: 'app-installation', kind: 'flow', host: 'github.com', path: '/apps/{slug}/installations/new', status: 'done',
190
+ demand: '10 of 85 applications send an owner to install their GitHub App', controls: ['Install', 'Install & Authorize', 'Cancel', 'All repositories', 'Only select repositories', 'Switch account'],
191
+ source: 'https://docs.github.com/apps/using-github-apps/installing-a-github-app-from-a-third-party',
192
+ },
193
+ {
194
+ id: 'pull-requests', kind: 'workspace', host: 'github.com', path: '/{owner}/{repo}/pulls', status: 'done',
195
+ demand: 'testers and agents review pull requests', source: 'https://docs.github.com/pull-requests',
196
+ },
197
+ ],
198
+ resources: {
199
+ // users and keys (semantics/users.ts)
200
+ email: { storedAs: 'user_email', idPrefix: 'useremail:' },
201
+ key: { storedAs: 'user_ssh_key', idPrefix: 'sshkey:' },
202
+ 'gpg-key': { storedAs: 'user_gpg_key', idPrefix: 'gpgkey:' },
203
+ // gists (semantics/gists.ts): one stored gist answers both the full and the listed shape
204
+ 'gist-simple': { storedAs: 'gist', idPrefix: 'gist:' },
205
+ 'base-gist': { storedAs: 'gist', idPrefix: 'gist:' },
206
+ // notification threads (semantics/activity.ts)
207
+ thread: { storedAs: 'notification', idPrefix: 'notification:' },
208
+ // organizations and teams (semantics/orgs.ts)
209
+ // is_verified: GitHub verifies an organization's domain; no API moves it
210
+ 'organization-full': { storedAs: 'organization', idPrefix: 'org:', notState: ['is_verified'] },
211
+ 'org-membership': {
212
+ storedAs: 'org_membership',
213
+ idPrefix: 'orgmember:',
214
+ notState: ['role'],
215
+ state: {
216
+ // the twin confirms a membership as it sets it (no invitation to accept offline); GitHub starts
217
+ // one pending until the member accepts
218
+ state: { initial: 'active', vendorInitial: 'pending', transitions: [{ operation: 'orgs/update-membership-for-authenticated-user', from: ['pending'], to: 'active', source: 'https://docs.github.com/rest/orgs/members#update-an-organization-membership-for-the-authenticated-user' }] },
219
+ },
220
+ },
221
+ // active: a setting an edit names
222
+ 'org-hook': { storedAs: 'org_webhook', idPrefix: 'orghook:', notState: ['active'] },
223
+ 'issue-type': { storedAs: 'issue_type', idPrefix: 'issuetype:', notState: ['color', 'is_enabled'] },
224
+ 'team-full': { storedAs: 'team', idPrefix: 'team:', notState: ['notification_setting', 'privacy', 'type'] },
225
+ team: { storedAs: 'team', idPrefix: 'team:', notState: ['access_source', 'type'] },
226
+ // archived, disabled, is_template: the repository's own, ruled on full-repository
227
+ 'team-repository': { storedAs: 'team_repo', idPrefix: 'teamrepo:', notState: ['archived', 'disabled', 'is_template'], key: 'teamrepo:{org}#{team_slug}#{owner}/{repo}' },
228
+ // Projects v2 (semantics/projects.ts): GitHub moves a project's state by GraphQL's
229
+ // updateProjectV2 (closed), no REST operation: another wire's move (`external`); the twin stores
230
+ // it as `closed`. is_template: a setting (markProjectV2AsTemplate).
231
+ 'projects-v2': {
232
+ storedAs: 'project',
233
+ idPrefix: 'project:',
234
+ notState: ['is_template'],
235
+ state: {
236
+ state: {
237
+ initial: 'open',
238
+ transitions: [
239
+ { actor: 'external', from: ['open'], to: 'closed', source: 'https://docs.github.com/graphql/reference/mutations#updateprojectv2' },
240
+ { actor: 'external', from: ['closed'], to: 'open', source: 'https://docs.github.com/graphql/reference/mutations#updateprojectv2' },
241
+ ],
242
+ },
243
+ },
244
+ },
245
+ 'projects-v2-field': { storedAs: 'project_field', idPrefix: 'projectfield:', notState: ['data_type'] },
246
+ 'projects-v2-item-with-content': { storedAs: 'project_item', idPrefix: 'projectitem:', notState: ['content_type'] },
247
+ // security alerts (semantics/security.ts): the twin accepts any requested state on an update, as
248
+ // it always has; GitHub's documented moves are the machines
249
+ 'code-scanning-alert-items': {
250
+ storedAs: 'code_scanning_alert',
251
+ idPrefix: 'csalert:',
252
+ parent: inRepository,
253
+ key: 'csalert:{owner}/{repo}#{alert_number}',
254
+ notState: ['dismissed_reason'],
255
+ state: {
256
+ state: {
257
+ initial: 'open',
258
+ transitions: [
259
+ { operation: 'code-scanning/update-alert', from: '*', to: 'dismissed', source: 'https://docs.github.com/rest/code-scanning/code-scanning#update-a-code-scanning-alert' },
260
+ { operation: 'code-scanning/update-alert', from: '*', to: 'open', source: 'https://docs.github.com/rest/code-scanning/code-scanning#update-a-code-scanning-alert' },
261
+ // an analysis that no longer finds it fixes it (semantics/code-scanning-upload.ts)
262
+ { actor: 'vendor', from: ['open'], to: 'fixed', source: 'https://docs.github.com/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-details' },
263
+ ],
264
+ },
265
+ },
266
+ },
267
+ 'code-scanning-analysis': { storedAs: 'code_scanning_analysis', idPrefix: 'csanalysis:', parent: inRepository, key: 'csanalysis:{analysis_id}' },
268
+ 'dependabot-alert': {
269
+ storedAs: 'dependabot_alert',
270
+ idPrefix: 'dependabot:',
271
+ parent: inRepository,
272
+ key: 'dependabot:{owner}/{repo}#{alert_number}',
273
+ notState: ['dismissed_reason'],
274
+ state: {
275
+ state: {
276
+ initial: 'open',
277
+ transitions: [
278
+ { operation: 'dependabot/update-alert', from: '*', to: 'dismissed', source: 'https://docs.github.com/rest/dependabot/alerts#update-a-dependabot-alert' },
279
+ { operation: 'dependabot/update-alert', from: '*', to: 'open', source: 'https://docs.github.com/rest/dependabot/alerts#update-a-dependabot-alert' },
280
+ // a push whose manifest no longer asks for a vulnerable version fixes it (semantics/push-reactions.ts)
281
+ { actor: 'vendor', from: ['open'], to: 'fixed', source: 'https://docs.github.com/code-security/dependabot/dependabot-alerts/about-dependabot-alerts' },
282
+ ],
283
+ },
284
+ },
285
+ },
286
+ 'secret-scanning-alert': {
287
+ storedAs: 'secret_scanning_alert',
288
+ idPrefix: 'secretscan:',
289
+ parent: inRepository,
290
+ key: 'secretscan:{owner}/{repo}#{alert_number}',
291
+ notState: ['resolution', 'validity'],
292
+ state: {
293
+ state: {
294
+ initial: 'open',
295
+ transitions: [
296
+ { operation: 'secret-scanning/update-alert', from: '*', to: 'resolved', source: 'https://docs.github.com/rest/secret-scanning/secret-scanning#update-a-secret-scanning-alert' },
297
+ { operation: 'secret-scanning/update-alert', from: '*', to: 'open', source: 'https://docs.github.com/rest/secret-scanning/secret-scanning#update-a-secret-scanning-alert' },
298
+ ],
299
+ },
300
+ },
301
+ },
302
+ // Pages builds (semantics/pages.ts): GitHub queues a requested build and builds it (queued → building →
303
+ // built or errored, docs.github.com/rest/pages/pages#request-a-github-pages-build); the twin records it built
304
+ // as it is requested (nothing builds offline), so it makes none of those moves
305
+ 'page-build': {
306
+ storedAs: 'pages_build',
307
+ idPrefix: 'pagesbuild:',
308
+ parent: inRepository,
309
+ key: 'pagesbuild:{build_id}',
310
+ state: { status: { initial: 'built', vendorInitial: 'queued', transitions: [] } },
311
+ },
312
+ // packages (semantics/packages.ts)
313
+ package: { storedAs: 'package', idPrefix: 'package:', notState: ['package_type', 'visibility'] },
314
+ 'package-version': { storedAs: 'package_version', idPrefix: 'packageversion:' },
315
+ // app installations (semantics/apps.ts)
316
+ installation: { storedAs: 'app_installation', idPrefix: 'installation:', notState: ['repository_selection'] },
317
+ // Actions (semantics/actions.ts): no runner executes anything; runs move only by rerun, cancel
318
+ // and the twin's runner door, from any state, as the twin always has
319
+ workflow: {
320
+ storedAs: 'workflow',
321
+ idPrefix: 'workflow:',
322
+ parent: inRepository,
323
+ state: {
324
+ state: {
325
+ initial: 'active',
326
+ transitions: [
327
+ { operation: 'actions/disable-workflow', from: ['active'], to: 'disabled_manually', source: 'https://docs.github.com/rest/actions/workflows#disable-a-workflow' },
328
+ { operation: 'actions/enable-workflow', from: ['disabled_manually', 'disabled_inactivity', 'disabled_fork'], to: 'active', source: 'https://docs.github.com/rest/actions/workflows#enable-a-workflow' },
329
+ ],
330
+ },
331
+ },
332
+ },
333
+ 'workflow-run': {
334
+ storedAs: 'workflow_run',
335
+ idPrefix: 'run:',
336
+ parent: inRepository,
337
+ key: 'run:{run_id}',
338
+ // the result a completed run records, not a state of its own
339
+ notState: ['conclusion'],
340
+ state: {
341
+ // a runner finishing it is the vendor's move (the twin's runner door, /_twin/.../complete)
342
+ status: {
343
+ initial: 'queued',
344
+ transitions: [
345
+ { operation: 'actions/re-run-workflow', from: '*', to: 'queued', source: 'https://docs.github.com/rest/actions/workflow-runs#re-run-a-workflow' },
346
+ { operation: 'actions/cancel-workflow-run', from: '*', to: 'completed', source: 'https://docs.github.com/rest/actions/workflow-runs#cancel-a-workflow-run' },
347
+ // a first-time contributor's run from a fork waits, completed as action_required, until a maintainer approves it
348
+ { operation: 'actions/approve-workflow-run', from: ['completed'], to: 'queued', source: 'https://docs.github.com/rest/actions/workflow-runs#approve-a-workflow-run-for-a-fork-pull-request' },
349
+ // a runner picking it up (the twin's runner door, /_twin/.../start)
350
+ { actor: 'vendor', from: ['queued'], to: 'in_progress', refusal: { status: 409, message: 'The run has already started' }, source: 'https://docs.github.com/actions/about-github-actions/understanding-github-actions#runners' },
351
+ { actor: 'vendor', from: ['queued', 'in_progress'], to: 'completed', refusal: { status: 409, message: 'The run has already completed' }, source: 'https://docs.github.com/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/re-running-workflows-and-jobs' },
352
+ ],
353
+ },
354
+ },
355
+ },
356
+ job: {
357
+ storedAs: 'workflow_job',
358
+ idPrefix: 'job:',
359
+ parent: inRepository,
360
+ key: 'job:{job_id}',
361
+ // the result a completed job records, not a state of its own
362
+ notState: ['conclusion'],
363
+ state: {
364
+ // a job moves with its run: by the run's rerun and cancel, and when the runner finishes the run
365
+ status: {
366
+ initial: 'queued',
367
+ transitions: [
368
+ { operation: 'actions/re-run-workflow', from: '*', to: 'queued', source: 'https://docs.github.com/rest/actions/workflow-runs#re-run-a-workflow' },
369
+ { operation: 'actions/cancel-workflow-run', from: '*', to: 'completed', source: 'https://docs.github.com/rest/actions/workflow-runs#cancel-a-workflow-run' },
370
+ { actor: 'vendor', from: ['queued'], to: 'in_progress', source: 'https://docs.github.com/rest/actions/workflow-jobs#get-a-job-for-a-workflow-run' },
371
+ { actor: 'vendor', from: ['queued', 'in_progress'], to: 'completed', source: 'https://docs.github.com/rest/actions/workflow-jobs#get-a-job-for-a-workflow-run' },
372
+ ],
373
+ },
374
+ },
375
+ },
376
+ artifact: { storedAs: 'actions_artifact', idPrefix: 'artifact:', parent: inRepository, key: 'artifact:{artifact_id}' },
377
+ // a secret or variable is stored under its `container` (a repository, `owner/repo:env` for an
378
+ // environment's): one resource spans both, so no parent field names the repository alone
379
+ 'actions-secret': { storedAs: 'actions_secret', idPrefix: 'secret:repo:' },
380
+ 'actions-variable': { storedAs: 'actions_variable', idPrefix: 'variable:repo:' },
381
+ 'organization-actions-secret': { storedAs: 'actions_secret', idPrefix: 'secret:org:', notState: ['visibility'] },
382
+ 'organization-actions-variable': { storedAs: 'actions_variable', idPrefix: 'variable:org:', notState: ['visibility'] },
383
+ // deploy keys and autolinks (semantics/keys.ts)
384
+ 'deploy-key': { storedAs: 'deploy_key', idPrefix: 'deploykey:', parent: inRepository, key: 'deploykey:{key_id}' },
385
+ // is_alphanumeric: how the prefix matches, set at creation
386
+ autolink: { storedAs: 'autolink', idPrefix: 'autolink:', parent: inRepository, key: 'autolink:{autolink_id}', notState: ['is_alphanumeric'] },
387
+ // rulesets and rule suites (semantics/rulesets.ts): enforcement is a setting an update names, any
388
+ // value from any value; a suite's results are the outcome it recorded
389
+ 'repository-ruleset': { storedAs: 'ruleset', idPrefix: 'ruleset:', parent: inRepository, key: 'ruleset:{ruleset_id}', notState: ['current_user_can_bypass', 'enforcement', 'source_type', 'target'] },
390
+ 'rule-suite': { storedAs: 'rule_suite', idPrefix: 'rulesuite:', parent: inRepository, key: 'rulesuite:{rule_suite_id}', notState: ['evaluation_result', 'result'] },
391
+ // deployments and environments (semantics/deployments.ts): a status is created with its state and
392
+ // never moves (the deployment's state is its latest status)
393
+ deployment: { storedAs: 'deployment', idPrefix: 'deployment:', parent: inRepository, key: 'deployment:{deployment_id}' },
394
+ 'deployment-status': { storedAs: 'deployment_status', idPrefix: 'depstatus:', parent: inRepository, key: 'depstatus:{status_id}', notState: ['state'] },
395
+ environment: { storedAs: 'environment', idPrefix: 'env:', parent: inRepository, key: 'env:{owner}/{repo}#{environment_name}' },
396
+ // releases (semantics/releases.ts); tags are derived from published releases, never stored
397
+ // a release is created a draft or published; an edit publishes a draft (`draft: false`) or unpublishes
398
+ // it (`draft: true`), from either value as the twin always took it
399
+ release: {
400
+ storedAs: 'release',
401
+ idPrefix: 'release:',
402
+ parent: inRepository,
403
+ key: 'release:{release_id}',
404
+ state: {
405
+ draft: {
406
+ initial: false,
407
+ transitions: [
408
+ { operation: 'repos/update-release', from: '*', to: 'false', source: 'https://docs.github.com/rest/releases/releases#update-a-release' },
409
+ { operation: 'repos/update-release', from: '*', to: 'true', source: 'https://docs.github.com/rest/releases/releases#update-a-release' },
410
+ ],
411
+ },
412
+ },
413
+ },
414
+ 'release-asset': {
415
+ storedAs: 'release_asset',
416
+ idPrefix: 'asset:',
417
+ parent: inRepository,
418
+ key: 'asset:{asset_id}',
419
+ state: {
420
+ // the upload completing moves it open → uploaded on GitHub (docs.github.com/rest/releases/assets#upload-a-release-asset);
421
+ // the twin records an asset already uploaded, so it makes no move
422
+ state: { initial: 'uploaded', vendorInitial: 'open', transitions: [] },
423
+ },
424
+ },
425
+ // repository webhooks (semantics/webhooks.ts)
426
+ // active: a setting an edit names
427
+ hook: { storedAs: 'webhook', idPrefix: 'webhook:', parent: inRepository, key: 'webhook:{hook_id}', notState: ['active'] },
428
+ // labels and milestones (semantics/labels.ts): a milestone's state is set by an edit, any value
429
+ // from any value, as the twin always took it
430
+ label: { storedAs: 'label', idPrefix: 'label:', parent: inRepository },
431
+ milestone: {
432
+ storedAs: 'milestone',
433
+ idPrefix: 'milestone:',
434
+ parent: inRepository,
435
+ key: '{owner}/{repo}#milestone:{milestone_number}', number: { field: 'number' },
436
+ state: {
437
+ state: {
438
+ initial: 'open',
439
+ transitions: [
440
+ { operation: 'issues/update-milestone', from: '*', to: 'closed', source: 'https://docs.github.com/rest/issues/milestones#update-a-milestone' },
441
+ { operation: 'issues/update-milestone', from: '*', to: 'open', source: 'https://docs.github.com/rest/issues/milestones#update-a-milestone' },
442
+ ],
443
+ },
444
+ },
445
+ },
446
+ // commit comments (semantics/commits.ts); commits are the git plane's, never stored
447
+ 'commit-comment': { storedAs: 'commit_comment', idPrefix: 'commitcomment:', parent: inRepository, key: 'commitcomment:{comment_id}', notState: ['author_association'] },
448
+ // statuses and check runs (semantics/checks.ts): a caller moves a run to whatever status it posts,
449
+ // and a conclusion completes it; suites are derived from runs, never stored
450
+ // a status is created with its state and never moves (a commit's state is its latest per context)
451
+ status: { storedAs: 'commit_status', idPrefix: 'status:', parent: inRepository, notState: ['state'] },
452
+ 'check-run': {
453
+ storedAs: 'check_run',
454
+ idPrefix: 'check:',
455
+ parent: inRepository,
456
+ key: 'check:{check_run_id}',
457
+ notState: ['conclusion'],
458
+ state: {
459
+ status: {
460
+ initial: 'queued',
461
+ transitions: [
462
+ { operation: 'checks/update', from: '*', to: 'in_progress', source: 'https://docs.github.com/rest/checks/runs#update-a-check-run' },
463
+ { operation: 'checks/update', from: '*', to: 'completed', source: 'https://docs.github.com/rest/checks/runs#update-a-check-run' },
464
+ ],
465
+ },
466
+ },
467
+ },
468
+ // Git Data (semantics/git.ts): refs are rows; blobs, trees, commits and tags are objects in the git
469
+ // plane the surface names no resource for
470
+ 'git-ref': { storedAs: 'git_ref', idPrefix: 'gitref:', parent: inRepository, key: 'gitref:{owner}/{repo}#refs/{ref}' },
471
+ // repositories, branches and collaborators (semantics/repos.ts, semantics/branches.ts): a
472
+ // repository's merge and squash settings are settings an edit names, not states. An edit archives
473
+ // a repository and unarchives it; GitHub disables one on its own (no API moves `disabled`, and the
474
+ // twin never does); is_template is a setting.
475
+ 'full-repository': {
476
+ storedAs: 'repository',
477
+ idPrefix: 'repo:',
478
+ notState: ['disabled', 'is_template', 'merge_commit_message', 'merge_commit_title', 'pull_request_creation_policy', 'squash_merge_commit_message', 'squash_merge_commit_title'],
479
+ state: {
480
+ archived: {
481
+ initial: false,
482
+ transitions: [
483
+ { operation: 'repos/update', from: '*', to: 'true', source: 'https://docs.github.com/rest/repos/repos#update-a-repository' },
484
+ { operation: 'repos/update', from: '*', to: 'false', source: 'https://docs.github.com/rest/repos/repos#update-a-repository' },
485
+ ],
486
+ },
487
+ },
488
+ },
489
+ 'short-branch': { storedAs: 'branch', idPrefix: 'branch:', parent: inRepository, key: 'branch:{owner}/{repo}#{branch}' },
490
+ collaborator: { storedAs: 'collaborator', idPrefix: 'collab:', parent: inRepository, key: 'collab:{owner}/{repo}#{username}' },
491
+ // issues, their comments and reactions (semantics/issues.ts). An issue is keyed by its repository
492
+ // and number; its number is counted with the repository's pull requests (one sequence for both), so
493
+ // `number` cannot declare it. Its state moves by an edit from any value (closing defaults
494
+ // state_reason to completed, reopening to reopened).
495
+ issue: {
496
+ storedAs: 'issue',
497
+ idPrefix: 'issue:',
498
+ parent: inRepository,
499
+ key: '{owner}/{repo}#issue:{issue_number}',
500
+ // draft: a pull request's, which an issue answer carries for a pull request's number
501
+ notState: ['author_association', 'draft', 'state_reason'],
502
+ state: {
503
+ state: {
504
+ initial: 'open',
505
+ transitions: [
506
+ { operation: 'issues/update', from: '*', to: 'closed', source: 'https://docs.github.com/rest/issues/issues#update-an-issue' },
507
+ { operation: 'issues/update', from: '*', to: 'open', source: 'https://docs.github.com/rest/issues/issues#update-an-issue' },
508
+ // a pull request merged into the default branch closes the issues it links with a closing keyword, and so
509
+ // does a commit pushed there that names them (semantics/pulls.ts, semantics/push-reactions.ts)
510
+ { operation: 'pulls/merge', from: ['open'], to: 'closed', source: 'https://docs.github.com/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue' },
511
+ { actor: 'external', from: ['open'], to: 'closed', source: 'https://docs.github.com/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue' },
512
+ ],
513
+ },
514
+ // locking a locked conversation (or unlocking an unlocked one) is answered 204 as any other
515
+ locked: {
516
+ initial: false,
517
+ transitions: [
518
+ { operation: 'issues/lock', from: '*', to: 'true', source: 'https://docs.github.com/rest/issues/issues#lock-an-issue' },
519
+ { operation: 'issues/unlock', from: '*', to: 'false', source: 'https://docs.github.com/rest/issues/issues#unlock-an-issue' },
520
+ ],
521
+ },
522
+ },
523
+ },
524
+ 'issue-comment': { storedAs: 'issue_comment', idPrefix: 'comment:', parent: inRepository, key: 'comment:{comment_id}', notState: ['author_association'] },
525
+ reaction: { storedAs: 'reaction', idPrefix: 'reaction:', parent: inRepository, key: 'reaction:{reaction_id}', notState: ['content'] },
526
+ 'pull-request-review-comment': { storedAs: 'pull_request_review_comment', idPrefix: 'comment:', parent: inRepository, key: 'comment:{comment_id}', notState: ['author_association', 'side', 'start_side', 'subject_type'] },
527
+ // pull requests and reviews (semantics/pulls.ts): one machine per state field. A merge needs an open,
528
+ // unmerged, non-draft pull request (the handler asks ctx.legal of all three); an edit moves state
529
+ // from any value. Deleting the head branch closes it (git/delete-ref, on the pull request's machine).
530
+ // GitHub's auto-merge (GraphQL's enablePullRequestAutoMerge) lands one when its checks pass; the twin does
531
+ // not have it, so makes no such move. Draft moves by GraphQL alone
532
+ // (markPullRequestReadyForReview, convertPullRequestToDraft: another wire, `external`), which also
533
+ // needs it open (a merged one is closed). The conversation locks by the issue operations on its number.
534
+ 'pull-request': {
535
+ storedAs: 'pull_request',
536
+ idPrefix: 'pr:',
537
+ parent: inRepository,
538
+ key: '{owner}/{repo}#{pull_number}',
539
+ notState: ['author_association'],
540
+ state: {
541
+ state: {
542
+ initial: 'open',
543
+ transitions: [
544
+ { operation: 'pulls/update', from: '*', to: 'closed', source: 'https://docs.github.com/rest/pulls/pulls#update-a-pull-request' },
545
+ { operation: 'pulls/update', from: '*', to: 'open', source: 'https://docs.github.com/rest/pulls/pulls#update-a-pull-request' },
546
+ { operation: 'pulls/merge', from: ['open'], to: 'closed', refusal: { status: 405, message: 'Pull Request is not mergeable' }, source: 'https://docs.github.com/rest/pulls/pulls#merge-a-pull-request' },
547
+ { operation: 'git/delete-ref', from: ['open'], to: 'closed', source: 'https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository#deleting-a-branch-used-for-a-pull-request' },
548
+ { actor: 'external', from: ['open'], refusal: { status: 422, message: 'Pull request must be open' }, source: 'https://docs.github.com/graphql/reference/mutations#markpullrequestreadyforreview' },
549
+ // deleting the head branch by a git push closes it too, the git wire's (github-git-http.ts); after the
550
+ // guard, so an ask that names no state finds the guard
551
+ { actor: 'external', from: ['open'], to: 'closed', source: 'https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository#deleting-a-branch-used-for-a-pull-request' },
552
+ ],
553
+ },
554
+ merged: {
555
+ initial: false,
556
+ transitions: [
557
+ { operation: 'pulls/merge', from: ['false'], to: 'true', refusal: { status: 405, message: 'Pull Request is not mergeable' }, source: 'https://docs.github.com/rest/pulls/pulls#merge-a-pull-request' },
558
+ ],
559
+ },
560
+ // a guard: a draft is never merged
561
+ draft: {
562
+ initial: false,
563
+ transitions: [
564
+ { operation: 'pulls/merge', from: ['false'], refusal: { status: 405, message: 'Draft pull requests cannot be merged.' }, source: 'https://docs.github.com/rest/pulls/pulls#merge-a-pull-request' },
565
+ { actor: 'external', from: ['true'], to: 'false', refusal: { status: 422, message: 'Pull request is already ready for review' }, source: 'https://docs.github.com/graphql/reference/mutations#markpullrequestreadyforreview' },
566
+ { actor: 'external', from: ['false'], to: 'true', refusal: { status: 422, message: 'Pull request is already a draft' }, source: 'https://docs.github.com/graphql/reference/mutations#convertpullrequesttodraft' },
567
+ ],
568
+ },
569
+ locked: {
570
+ initial: false,
571
+ transitions: [
572
+ { operation: 'issues/lock', from: '*', to: 'true', source: 'https://docs.github.com/rest/issues/issues#lock-an-issue' },
573
+ { operation: 'issues/unlock', from: '*', to: 'false', source: 'https://docs.github.com/rest/issues/issues#unlock-an-issue' },
574
+ ],
575
+ },
576
+ },
577
+ },
578
+ 'pull-request-review': {
579
+ storedAs: 'pull_request_review',
580
+ idPrefix: 'review:',
581
+ parent: inRepository,
582
+ key: 'review:{review_id}',
583
+ notState: ['author_association'],
584
+ state: {
585
+ // set by the submitting event (APPROVE → APPROVED …); a dismissal overlays DISMISSED
586
+ state: { initial: 'COMMENTED', transitions: [{ operation: 'pulls/dismiss-review', from: '*', to: 'DISMISSED', source: 'https://docs.github.com/rest/pulls/reviews#dismiss-a-review-for-a-pull-request' }] },
587
+ },
588
+ },
589
+ // codespaces (semantics/codespaces.ts): start and stop move from any state, as the twin always has
590
+ codespace: {
591
+ storedAs: 'codespace',
592
+ idPrefix: 'codespace:',
593
+ notState: ['location'],
594
+ state: {
595
+ state: {
596
+ initial: 'Available',
597
+ transitions: [
598
+ { operation: 'codespaces/start-for-authenticated-user', from: '*', to: 'Available', source: 'https://docs.github.com/rest/codespaces/codespaces#start-a-codespace-for-the-authenticated-user' },
599
+ { operation: 'codespaces/stop-for-authenticated-user', from: '*', to: 'Shutdown', source: 'https://docs.github.com/rest/codespaces/codespaces#stop-a-codespace-for-the-authenticated-user' },
600
+ ],
601
+ },
602
+ },
603
+ },
604
+ },
605
+ };