Skip to content

Release Compliance

New here? Start with the Legal & Compliance Workflow to see how this tool (the dev-side audit) and Third-Party Notices (the shippable attribution) fit together.

Convenience tool, not legal advice — you own your attribution obligations. This automates the third-party UPM-package surface of attribution into reviewable, diffable artifacts. It does not cover trademark, marketing / AI-generation disclosures, or licenses for loose assets you drop into Assets/ (fonts, art, audio, .unitypackage plugins) — those remain yours to add. It does not guarantee legal compliance.

Release Compliance extends the Third-Party Notices generator. Alongside the existing THIRD_PARTY_NOTICES.txt, it produces two review-oriented artifacts and a persistent decisions file:

Artifact What it is
compliance-manifest.json Structured, per-component record: identity, version, source, direct/transitive, resolution method, confidence, evidence paths. Deterministic (sorted, no timestamps) so it diffs cleanly in review and CI.
CREDITS.md Concise human-readable attribution — one line per package, no full license text.
OxHeartComplianceOverrides.json Your persistent decisions (manual entries, per-field overrides, orphan handling). Stored outside the package (default ProjectSettings/) so a package upgrade can never overwrite it.

Generating

Tools ▸ OxHeart ▸ Legal ▸ Generate Compliance Artifacts writes compliance-manifest.json and CREDITS.md to Assets/Legal/ and reports what changed since the last run. Tools ▸ OxHeart ▸ Legal ▸ Preview Compliance Diff logs the added / removed / changed components without writing.

How components are resolved

  • Direct vs. transitive comes from Packages/packages-lock.json (depth 0 = direct).
  • License id is resolved from both available sources and never guessed:
    1. the package's package.json license field, if it declares one;
    2. the package's LICENSE file text, matched against distinctive licence markers — an explicit SPDX-License-Identifier: tag, or a canonical phrase of a known licence (MIT, Apache-2.0, BSD-2/3-Clause, ISC, MPL-2.0, the Unity Companion and Unity Package Distribution licences -> LicenseRef-Unity-*, etc.).

Both are read whenever both are present, and then reconciled:

package.json LICENSE file Result
declares a licence agrees, or is unrecognised that licence, high confidence
declares a licence identifies a different licence UNKNOWN, unknown confidence — both evidence paths are kept so you can see what disagreed
declares nothing identifies a licence that licence, high confidence
declares nothing unrecognised or absent UNKNOWN — fill it in with a field override

A component reported as UNKNOWN when its package.json clearly says MIT is therefore not a bug: it means the co-located LICENSE file says something else, and the tool refuses to pick a side for you. (Most Unity packages declare no package.json license, so the LICENSE file is what resolves them.) - Confidence is high when identity and a licence id both resolve, medium when identity is known but the licence is not, and unknown otherwise.

Overrides and orphans

Edit ProjectSettings/OxHeartComplianceOverrides.json (a template is created on first generate):

  • Manual components — add attribution for things not in the package graph (they win on an id collision, and are marked manual).
  • Field overrides — correct a single resolved field (e.g. set a licenseId a package failed to declare).
  • Orphan decisions — an orphan is a field override whose id resolution no longer produces, usually because the package was removed or renamed. map rebinds that override onto a detected replacement id ("id": "com.old.pkg", "decision": "map", "mapToId": "com.new.pkg"), so a licenseId you filled in by hand survives a package rename. keep and remove are recorded intent — they mark whether you have reviewed the orphan; neither adds or drops a component, because an orphaned field override has no component record to act on.

A decision is applied only when its id is genuinely absent from the detected set. If the package comes back, the decision goes dormant rather than deleting or renaming the live dependency — a stale remove can never silently drop a real component from the manifest.

Overrides are applied last and survive regeneration. Manual components are never reported as orphans: they exist precisely because resolution cannot find them.

Determinism and CI

Two generates with no project change produce byte-identical compliance-manifest.json and CREDITS.md (they carry no timestamps or machine paths), so you can commit them and let a diff in review or CI surface exactly which components were added, removed, or changed.

To keep that true on every machine, a package resolved outside the project folder (a local UPM dependency on a path elsewhere on disk) has its evidence recorded against the package id rather than its location on disk — <external>/com.example.foo/package.json, never the absolute disk path. The same dependency therefore produces identical evidence on your machine and on CI, wherever each has it checked out.

Both artifacts also carry the disclaimer and scope statement from the top of this page, so the machine-readable manifest cannot be separated from the caveat it ships under.

Build-time enforcement (opt-in)

On the Third-Party Notices manifest asset (also the home of the compliance output/overrides paths), two policies gate a player build — both Off by default:

  • On UNKNOWN license — Warn or Fail the build when any component's license is unresolved.
  • On changes — Warn or Fail when compliance changed since the last accepted compliance-manifest.json (or on the very first generation).

Enable Regenerate on build to rewrite the artifacts as part of each build. A Fail policy stops the build with a message naming the offending components; lower it to Warn/Off, or record the missing licenses in the overrides file, to proceed. (Convenience tool, not legal advice.)

Order matters, and it is deliberate: policy is evaluated before anything is written. A build rejected by a Fail policy leaves the accepted compliance-manifest.json byte-for-byte unchanged, so retrying the build fails again until you accept the new state explicitly — by running Generate Compliance Artifacts from the menu, or by recording a decision in the overrides file. If the regeneration ran first, a rejected build would overwrite its own baseline and the retry would compare the new state against itself, find nothing changed, and pass. The gate would clear itself.