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,.unitypackageplugins) — 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(depth0 = direct). - License id is resolved from both available sources and never guessed:
- the package's
package.jsonlicensefield, if it declares one; - the package's
LICENSEfile text, matched against distinctive licence markers — an explicitSPDX-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.).
- the package's
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
licenseIda 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.
maprebinds that override onto a detected replacement id ("id": "com.old.pkg", "decision": "map", "mapToId": "com.new.pkg"), so alicenseIdyou filled in by hand survives a package rename.keepandremoveare 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 —
WarnorFailthe build when any component's license is unresolved. - On changes —
WarnorFailwhen compliance changed since the last acceptedcompliance-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.