@agilno-tech/rivet 0.1.0-alpha.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 (283) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +59 -0
  4. package/bin/cli.js +42 -0
  5. package/dist/agents/backend-django-agent/SKILL.md +35 -0
  6. package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
  7. package/dist/agents/boss-agent/SKILL.md +28 -0
  8. package/dist/agents/bounded-worker/SKILL.md +28 -0
  9. package/dist/agents/data-performance-agent/SKILL.md +32 -0
  10. package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
  11. package/dist/agents/devops-agent/SKILL.md +34 -0
  12. package/dist/agents/engineering-manager/SKILL.md +28 -0
  13. package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
  14. package/dist/agents/frontend-web-agent/SKILL.md +31 -0
  15. package/dist/agents/incident-response-agent/SKILL.md +42 -0
  16. package/dist/agents/jest-agent/SKILL.md +77 -0
  17. package/dist/agents/mobile-agent/SKILL.md +32 -0
  18. package/dist/agents/playwright-agent/SKILL.md +54 -0
  19. package/dist/agents/product-design-manager/SKILL.md +27 -0
  20. package/dist/agents/qa-agent/SKILL.md +29 -0
  21. package/dist/agents/quality-manager/SKILL.md +28 -0
  22. package/dist/agents/vitest-agent/SKILL.md +77 -0
  23. package/dist/governance/pre-push-rules.md +75 -0
  24. package/dist/governance/prompt-hygiene.md +22 -0
  25. package/dist/governance/review-checklist.md +24 -0
  26. package/dist/governance/safety-and-data.md +27 -0
  27. package/dist/governance/usage-rules.md +21 -0
  28. package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
  29. package/dist/mandatory/agentic-goal/SKILL.md +42 -0
  30. package/dist/mandatory/agentic-status/SKILL.md +74 -0
  31. package/dist/mandatory/apply-design/SKILL.md +228 -0
  32. package/dist/mandatory/check-ac/SKILL.md +124 -0
  33. package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
  34. package/dist/mandatory/design/SKILL.md +312 -0
  35. package/dist/mandatory/feature-workflow/SKILL.md +87 -0
  36. package/dist/mandatory/hotfix/SKILL.md +195 -0
  37. package/dist/mandatory/pre-push/SKILL.md +272 -0
  38. package/dist/mandatory/project-context/SKILL.md +180 -0
  39. package/dist/mandatory/release-docs/SKILL.md +191 -0
  40. package/dist/mandatory/review-pr/SKILL.md +323 -0
  41. package/dist/mandatory/security-review/SKILL.md +157 -0
  42. package/dist/skills/api-contract/SKILL.md +77 -0
  43. package/dist/skills/backend-django/SKILL.md +27 -0
  44. package/dist/skills/backend-nestjs/SKILL.md +65 -0
  45. package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
  46. package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
  47. package/dist/skills/database-migration/SKILL.md +97 -0
  48. package/dist/skills/debugging/SKILL.md +32 -0
  49. package/dist/skills/devops-infra/SKILL.md +26 -0
  50. package/dist/skills/documentation/SKILL.md +24 -0
  51. package/dist/skills/frontend-nextjs/SKILL.md +27 -0
  52. package/dist/skills/incident-postmortem/SKILL.md +38 -0
  53. package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
  54. package/dist/skills/mobile-react-native/SKILL.md +37 -0
  55. package/dist/skills/postgres-analytics/SKILL.md +29 -0
  56. package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
  57. package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
  58. package/dist/skills/refactoring/SKILL.md +32 -0
  59. package/dist/skills/sprint-planning/SKILL.md +32 -0
  60. package/dist/skills/testing-quality/SKILL.md +62 -0
  61. package/dist/v2/protocols/agent-orchestration.md +49 -0
  62. package/dist/v2/protocols/delivery.md +45 -0
  63. package/dist/v2/protocols/design-authority.md +29 -0
  64. package/dist/v2/protocols/goal-graph.md +44 -0
  65. package/dist/v2/protocols/pattern-first-development.md +29 -0
  66. package/dist/v2/protocols/qa-evidence.md +29 -0
  67. package/dist/v2/protocols/security.md +29 -0
  68. package/dist/v2/schemas/event.schema.json +86 -0
  69. package/dist/v2/schemas/evidence.schema.json +94 -0
  70. package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
  71. package/dist/v2/schemas/feature-plan.schema.json +95 -0
  72. package/dist/v2/schemas/goal-graph.schema.json +61 -0
  73. package/dist/v2/schemas/integration.schema.json +119 -0
  74. package/dist/v2/schemas/orchestration.schema.json +69 -0
  75. package/dist/v2/schemas/project.schema.json +209 -0
  76. package/dist/v2/schemas/providers.schema.json +142 -0
  77. package/dist/v2/schemas/quality.schema.json +50 -0
  78. package/dist/v2/schemas/work-action.schema.json +44 -0
  79. package/dist/v2/schemas/work-request.schema.json +253 -0
  80. package/dist/v2/templates/evidence/qa-bundle.json +194 -0
  81. package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
  82. package/dist/v2/templates/harness/SKILL.md +90 -0
  83. package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
  84. package/dist/v2/templates/project/.rivet/project.yaml +14 -0
  85. package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
  86. package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
  87. package/package.json +58 -0
  88. package/protocols/agent-orchestration.md +49 -0
  89. package/protocols/delivery.md +45 -0
  90. package/protocols/design-authority.md +29 -0
  91. package/protocols/goal-graph.md +44 -0
  92. package/protocols/pattern-first-development.md +29 -0
  93. package/protocols/qa-evidence.md +29 -0
  94. package/protocols/security.md +29 -0
  95. package/schemas/event.schema.json +86 -0
  96. package/schemas/evidence.schema.json +94 -0
  97. package/schemas/feature-decomposition.schema.json +48 -0
  98. package/schemas/feature-plan.schema.json +95 -0
  99. package/schemas/goal-graph.schema.json +61 -0
  100. package/schemas/integration.schema.json +119 -0
  101. package/schemas/orchestration.schema.json +69 -0
  102. package/schemas/project.schema.json +209 -0
  103. package/schemas/providers.schema.json +142 -0
  104. package/schemas/quality.schema.json +50 -0
  105. package/schemas/work-action.schema.json +44 -0
  106. package/schemas/work-request.schema.json +253 -0
  107. package/src/adapters/confluence.js +138 -0
  108. package/src/adapters/contract.js +657 -0
  109. package/src/adapters/factory.js +122 -0
  110. package/src/adapters/figma.js +178 -0
  111. package/src/adapters/fixtures.js +183 -0
  112. package/src/adapters/github.js +314 -0
  113. package/src/adapters/http.js +704 -0
  114. package/src/adapters/jira.js +195 -0
  115. package/src/adapters/linear.js +171 -0
  116. package/src/adapters/node-transport.js +59 -0
  117. package/src/cli/integration-setup-prompt.js +32 -0
  118. package/src/cli/interrupt.js +25 -0
  119. package/src/cli/main.js +596 -0
  120. package/src/cli/output.js +174 -0
  121. package/src/cli/parse-args.js +246 -0
  122. package/src/cli/project-discovery.js +70 -0
  123. package/src/cli/task-confirmation.js +38 -0
  124. package/src/cli/task-presentation.js +22 -0
  125. package/src/clients/claude.js +214 -0
  126. package/src/clients/codex.js +180 -0
  127. package/src/clients/compatibility.js +23 -0
  128. package/src/clients/contract.js +224 -0
  129. package/src/clients/fake.js +170 -0
  130. package/src/clients/process-runner.js +657 -0
  131. package/src/clients/result-contract.js +78 -0
  132. package/src/commands/delivery-publish.js +80 -0
  133. package/src/commands/delivery-remote.js +230 -0
  134. package/src/commands/delivery-review-update.js +39 -0
  135. package/src/commands/delivery-tracker.js +94 -0
  136. package/src/commands/delivery-transition.js +79 -0
  137. package/src/commands/delivery.js +223 -0
  138. package/src/commands/dependency-approval.js +21 -0
  139. package/src/commands/doctor.js +191 -0
  140. package/src/commands/evidence.js +44 -0
  141. package/src/commands/feature.js +212 -0
  142. package/src/commands/goals.js +231 -0
  143. package/src/commands/human-run.js +208 -0
  144. package/src/commands/human-task.js +278 -0
  145. package/src/commands/init.js +848 -0
  146. package/src/commands/install.js +814 -0
  147. package/src/commands/integration-setup.js +65 -0
  148. package/src/commands/integrations.js +54 -0
  149. package/src/commands/models.js +158 -0
  150. package/src/commands/orchestrate.js +61 -0
  151. package/src/commands/preflight.js +125 -0
  152. package/src/commands/protocols.js +228 -0
  153. package/src/commands/repositories.js +77 -0
  154. package/src/commands/setup-remote.js +41 -0
  155. package/src/commands/setup.js +214 -0
  156. package/src/commands/status.js +224 -0
  157. package/src/commands/support.js +44 -0
  158. package/src/commands/task-completion.js +104 -0
  159. package/src/commands/uninstall.js +234 -0
  160. package/src/commands/verify.js +171 -0
  161. package/src/commands/work.js +220 -0
  162. package/src/commands/worktrees.js +52 -0
  163. package/src/config/command-readiness.js +218 -0
  164. package/src/config/commands.js +206 -0
  165. package/src/config/defaults.js +55 -0
  166. package/src/config/load.js +230 -0
  167. package/src/config/validate.js +560 -0
  168. package/src/delivery/bitbucket-review.js +123 -0
  169. package/src/delivery/branch-publication.js +23 -0
  170. package/src/delivery/contract.js +379 -0
  171. package/src/delivery/github-deployment.js +132 -0
  172. package/src/delivery/github-review.js +5 -0
  173. package/src/delivery/github.js +382 -0
  174. package/src/delivery/gitlab-review.js +5 -0
  175. package/src/delivery/gitlab.js +395 -0
  176. package/src/delivery/prepare.js +90 -0
  177. package/src/delivery/publication-process.js +33 -0
  178. package/src/delivery/publication-transport.js +112 -0
  179. package/src/delivery/review-request.js +184 -0
  180. package/src/delivery/review-update.js +74 -0
  181. package/src/delivery/service.js +400 -0
  182. package/src/delivery/store.js +76 -0
  183. package/src/delivery/tracker-target.js +51 -0
  184. package/src/delivery/tracker-transition.js +158 -0
  185. package/src/delivery/tracker.js +164 -0
  186. package/src/discovery/git.js +85 -0
  187. package/src/discovery/portable.js +108 -0
  188. package/src/discovery/project.js +345 -0
  189. package/src/discovery/tools.js +145 -0
  190. package/src/evaluations/approval.js +20 -0
  191. package/src/evaluations/cost-policy.js +20 -0
  192. package/src/evaluations/harness-attempt.js +113 -0
  193. package/src/evaluations/live-runner.js +55 -0
  194. package/src/evaluations/profile.js +28 -0
  195. package/src/evaluations/report.js +11 -0
  196. package/src/evaluations/text-attempt.js +18 -0
  197. package/src/evidence/checksum.js +87 -0
  198. package/src/evidence/collect.js +727 -0
  199. package/src/evidence/validate.js +354 -0
  200. package/src/feature/accepted-integration.js +46 -0
  201. package/src/feature/actions.js +37 -0
  202. package/src/feature/client-profile.js +56 -0
  203. package/src/feature/decomposition-contract.js +58 -0
  204. package/src/feature/host-execution.js +633 -0
  205. package/src/feature/host-lock-recovery.js +96 -0
  206. package/src/feature/host-run-lock.js +32 -0
  207. package/src/feature/local-approval.js +183 -0
  208. package/src/feature/plan-contract.js +215 -0
  209. package/src/feature/planner.js +253 -0
  210. package/src/feature/run-store.js +241 -0
  211. package/src/feature/runtime-bridge.js +840 -0
  212. package/src/feature/verification-report.js +168 -0
  213. package/src/feature/workflow.js +375 -0
  214. package/src/git/client.js +509 -0
  215. package/src/git/integration-worktree.js +153 -0
  216. package/src/git/reconcile.js +222 -0
  217. package/src/git/reservations.js +450 -0
  218. package/src/git/worktrees.js +478 -0
  219. package/src/graph/completion.js +189 -0
  220. package/src/graph/fixtures.js +116 -0
  221. package/src/graph/reducer.js +124 -0
  222. package/src/graph/scheduler.js +252 -0
  223. package/src/graph/validate.js +106 -0
  224. package/src/install/managed.js +579 -0
  225. package/src/install/project-reference.js +33 -0
  226. package/src/install/project-runtime.js +142 -0
  227. package/src/install/runtime-integrity.cjs +141 -0
  228. package/src/integrations/capabilities.js +69 -0
  229. package/src/integrations/host-observation.js +26 -0
  230. package/src/integrations/registry.js +56 -0
  231. package/src/models/delegate.js +102 -0
  232. package/src/models/profiles.js +53 -0
  233. package/src/models/protocols.js +208 -0
  234. package/src/models/registry.js +83 -0
  235. package/src/models/transport.js +59 -0
  236. package/src/policy/approvals.js +251 -0
  237. package/src/policy/authority.js +282 -0
  238. package/src/policy/budget.js +149 -0
  239. package/src/policy/command-bootstrap.js +219 -0
  240. package/src/policy/commands.js +803 -0
  241. package/src/prompts/launch-contract.js +43 -0
  242. package/src/prompts/planning-contract.js +92 -0
  243. package/src/protocols/presentation.js +53 -0
  244. package/src/protocols/project.js +289 -0
  245. package/src/quality/runner.js +497 -0
  246. package/src/quality/traceability.js +215 -0
  247. package/src/repositories/identity.js +59 -0
  248. package/src/repositories/index.js +2 -0
  249. package/src/repositories/provider.js +210 -0
  250. package/src/runtime/application.js +353 -0
  251. package/src/runtime/dependency-directory.js +33 -0
  252. package/src/runtime/harness-discovery.js +121 -0
  253. package/src/runtime/heartbeat.js +47 -0
  254. package/src/runtime/instance-store.js +112 -0
  255. package/src/runtime/orchestrator.js +1133 -0
  256. package/src/runtime/portable-dependencies.js +94 -0
  257. package/src/runtime/recovery.js +190 -0
  258. package/src/runtime/retry.js +54 -0
  259. package/src/runtime/supervisor.js +166 -0
  260. package/src/runtime/worktree-bootstrap.js +169 -0
  261. package/src/state/event-store.js +333 -0
  262. package/src/state/lock.js +266 -0
  263. package/src/state/paths.js +274 -0
  264. package/src/state/redact.js +106 -0
  265. package/src/state/snapshot-store.js +310 -0
  266. package/src/status/public/app.js +96 -0
  267. package/src/status/public/index.html +37 -0
  268. package/src/status/public/styles.css +50 -0
  269. package/src/status/server.js +323 -0
  270. package/src/status/view-model.js +356 -0
  271. package/src/support/bundle.js +195 -0
  272. package/src/support/failure-report.js +96 -0
  273. package/src/work-request/contract.js +250 -0
  274. package/src/work-request/host.js +78 -0
  275. package/src/work-request/local.js +163 -0
  276. package/src/work-request/tracker.js +105 -0
  277. package/templates/evidence/qa-bundle.json +194 -0
  278. package/templates/github-actions/rivet-deploy.yml +98 -0
  279. package/templates/harness/SKILL.md +90 -0
  280. package/templates/project/.rivet/orchestration.yaml +42 -0
  281. package/templates/project/.rivet/project.yaml +14 -0
  282. package/templates/project/.rivet/providers.yaml +8 -0
  283. package/templates/project/.rivet/quality.yaml +16 -0
@@ -0,0 +1,5 @@
1
+ import { createReviewRequestExecutor } from './review-request.js';
2
+
3
+ export function createGithubReviewExecutor(input) {
4
+ return createReviewRequestExecutor('github', input);
5
+ }
@@ -0,0 +1,382 @@
1
+ import { createProviderHttpClient } from '../adapters/http.js';
2
+ import { captureRecord, createProviderWireBody } from '../adapters/contract.js';
3
+ import { parseRepositoryRemote } from '../repositories/identity.js';
4
+ import { createTrustedDeliveryExecutor } from './service.js';
5
+ import {
6
+ ensure,
7
+ exact,
8
+ factsDigest,
9
+ hash,
10
+ plain,
11
+ ready,
12
+ sha,
13
+ timestamp,
14
+ validateCandidate,
15
+ } from './contract.js';
16
+
17
+ // GitHub atomically conditions merge on the head SHA only. Target ref and policy
18
+ // are rechecked, but concurrent administrative changes are outside this guarantee.
19
+ export function createGithubDeliveryExecutor(inputConfig) {
20
+ const input = captureRecord(
21
+ inputConfig,
22
+ new Set(['repository', 'transport', 'headers', 'clock', 'timeoutMs']),
23
+ ['repository', 'transport'],
24
+ 'invalid-config'
25
+ );
26
+ const supplied = plain(input.repository);
27
+ const repository = parseRepositoryRemote(supplied.url);
28
+ ensure(repository.provider === 'github' && hash(repository) === hash(supplied));
29
+ const clock = input.clock ?? (() => new Date().toISOString());
30
+ ensure(typeof clock === 'function');
31
+ const http = createProviderHttpClient({
32
+ provider: 'github',
33
+ baseUrl: 'https://api.github.com',
34
+ transport: input.transport,
35
+ ...(input.headers === undefined ? {} : { headers: input.headers }),
36
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
37
+ allowEncodedSlash: true,
38
+ });
39
+ const root = `/repos/${repository.fullName}`;
40
+ const get = async (path) => (await http.request({ method: 'GET', path: root + path })).data;
41
+ const pages = (path, options = {}) => http.paginate({ path: root + path, ...options });
42
+ function target(value) {
43
+ validateCandidate(value);
44
+ ensure(hash(value.repository) === hash(repository), 'provider-mismatch');
45
+ }
46
+ function number(value) {
47
+ ensure(Number.isSafeInteger(value) && value > 0 && value <= 1_000_000_000);
48
+ return value;
49
+ }
50
+ function pull(value, candidate, expected) {
51
+ ensure(
52
+ value?.number === expected &&
53
+ ['open', 'closed'].includes(value.state) &&
54
+ typeof value.merged === 'boolean' &&
55
+ typeof value.draft === 'boolean',
56
+ 'changed-facts'
57
+ );
58
+ ensure(
59
+ value.head?.sha === candidate.headSha &&
60
+ value.head?.ref === candidate.sourceBranch &&
61
+ value.head?.repo?.full_name === repository.fullName &&
62
+ value.base?.ref === candidate.targetBranch &&
63
+ value.base?.repo?.full_name === repository.fullName &&
64
+ value.html_url === `${repository.url}/pull/${expected}`,
65
+ 'changed-facts'
66
+ );
67
+ sha(value.base.sha);
68
+ return {
69
+ number: expected,
70
+ state: value.merged ? 'merged' : value.state,
71
+ url: value.html_url,
72
+ headSha: value.head.sha,
73
+ baseSha: value.base.sha,
74
+ draft: value.draft,
75
+ commitSha: value.merged ? sha(value.merge_commit_sha) : null,
76
+ };
77
+ }
78
+ async function locate(candidate, selected) {
79
+ let n = selected ?? candidate.reviewNumber;
80
+ if (n === null || n === undefined) {
81
+ const matches = await pages(
82
+ `/pulls?state=open&head=${encodeURIComponent(repository.namespace + ':' + candidate.sourceBranch)}&base=${encodeURIComponent(candidate.targetBranch)}&per_page=100`,
83
+ { identityKey: 'number' }
84
+ );
85
+ ensure(matches.length === 1, 'ambiguous-review');
86
+ n = number(matches[0].number);
87
+ }
88
+ number(n);
89
+ if (candidate.reviewNumber !== null) ensure(candidate.reviewNumber === n, 'changed-facts');
90
+ return pull(await get(`/pulls/${n}`), candidate, n);
91
+ }
92
+ async function policy(candidate) {
93
+ const branch = encodeURIComponent(candidate.targetBranch);
94
+ const protection = await get(`/branches/${branch}/protection`);
95
+ const rules = await pages(`/rules/branches/${branch}?per_page=100`);
96
+ return { protection, rules };
97
+ }
98
+ function supported({ protection: p, rules }) {
99
+ const known = new Set([
100
+ 'url',
101
+ 'required_status_checks',
102
+ 'enforce_admins',
103
+ 'required_pull_request_reviews',
104
+ 'restrictions',
105
+ 'required_linear_history',
106
+ 'allow_force_pushes',
107
+ 'allow_deletions',
108
+ 'block_creations',
109
+ 'required_conversation_resolution',
110
+ 'lock_branch',
111
+ 'allow_fork_syncing',
112
+ 'required_signatures',
113
+ ]);
114
+ if (p && Object.keys(p).some((key) => !known.has(key))) return false;
115
+ if (
116
+ !p ||
117
+ !Array.isArray(rules) ||
118
+ rules.length !== 0 ||
119
+ p.enforce_admins?.enabled !== true ||
120
+ p.required_conversation_resolution?.enabled === true ||
121
+ p.lock_branch?.enabled === true
122
+ )
123
+ return false;
124
+ const c = p.required_status_checks,
125
+ r = p.required_pull_request_reviews;
126
+ if (
127
+ !c ||
128
+ c.strict !== true ||
129
+ !Array.isArray(c.checks) ||
130
+ !Array.isArray(c.contexts) ||
131
+ c.checks.length === 0 ||
132
+ !r ||
133
+ r.require_code_owner_reviews !== false ||
134
+ r.require_last_push_approval !== false ||
135
+ r.dismiss_stale_reviews !== true ||
136
+ !Number.isSafeInteger(r.required_approving_review_count) ||
137
+ r.required_approving_review_count < 0
138
+ )
139
+ return false;
140
+ const bypass = r.bypass_pull_request_allowances;
141
+ if (
142
+ bypass &&
143
+ !['users', 'teams', 'apps'].every((key) => Array.isArray(bypass[key]) && bypass[key].length === 0)
144
+ )
145
+ return false;
146
+ if (
147
+ !c.checks.every(
148
+ (item) =>
149
+ typeof item.context === 'string' &&
150
+ item.context.length > 0 &&
151
+ (item.app_id === null || (Number.isSafeInteger(item.app_id) && item.app_id >= -1))
152
+ )
153
+ )
154
+ return false;
155
+ return c.contexts.every((context) => c.checks.some((item) => item.context === context));
156
+ }
157
+ async function observe(candidate) {
158
+ target(candidate);
159
+ const first = await locate(candidate);
160
+ const source = await get(`/branches/${encodeURIComponent(candidate.sourceBranch)}`);
161
+ const base = await get(`/branches/${encodeURIComponent(candidate.targetBranch)}`);
162
+ ensure(
163
+ source.name === candidate.sourceBranch &&
164
+ source.commit?.sha === candidate.headSha &&
165
+ base.name === candidate.targetBranch,
166
+ 'changed-facts'
167
+ );
168
+ const baseSha = sha(base.commit?.sha);
169
+ ensure(first.baseSha === baseSha, 'changed-facts');
170
+ let policies,
171
+ checks = { policy: 'unknown', satisfied: null, evidenceDigest: hash({ unavailable: true }) },
172
+ reviews = { ...checks };
173
+ try {
174
+ policies = await policy(candidate);
175
+ } catch {
176
+ policies = null;
177
+ }
178
+ if (policies && supported(policies)) {
179
+ const comparison = await get(`/compare/${baseSha}...${candidate.headSha}`);
180
+ const runs = await pages(`/commits/${candidate.headSha}/check-runs?per_page=100`, {
181
+ itemsKey: 'check_runs',
182
+ identityKey: 'id',
183
+ totalKey: 'total_count',
184
+ });
185
+ const statuses = await pages(`/commits/${candidate.headSha}/statuses?per_page=100`, {
186
+ identityKey: 'id',
187
+ });
188
+ for (const run of runs)
189
+ ensure(
190
+ Number.isSafeInteger(run.id) &&
191
+ run.id > 0 &&
192
+ run.head_sha === candidate.headSha &&
193
+ typeof run.name === 'string' &&
194
+ typeof run.status === 'string',
195
+ 'invalid-provider-evidence'
196
+ );
197
+ for (const status of statuses)
198
+ ensure(
199
+ Number.isSafeInteger(status.id) &&
200
+ status.id > 0 &&
201
+ typeof status.context === 'string' &&
202
+ ['success', 'failure', 'error', 'pending'].includes(status.state),
203
+ 'invalid-provider-evidence'
204
+ );
205
+ const latest = (items) => items.reduce((a, b) => (!a || b.id > a.id ? b : a), null);
206
+ const checkPass = policies.protection.required_status_checks.checks.every((required) => {
207
+ const appBound = required.app_id !== null && required.app_id !== -1;
208
+ const matchingRuns = runs.filter(
209
+ (item) => item.name === required.context && (!appBound || item.app?.id === required.app_id)
210
+ );
211
+ const status = latest(statuses.filter((item) => item.context === required.context));
212
+ const runPass =
213
+ matchingRuns.length > 0 &&
214
+ matchingRuns.every(
215
+ (run) => run.status === 'completed' && ['success', 'neutral', 'skipped'].includes(run.conclusion)
216
+ );
217
+ // A context reported by both APIs must pass both. Legacy statuses cannot
218
+ // establish an app-bound requirement without the matching check run.
219
+ return appBound
220
+ ? Boolean(runPass && (!status || status.state === 'success'))
221
+ : Boolean(
222
+ (matchingRuns.length > 0 || status) &&
223
+ (matchingRuns.length === 0 || runPass) &&
224
+ (!status || status.state === 'success')
225
+ );
226
+ });
227
+ const reviewItems = await pages(`/pulls/${first.number}/reviews?per_page=100`, { identityKey: 'id' });
228
+ const effective = new Map();
229
+ for (const item of reviewItems) {
230
+ ensure(
231
+ Number.isSafeInteger(item.id) &&
232
+ item.id > 0 &&
233
+ typeof item.user?.login === 'string' &&
234
+ /^[A-Za-z0-9-]{1,100}$/.test(item.user.login) &&
235
+ ['PENDING', 'COMMENTED', 'APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(item.state),
236
+ 'invalid-provider-evidence'
237
+ );
238
+ if (['PENDING', 'COMMENTED'].includes(item.state)) continue;
239
+ const prior = effective.get(item.user.login);
240
+ if (!prior || item.id > prior.id) effective.set(item.user.login, item);
241
+ }
242
+ let approvals = 0,
243
+ blocked = false;
244
+ const eligibility = [];
245
+ for (const [login, item] of [...effective].sort(([a], [b]) => a.localeCompare(b))) {
246
+ if (item.state === 'CHANGES_REQUESTED') blocked = true;
247
+ if (item.state !== 'APPROVED' || item.commit_id !== candidate.headSha) continue;
248
+ const permission = await get(`/collaborators/${encodeURIComponent(login)}/permission`);
249
+ ensure(
250
+ permission.user?.login === login && typeof permission.permission === 'string',
251
+ 'invalid-provider-evidence'
252
+ );
253
+ eligibility.push({ login, permission: permission.permission });
254
+ if (['admin', 'write', 'maintain'].includes(permission.permission)) approvals++;
255
+ }
256
+ const consistent =
257
+ comparison.merge_base_commit?.sha === baseSha && ['ahead', 'identical'].includes(comparison.status);
258
+ checks = {
259
+ policy: 'known',
260
+ satisfied: !first.draft && first.state === 'open' && consistent && checkPass,
261
+ evidenceDigest: hash({
262
+ policies,
263
+ runs,
264
+ statuses,
265
+ comparison: { status: comparison.status, base: comparison.merge_base_commit?.sha },
266
+ }),
267
+ };
268
+ reviews = {
269
+ policy: 'known',
270
+ satisfied:
271
+ !blocked &&
272
+ approvals >= policies.protection.required_pull_request_reviews.required_approving_review_count,
273
+ evidenceDigest: hash({ policies, reviewItems, eligibility }),
274
+ };
275
+ }
276
+ const final = await locate(candidate, first.number);
277
+ const finalSource = await get(`/branches/${encodeURIComponent(candidate.sourceBranch)}`),
278
+ finalBase = await get(`/branches/${encodeURIComponent(candidate.targetBranch)}`);
279
+ ensure(
280
+ hash(first) === hash(final) &&
281
+ finalSource.name === candidate.sourceBranch &&
282
+ finalBase.name === candidate.targetBranch &&
283
+ finalSource.commit?.sha === candidate.headSha &&
284
+ finalBase.commit?.sha === baseSha,
285
+ 'changed-facts'
286
+ );
287
+ if (policies) ensure(hash(await policy(candidate)) === hash(policies), 'changed-facts');
288
+ return plain({
289
+ repositoryUrl: repository.url,
290
+ sourceBranch: candidate.sourceBranch,
291
+ targetBranch: candidate.targetBranch,
292
+ headSha: candidate.headSha,
293
+ baseSha,
294
+ review: { number: first.number, state: first.state, url: first.url, headSha: candidate.headSha },
295
+ checks: { headSha: candidate.headSha, ...checks },
296
+ reviews: { headSha: candidate.headSha, ...reviews },
297
+ observedAt: timestamp(clock()),
298
+ });
299
+ }
300
+ function operation(input) {
301
+ const op = plain(input);
302
+ target(op.candidate);
303
+ ensure(op.action === 'merge');
304
+ exact(op.payload, ['reviewNumber', 'mergeMethod']);
305
+ number(op.payload.reviewNumber);
306
+ ensure(['merge', 'squash', 'rebase'].includes(op.payload.mergeMethod));
307
+ return op;
308
+ }
309
+ function receipt(op, result) {
310
+ return plain({
311
+ status: 'succeeded',
312
+ operationDigest: op.digest,
313
+ headSha: op.candidate.headSha,
314
+ evidenceDigest: hash(result),
315
+ resourceUrl: result.url,
316
+ commitSha: result.commitSha,
317
+ });
318
+ }
319
+ async function dispatch(input, context) {
320
+ const op = operation(input);
321
+ const deadlineContext = plain(context);
322
+ exact(deadlineContext, ['deadline']);
323
+ const deadline = Date.parse(timestamp(deadlineContext.deadline));
324
+ const timely = () => ensure(Date.parse(timestamp(clock())) < deadline, 'dispatch-expired');
325
+ timely();
326
+ const facts = await observe(op.candidate);
327
+ ensure(
328
+ ready(facts) &&
329
+ facts.review.number === op.payload.reviewNumber &&
330
+ factsDigest(facts) === op.factsDigest,
331
+ 'changed-facts'
332
+ );
333
+ const wireBody = createProviderWireBody({
334
+ provider: 'github',
335
+ action: 'merge',
336
+ resourceId: `${repository.fullName}#${op.payload.reviewNumber}`,
337
+ expectedState: 'open',
338
+ expectedVersion: op.candidate.headSha,
339
+ idempotencyKey: op.digest,
340
+ payload: { sha: op.candidate.headSha, merge_method: op.payload.mergeMethod },
341
+ });
342
+ timely();
343
+ const controller = new AbortController();
344
+ const remaining = deadline - Date.parse(timestamp(clock()));
345
+ ensure(remaining > 0, 'dispatch-expired');
346
+ const timer = setTimeout(() => controller.abort(), remaining);
347
+ let response;
348
+ try {
349
+ response = (await http.request({
350
+ method: 'PUT',
351
+ path: `${root}/pulls/${op.payload.reviewNumber}/merge`,
352
+ wireBody,
353
+ signal: controller.signal,
354
+ })).data;
355
+ } finally {
356
+ clearTimeout(timer);
357
+ }
358
+ ensure(response?.merged === true, 'unverified-effect');
359
+ sha(response.sha);
360
+ const result = await locate(op.candidate, op.payload.reviewNumber);
361
+ ensure(result.state === 'merged' && result.commitSha === response.sha, 'unverified-effect');
362
+ return receipt(op, result);
363
+ }
364
+ async function reconcile(input) {
365
+ try {
366
+ const op = operation(input),
367
+ result = await locate(op.candidate, op.payload.reviewNumber);
368
+ return result.state === 'merged'
369
+ ? { status: 'succeeded', receipt: receipt(op, result) }
370
+ : { status: 'unknown' };
371
+ } catch {
372
+ return { status: 'unknown' };
373
+ }
374
+ }
375
+ return createTrustedDeliveryExecutor({
376
+ provider: 'github',
377
+ capabilities: [{ action: 'merge', conditionalHead: true, reconcile: true }],
378
+ observe,
379
+ dispatch,
380
+ reconcile,
381
+ });
382
+ }
@@ -0,0 +1,5 @@
1
+ import { createReviewRequestExecutor } from './review-request.js';
2
+
3
+ export function createGitlabReviewExecutor(input) {
4
+ return createReviewRequestExecutor('gitlab', input);
5
+ }