@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.
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +59 -0
- package/bin/cli.js +42 -0
- package/dist/agents/backend-django-agent/SKILL.md +35 -0
- package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
- package/dist/agents/boss-agent/SKILL.md +28 -0
- package/dist/agents/bounded-worker/SKILL.md +28 -0
- package/dist/agents/data-performance-agent/SKILL.md +32 -0
- package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
- package/dist/agents/devops-agent/SKILL.md +34 -0
- package/dist/agents/engineering-manager/SKILL.md +28 -0
- package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
- package/dist/agents/frontend-web-agent/SKILL.md +31 -0
- package/dist/agents/incident-response-agent/SKILL.md +42 -0
- package/dist/agents/jest-agent/SKILL.md +77 -0
- package/dist/agents/mobile-agent/SKILL.md +32 -0
- package/dist/agents/playwright-agent/SKILL.md +54 -0
- package/dist/agents/product-design-manager/SKILL.md +27 -0
- package/dist/agents/qa-agent/SKILL.md +29 -0
- package/dist/agents/quality-manager/SKILL.md +28 -0
- package/dist/agents/vitest-agent/SKILL.md +77 -0
- package/dist/governance/pre-push-rules.md +75 -0
- package/dist/governance/prompt-hygiene.md +22 -0
- package/dist/governance/review-checklist.md +24 -0
- package/dist/governance/safety-and-data.md +27 -0
- package/dist/governance/usage-rules.md +21 -0
- package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
- package/dist/mandatory/agentic-goal/SKILL.md +42 -0
- package/dist/mandatory/agentic-status/SKILL.md +74 -0
- package/dist/mandatory/apply-design/SKILL.md +228 -0
- package/dist/mandatory/check-ac/SKILL.md +124 -0
- package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
- package/dist/mandatory/design/SKILL.md +312 -0
- package/dist/mandatory/feature-workflow/SKILL.md +87 -0
- package/dist/mandatory/hotfix/SKILL.md +195 -0
- package/dist/mandatory/pre-push/SKILL.md +272 -0
- package/dist/mandatory/project-context/SKILL.md +180 -0
- package/dist/mandatory/release-docs/SKILL.md +191 -0
- package/dist/mandatory/review-pr/SKILL.md +323 -0
- package/dist/mandatory/security-review/SKILL.md +157 -0
- package/dist/skills/api-contract/SKILL.md +77 -0
- package/dist/skills/backend-django/SKILL.md +27 -0
- package/dist/skills/backend-nestjs/SKILL.md +65 -0
- package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
- package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
- package/dist/skills/database-migration/SKILL.md +97 -0
- package/dist/skills/debugging/SKILL.md +32 -0
- package/dist/skills/devops-infra/SKILL.md +26 -0
- package/dist/skills/documentation/SKILL.md +24 -0
- package/dist/skills/frontend-nextjs/SKILL.md +27 -0
- package/dist/skills/incident-postmortem/SKILL.md +38 -0
- package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
- package/dist/skills/mobile-react-native/SKILL.md +37 -0
- package/dist/skills/postgres-analytics/SKILL.md +29 -0
- package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
- package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
- package/dist/skills/refactoring/SKILL.md +32 -0
- package/dist/skills/sprint-planning/SKILL.md +32 -0
- package/dist/skills/testing-quality/SKILL.md +62 -0
- package/dist/v2/protocols/agent-orchestration.md +49 -0
- package/dist/v2/protocols/delivery.md +45 -0
- package/dist/v2/protocols/design-authority.md +29 -0
- package/dist/v2/protocols/goal-graph.md +44 -0
- package/dist/v2/protocols/pattern-first-development.md +29 -0
- package/dist/v2/protocols/qa-evidence.md +29 -0
- package/dist/v2/protocols/security.md +29 -0
- package/dist/v2/schemas/event.schema.json +86 -0
- package/dist/v2/schemas/evidence.schema.json +94 -0
- package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
- package/dist/v2/schemas/feature-plan.schema.json +95 -0
- package/dist/v2/schemas/goal-graph.schema.json +61 -0
- package/dist/v2/schemas/integration.schema.json +119 -0
- package/dist/v2/schemas/orchestration.schema.json +69 -0
- package/dist/v2/schemas/project.schema.json +209 -0
- package/dist/v2/schemas/providers.schema.json +142 -0
- package/dist/v2/schemas/quality.schema.json +50 -0
- package/dist/v2/schemas/work-action.schema.json +44 -0
- package/dist/v2/schemas/work-request.schema.json +253 -0
- package/dist/v2/templates/evidence/qa-bundle.json +194 -0
- package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
- package/dist/v2/templates/harness/SKILL.md +90 -0
- package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
- package/dist/v2/templates/project/.rivet/project.yaml +14 -0
- package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
- package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
- package/package.json +58 -0
- package/protocols/agent-orchestration.md +49 -0
- package/protocols/delivery.md +45 -0
- package/protocols/design-authority.md +29 -0
- package/protocols/goal-graph.md +44 -0
- package/protocols/pattern-first-development.md +29 -0
- package/protocols/qa-evidence.md +29 -0
- package/protocols/security.md +29 -0
- package/schemas/event.schema.json +86 -0
- package/schemas/evidence.schema.json +94 -0
- package/schemas/feature-decomposition.schema.json +48 -0
- package/schemas/feature-plan.schema.json +95 -0
- package/schemas/goal-graph.schema.json +61 -0
- package/schemas/integration.schema.json +119 -0
- package/schemas/orchestration.schema.json +69 -0
- package/schemas/project.schema.json +209 -0
- package/schemas/providers.schema.json +142 -0
- package/schemas/quality.schema.json +50 -0
- package/schemas/work-action.schema.json +44 -0
- package/schemas/work-request.schema.json +253 -0
- package/src/adapters/confluence.js +138 -0
- package/src/adapters/contract.js +657 -0
- package/src/adapters/factory.js +122 -0
- package/src/adapters/figma.js +178 -0
- package/src/adapters/fixtures.js +183 -0
- package/src/adapters/github.js +314 -0
- package/src/adapters/http.js +704 -0
- package/src/adapters/jira.js +195 -0
- package/src/adapters/linear.js +171 -0
- package/src/adapters/node-transport.js +59 -0
- package/src/cli/integration-setup-prompt.js +32 -0
- package/src/cli/interrupt.js +25 -0
- package/src/cli/main.js +596 -0
- package/src/cli/output.js +174 -0
- package/src/cli/parse-args.js +246 -0
- package/src/cli/project-discovery.js +70 -0
- package/src/cli/task-confirmation.js +38 -0
- package/src/cli/task-presentation.js +22 -0
- package/src/clients/claude.js +214 -0
- package/src/clients/codex.js +180 -0
- package/src/clients/compatibility.js +23 -0
- package/src/clients/contract.js +224 -0
- package/src/clients/fake.js +170 -0
- package/src/clients/process-runner.js +657 -0
- package/src/clients/result-contract.js +78 -0
- package/src/commands/delivery-publish.js +80 -0
- package/src/commands/delivery-remote.js +230 -0
- package/src/commands/delivery-review-update.js +39 -0
- package/src/commands/delivery-tracker.js +94 -0
- package/src/commands/delivery-transition.js +79 -0
- package/src/commands/delivery.js +223 -0
- package/src/commands/dependency-approval.js +21 -0
- package/src/commands/doctor.js +191 -0
- package/src/commands/evidence.js +44 -0
- package/src/commands/feature.js +212 -0
- package/src/commands/goals.js +231 -0
- package/src/commands/human-run.js +208 -0
- package/src/commands/human-task.js +278 -0
- package/src/commands/init.js +848 -0
- package/src/commands/install.js +814 -0
- package/src/commands/integration-setup.js +65 -0
- package/src/commands/integrations.js +54 -0
- package/src/commands/models.js +158 -0
- package/src/commands/orchestrate.js +61 -0
- package/src/commands/preflight.js +125 -0
- package/src/commands/protocols.js +228 -0
- package/src/commands/repositories.js +77 -0
- package/src/commands/setup-remote.js +41 -0
- package/src/commands/setup.js +214 -0
- package/src/commands/status.js +224 -0
- package/src/commands/support.js +44 -0
- package/src/commands/task-completion.js +104 -0
- package/src/commands/uninstall.js +234 -0
- package/src/commands/verify.js +171 -0
- package/src/commands/work.js +220 -0
- package/src/commands/worktrees.js +52 -0
- package/src/config/command-readiness.js +218 -0
- package/src/config/commands.js +206 -0
- package/src/config/defaults.js +55 -0
- package/src/config/load.js +230 -0
- package/src/config/validate.js +560 -0
- package/src/delivery/bitbucket-review.js +123 -0
- package/src/delivery/branch-publication.js +23 -0
- package/src/delivery/contract.js +379 -0
- package/src/delivery/github-deployment.js +132 -0
- package/src/delivery/github-review.js +5 -0
- package/src/delivery/github.js +382 -0
- package/src/delivery/gitlab-review.js +5 -0
- package/src/delivery/gitlab.js +395 -0
- package/src/delivery/prepare.js +90 -0
- package/src/delivery/publication-process.js +33 -0
- package/src/delivery/publication-transport.js +112 -0
- package/src/delivery/review-request.js +184 -0
- package/src/delivery/review-update.js +74 -0
- package/src/delivery/service.js +400 -0
- package/src/delivery/store.js +76 -0
- package/src/delivery/tracker-target.js +51 -0
- package/src/delivery/tracker-transition.js +158 -0
- package/src/delivery/tracker.js +164 -0
- package/src/discovery/git.js +85 -0
- package/src/discovery/portable.js +108 -0
- package/src/discovery/project.js +345 -0
- package/src/discovery/tools.js +145 -0
- package/src/evaluations/approval.js +20 -0
- package/src/evaluations/cost-policy.js +20 -0
- package/src/evaluations/harness-attempt.js +113 -0
- package/src/evaluations/live-runner.js +55 -0
- package/src/evaluations/profile.js +28 -0
- package/src/evaluations/report.js +11 -0
- package/src/evaluations/text-attempt.js +18 -0
- package/src/evidence/checksum.js +87 -0
- package/src/evidence/collect.js +727 -0
- package/src/evidence/validate.js +354 -0
- package/src/feature/accepted-integration.js +46 -0
- package/src/feature/actions.js +37 -0
- package/src/feature/client-profile.js +56 -0
- package/src/feature/decomposition-contract.js +58 -0
- package/src/feature/host-execution.js +633 -0
- package/src/feature/host-lock-recovery.js +96 -0
- package/src/feature/host-run-lock.js +32 -0
- package/src/feature/local-approval.js +183 -0
- package/src/feature/plan-contract.js +215 -0
- package/src/feature/planner.js +253 -0
- package/src/feature/run-store.js +241 -0
- package/src/feature/runtime-bridge.js +840 -0
- package/src/feature/verification-report.js +168 -0
- package/src/feature/workflow.js +375 -0
- package/src/git/client.js +509 -0
- package/src/git/integration-worktree.js +153 -0
- package/src/git/reconcile.js +222 -0
- package/src/git/reservations.js +450 -0
- package/src/git/worktrees.js +478 -0
- package/src/graph/completion.js +189 -0
- package/src/graph/fixtures.js +116 -0
- package/src/graph/reducer.js +124 -0
- package/src/graph/scheduler.js +252 -0
- package/src/graph/validate.js +106 -0
- package/src/install/managed.js +579 -0
- package/src/install/project-reference.js +33 -0
- package/src/install/project-runtime.js +142 -0
- package/src/install/runtime-integrity.cjs +141 -0
- package/src/integrations/capabilities.js +69 -0
- package/src/integrations/host-observation.js +26 -0
- package/src/integrations/registry.js +56 -0
- package/src/models/delegate.js +102 -0
- package/src/models/profiles.js +53 -0
- package/src/models/protocols.js +208 -0
- package/src/models/registry.js +83 -0
- package/src/models/transport.js +59 -0
- package/src/policy/approvals.js +251 -0
- package/src/policy/authority.js +282 -0
- package/src/policy/budget.js +149 -0
- package/src/policy/command-bootstrap.js +219 -0
- package/src/policy/commands.js +803 -0
- package/src/prompts/launch-contract.js +43 -0
- package/src/prompts/planning-contract.js +92 -0
- package/src/protocols/presentation.js +53 -0
- package/src/protocols/project.js +289 -0
- package/src/quality/runner.js +497 -0
- package/src/quality/traceability.js +215 -0
- package/src/repositories/identity.js +59 -0
- package/src/repositories/index.js +2 -0
- package/src/repositories/provider.js +210 -0
- package/src/runtime/application.js +353 -0
- package/src/runtime/dependency-directory.js +33 -0
- package/src/runtime/harness-discovery.js +121 -0
- package/src/runtime/heartbeat.js +47 -0
- package/src/runtime/instance-store.js +112 -0
- package/src/runtime/orchestrator.js +1133 -0
- package/src/runtime/portable-dependencies.js +94 -0
- package/src/runtime/recovery.js +190 -0
- package/src/runtime/retry.js +54 -0
- package/src/runtime/supervisor.js +166 -0
- package/src/runtime/worktree-bootstrap.js +169 -0
- package/src/state/event-store.js +333 -0
- package/src/state/lock.js +266 -0
- package/src/state/paths.js +274 -0
- package/src/state/redact.js +106 -0
- package/src/state/snapshot-store.js +310 -0
- package/src/status/public/app.js +96 -0
- package/src/status/public/index.html +37 -0
- package/src/status/public/styles.css +50 -0
- package/src/status/server.js +323 -0
- package/src/status/view-model.js +356 -0
- package/src/support/bundle.js +195 -0
- package/src/support/failure-report.js +96 -0
- package/src/work-request/contract.js +250 -0
- package/src/work-request/host.js +78 -0
- package/src/work-request/local.js +163 -0
- package/src/work-request/tracker.js +105 -0
- package/templates/evidence/qa-bundle.json +194 -0
- package/templates/github-actions/rivet-deploy.yml +98 -0
- package/templates/harness/SKILL.md +90 -0
- package/templates/project/.rivet/orchestration.yaml +42 -0
- package/templates/project/.rivet/project.yaml +14 -0
- package/templates/project/.rivet/providers.yaml +8 -0
- package/templates/project/.rivet/quality.yaml +16 -0
|
@@ -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
|
+
}
|