Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Merge branch 'worktree-agent-a93665b7de3c8ad4e' into ship-fixes

# Conflicts: # apps/api/src/operations.rs # apps/web/app/routes/repo/pull.tsx

syntaqxcommitted Parents5aaf3a18e64619Browse files
20 files+192−164 files not counted0/20 viewed
+1−0
275275 Op::Security(SecurityOp::GetDependencyGraph),
276276 Op::Security(SecurityOp::GetSbom),
277277 Op::Security(SecurityOp::CompareDependencies),
278+ Op::Security(SecurityOp::RerunDependencyReview),
278279 ],
279280 ),
280281 (
+2−1
807807 }
808808
809809 impl Op {
810− pub const ALL: [Op; 402] = [
810+ pub const ALL: [Op; 403] = [
811811 Op::Whoami,
812812 Op::GetWorkspace,
813813 Op::CreateWorkspace,
10401040 Op::Security(SecurityOp::GetDependencyGraph),
10411041 Op::Security(SecurityOp::GetSbom),
10421042 Op::Security(SecurityOp::CompareDependencies),
1043+ Op::Security(SecurityOp::RerunDependencyReview),
10431044 Op::Security(SecurityOp::GetSettings),
10441045 Op::Security(SecurityOp::UpdateSettings),
10451046 Op::Security(SecurityOp::GetWorkspaceSettings),
+46−0
92869286 "deny_licenses": []
92879287 }
92889288 },
9289+ "rerun_dependency_review": {
9290+ "params": {
9291+ "owner": "flagon-io",
9292+ "name": "hello",
9293+ "number": 12
9294+ },
9295+ "response": {
9296+ "base": "4b1d7c0e9a2f6e3d8c5b0a7f4e1d2c3b4a5f6e7d",
9297+ "head": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13",
9298+ "changes": [
9299+ {
9300+ "change_type": "added",
9301+ "manifest": "package-lock.json",
9302+ "ecosystem": "npm",
9303+ "name": "lodash",
9304+ "version": "4.17.21",
9305+ "relationship": "direct",
9306+ "development": false,
9307+ "license": "MIT",
9308+ "purl": "pkg:npm/lodash@4.17.21",
9309+ "vulnerabilities": [],
9310+ "denied_license": false,
9311+ "failing": false
9312+ },
9313+ {
9314+ "change_type": "removed",
9315+ "manifest": "package-lock.json",
9316+ "ecosystem": "npm",
9317+ "name": "lodash",
9318+ "version": "4.17.20",
9319+ "relationship": "direct",
9320+ "development": false,
9321+ "license": "MIT",
9322+ "purl": "pkg:npm/lodash@4.17.20",
9323+ "vulnerabilities": [],
9324+ "denied_license": false,
9325+ "failing": false
9326+ }
9327+ ],
9328+ "passed": true,
9329+ "headline": "2 dependency changes, none vulnerable",
9330+ "fail_on": "high",
9331+ "deny_licenses": []
9332+ },
9333+ "notes": "The pull request's one dependency review comment is changed in place; nothing new is posted when the review says the same."
9334+ },
92899335 "get_security_settings": {
92909336 "params": {
92919337 "owner": "flagon-io",
+1−0
332332 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
333333 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
334334 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
335+ route("POST", "/repos/:owner/:name/pulls/:number/dependency-review/rerun", Op::Security(SecurityOp::RerunDependencyReview), &[]),
335336 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
336337 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
337338 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
+18−1
5454 GetDependencyGraph,
5555 GetSbom,
5656 CompareDependencies,
57+ RerunDependencyReview,
5758 GetSettings,
5859 UpdateSettings,
5960 GetWorkspaceSettings,
7374 /// Every one: `Op::ALL` lists each as `Op::Security(…)`, which a test
7475 /// checks against this.
7576 #[cfg(test)]
76− pub const ALL: [SecurityOp; 38] = [
77+ pub const ALL: [SecurityOp; 39] = [
7778 SecurityOp::ListSecretAlerts,
7879 SecurityOp::GetSecretAlert,
7980 SecurityOp::UpdateSecretAlert,
99100 SecurityOp::GetDependencyGraph,
100101 SecurityOp::GetSbom,
101102 SecurityOp::CompareDependencies,
103+ SecurityOp::RerunDependencyReview,
102104 SecurityOp::GetSettings,
103105 SecurityOp::UpdateSettings,
104106 SecurityOp::GetWorkspaceSettings,
141143 SecurityOp::GetDependencyGraph => "get_dependency_graph",
142144 SecurityOp::GetSbom => "get_sbom",
143145 SecurityOp::CompareDependencies => "compare_dependencies",
146+ SecurityOp::RerunDependencyReview => "rerun_dependency_review",
144147 SecurityOp::GetSettings => "get_security_settings",
145148 SecurityOp::UpdateSettings => "update_security_settings",
146149 SecurityOp::GetWorkspaceSettings => "get_workspace_security_settings",
185188 SecurityOp::GetDependencyGraph => "Get the dependency graph",
186189 SecurityOp::GetSbom => "Export an SBOM",
187190 SecurityOp::CompareDependencies => "Compare dependencies",
191+ SecurityOp::RerunDependencyReview => "Re-run dependency review",
188192 SecurityOp::GetSettings => "Get a repository's security settings",
189193 SecurityOp::UpdateSettings => "Update a repository's security settings",
190194 SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings",
228232 SecurityOp::GetDependencyGraph => "Get the dependency graph: every package the lockfiles on the default branch resolve, per lockfile, with whether it is direct or transitive (where the lockfile says), for development, its license when recorded, its package URL and its open vulnerability alerts.",
229233 SecurityOp::GetSbom => "Export the dependency graph as an SPDX 2.3 JSON document, in sbom. Every package is named by its package URL.",
230234 SecurityOp::CompareDependencies => "Compare the dependencies at two commits, branches or tags (basehead, as base...head): what was added and removed per lockfile, with the known vulnerabilities of what was added and whether it passes the repository's dependency review policy. Needs the g1t plan on a private repository.",
235+ SecurityOp::RerunDependencyReview => "Run the Dependency review check again on an open pull request's head, with the advisories known now: its status is set again and its one comment on the pull request is changed in place (or added, when it has dependency changes to show and none is there yet). Returns the review. g1t reviews a pull request by itself when it opens and when its head moves, carrying the result over when the head's lockfile changes are the ones reviewed before. Takes the Write role. Needs the g1t plan on a private repository.",
231236 SecurityOp::GetSettings => "Get a repository's security settings: when the Code scanning check fails, dependency review and its policy, whether secrets in the history open alerts, whether it takes private vulnerability reports, its workspace's settings, and whether the paid features are on for it.",
232237 SecurityOp::UpdateSettings => "Change a repository's security settings: code_scanning_gate (none, errors, critical, high, medium or any), dependency_review, review_fail_on (critical, high, medium, low or none), review_deny_licenses (SPDX ids), review_comment, history_alerts (whether secrets found in the history open alerts; push protection stays on) and private_vulnerability_reporting. Takes the Admin role, or a security manager of the workspace. Require the Code scanning and Dependency review checks in branch protection to gate merges on them.",
233238 SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings (delegated bypass, validity checks) and whether it has Security and quality, which comes with the g1t plan.",
428433 &["repo", "id", "state"],
429434 ),
430435 SecurityOp::GetDependencyGraph | SecurityOp::GetSbom | SecurityOp::GetSettings => (json!({ "repo": repo() }), &["repo"]),
436+ SecurityOp::RerunDependencyReview => (
437+ json!({ "repo": repo(), "number": { "type": "integer", "description": "The pull request's number." } }),
438+ &["repo", "number"],
439+ ),
431440 SecurityOp::CompareDependencies => (
432441 json!({
433442 "repo": repo(),
10371046 };
10381047 pass(services, "dependency_review", &DependencyReviewArgs { viewer: viewer.clone(), repo, base, head }).await
10391048 }
1049+ SecurityOp::RerunDependencyReview => {
1050+ let Some(repo) = repo else { return failed(FailureCode::Invalid, &need_repo()) };
1051+ let Some(number) = whole(input, "number").filter(|number| *number > 0) else {
1052+ return failed(FailureCode::Invalid, "number is the pull request's number.");
1053+ };
1054+ pass(services, "rerun_dependency_review", &PullScanningArgs { viewer: viewer.clone(), repo, number }).await
1055+ }
10401056 SecurityOp::GetSettings => {
10411057 let Some(repo) = repo else { return failed(FailureCode::Invalid, &need_repo()) };
10421058 pass(services, "security_settings", &SecuritySettingsArgs { viewer: viewer.clone(), repo }).await
12411257 }
12421258 assert_eq!(scope_for("get_sbom"), Some(Scope::SecurityRead));
12431259 assert_eq!(scope_for("upload_sarif"), Some(Scope::SecurityWrite));
1260+ assert_eq!(scope_for("rerun_dependency_review"), Some(Scope::SecurityWrite));
12441261 // No run decides about security.
12451262 for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings"] {
12461263 assert!(g1t_contracts::credentials::NEVER.contains(&name), "{name}");
+1−0
515515 a("dependency_graph", Op::Security(SecurityOp::GetDependencyGraph), "Every package the lockfiles resolve, direct or transitive"),
516516 a("sbom", Op::Security(SecurityOp::GetSbom), "The dependency graph as an SPDX 2.3 document"),
517517 a("compare_dependencies", Op::Security(SecurityOp::CompareDependencies), "What changes in dependencies between base...head"),
518+ a("rerun_dependency_review", Op::Security(SecurityOp::RerunDependencyReview), "Run a pull request's Dependency review check again"),
518519 a("settings", Op::Security(SecurityOp::GetSettings), "A repository's security settings"),
519520 a("update_settings", Op::Security(SecurityOp::UpdateSettings), "Change when checks fail and dependency review's policy"),
520521 a("workspace_settings", Op::Security(SecurityOp::GetWorkspaceSettings), "A workspace's delegated bypass and validity checks"),
+13−1
8080 `g1t.sh/<owner>/<project>/security/pulls/<number>`. Require **Dependency
8181 review** under **Settings → Branches** to block merges on it.
8282
83+A pull request has one review comment. When a later review says something
84+new, that comment is changed in place; when it says the same, the comment
85+is left alone. A push whose lockfile changes are the ones already reviewed,
86+such as catching the branch up with its base, is not reviewed again: the
87+check's result carries over to the new head.
88+
89+Advisories are published every day, so a review can be made again: **Re-run
90+dependency review** in the pull request's checks, or **Re-run** on its
91+Security checks page, takes the Write role. It updates the check and the
92+same comment.
93+
8394 In the repository's **Security settings**:
8495
8596 | Setting | Default | |
115126 | `GET /repos/{owner}/{name}/dependency-graph` | The graph, per lockfile. | `security:read` |
116127 | `GET /repos/{owner}/{name}/dependency-graph/sbom` | The SPDX document, in `sbom`. | `security:read` |
117128 | `GET /repos/{owner}/{name}/dependency-graph/compare/{basehead}` | What changed between two commits, and whether it passes review. | `security:read` |
129+| `POST /repos/{owner}/{name}/pulls/{number}/dependency-review/rerun` | Runs a pull request's Dependency review check again and returns the review. | `security:write` |
118130 | `GET /repos/{owner}/{name}/vulnerability-alerts` | Lists alerts; filter with `state`, `severity`, `ecosystem`, `package`. | `security:read` |
119131 | `GET /workspaces/{workspace}/vulnerability-alerts` | The same across a workspace. | `security:read` |
120132 | `GET /repos/{owner}/{name}/vulnerability-alerts/{id}` | One alert. | `security:read` |
121133 | `PATCH /repos/{owner}/{name}/vulnerability-alerts/{id}` | `state` `dismissed` with `reason` and `comment`, or `open`. | `security:write` |
122134
123135 Over MCP, the [`security` tool](/reference/mcp/#security) has
124−`dependency_graph`, `sbom`, `compare_dependencies`, `vulnerability_alerts`,
136+`dependency_graph`, `sbom`, `compare_dependencies`, `rerun_dependency_review`, `vulnerability_alerts`,
125137 `vulnerability_alert` and `update_vulnerability_alert`. Webhooks:
126138 `vulnerability_alert.created`, `.fixed`, `.dismissed` and `.reopened`.
+1−0
618618 | [`dependency_graph`](/reference/api/supply-chain/get-dependency-graph/) | Every package the lockfiles resolve, direct or transitive, with licenses. | `repo` | `security:read` |
619619 | [`sbom`](/reference/api/supply-chain/get-sbom/) | The dependency graph as an SPDX 2.3 document, in `sbom`. | `repo` | `security:read` |
620620 | [`compare_dependencies`](/reference/api/supply-chain/compare-dependencies/) | What changes between `basehead` (`base...head`), and whether it passes dependency review. | `repo`, `basehead` | `security:read` |
621+| [`rerun_dependency_review`](/reference/api/supply-chain/rerun-dependency-review/) | Runs a pull request's Dependency review check again; its one comment changes in place. Write role. | `repo`, `number` | `security:write` |
621622 | [`settings`](/reference/api/security-settings/get-security-settings/) | A repository's security settings, and whether the paid features are on. | `repo` | `security:read` |
622623 | [`update_settings`](/reference/api/security-settings/update-security-settings/) | Change `code_scanning_gate`, `dependency_review`, `review_fail_on`, `review_deny_licenses`, `review_comment`. Admin role, or a security manager. | `repo` | `security:write` |
623624 | [`workspace_settings`](/reference/api/security-settings/get-workspace-security-settings/) | A workspace's delegated bypass and validity checks. | `workspace` | `security:read` |
+0−0

Binary or large file; its contents are not shown.

+24−1
2121 import { useMemo, useState } from "react";
2222 import { Form, Link } from "react-router";
2323
24−import type { CheckResult, CheckRun, CommitStatus, Job, Mergeable, Pull, RequiredCheck } from "@g1t/contracts";
24+import {
25+ type CheckResult,
26+ type CheckRun,
27+ type CommitStatus,
28+ DEPENDENCY_REVIEW_CHECK,
29+ type Job,
30+ type Mergeable,
31+ type Pull,
32+ type RequiredCheck,
33+} from "@g1t/contracts";
2534
2635 import { useAddresses } from "../lib/addresses";
2736 import { duration } from "./actions";
406415 pull.fork ? `/${pull.fork.namespace}/${pull.fork.name}/commit/${sha}` : `${base}/commit/${sha}`;
407416 const older = run != null && pull.headCommit != null && run.headCommit !== pull.headCommit;
408417 const failedRuns = statuses.filter((status) => statusStanding(status) === "failed" && runIdOf(status));
418+ // g1t's own Dependency review check, which anyone who may start runs can
419+ // have made again (after an advisory is published, say).
420+ const dependencyReview = statuses.some((status) => status.context === DEPENDENCY_REVIEW_CHECK && status.source === "security");
409421
410422 return (
411423 <div aria-busy={loading || undefined}>
458470 </Hint>
459471 </Form>
460472 ))}
473+ {canRerunWorkflows && dependencyReview && !settled && (
474+ <Form method="post">
475+ <input type="hidden" name="action" value="rerun-dependency-review" />
476+ <Hint label="Review its dependency changes again, with the advisories known now">
477+ <SubmitButton variant="outline" match={{ action: "rerun-dependency-review" }} pending="Re-running…">
478+ <RotateCw size={13} />
479+ Re-run dependency review
480+ </SubmitButton>
481+ </Hint>
482+ </Form>
483+ )}
461484 </div>
462485 </div>
463486 {error && (
+12−3
9090 import { ViolationList } from "../../components/rules";
9191 import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
9292 import { notFound } from "../../lib/not-found.server";
93−import { actions, deployments, identity, inbox, repos, work } from "../../lib/services.server";
93+import { actions, deployments, identity, inbox, repos, securitySuite, work } from "../../lib/services.server";
9494 import { commitChecksFor } from "../../lib/commit-checks.server";
9595 import { CommitChecksBadge } from "../../components/commit-checks";
9696 import { DeployLink } from "../../components/deploy";
395395 const PULL_NEEDS: Record<string, Capability> = {
396396 "rerun-workflow": "run",
397397 "rerun-failed": "run",
398+ "rerun-dependency-review": "run",
398399 merge: "merge",
399400 unqueue: "merge",
400401 milestone: "triage",
488489 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
489490 return rerun.ok ? null : { error: rerun.error.message, action };
490491 }
492+ // The Dependency review check, made again on its head: its comment
493+ // changes in place. Who may is the security service's call.
494+ if (action === "rerun-dependency-review") {
495+ const rerun = await securitySuite.rerunDependencyReview(user, path, number);
496+ return rerun.ok ? null : { error: rerun.error.message, action };
497+ }
491498 // Every failed run on its head, run again: Mission control's quick action.
492499 if (action === "rerun-failed") {
493500 const found = await work.getPull(path, number, user);
767774 canRerunWorkflows={canMerge}
768775 settingsUrl={canProtect ? `${base}/settings/branches` : null}
769776 conflicting={mergeable === "conflicting" && !noChecks}
770− error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
777+ error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" || actionData?.action === "rerun-dependency-review"
778+ ? actionData.error
779+ : null}
771780 loading={loading}
772781 />
773782 );
14721481 </Form>
14731482 )}
14741483 {actionData &&
1475− !["merge", "comment", "rerun-failed", "rerun-workflow", "update", "reviewers", "assign", "edit-comment", "delete-comment", "auto-merge-on", "auto-merge-off", "delete-branch", "restore-branch"].includes(
1484+ !["merge", "comment", "rerun-failed", "rerun-workflow", "rerun-dependency-review", "update", "reviewers", "assign", "edit-comment", "delete-comment", "auto-merge-on", "auto-merge-off", "delete-branch", "restore-branch"].includes(
14761485 String(actionData.action),
14771486 ) && <ErrorText>{actionData.error}</ErrorText>}
14781487 </div>
+27−6
1−import { ArrowLeft, CircleAlert, CircleCheck } from "lucide-react";
2−import { Link } from "react-router";
1+import { ArrowLeft, CircleAlert, CircleCheck, RotateCw } from "lucide-react";
2+import { Form, Link } from "react-router";
33
44 import type { Route } from "./+types/security-pull";
55 import { page } from "../../lib/meta";
66 import { SeverityBadge } from "../../components/security";
77 import { CARD, LIST, SectionHeader, shortRule } from "../../components/security-suite";
8+import { ErrorText, SubmitButton } from "../../components/ui";
89 import { Badge } from "../../components/ui/badge";
910 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table";
1011 import { cn } from "../../lib/cn";
1112 import { securitySuite } from "../../lib/services.server";
12−import { getViewer, requireUser, unwrap } from "../../lib/session.server";
13−import { requireInsider } from "../../lib/access.server";
13+import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
14+import { refusal, requireInsider } from "../../lib/access.server";
1415
1516 export function meta({ params, ...args }: Route.MetaArgs) {
1617 return page(args, { title: `Security checks · #${params.number} · ${params.owner}/${params.repo} · g1t` });
2324 return { scanning: unwrap(await securitySuite.pullScanning(repo, Number(params.number) || 0, viewer)) };
2425 }
2526
27+/** The Dependency review check made again on the pull request's head; its comment changes in place. */
28+export async function action({ params, context, request }: Route.ActionArgs) {
29+ assertSameOrigin(request);
30+ const user = requireUser(context, request);
31+ const refused = await refusal(context, params, "run");
32+ if (refused) return { error: refused };
33+ const repo = { namespace: params.owner, name: params.repo };
34+ const rerun = await securitySuite.rerunDependencyReview(user, repo, Number(params.number) || 0);
35+ return rerun.ok ? null : { error: rerun.error.message };
36+}
37+
2638 function Verdict({ passed, text }: { passed: boolean; text: string }) {
2739 return (
2840 <p className={`flex items-center gap-1.5 text-sm ${passed ? "text-success" : "text-danger"}`}>
3345 }
3446
3547 /** What the Code scanning and Dependency review checks found on a pull request: the page their statuses link to. */
36−export default function PullSecurity({ loaderData, params }: Route.ComponentProps) {
48+export default function PullSecurity({ loaderData, actionData, params }: Route.ComponentProps) {
3749 const { scanning } = loaderData;
3850 const base = `/${params.owner}/${params.repo}`;
3951 const onLines = scanning.results.filter((result) => result.new && result.onChangedLine);
8294 )}
8395 </section>
8496 <section className="space-y-3">
85− <h3 className="text-base font-semibold tracking-tight">Dependency review</h3>
97+ <div className="flex flex-wrap items-center justify-between gap-2">
98+ <h3 className="text-base font-semibold tracking-tight">Dependency review</h3>
99+ <Form method="post">
100+ <SubmitButton variant="outline" pending="Re-running…">
101+ <RotateCw size={13} />
102+ Re-run
103+ </SubmitButton>
104+ </Form>
105+ </div>
106+ {actionData?.error && <ErrorText>{actionData.error}</ErrorText>}
86107 {!scanning.review ? (
87108 <p className="text-sm text-muted">Not reviewed yet: it runs when a pull request opens and each time its head moves.</p>
88109 ) : (
+1−0
10951095 ("get_dependency_graph", Scope::SecurityRead),
10961096 ("get_sbom", Scope::SecurityRead),
10971097 ("compare_dependencies", Scope::SecurityRead),
1098+ ("rerun_dependency_review", Scope::SecurityWrite),
10981099 ("get_security_settings", Scope::SecurityRead),
10991100 ("get_workspace_security_settings", Scope::SecurityRead),
11001101 ("get_security_overview", Scope::SecurityRead),
+14−0
64496449 body: &[],
64506450 },
64516451 Operation {
6452+ id: "rerun_dependency_review",
6453+ method: "POST",
6454+ path: "/repos/{owner}/{name}/pulls/{number}/dependency-review/rerun",
6455+ tag: "Supply chain",
6456+ summary: "Re-run dependency review",
6457+ scope: Some("security:write"),
6458+ params: &[
6459+ Param { name: "owner", location: In::Path, kind: "string", required: true, values: &[] },
6460+ Param { name: "name", location: In::Path, kind: "string", required: true, values: &[] },
6461+ Param { name: "number", location: In::Path, kind: "integer", required: true, values: &[] },
6462+ ],
6463+ body: &[],
6464+ },
6465+ Operation {
64526466 id: "update_vulnerability_alert",
64536467 method: "PATCH",
64546468 path: "/repos/{owner}/{name}/vulnerability-alerts/{id}",
+30−3
66 use std::collections::{BTreeMap, BTreeSet};
77
88 use serde::{Deserialize, Serialize};
9+use sha2::{Digest, Sha256};
910
1011 use crate::graph::Dependency;
1112 use crate::osv::Severity;
111112 pub denied_license: bool,
112113 }
113114
115+/// How the pull request comment starts: g1t finds its own comment again
116+/// by it, to change it rather than add another.
117+pub const HEADING: &str = "### Dependency review";
118+
119+/// The comment on a pull request that changes no dependencies.
120+pub fn unchanged_summary() -> String {
121+ format!("{HEADING}\n\nThis pull request changes no dependencies.\n")
122+}
123+
124+/// A short, stable key for `text`: what a review was of, so that the same
125+/// change is not reviewed twice.
126+pub fn key(text: &str) -> String {
127+ Sha256::digest(text.as_bytes())[..16].iter().map(|byte| format!("{byte:02x}")).collect()
128+}
129+
114130 /// The whole review.
115131 #[derive(Clone, Debug, PartialEq, Eq)]
116132 pub struct Verdict {
180196 /// The pull request comment: what changed, per lockfile, and why the
181197 /// check failed if it did. Markdown.
182198 pub fn summary(&self, policy: &Policy) -> String {
183− let mut text = String::from("### Dependency review\n\n");
184199 if self.added.is_empty() && self.removed.is_empty() {
185− text.push_str("This pull request changes no dependencies.\n");
186− return text;
200+ return unchanged_summary();
187201 }
202+ let mut text = format!("{HEADING}\n\n");
188203 text.push_str(if self.passed { "**Passed.** " } else { "**Failed.** " });
189204 text.push_str(&self.headline());
190205 text.push_str(".\n\n");
296311 }
297312
298313 #[test]
314+ fn every_summary_starts_with_the_heading_g1t_finds_it_by() {
315+ let policy = Policy { fail_on: Some(Severity::High), deny_licenses: Vec::new() };
316+ let none = judge(&[], &BTreeMap::new(), &policy);
317+ assert_eq!(none.summary(&policy), unchanged_summary());
318+ let some = judge(&diff(&[], &[dep("chalk", "5.0.0", None)]), &BTreeMap::new(), &policy);
319+ assert!(unchanged_summary().starts_with(HEADING) && some.summary(&policy).starts_with(&format!("{HEADING}\n\n**Passed.**")));
320+ assert_eq!(key("a"), key("a"));
321+ assert_ne!(key("a"), key("b"));
322+ assert_eq!(key("a").len(), 32);
323+ }
324+
325+ #[test]
299326 fn the_review_fails_at_the_configured_severity() {
300327 let changes = diff(&[dep("lodash", "4.17.21", None)], &[dep("lodash", "4.17.20", None)]);
301328 let findings = BTreeMap::from([("pkg:npm/lodash@4.17.20".to_owned(), vec![finding(Severity::High)])]);
+1−0
458458 ["get_dependency_graph", "security:read"],
459459 ["get_sbom", "security:read"],
460460 ["compare_dependencies", "security:read"],
461+ ["rerun_dependency_review", "security:write"],
461462 ["get_security_settings", "security:read"],
462463 ["get_workspace_security_settings", "security:read"],
463464 ["get_security_overview", "security:read"],
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…

Comments

Sign in to comment.