auditDependencies function

List<SbomIssue> auditDependencies({
  1. required List<DependencyEntry> entries,
  2. required Map<String, LicenseCategory> licenseByPackage,
  3. required int nowMs,
  4. List<VulnerabilityAdvisory> advisories = const [],
  5. List<SbomSuppression> suppressions = const [],
  6. Set<String> unpinnedPackages = const {},
})

Cross-checks entries against license classifications, an optional advisories list, and pinnedOnly policy, returning every issue not covered by an unexpired SbomSuppression.

licenseByPackage maps a hosted package's name to its classifyLicenseText result — a package this map has no entry for (e.g. its LICENSE file couldn't be read) is treated as LicenseCategory.unknown, same as an empty/unparseable one.

Implementation

List<SbomIssue> auditDependencies({
  required List<DependencyEntry> entries,
  required Map<String, LicenseCategory> licenseByPackage,
  required int nowMs,
  List<VulnerabilityAdvisory> advisories = const [],
  List<SbomSuppression> suppressions = const [],
  Set<String> unpinnedPackages = const {},
}) {
  bool suppressed(String package, SbomIssueKind kind) => suppressions.any(
    (s) => s.package == package && s.kind == kind && s.expiresAtMs > nowMs,
  );

  final issues = <SbomIssue>[];

  for (final entry in entries) {
    if (entry.source != DependencySource.hosted) continue;
    final category = licenseByPackage[entry.name] ?? LicenseCategory.unknown;
    if (category == LicenseCategory.copyleft &&
        !suppressed(entry.name, SbomIssueKind.copyleftLicense)) {
      issues.add(
        SbomIssue(
          package: entry.name,
          kind: SbomIssueKind.copyleftLicense,
          severity: AdvisorySeverity.high,
          detail:
              'license copyleft (GPL/LGPL/AGPL họ) — cần review pháp lý trước khi publish',
        ),
      );
    } else if (category == LicenseCategory.unknown &&
        !suppressed(entry.name, SbomIssueKind.unknownLicense)) {
      issues.add(
        SbomIssue(
          package: entry.name,
          kind: SbomIssueKind.unknownLicense,
          severity: AdvisorySeverity.low,
          detail:
              'không xác định được license (thiếu LICENSE file hoặc không khớp header đã biết)',
        ),
      );
    }
  }

  for (final advisory in advisories) {
    final match = entries.any(
      (e) => e.name == advisory.package && e.version == advisory.version,
    );
    if (match && !suppressed(advisory.package, SbomIssueKind.vulnerability)) {
      issues.add(
        SbomIssue(
          package: advisory.package,
          kind: SbomIssueKind.vulnerability,
          severity: advisory.severity,
          detail: advisory.description,
        ),
      );
    }
  }

  for (final package in unpinnedPackages) {
    if (!suppressed(package, SbomIssueKind.unpinnedConstraint)) {
      issues.add(
        SbomIssue(
          package: package,
          kind: SbomIssueKind.unpinnedConstraint,
          severity: AdvisorySeverity.medium,
          detail:
              'pubspec.yaml không giới hạn version (any) — có thể resolve bất kỳ bản nào kể cả bản hỏng/độc hại',
        ),
      );
    }
  }

  return issues;
}