Skip to content
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
test-output.txt
TestResults/SightAdapt.trx
src/SightAdapt/obj/project.assets.json
tests/SightAdapt.Tests/obj/project.assets.json
if-no-files-found: warn
retention-days: 7

Expand Down Expand Up @@ -106,6 +107,12 @@ jobs:
.\tools\generate-sbom.ps1
-PublishDirectory artifacts/win-x64

- name: Verify unknown transitive licenses are rejected
shell: pwsh
run: >-
.\tools\test-sbom-license-negative.ps1
-PublishDirectory artifacts/win-x64

- name: Verify incomplete, stale and unmapped packages are rejected
shell: pwsh
run: >-
Expand Down
13 changes: 10 additions & 3 deletions docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dotnet test .\tests\SightAdapt.Tests\SightAdapt.Tests.csproj `
--no-restore
```

Both `project.assets.json` files are required later. The SBOM generator reads all package entries and dependency edges from the application and test restore graphs, not only direct `PackageReference` elements.

## 3. Publish and capture bundle inputs

```powershell
Expand Down Expand Up @@ -82,14 +84,19 @@ The step also adds exact package notice sections for components not covered by t
-PublishDirectory .\artifacts\win-x64
```

The generator writes `DEPENDENCIES.md`, schema-2 `LICENSE-REPORT.json` and SPDX 2.3 `SBOM.spdx.json` from the same inventory. For every NuGet package it retains exact package SHA-512 and `.nuspec` SHA-256 plus declared license metadata. Direct and transitive application/test packages are discovered automatically; the dependency policy supplies decisions and custom overrides rather than the inventory itself.

## 8. Run negative checks

```powershell
.\tools\test-sbom-license-negative.ps1 `
-PublishDirectory .\artifacts\win-x64

.\tools\test-release-compliance-negative.ps1 `
-PublishDirectory .\artifacts\win-x64
```

The validators must reject incomplete, stale and deliberately unmapped packages.
The validators must reject a transitive package with no resolved license, an incomplete package, stale redistribution metadata and a package with a deliberately removed component mapping.

## 9. Create and verify the final ZIP

Expand All @@ -111,7 +118,7 @@ Compress-Archive `
-ArchivePath $archive
```

Retain the verified archive and compliance report together.
The compliance gate checks license evidence, package counts, graph edges and scope-aware SPDX relationships in addition to the existing package invariants. Retain the verified archive and compliance report together.

## Expected package root

Expand Down Expand Up @@ -140,4 +147,4 @@ Remove-Item .\artifacts\SightAdapt-*-win-x64.zip -Force -ErrorAction SilentlyCon
Remove-Item .\artifacts\SightAdapt-*-win-x64-compliance.json -Force -ErrorAction SilentlyContinue
```

Do not publish when metadata review, notice import, component coverage, SBOM/license review, negative checks or final-package validation fails.
Do not publish when metadata review, notice import, component coverage, complete dependency/license review, negative checks or final-package validation fails.
41 changes: 30 additions & 11 deletions docs/PACKAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@ The machine-readable source of truth is `release/required-files.txt`.
| `DOTNET-NOTICE-METADATA.json` | Exact package, component, notice and checksum evidence |
| `MICROSOFT-DOTNET-REDISTRIBUTION.txt` | Generated maintainer-reviewed redistribution notice |
| `THIRD-PARTY-NAMES-AND-DRM-NOTICE.txt` | Mark ownership, no-endorsement and DRM/access-control boundaries |
| `DEPENDENCIES.md` | Human-readable dependency inventory |
| `SBOM.spdx.json` | SPDX 2.3 component and shipped-file inventory |
| `LICENSE-REPORT.json` | Dependency-license policy result |
| `DEPENDENCIES.md` | Human-readable dependency inventory generated from complete restore graphs |
| `SBOM.spdx.json` | SPDX 2.3 package graph and shipped-file inventory |
| `LICENSE-REPORT.json` | Schema-2 dependency-license evidence and policy result |
| `PRIVACY.md` | Application privacy and local-data notice |

A package is incomplete if a required file is missing, unreadable, stale, inconsistent with pinned inputs or contains a binary without exact component notice evidence.
A package is incomplete if a required file is missing, unreadable, stale, inconsistent with pinned inputs, contains a binary without exact component notice evidence or has an unresolved dependency/license result.

## Publish sequence

1. `SightAdapt.csproj` copies the static legal baseline and captures SDK `FilesToBundle` inputs.
2. `generate-dotnet-notices.ps1` imports exact official .NET legal text.
3. `generate-dotnet-component-coverage.ps1` maps all embedded and loose package components and adds package-specific notices.
4. `generate-dotnet-redistribution-notice.ps1` renders the reviewed redistribution summary.
5. `generate-sbom.ps1` generates the dependency inventory, SPDX SBOM and license report.
5. `generate-sbom.ps1` traverses complete application/test restore graphs, collects package license evidence and generates `DEPENDENCIES.md`, `LICENSE-REPORT.json` and `SBOM.spdx.json`.
6. Negative tests prove rejection of incomplete, stale, unmapped and unknown-license inputs.

## Exact component coverage

Expand All @@ -50,6 +51,20 @@ The reviewed alpha contains 452 mapped package components across:

`Microsoft.Windows.SDK.NET.Ref` is classified as `shipped-embedded`, because `Microsoft.Windows.SDK.NET.dll` and `WinRT.Runtime.dll` are present in `FilesToBundle`.

## Complete dependency and license inventory

The SBOM generator uses both application and test `project.assets.json` files. Every NuGet package in `libraries`, every package dependency edge in `targets`, every framework download dependency, each package contributing published components, the pinned SDK and each maintained GitHub Action is inventoried.

For NuGet packages the generated evidence retains exact package SHA-512, `.nuspec` SHA-256, declared license metadata, packaged-license SHA-256 where applicable, repository information and direct/transitive scope. `release/dependency-policy.json` supplies allow/deny/review decisions and selected custom-license overrides; it does not define which packages exist.

SPDX root relationships distinguish scopes:

- shipped packages: `SightAdapt DEPENDS_ON package`;
- test-only packages: `package TEST_DEPENDENCY_OF SightAdapt`;
- SDK, Actions, restore-only and application-build packages: `package BUILD_DEPENDENCY_OF SightAdapt`.

Package-to-package restore edges remain `DEPENDS_ON` and identify the originating graph.

## Final package validation

```powershell
Expand All @@ -75,12 +90,16 @@ The general compliance gate validates package, metadata, license-report and SBOM
## Negative checks

```powershell
.\tools\test-sbom-license-negative.ps1 `
-PublishDirectory '.\artifacts\win-x64'

.\tools\test-release-compliance-negative.ps1 `
-PublishDirectory '.\artifacts\win-x64'
```

The workflow proves rejection of:

- a transitive package with valid package metadata but no declared/resolved license;
- an incomplete package;
- stale redistribution metadata;
- a package containing a real runtime DLL after its component mapping is deliberately removed.
Expand All @@ -92,16 +111,16 @@ The same legal/compliance bundle is required for Actions artifacts, manual ZIPs,
## Release checklist

1. verify canonical release metadata and maintainer decision;
2. restore, build and test;
3. publish and capture `FilesToBundle`;
2. restore application and test projects;
3. build, test, publish and capture `FilesToBundle`;
4. import exact official .NET notices;
5. generate exact package/component coverage;
6. generate the redistribution summary;
7. generate SBOM, license report and dependency inventory;
8. resolve every policy or component-mapping failure;
9. prove incomplete, stale and unmapped packages are rejected;
7. generate complete SBOM, license report and dependency inventory;
8. resolve every unknown, denied, review-required or evidence failure;
9. prove unknown-transitive, incomplete, stale and unmapped inputs are rejected;
10. create and run both validators against the final package;
11. retain the package and compliance report;
12. publish identical verified bytes to official mirrors.

Do not publish when any notice, package hash, component mapping, SBOM, license, metadata or maintainer-decision check fails.
Do not publish when any notice, package hash, component mapping, restore graph, SBOM, license, metadata or maintainer-decision check fails.
83 changes: 54 additions & 29 deletions docs/legal/SBOM-AND-LICENSE-REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,82 @@

## Scope

Every SightAdapt release candidate produces a machine-readable SPDX 2.3 software bill of materials and a machine-readable license-policy report. These files describe the published application container, exact .NET runtime packs, application/test/build dependencies and every file placed in the final publish directory.
Every SightAdapt release candidate produces a machine-readable SPDX 2.3 software bill of materials, a machine-readable license-evidence report and a human-readable dependency summary. The inventory covers the published application, exact embedded/runtime packages, every application package, the complete test graph, SDK/build inputs and GitHub Actions used by the maintained workflow.

This process is supply-chain and release documentation. It does not change SightAdapt application behavior.
This process is supply-chain and release documentation. It does not change SightAdapt application behavior and does not claim that an internal license decision is external legal advice.

## Authoritative inputs
## Authoritative inventory inputs

`tools/generate-sbom.ps1` reads:

- `Directory.Build.props` for the product, SDK, runtime and RID versions;
- `src/SightAdapt/obj/project.assets.json` for the actual application restore graph;
- `DOTNET-NOTICE-METADATA.json` for exact runtime-pack and Microsoft source-package evidence;
- application and test project files for direct NuGet references;
- `.github/workflows/build.yml` for GitHub Actions build dependencies;
- `release/dependency-policy.json` for reviewed suppliers, licenses, versions, scopes and allow/deny/review decisions;
- `src/SightAdapt/obj/project.assets.json`, including every package in `libraries` and every package/dependency edge in `targets`;
- `tests/SightAdapt.Tests/obj/project.assets.json`, including direct and transitive test packages and their graph edges;
- `DOTNET-NOTICE-METADATA.json` for exact packages and component hashes proven to be embedded in `SightAdapt.exe` or shipped as loose binaries;
- `.github/workflows/build.yml` for maintained build actions;
- `Directory.Build.props` for product, SDK, runtime and RID identity;
- `release/dependency-policy.json` for license allow/deny/review decisions, version constraints and explicit custom-license overrides;
- the final publish directory for file names and SHA-256 checksums.

A component not present in the policy is unresolved and fails the release step. A component version different from the reviewed version also fails. Denied or unreviewed license expressions fail before archive creation.
The policy is an override and decision layer, not the component inventory. A new direct or transitive NuGet package is discovered from restore data even when it has no policy entry.

## Generated files
## NuGet license evidence

For every discovered NuGet package the generator locates the exact restored package and records:

- exact package identity and NuGet SHA-512;
- SHA-256 of the exact `.nuspec` file;
- declared license expression, license-file path or license URL from `.nuspec`;
- SHA-256 of a packaged license file when one is declared;
- package repository URL and commit where supplied;
- package authors/supplier evidence.

An SPDX expression present in exact package metadata is evaluated directly against the allow/deny/review lists. A policy override is required only for package-specific or custom terms that cannot be represented by the package's expression alone. Missing package cache data, missing `.nuspec`, unknown licenses, denied licenses and review-required licenses fail CI.

The generator writes these files at the package root:
Exact published Microsoft components continue to use the component-level evidence produced by #83. The runtime packs and `Microsoft.Windows.SDK.NET.Ref` retain exact package SHA-512, component SHA-256 and package-specific notice mappings.

## Generated files

| File | Purpose |
|---|---|
| `SBOM.spdx.json` | SPDX 2.3 document containing packages, scopes, suppliers, versions, declared/concluded licenses, source references, file inventory, relationships and checksums |
| `LICENSE-REPORT.json` | License-policy result with allowed, denied and review lists plus the evaluated component inventory and failures |
| `DEPENDENCIES.md` | Human-readable summary generated from the same evaluated component list |
| `SBOM.spdx.json` | SPDX 2.3 document containing every package, complete dependency edges, scopes, versions, suppliers, licenses, purls, package checksums and packaged-file SHA-256 values |
| `LICENSE-REPORT.json` | Schema-2 inventory and policy result containing direct/transitive classification, evidence hashes, application/test graph counts and failures |
| `DEPENDENCIES.md` | Human-readable summary generated from the same authoritative component list |

The SBOM excludes only its own final bytes from the file inventory. `SightAdapt.exe` is the documented single-file container for embedded components; separately shipped files remain individually hashed.

The SBOM intentionally treats `SightAdapt.exe` as the single-file publication container. Runtime packs embedded by self-contained single-file publication are represented as package components and related to the SightAdapt package. Every separately shipped file is represented with its SHA-256 checksum. The SBOM excludes only itself from its file list because a file cannot contain a stable checksum of its own final bytes.
## SPDX relationship rules

The document describes the SightAdapt package. Relationships are intentionally scope-aware:

- packages proven to be shipped are represented as `SightAdapt DEPENDS_ON <package>`;
- test-only packages are represented as `<package> TEST_DEPENDENCY_OF SightAdapt`;
- SDK, Actions, restore-only and application-build packages are represented as `<package> BUILD_DEPENDENCY_OF SightAdapt`;
- package-to-package dependency edges from each restore target use `DEPENDS_ON` and retain the originating application or test graph in the relationship comment;
- SightAdapt `CONTAINS` every separately packaged file.

Build and test tools are therefore not presented as runtime dependencies of the delivered application.

## License policy

`release/dependency-policy.json` is the review source of truth. It defines:
`release/dependency-policy.json` schema 2 defines:

- allowed license expressions;
- permissive expressions approved by maintainers;
- explicitly denied expressions;
- expressions requiring review;
- expected component versions or version sources;
- supplier and source information;
- whether a component is shipped, restore-only, test-only or build-only.
- expressions requiring explicit review;
- custom `LicenseRef` records included in the SPDX document;
- version and supplier overrides for selected components;
- package-specific conclusions where exact package metadata uses a file, URL or custom terms.

Custom Microsoft `LicenseRef` expressions point to the exact Microsoft license, redistribution and third-party notice files included in the same package. They do not relicense Microsoft components under MIT.
A package absent from `components` is allowed only when its exact metadata resolves to an allowed license. Absence from the policy never removes it from the inventory.

## Release behavior
## Negative test

The maintained workflow runs SBOM generation after self-contained publication and exact-version .NET notice generation, but before ZIP creation. A failed component or license review prevents archive publication. `release/required-files.txt` requires the SBOM and report in every final distribution, so GitHub artifacts, manual ZIPs, installers, store packages, portable packages and mirrors must preserve them.
`tools/test-sbom-license-negative.ps1` adds a deliberately transitive package to a copy of the real test restore graph. The fixture has valid package/nuspec checksums but no license metadata. CI must reject it specifically as an unresolved transitive license.

The final ZIP retained for a release therefore contains the exact SBOM, license report and human-readable dependency inventory associated with the shipped bytes.
## Release behavior

## Maintenance
The workflow restores both projects, retains both `project.assets.json` files in diagnostics, generates the real SBOM/license report, runs the unknown-transitive negative test and stops before archive creation on any failure. The final package contains the exact `SBOM.spdx.json`, `LICENSE-REPORT.json` and `DEPENDENCIES.md` associated with its bytes.

Update the dependency policy when any package, runtime pack, SDK, GitHub Action, supplier, license or version changes. Do not add an unknown component with an assumed license. Review the authoritative package/repository terms, record the result in the policy and preserve any required notices.
## Maintenance

The SBOM is evidence of composition. It does not replace legal review of ambiguous, custom or conflicting terms.
When dependencies change, do not add a handwritten inventory entry. Restore both projects and let the generator discover the graph. Update the policy only when a version constraint, supplier decision, custom license conclusion or allow/deny/review rule changes. Preserve additional notice text when a newly discovered package requires it.
Loading
Loading