diff --git a/compliance/README.md b/compliance/README.md new file mode 100644 index 0000000..b00afae --- /dev/null +++ b/compliance/README.md @@ -0,0 +1,26 @@ +# Model and runtime asset inventory + +`model-assets.json` is the machine-readable source of truth for the model and +model-runtime compliance work in issue #108. It records what upstream projects +say at exact revisions, how LSDJ obtains each asset, and which decisions still +require project-owner review. It does not approve a use or provide legal advice. + +Run the dependency-free validator and its mutation tests with: + +```sh +python3 compliance/validate_inventory.py +python3 -m unittest compliance.test_inventory +``` + +The validator fails for missing required fields, branch-like or short revisions, +revision URLs that do not contain the pinned hash, unsafe installer settings, +unresolved dependency/catalog IDs, and mutable runtime download behavior that is +not kept as a release gate. An upstream source revision may be recorded as +`unresolved_upstream` only with a null value, a canonical evidence URL, and an +explicit release gate; this avoids inventing precision the reviewed evidence does +not support. + +When a runtime or model revision changes, update this manifest, the human notice +document, and the release checklist in the same pull request. Evidence URLs for +versioned source/model artifacts should point at the exact commit or snapshot; +policy URLs may remain canonical because upstream policies are living documents. diff --git a/compliance/__init__.py b/compliance/__init__.py new file mode 100644 index 0000000..1624ffc --- /dev/null +++ b/compliance/__init__.py @@ -0,0 +1 @@ +"""Compliance inventory validation helpers.""" diff --git a/compliance/model-assets.json b/compliance/model-assets.json new file mode 100644 index 0000000..cc278f6 --- /dev/null +++ b/compliance/model-assets.json @@ -0,0 +1,1077 @@ +{ + "schema_version": 1, + "inventory_revision": "2026-08-08.2", + "audited_at": "2026-08-08", + "audit_base_revision": "c9cd822ef6cbb86711e72d35f0f7e50a126d666f", + "purpose": "Revision-specific technical provenance and notice inputs for LSDJ model and model-runtime assets. This inventory records upstream statements and unresolved owner-review gates; it is not legal advice or a project-use approval.", + "project_use": { + "reported_context": "GitHub issue #108 describes LSDJ as open-source and non-commercial.", + "owner_confirmation_status": "pending", + "future_distribution_or_commercial_change_is_release_gate": true, + "public_record_must_exclude": [ + "revenue details", + "account details", + "contracts", + "credentials" + ] + }, + "catalogs": { + "bundled_lora_ids": [], + "official_lora_ids": [], + "documented_reference_lora_ids": [ + "motif-maqam-lora" + ], + "note": "The application has no built-in or Stability-AI-official LoRA catalog at the audited revision. The Motif Maqam adapter is the sole public adapter named in LSDJ documentation and tests, so it is inventoried independently as a documented reference, not represented as an official Stability AI artifact." + }, + "assets": [ + { + "id": "lsdj-source", + "name": "LSDJ application source", + "family": "lsdj", + "asset_type": "application_code", + "support_status": "current", + "upstream": { + "project": "LSDJ", + "canonical_url": "https://github.com/protocol-works/lsdj" + }, + "revision": { + "kind": "git_commit", + "value": "c9cd822ef6cbb86711e72d35f0f7e50a126d666f", + "url": "https://github.com/protocol-works/lsdj/tree/c9cd822ef6cbb86711e72d35f0f7e50a126d666f" + }, + "licenses": { + "code": [ + { + "status": "unresolved", + "identifier": "NOASSERTION", + "name": "No project LICENSE or NOTICE file found at the audited revision", + "scope": "LSDJ-authored application code", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "This entry is application code, not model weights", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [], + "attribution": [], + "sources": [] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "bundled_in_application", + "source_url": "https://github.com/protocol-works/lsdj/tree/c9cd822ef6cbb86711e72d35f0f7e50a126d666f", + "installer_contains_asset": true, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "Project owners must select and add the LSDJ code license before a public source or binary release can claim a license." + }, + "dependencies": [], + "owner_review": { + "required": true, + "status": "pending", + "question": "Select the LSDJ code license and copyright/notice text, then confirm how it is carried in source and packaged applications.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://github.com/protocol-works/lsdj/tree/c9cd822ef6cbb86711e72d35f0f7e50a126d666f" + ] + }, + { + "id": "google-mrt2-runtime-code", + "name": "Google Magenta RealTime 2 Python runtime", + "family": "magenta-realtime-2", + "asset_type": "runtime_code", + "support_status": "current", + "upstream": { + "project": "magenta/magenta-realtime", + "canonical_url": "https://github.com/magenta/magenta-realtime" + }, + "revision": { + "kind": "git_commit", + "value": "4bf995bdd9c29b818543574e1b3a6e67867c9a58", + "url": "https://github.com/magenta/magenta-realtime/tree/4bf995bdd9c29b818543574e1b3a6e67867c9a58" + }, + "package": { + "name": "magenta-rt", + "version": "2.0.2", + "url": "https://pypi.org/project/magenta-rt/2.0.2/", + "wheel_sha256": "92df64a8150a6bff85ab9a0db0b54929a69b2538949df72677af2dc25a6a3845", + "sdist_sha256": "4f6241955094e5c38142deef820a03ce8e64bd1b300223e0d78a9195056c9b98" + }, + "licenses": { + "code": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "Magenta RealTime source and Python package", + "terms_url": "https://github.com/magenta/magenta-realtime/blob/4bf995bdd9c29b818543574e1b3a6e67867c9a58/LICENSE", + "notice_url": null + } + ], + "weights": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Weights are inventoried separately", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [ + "Include the Apache-2.0 license with redistributed code and preserve applicable copyright, patent, trademark, and attribution notices." + ], + "attribution": [ + "Copyright 2026 Google LLC" + ], + "sources": [ + "https://github.com/magenta/magenta-realtime/blob/4bf995bdd9c29b818543574e1b3a6e67867c9a58/LICENSE" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "bundled_runtime_dependency", + "source_url": "https://github.com/magenta/magenta-realtime/tree/4bf995bdd9c29b818543574e1b3a6e67867c9a58", + "installer_contains_asset": true, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "The package is hash-locked in backend/uv.lock. Owner review must confirm the packaged third-party notice location before release." + }, + "dependencies": [ + "google-mrt2-weights" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm the Apache-2.0 runtime notice is included in every packaged sidecar/application artifact.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://github.com/magenta/magenta-realtime/tree/4bf995bdd9c29b818543574e1b3a6e67867c9a58", + "https://pypi.org/project/magenta-rt/2.0.2/" + ] + }, + { + "id": "google-mrt2-weights", + "name": "Google Magenta RealTime 2 weights and shared resources", + "family": "magenta-realtime-2", + "asset_type": "model_weights", + "support_status": "current", + "upstream": { + "project": "google/magenta-realtime-2", + "canonical_url": "https://huggingface.co/google/magenta-realtime-2" + }, + "revision": { + "kind": "model_snapshot", + "value": "010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc", + "url": "https://huggingface.co/google/magenta-realtime-2/tree/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc" + }, + "included_artifacts": [ + "models/mrt2_base/*", + "models/mrt2_small/*", + "resources/musiccoca/*", + "resources/spectrostream/*" + ], + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Runtime code is inventoried separately", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "declared", + "identifier": "CC-BY-4.0", + "name": "Creative Commons Attribution 4.0 International", + "scope": "MRT2 model weights and resource models in the Google snapshot", + "terms_url": "https://creativecommons.org/licenses/by/4.0/legalcode", + "notice_url": "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md" + } + ] + }, + "notices": { + "required_text": [ + "Attribute Google DeepMind and link the CC-BY-4.0 license and revision-specific model card.", + "Retain the model card's responsible-use statement with user-facing model notices." + ], + "attribution": [ + "Magenta RealTime 2 — Authors: Google DeepMind", + "Copyright 2026 Google LLC" + ], + "sources": [ + "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md", + "https://creativecommons.org/licenses/by/4.0/legalcode" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "downloaded_from_upstream", + "source_url": "https://huggingface.co/google/magenta-realtime-2/tree/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": false, + "release_gate": true, + "notes": "magenta-rt 2.0.2's Hugging Face downloader omits revision=, so the current application follows mutable repository state. A release must pin and verify this snapshot before download. Model weights must remain outside installers unless exact-revision redistribution is separately confirmed." + }, + "dependencies": [], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm the project-use and attribution path for CC-BY-4.0 weights and whether user acknowledgement is required before first download.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/google/magenta-realtime-2/tree/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc", + "https://github.com/magenta/magenta-realtime/blob/4bf995bdd9c29b818543574e1b3a6e67867c9a58/MODEL.md" + ] + }, + { + "id": "pytorch-mrt2-port-code", + "name": "Apolinario / multimodalart Magenta RealTime 2 PyTorch port", + "family": "magenta-realtime-2-pytorch", + "asset_type": "runtime_code", + "support_status": "planned_conditional", + "upstream": { + "project": "multimodalart/magenta-realtime-torch", + "canonical_url": "https://github.com/multimodalart/magenta-realtime-torch" + }, + "revision": { + "kind": "git_commit", + "value": "6d076baa3df3b10448876c400521a015a5137c59", + "url": "https://github.com/multimodalart/magenta-realtime-torch/tree/6d076baa3df3b10448876c400521a015a5137c59" + }, + "licenses": { + "code": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "PyTorch port source code", + "terms_url": "https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE", + "notice_url": null + } + ], + "weights": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Converted weights are inventoried separately", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [ + "Include the Apache-2.0 license with redistributed port code and preserve applicable upstream notices." + ], + "attribution": [ + "PyTorch port by multimodalart / fffiloni (Apolinario)" + ], + "sources": [ + "https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "planned_bundled_runtime_dependency", + "source_url": "https://github.com/multimodalart/magenta-realtime-torch/tree/6d076baa3df3b10448876c400521a015a5137c59", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "Issue #109 found no PyTorch-specific release/tag. Production may consume only the audited commit/snapshots and remains conditional on hardware and license gates." + }, + "dependencies": [ + "pytorch-mrt2-base-weights", + "pytorch-mrt2-small-weights", + "pytorch-musiccoca-processor", + "google-mrt2-weights" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm the code notice path and that the port remains a thin pinned upstream dependency rather than copied code.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://github.com/multimodalart/magenta-realtime-torch/tree/6d076baa3df3b10448876c400521a015a5137c59", + "https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE" + ] + }, + { + "id": "pytorch-mrt2-base-weights", + "name": "Magenta community MRT2 base PyTorch snapshot", + "family": "magenta-realtime-2-pytorch", + "asset_type": "model_weights_and_remote_code", + "support_status": "planned_conditional", + "upstream": { + "project": "magenta-community/magenta-realtime-2", + "canonical_url": "https://huggingface.co/magenta-community/magenta-realtime-2" + }, + "revision": { + "kind": "model_snapshot", + "value": "92087988d05d0fe38b11f021f0b0d00a75afb86b", + "url": "https://huggingface.co/magenta-community/magenta-realtime-2/tree/92087988d05d0fe38b11f021f0b0d00a75afb86b" + }, + "licenses": { + "code": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "Snapshot remote-code files, per model-card metadata", + "terms_url": "https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE", + "notice_url": "https://huggingface.co/magenta-community/magenta-realtime-2/blob/92087988d05d0fe38b11f021f0b0d00a75afb86b/README.md" + } + ], + "weights": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "Converted snapshot model-card declaration", + "terms_url": "https://huggingface.co/magenta-community/magenta-realtime-2/blob/92087988d05d0fe38b11f021f0b0d00a75afb86b/README.md", + "notice_url": "https://huggingface.co/magenta-community/magenta-realtime-2/blob/92087988d05d0fe38b11f021f0b0d00a75afb86b/README.md" + }, + { + "status": "underlying", + "identifier": "CC-BY-4.0", + "name": "Creative Commons Attribution 4.0 International", + "scope": "Google source weights; the port card says these weights are re-keyed and numerically identical", + "terms_url": "https://creativecommons.org/licenses/by/4.0/legalcode", + "notice_url": "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md" + }, + { + "status": "unresolved", + "identifier": "NOASSERTION", + "name": "Effective converted-weight license and attribution path requires owner review", + "scope": "Conflict between Apache-2.0 model-card metadata and CC-BY-4.0 underlying weights", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [ + "Until reviewed, display both the converted snapshot's Apache-2.0 declaration and the Google source-weight CC-BY-4.0 provenance; do not imply that re-keying relicensed the weights." + ], + "attribution": [ + "Magenta RealTime 2 — Google DeepMind; PyTorch conversion by magenta-community / multimodalart" + ], + "sources": [ + "https://huggingface.co/magenta-community/magenta-realtime-2/blob/92087988d05d0fe38b11f021f0b0d00a75afb86b/README.md", + "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "planned_download_from_upstream", + "source_url": "https://huggingface.co/magenta-community/magenta-realtime-2/tree/92087988d05d0fe38b11f021f0b0d00a75afb86b", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "Download only from the exact snapshot. Do not bundle or mirror until the Apache model-card versus underlying CC-BY-4.0 ambiguity is explicitly resolved by project owners." + }, + "dependencies": [ + "google-mrt2-weights", + "pytorch-musiccoca-processor" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Resolve and record the effective redistribution, attribution, and acknowledgement path for Apache-labeled re-keyed weights derived from CC-BY-4.0 Google weights.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/magenta-community/magenta-realtime-2/tree/92087988d05d0fe38b11f021f0b0d00a75afb86b", + "https://huggingface.co/google/magenta-realtime-2/tree/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc" + ] + }, + { + "id": "pytorch-mrt2-small-weights", + "name": "Magenta community MRT2 small PyTorch snapshot", + "family": "magenta-realtime-2-pytorch", + "asset_type": "model_weights_and_remote_code", + "support_status": "planned_conditional", + "upstream": { + "project": "magenta-community/magenta-realtime-2-small", + "canonical_url": "https://huggingface.co/magenta-community/magenta-realtime-2-small" + }, + "revision": { + "kind": "model_snapshot", + "value": "7037d99551c84ac5c6afb7f1a5e58c65e7233dbb", + "url": "https://huggingface.co/magenta-community/magenta-realtime-2-small/tree/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb" + }, + "licenses": { + "code": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "Snapshot remote-code files, per model-card metadata", + "terms_url": "https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE", + "notice_url": "https://huggingface.co/magenta-community/magenta-realtime-2-small/blob/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb/README.md" + } + ], + "weights": [ + { + "status": "declared", + "identifier": "Apache-2.0", + "name": "Apache License 2.0", + "scope": "Converted snapshot model-card declaration", + "terms_url": "https://huggingface.co/magenta-community/magenta-realtime-2-small/blob/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb/README.md", + "notice_url": "https://huggingface.co/magenta-community/magenta-realtime-2-small/blob/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb/README.md" + }, + { + "status": "underlying", + "identifier": "CC-BY-4.0", + "name": "Creative Commons Attribution 4.0 International", + "scope": "Google source weights; the port card says these weights are re-keyed and numerically identical", + "terms_url": "https://creativecommons.org/licenses/by/4.0/legalcode", + "notice_url": "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md" + }, + { + "status": "unresolved", + "identifier": "NOASSERTION", + "name": "Effective converted-weight license and attribution path requires owner review", + "scope": "Conflict between Apache-2.0 model-card metadata and CC-BY-4.0 underlying weights", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [ + "Until reviewed, display both the converted snapshot's Apache-2.0 declaration and the Google source-weight CC-BY-4.0 provenance; do not imply that re-keying relicensed the weights." + ], + "attribution": [ + "Magenta RealTime 2 — Google DeepMind; PyTorch conversion by magenta-community / multimodalart" + ], + "sources": [ + "https://huggingface.co/magenta-community/magenta-realtime-2-small/blob/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb/README.md", + "https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "planned_download_from_upstream", + "source_url": "https://huggingface.co/magenta-community/magenta-realtime-2-small/tree/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "Download only from the exact snapshot. Do not bundle or mirror until the Apache model-card versus underlying CC-BY-4.0 ambiguity is explicitly resolved by project owners." + }, + "dependencies": [ + "google-mrt2-weights", + "pytorch-musiccoca-processor" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Resolve and record the effective redistribution, attribution, and acknowledgement path for Apache-labeled re-keyed weights derived from CC-BY-4.0 Google weights.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/magenta-community/magenta-realtime-2-small/tree/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb", + "https://huggingface.co/google/magenta-realtime-2/tree/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc" + ] + }, + { + "id": "pytorch-musiccoca-processor", + "name": "Magenta community MusicCoCa PyTorch processor", + "family": "magenta-realtime-2-pytorch", + "asset_type": "processor_weights", + "support_status": "planned_conditional", + "upstream": { + "project": "magenta-community/magenta-rt-musiccoca-torch", + "canonical_url": "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch" + }, + "revision": { + "kind": "model_snapshot", + "value": "236c488e38aa98643805514996934d705668298b", + "url": "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/tree/236c488e38aa98643805514996934d705668298b" + }, + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not separately declared", + "scope": "This inventory treats the repository as processor artifacts; conversion scripts need owner review if bundled", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "declared", + "identifier": "CC-BY-4.0", + "name": "Creative Commons Attribution 4.0 International", + "scope": "Converted MusicCoCa text encoder, quantizer, and tokenizer artifacts", + "terms_url": "https://creativecommons.org/licenses/by/4.0/legalcode", + "notice_url": "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/blob/236c488e38aa98643805514996934d705668298b/README.md" + } + ] + }, + "notices": { + "required_text": [ + "Attribute the Google MusicCoCa source and the magenta-community conversion; link CC-BY-4.0 and the exact processor card." + ], + "attribution": [ + "MusicCoCa from Google Magenta RealTime 2; PyTorch conversion by magenta-community" + ], + "sources": [ + "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/blob/236c488e38aa98643805514996934d705668298b/README.md" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "planned_download_from_upstream", + "source_url": "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/tree/236c488e38aa98643805514996934d705668298b", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "The #110 adapter must override the port's mutable default and resolve this exact snapshot locally." + }, + "dependencies": [ + "google-mrt2-weights" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm attribution and packaging rules for the CC-BY-4.0 converted processor artifacts and any bundled conversion code.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/tree/236c488e38aa98643805514996934d705668298b" + ] + }, + { + "id": "stable-audio-3-code", + "name": "Stable Audio 3 inference code", + "family": "stable-audio-3", + "asset_type": "runtime_code", + "support_status": "current_and_planned", + "upstream": { + "project": "Stability-AI/stable-audio-3", + "canonical_url": "https://github.com/Stability-AI/stable-audio-3" + }, + "revision": { + "kind": "git_commit", + "value": "0385302ea26522f00c80392c4b708df5ebf1adf5", + "url": "https://github.com/Stability-AI/stable-audio-3/tree/0385302ea26522f00c80392c4b708df5ebf1adf5" + }, + "licenses": { + "code": [ + { + "status": "declared", + "identifier": "MIT", + "name": "MIT License", + "scope": "Stable Audio 3 source, including the MLX and TFLite runtime code at the pinned commit", + "terms_url": "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/LICENSE", + "notice_url": "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/LICENSE" + } + ], + "weights": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Weights are inventoried separately", + "terms_url": null, + "notice_url": null + } + ] + }, + "notices": { + "required_text": [ + "Include the MIT copyright and permission notice in copies or substantial portions of the Stable Audio 3 software." + ], + "attribution": [ + "Copyright (c) 2026 Stability AI" + ], + "sources": [ + "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/LICENSE" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": false, + "privacy_url": null, + "acceptable_use_url": null + }, + "distribution": { + "mode": "downloaded_from_upstream", + "source_url": "https://github.com/Stability-AI/stable-audio-3/archive/0385302ea26522f00c80392c4b708df5ebf1adf5.tar.gz", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": true, + "release_gate": true, + "notes": "The current model manager downloads the exact source archive recorded in sa3-pin.json. Packaged notices still need owner confirmation." + }, + "dependencies": [ + "stable-audio-3-optimized-weights" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm the MIT notice is present in source, About/Licenses, and any package that includes or downloads this runtime.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://github.com/Stability-AI/stable-audio-3/tree/0385302ea26522f00c80392c4b708df5ebf1adf5", + "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/LICENSE" + ] + }, + { + "id": "stable-audio-3-optimized-weights", + "name": "Stable Audio 3 optimized MLX and TFLite weights", + "family": "stable-audio-3", + "asset_type": "model_weights", + "support_status": "current_and_planned", + "upstream": { + "project": "stabilityai/stable-audio-3-optimized", + "canonical_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized" + }, + "revision": { + "kind": "model_snapshot", + "value": "6736003cb57d06b7b1fdc36fad31b2a3709e4774", + "url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/tree/6736003cb57d06b7b1fdc36fad31b2a3709e4774" + }, + "included_artifacts": [ + "MLX/dit_sm-music_f16.npz", + "MLX/dit_sm-sfx_f16.npz", + "MLX/dit_medium_f16.npz", + "MLX/same_s_encoder_f32.npz", + "MLX/same_s_decoder_f32.npz", + "MLX/same_l_encoder_f32.npz", + "MLX/same_l_decoder_f32.npz", + "MLX/t5gemma_f16.npz", + "tflite/sa3-sm-music/*", + "tflite/sa3-sm-sfx/*", + "tflite/sa3-m/*", + "tflite/same-s/*", + "tflite/same-l/*", + "tflite/t5gemma/encoder_fp16.tflite" + ], + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Runtime code is inventoried separately", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "underlying", + "identifier": "LicenseRef-Stability-AI-Community", + "name": "Stability AI Community License", + "scope": "Stable Audio 3 model, codec, conditioner, and optimized derivative weights", + "terms_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE.md", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE" + }, + { + "status": "underlying", + "identifier": "LicenseRef-Gemma-Terms", + "name": "Gemma Terms of Use", + "scope": "Redistributed T5Gemma-derived encoder weights and tokenizer components inside the optimized snapshot", + "terms_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE" + } + ] + }, + "notices": { + "required_text": [ + "This Stability AI Model is licensed under the Stability AI Community License, Copyright © Stability AI Ltd. All Rights Reserved", + "Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms", + "Display “Powered by Stability AI” in an applicable user-facing or documentation surface." + ], + "attribution": [ + "Stable Audio 3 by Stability AI", + "T5Gemma text encoder from Google" + ], + "sources": [ + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE.md", + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md", + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": true, + "privacy_url": "https://stability.ai/privacy-policy", + "acceptable_use_url": "https://stability.ai/use-policy" + }, + "distribution": { + "mode": "downloaded_from_upstream", + "source_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/tree/6736003cb57d06b7b1fdc36fad31b2a3709e4774", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": false, + "release_gate": true, + "notes": "The pinned runtime's weights.py calls hf_hub_download without revision=. Current downloads therefore follow mutable repository state even though this audit records a snapshot. Pin and verify before release. Non-gated anonymous access does not remove Stability Community or Gemma obligations. Do not put these weights in installers without exact-revision confirmation." + }, + "dependencies": [ + "t5gemma-b-b-ul2" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Confirm the open-source/non-commercial Stability Community path, Gemma derivative distribution path, exact notices, first-download acknowledgement, and no-bundle policy for this snapshot.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/stabilityai/stable-audio-3-optimized/tree/6736003cb57d06b7b1fdc36fad31b2a3709e4774", + "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/optimized/mlx/scripts/weights.py", + "https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/optimized/tflite/scripts/weights.py" + ] + }, + { + "id": "t5gemma-b-b-ul2", + "name": "Google T5Gemma B-B UL2", + "family": "t5gemma", + "asset_type": "model_weights_and_tokenizer", + "support_status": "underlying_component", + "upstream": { + "project": "google/t5gemma-b-b-ul2", + "canonical_url": "https://huggingface.co/google/t5gemma-b-b-ul2" + }, + "revision": { + "kind": "unresolved_upstream", + "value": null, + "url": "https://huggingface.co/google/t5gemma-b-b-ul2" + }, + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable to the derived encoder artifact tracked here", + "scope": "T5Gemma model and tokenizer assets", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "declared", + "identifier": "LicenseRef-Gemma-Terms", + "name": "Gemma Terms of Use", + "scope": "T5Gemma model weights, tokenizer, and model derivatives", + "terms_url": "https://ai.google.dev/gemma/terms", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md" + } + ] + }, + "notices": { + "required_text": [ + "Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms", + "Provide the Gemma terms and incorporated prohibited-use policy when distributing Gemma or a model derivative, subject to owner review of the exact path." + ], + "attribution": [ + "T5Gemma by Google" + ], + "sources": [ + "https://ai.google.dev/gemma/terms", + "https://ai.google.dev/gemma/prohibited_use_policy" + ] + }, + "access": { + "gated": true, + "account_required": true, + "credential_required": true, + "terms_acceptance_required": true, + "privacy_url": "https://policies.google.com/privacy", + "acceptable_use_url": "https://ai.google.dev/gemma/prohibited_use_policy" + }, + "distribution": { + "mode": "underlying_component_in_upstream_download", + "source_url": "https://huggingface.co/google/t5gemma-b-b-ul2", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": false, + "release_gate": true, + "notes": "The exact T5Gemma source revision used to produce Stability's optimized derivative is not identified by the pinned optimized repository and remains unresolved. Direct Hugging Face access is manually gated and requires an account, credentials, and terms acceptance. LSDJ currently receives the derivative inside Stability AI's non-gated snapshot; that convenience does not remove the Gemma terms. No credential should be required for the optimized source, and no token may be logged or stored in plaintext app data." + }, + "dependencies": [], + "owner_review": { + "required": true, + "status": "pending", + "question": "Identify or confirm that no exact T5Gemma source revision is available, then confirm the Gemma model-derivative distribution and acknowledgement path for T5Gemma embedded in Stable Audio optimized weights.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/google/t5gemma-b-b-ul2", + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md" + ] + }, + { + "id": "stable-audio-3-medium-source-weights", + "name": "Stable Audio 3 Medium upstream source family", + "family": "stable-audio-3", + "asset_type": "model_weights_provenance", + "support_status": "provenance_only", + "upstream": { + "project": "stabilityai/stable-audio-3-medium", + "canonical_url": "https://huggingface.co/stabilityai/stable-audio-3-medium" + }, + "revision": { + "kind": "unresolved_upstream", + "value": null, + "url": "https://huggingface.co/stabilityai/stable-audio-3-medium" + }, + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Model checkpoint provenance entry", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "declared", + "identifier": "LicenseRef-Stability-AI-Community", + "name": "Stability AI Community License", + "scope": "Base-family terms asserted by the pinned optimized model and relevant to the Maqam LoRA; exact conversion-source revision unresolved", + "terms_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE.md", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE" + }, + { + "status": "underlying", + "identifier": "LicenseRef-Gemma-Terms", + "name": "Gemma Terms of Use", + "scope": "T5Gemma component terms carried by the pinned optimized snapshot", + "terms_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE" + } + ] + }, + "notices": { + "required_text": [ + "Show the Stability AI Community License, Stability privacy link, Gemma terms, and incorporated use restrictions before any direct gated download." + ], + "attribution": [ + "Stable Audio 3 by Stability AI; T5Gemma component by Google" + ], + "sources": [ + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/README.md" + ] + }, + "access": { + "gated": true, + "account_required": true, + "credential_required": true, + "terms_acceptance_required": true, + "privacy_url": "https://stability.ai/privacy-policy", + "acceptable_use_url": "https://stability.ai/use-policy" + }, + "distribution": { + "mode": "not_directly_downloaded_by_lsdj", + "source_url": "https://huggingface.co/stabilityai/stable-audio-3-medium", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": false, + "release_gate": true, + "notes": "The optimized model and Maqam LoRA name this upstream model family, but the reviewed evidence does not identify the exact source revision used for conversion or training. LSDJ downloads the separately pinned optimized snapshot instead." + }, + "dependencies": [ + "t5gemma-b-b-ul2" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Identify or confirm that no exact Stable Audio 3 Medium source revision is available, then confirm that optimized weights and LoRA references carry the applicable base-model terms and notices.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/stabilityai/stable-audio-3-medium", + "https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/README.md" + ] + }, + { + "id": "motif-maqam-lora", + "name": "Motif Technologies Stable Audio 3 Maqam LoRA", + "family": "stable-audio-3-lora", + "asset_type": "lora", + "support_status": "documented_reference", + "upstream": { + "project": "motiftechnologies/stable-audio-3-maqam-lora", + "canonical_url": "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora" + }, + "revision": { + "kind": "model_snapshot", + "value": "3e1d9aa6fcb72a619b4ced00a240c5039f76daf0", + "url": "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/tree/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0" + }, + "licenses": { + "code": [ + { + "status": "not_applicable", + "identifier": "NONE", + "name": "Not applicable", + "scope": "Adapter weights and metadata only", + "terms_url": null, + "notice_url": null + } + ], + "weights": [ + { + "status": "unresolved", + "identifier": "NOASSERTION", + "name": "The model card says license: other but supplies no license text or redistribution grant", + "scope": "adapter_model.safetensors and adapter metadata", + "terms_url": null, + "notice_url": "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/blob/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0/README.md" + }, + { + "status": "underlying", + "identifier": "LicenseRef-Stability-AI-Community", + "name": "Stability AI Community License", + "scope": "Stable Audio 3 Medium base-model obligations; exact LoRA applicability is an owner-review question", + "terms_url": "https://huggingface.co/stabilityai/stable-audio-3-medium/blob/27b5a21b791b1b033d193a9e1e3ce78493f102f9/LICENSE.md", + "notice_url": "https://huggingface.co/stabilityai/stable-audio-3-medium/blob/27b5a21b791b1b033d193a9e1e3ce78493f102f9/NOTICE" + } + ] + }, + "notices": { + "required_text": [ + "Do not call this a built-in or Stability-AI-official adapter and do not mirror or redistribute it until the adapter author supplies or confirms a license.", + "For a user-initiated upstream download, show adapter provenance, exact revision, unresolved license status, and the Stable Audio 3 Medium base-model terms." + ], + "attribution": [ + "Fine-tuned by Motif Technologies; base model Stable Audio 3 Medium by Stability AI" + ], + "sources": [ + "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/blob/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0/README.md" + ] + }, + "access": { + "gated": false, + "account_required": false, + "credential_required": false, + "terms_acceptance_required": true, + "privacy_url": "https://huggingface.co/privacy", + "acceptable_use_url": "https://stability.ai/use-policy" + }, + "distribution": { + "mode": "user_initiated_download_from_upstream", + "source_url": "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/tree/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0", + "installer_contains_asset": false, + "installer_contains_weights": false, + "redistribution_confirmed": false, + "immutable_reference_enforced": false, + "release_gate": true, + "notes": "The current importer fetches resolve/main and therefore does not enforce this audited snapshot. Pin a revision before treating this as an LSDJ-provided download. The generic user-import flow remains separate and must show a responsibility/provenance notice." + }, + "dependencies": [ + "stable-audio-3-medium-source-weights" + ], + "owner_review": { + "required": true, + "status": "pending", + "question": "Obtain or identify the adapter's license and specific download/mirroring permission; until then, keep it a documented user-directed upstream reference only.", + "issue": "https://github.com/protocol-works/lsdj/issues/108" + }, + "evidence": [ + "https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/tree/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0" + ] + } + ] +} diff --git a/compliance/test_inventory.py b/compliance/test_inventory.py new file mode 100644 index 0000000..9e6cbf9 --- /dev/null +++ b/compliance/test_inventory.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import copy +import unittest +from pathlib import Path + +from compliance.validate_inventory import load, validate + + +MANIFEST = Path(__file__).with_name("model-assets.json") + + +class InventoryValidationTests(unittest.TestCase): + def setUp(self) -> None: + self.data = load(MANIFEST) + + def test_repository_inventory_is_valid(self) -> None: + self.assertEqual(validate(self.data), []) + + def test_missing_required_asset_field_is_rejected(self) -> None: + broken = copy.deepcopy(self.data) + del broken["assets"][0]["notices"] + self.assertTrue( + any("missing field 'notices'" in error for error in validate(broken)) + ) + + def test_mutable_revision_is_rejected(self) -> None: + broken = copy.deepcopy(self.data) + broken["assets"][0]["revision"] = { + "kind": "git_commit", + "value": "main", + "url": "https://github.com/protocol-works/lsdj/tree/main", + } + errors = validate(broken) + self.assertTrue(any("40-character hash" in error for error in errors)) + self.assertTrue(any("mutable branch" in error for error in errors)) + + def test_revision_url_must_name_exact_revision(self) -> None: + broken = copy.deepcopy(self.data) + broken["assets"][0]["revision"]["url"] = ( + "https://github.com/protocol-works/lsdj" + ) + self.assertTrue( + any( + "must contain the exact revision" in error for error in validate(broken) + ) + ) + + def test_unresolved_upstream_revision_is_explicit_and_gated(self) -> None: + broken = copy.deepcopy(self.data) + asset = next( + item for item in broken["assets"] if item["id"] == "t5gemma-b-b-ul2" + ) + asset["distribution"]["release_gate"] = False + errors = validate(broken) + self.assertTrue(any("must remain a release gate" in error for error in errors)) + + broken = copy.deepcopy(self.data) + asset = next( + item for item in broken["assets"] if item["id"] == "t5gemma-b-b-ul2" + ) + asset["revision"]["value"] = "97ea9b7e92738bb57437867277ae38e65345b8d7" + errors = validate(broken) + self.assertTrue(any("use null" in error for error in errors)) + + def test_non_object_asset_returns_errors_without_crashing(self) -> None: + broken = copy.deepcopy(self.data) + broken["assets"].append("not-an-object") + errors = validate(broken) + self.assertTrue(any("expected an object" in error for error in errors)) + + def test_non_object_nested_fields_return_errors_without_crashing(self) -> None: + broken = copy.deepcopy(self.data) + asset = next( + item for item in broken["assets"] if item["id"] == "t5gemma-b-b-ul2" + ) + asset["distribution"] = "not-an-object" + errors = validate(broken) + self.assertTrue( + any("distribution: expected an object" in error for error in errors) + ) + + broken = copy.deepcopy(self.data) + broken["assets"][0]["revision"] = "not-an-object" + errors = validate(broken) + self.assertTrue( + any("revision: expected an object" in error for error in errors) + ) + + def test_unhashable_nested_values_return_errors_without_crashing(self) -> None: + mutations = [ + ( + "project status", + lambda data: data["project_use"].__setitem__( + "owner_confirmation_status", {} + ), + "owner_confirmation_status: invalid status", + ), + ( + "revision kind", + lambda data: data["assets"][0]["revision"].__setitem__("kind", {}), + "revision.kind: invalid revision kind", + ), + ( + "license status", + lambda data: data["assets"][0]["licenses"]["code"][0].__setitem__( + "status", {} + ), + "status: invalid status", + ), + ( + "license identifier", + lambda data: data["assets"][1]["licenses"]["code"][0].__setitem__( + "identifier", {} + ), + "identifier: expected a string", + ), + ( + "owner status", + lambda data: data["assets"][0]["owner_review"].__setitem__( + "status", {} + ), + "owner_review.status: invalid status", + ), + ( + "dependency id", + lambda data: data["assets"][1]["dependencies"].append({}), + "dependencies: expected string asset ids", + ), + ( + "catalog id", + lambda data: data["catalogs"]["official_lora_ids"].append({}), + "official_lora_ids: expected string asset ids", + ), + ] + for label, mutate, expected in mutations: + with self.subTest(label=label): + broken = copy.deepcopy(self.data) + mutate(broken) + errors = validate(broken) + self.assertTrue(any(expected in error for error in errors), errors) + + def test_unconfirmed_weights_cannot_be_in_installer(self) -> None: + broken = copy.deepcopy(self.data) + asset = next( + item for item in broken["assets"] if item["id"] == "google-mrt2-weights" + ) + asset["distribution"]["installer_contains_weights"] = True + self.assertTrue( + any("unconfirmed weights" in error for error in validate(broken)) + ) + + def test_mutable_runtime_path_must_remain_release_gate(self) -> None: + broken = copy.deepcopy(self.data) + asset = next( + item for item in broken["assets"] if item["id"] == "google-mrt2-weights" + ) + asset["distribution"]["release_gate"] = False + self.assertTrue( + any("mutable runtime path" in error for error in validate(broken)) + ) + + def test_catalog_ids_must_resolve_to_loras(self) -> None: + broken = copy.deepcopy(self.data) + broken["catalogs"]["official_lora_ids"] = ["google-mrt2-weights"] + self.assertTrue(any("is not a LoRA" in error for error in validate(broken))) + + +if __name__ == "__main__": + unittest.main() diff --git a/compliance/validate_inventory.py b/compliance/validate_inventory.py new file mode 100644 index 0000000..c408c53 --- /dev/null +++ b/compliance/validate_inventory.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python3 +"""Validate the revision-specific model/runtime compliance inventory. + +This is deliberately standard-library-only so release jobs can run it before +installing any application dependencies. +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any +from urllib.parse import urlparse + + +ROOT_REQUIRED = { + "schema_version", + "inventory_revision", + "audited_at", + "audit_base_revision", + "purpose", + "project_use", + "catalogs", + "assets", +} +ASSET_REQUIRED = { + "id", + "name", + "family", + "asset_type", + "support_status", + "upstream", + "revision", + "licenses", + "notices", + "access", + "distribution", + "dependencies", + "owner_review", + "evidence", +} +LICENSE_REQUIRED = { + "status", + "identifier", + "name", + "scope", + "terms_url", + "notice_url", +} +ACCESS_REQUIRED = { + "gated", + "account_required", + "credential_required", + "terms_acceptance_required", + "privacy_url", + "acceptable_use_url", +} +DISTRIBUTION_REQUIRED = { + "mode", + "source_url", + "installer_contains_asset", + "installer_contains_weights", + "redistribution_confirmed", + "immutable_reference_enforced", + "release_gate", + "notes", +} +NOTICE_REQUIRED = {"required_text", "attribution", "sources"} +OWNER_REVIEW_REQUIRED = {"required", "status", "question", "issue"} +REVISION_KINDS = {"git_commit", "model_snapshot", "unresolved_upstream"} +LICENSE_STATUSES = {"declared", "underlying", "unresolved", "not_applicable"} +OWNER_STATUSES = {"pending", "confirmed", "not_required"} +COMMIT_RE = re.compile(r"^[0-9a-f]{40}$") +DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") +INVENTORY_REV_RE = re.compile(r"^\d{4}-\d{2}-\d{2}\.\d+$") +MUTABLE_URL_RE = re.compile(r"/(?:blob|tree|resolve)/(?:main|master|HEAD)(?:/|$)", re.I) + + +def _missing(value: dict[str, Any], required: set[str], path: str) -> list[str]: + return [ + f"{path}: missing field {field!r}" for field in sorted(required - value.keys()) + ] + + +def _is_https(value: Any) -> bool: + return ( + isinstance(value, str) + and urlparse(value).scheme == "https" + and bool(urlparse(value).netloc) + ) + + +def _url_error(value: Any, path: str, *, nullable: bool = False) -> list[str]: + if nullable and value is None: + return [] + return [] if _is_https(value) else [f"{path}: must be an https URL"] + + +def validate(data: Any) -> list[str]: + """Return all validation errors without stopping at the first one.""" + if not isinstance(data, dict): + return ["root: expected an object"] + errors = _missing(data, ROOT_REQUIRED, "root") + if errors: + return errors + + if data["schema_version"] != 1: + errors.append("root.schema_version: only version 1 is supported") + if not INVENTORY_REV_RE.fullmatch(str(data["inventory_revision"])): + errors.append("root.inventory_revision: expected YYYY-MM-DD.N") + if not DATE_RE.fullmatch(str(data["audited_at"])): + errors.append("root.audited_at: expected YYYY-MM-DD") + if not COMMIT_RE.fullmatch(str(data["audit_base_revision"])): + errors.append("root.audit_base_revision: expected a full 40-character commit") + + project_use = data["project_use"] + if not isinstance(project_use, dict): + errors.append("root.project_use: expected an object") + else: + required = { + "reported_context", + "owner_confirmation_status", + "future_distribution_or_commercial_change_is_release_gate", + "public_record_must_exclude", + } + errors.extend(_missing(project_use, required, "root.project_use")) + owner_confirmation_status = project_use.get("owner_confirmation_status") + if not isinstance( + owner_confirmation_status, str + ) or owner_confirmation_status not in {"pending", "confirmed"}: + errors.append("root.project_use.owner_confirmation_status: invalid status") + if ( + project_use.get("future_distribution_or_commercial_change_is_release_gate") + is not True + ): + errors.append( + "root.project_use: future use/distribution changes must be a release gate" + ) + + assets = data["assets"] + if not isinstance(assets, list) or not assets: + errors.append("root.assets: expected a non-empty list") + return errors + + ids: set[str] = set() + for index, asset in enumerate(assets): + path = f"root.assets[{index}]" + if not isinstance(asset, dict): + errors.append(f"{path}: expected an object") + continue + missing = _missing(asset, ASSET_REQUIRED, path) + errors.extend(missing) + if missing: + continue + + asset_id = asset["id"] + if not isinstance(asset_id, str) or not re.fullmatch( + r"[a-z0-9][a-z0-9-]*", asset_id + ): + errors.append(f"{path}.id: expected a lowercase kebab-case identifier") + elif asset_id in ids: + errors.append(f"{path}.id: duplicate identifier {asset_id!r}") + else: + ids.add(asset_id) + + upstream = asset["upstream"] + if not isinstance(upstream, dict): + errors.append(f"{path}.upstream: expected an object") + else: + errors.extend( + _missing(upstream, {"project", "canonical_url"}, f"{path}.upstream") + ) + errors.extend( + _url_error( + upstream.get("canonical_url"), f"{path}.upstream.canonical_url" + ) + ) + + revision = asset["revision"] + if not isinstance(revision, dict): + errors.append(f"{path}.revision: expected an object") + else: + errors.extend( + _missing(revision, {"kind", "value", "url"}, f"{path}.revision") + ) + kind = revision.get("kind") + value = revision.get("value") + url = revision.get("url") + distribution_for_revision = asset.get("distribution") + distribution_for_revision = ( + distribution_for_revision + if isinstance(distribution_for_revision, dict) + else {} + ) + if not isinstance(kind, str) or kind not in REVISION_KINDS: + errors.append(f"{path}.revision.kind: invalid revision kind") + if kind == "unresolved_upstream": + if value is not None: + errors.append( + f"{path}.revision.value: unresolved upstream revisions use null" + ) + if distribution_for_revision.get("immutable_reference_enforced"): + errors.append( + f"{path}.revision: unresolved upstream revision cannot be marked immutable" + ) + if not distribution_for_revision.get("release_gate"): + errors.append( + f"{path}.revision: unresolved upstream revision must remain a release gate" + ) + elif not isinstance(value, str) or not COMMIT_RE.fullmatch(value): + errors.append( + f"{path}.revision.value: expected a full immutable 40-character hash" + ) + errors.extend(_url_error(url, f"{path}.revision.url")) + if isinstance(url, str) and MUTABLE_URL_RE.search(url): + errors.append( + f"{path}.revision.url: mutable branch references are forbidden" + ) + if ( + kind != "unresolved_upstream" + and isinstance(url, str) + and isinstance(value, str) + and value not in url + ): + errors.append( + f"{path}.revision.url: must contain the exact revision value" + ) + + licenses = asset["licenses"] + if not isinstance(licenses, dict): + errors.append(f"{path}.licenses: expected an object") + else: + errors.extend(_missing(licenses, {"code", "weights"}, f"{path}.licenses")) + for license_kind in ("code", "weights"): + records = licenses.get(license_kind) + license_path = f"{path}.licenses.{license_kind}" + if not isinstance(records, list) or not records: + errors.append(f"{license_path}: expected a non-empty list") + continue + for license_index, record in enumerate(records): + record_path = f"{license_path}[{license_index}]" + if not isinstance(record, dict): + errors.append(f"{record_path}: expected an object") + continue + record_missing = _missing(record, LICENSE_REQUIRED, record_path) + errors.extend(record_missing) + if record_missing: + continue + status = record["status"] + identifier = record["identifier"] + if not isinstance(status, str) or status not in LICENSE_STATUSES: + errors.append(f"{record_path}.status: invalid status") + if not isinstance(identifier, str): + errors.append(f"{record_path}.identifier: expected a string") + if isinstance(status, str) and status in {"declared", "underlying"}: + if not isinstance(identifier, str) or identifier in { + "NONE", + "NOASSERTION", + }: + errors.append( + f"{record_path}.identifier: applicable license needs an identifier" + ) + errors.extend( + _url_error(record["terms_url"], f"{record_path}.terms_url") + ) + elif status == "unresolved" and identifier != "NOASSERTION": + errors.append( + f"{record_path}.identifier: unresolved licenses use NOASSERTION" + ) + elif status == "not_applicable" and identifier != "NONE": + errors.append( + f"{record_path}.identifier: non-applicable licenses use NONE" + ) + errors.extend( + _url_error( + record["notice_url"], + f"{record_path}.notice_url", + nullable=True, + ) + ) + + notices = asset["notices"] + if not isinstance(notices, dict): + errors.append(f"{path}.notices: expected an object") + else: + errors.extend(_missing(notices, NOTICE_REQUIRED, f"{path}.notices")) + for field in ("required_text", "attribution", "sources"): + if field in notices and not isinstance(notices[field], list): + errors.append(f"{path}.notices.{field}: expected a list") + for source_index, source in enumerate(notices.get("sources", [])): + errors.extend( + _url_error(source, f"{path}.notices.sources[{source_index}]") + ) + + access = asset["access"] + if not isinstance(access, dict): + errors.append(f"{path}.access: expected an object") + else: + errors.extend(_missing(access, ACCESS_REQUIRED, f"{path}.access")) + for field in ( + "gated", + "account_required", + "credential_required", + "terms_acceptance_required", + ): + if field in access and not isinstance(access[field], bool): + errors.append(f"{path}.access.{field}: expected a boolean") + for field in ("privacy_url", "acceptable_use_url"): + errors.extend( + _url_error( + access.get(field), f"{path}.access.{field}", nullable=True + ) + ) + if access.get("gated") and not access.get("terms_acceptance_required"): + errors.append( + f"{path}.access: gated assets must record terms acceptance" + ) + + distribution = asset["distribution"] + if not isinstance(distribution, dict): + errors.append(f"{path}.distribution: expected an object") + else: + errors.extend( + _missing(distribution, DISTRIBUTION_REQUIRED, f"{path}.distribution") + ) + source_url = distribution.get("source_url") + errors.extend(_url_error(source_url, f"{path}.distribution.source_url")) + if isinstance(source_url, str) and MUTABLE_URL_RE.search(source_url): + errors.append( + f"{path}.distribution.source_url: mutable branch references are forbidden" + ) + revision_for_distribution = asset.get("revision") + revision_for_distribution = ( + revision_for_distribution + if isinstance(revision_for_distribution, dict) + else {} + ) + revision_value = revision_for_distribution.get("value") + if ( + revision_for_distribution.get("kind") != "unresolved_upstream" + and isinstance(source_url, str) + and isinstance(revision_value, str) + and revision_value not in source_url + ): + errors.append( + f"{path}.distribution.source_url: must contain the exact revision value" + ) + for field in ( + "installer_contains_asset", + "installer_contains_weights", + "redistribution_confirmed", + "immutable_reference_enforced", + "release_gate", + ): + if field in distribution and not isinstance(distribution[field], bool): + errors.append(f"{path}.distribution.{field}: expected a boolean") + if distribution.get("installer_contains_weights") and not distribution.get( + "redistribution_confirmed" + ): + errors.append( + f"{path}.distribution: unconfirmed weights must not be placed in installers" + ) + if not distribution.get( + "immutable_reference_enforced" + ) and not distribution.get("release_gate"): + errors.append( + f"{path}.distribution: a mutable runtime path must remain a release gate" + ) + + owner_review = asset["owner_review"] + if not isinstance(owner_review, dict): + errors.append(f"{path}.owner_review: expected an object") + else: + errors.extend( + _missing(owner_review, OWNER_REVIEW_REQUIRED, f"{path}.owner_review") + ) + owner_status = owner_review.get("status") + if not isinstance(owner_status, str) or owner_status not in OWNER_STATUSES: + errors.append(f"{path}.owner_review.status: invalid status") + errors.extend( + _url_error(owner_review.get("issue"), f"{path}.owner_review.issue") + ) + if ( + owner_review.get("required") is True + and owner_review.get("status") == "not_required" + ): + errors.append( + f"{path}.owner_review: required review cannot be marked not_required" + ) + + if not isinstance(asset["dependencies"], list): + errors.append(f"{path}.dependencies: expected a list") + if not isinstance(asset["evidence"], list) or not asset["evidence"]: + errors.append(f"{path}.evidence: expected a non-empty list") + else: + for evidence_index, evidence in enumerate(asset["evidence"]): + errors.extend( + _url_error(evidence, f"{path}.evidence[{evidence_index}]") + ) + + for index, asset in enumerate(assets): + if not isinstance(asset, dict) or "dependencies" not in asset: + continue + for dependency in ( + asset["dependencies"] if isinstance(asset["dependencies"], list) else [] + ): + if not isinstance(dependency, str): + errors.append( + f"root.assets[{index}].dependencies: expected string asset ids" + ) + continue + if dependency not in ids: + errors.append( + f"root.assets[{index}].dependencies: unknown asset id {dependency!r}" + ) + if dependency == asset.get("id"): + errors.append( + f"root.assets[{index}].dependencies: self-dependency is forbidden" + ) + + assets_by_id = { + item["id"]: item + for item in assets + if isinstance(item, dict) and isinstance(item.get("id"), str) + } + catalogs = data["catalogs"] + if not isinstance(catalogs, dict): + errors.append("root.catalogs: expected an object") + else: + required = { + "bundled_lora_ids", + "official_lora_ids", + "documented_reference_lora_ids", + "note", + } + errors.extend(_missing(catalogs, required, "root.catalogs")) + for field in ( + "bundled_lora_ids", + "official_lora_ids", + "documented_reference_lora_ids", + ): + values = catalogs.get(field) + if not isinstance(values, list): + errors.append(f"root.catalogs.{field}: expected a list") + continue + for asset_id in values: + if not isinstance(asset_id, str): + errors.append(f"root.catalogs.{field}: expected string asset ids") + continue + if asset_id not in ids: + errors.append( + f"root.catalogs.{field}: unknown asset id {asset_id!r}" + ) + else: + asset = assets_by_id[asset_id] + if asset.get("asset_type") != "lora": + errors.append( + f"root.catalogs.{field}: {asset_id!r} is not a LoRA" + ) + + return errors + + +def load(path: Path) -> Any: + with path.open(encoding="utf-8") as handle: + return json.load(handle) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "path", + nargs="?", + type=Path, + default=Path(__file__).with_name("model-assets.json"), + ) + args = parser.parse_args(argv) + try: + data = load(args.path) + except (OSError, json.JSONDecodeError) as exc: + print(f"{args.path}: {exc}", file=sys.stderr) + return 2 + errors = validate(data) + if errors: + for error in errors: + print(error, file=sys.stderr) + return 1 + print(f"validated {len(data['assets'])} assets in {args.path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/model-license-release-checklist.md b/docs/model-license-release-checklist.md new file mode 100644 index 0000000..e38b7d3 --- /dev/null +++ b/docs/model-license-release-checklist.md @@ -0,0 +1,84 @@ +# Model licensing release checklist + +Run this checklist for every public release and whenever a model, runtime, +download source, license, terms page, product distribution model, or commercial +status changes. This is a release-control checklist, not legal advice. + +## Automated inventory gate + +- [ ] `python3 compliance/validate_inventory.py` passes. +- [ ] `python3 -m unittest compliance.test_inventory` passes. +- [ ] `audit_base_revision` is updated to the release candidate's full commit. +- [ ] Each application runtime/package pin matches its manifest entry and lockfile. +- [ ] Every model/processor/LoRA download supplies the exact manifest revision; + no `main`, `master`, `HEAD`, latest tag, or omitted revision remains. +- [ ] Download verification rejects a source/revision mismatch and partial files. +- [ ] No installer contains a manifest weight/adapter whose + `redistribution_confirmed` value is false. + +## Project-owner record + +- [ ] Owners selected and committed the LSDJ code license and notice. +- [ ] Owners confirmed the current open-source/non-commercial project-use path + for Google MRT2, the PyTorch conversion, Stable Audio 3, and T5Gemma. +- [ ] Owners resolved the Apache model-card vs underlying CC-BY-4.0 treatment for + both re-keyed PyTorch MRT2 snapshots. +- [ ] Owners confirmed the Stable Audio Community and Gemma derivative path for + the exact optimized snapshot. +- [ ] Owners either identified specific permission for each official/downloadable + LoRA or removed it from the official catalog. No sensitive account, + contract, credential, or revenue information was put in the public record. +- [ ] Any change in product distribution or commercial status triggered a fresh + owner review before publication. + +## Application and package notices + +- [ ] About/Licenses lists each manifest asset name, exact revision, upstream + link, code license, weight/model terms, attribution, and owner-review state. +- [ ] The packaged notices include applicable Apache/MIT notices, Google MRT2 + CC-BY attribution, Stability Community attribution and “Powered by + Stability AI” display, and the Gemma notice/terms link. +- [ ] LSDJ's code license is visually separate from third-party model terms and + explicitly says it does not relicense model weights or LoRAs. +- [ ] Release notes link this notice document and state whether models are + downloaded rather than included. +- [ ] Platform packages (macOS, Linux, Windows) contain the same notice version. + +## Download acknowledgement and access + +- [ ] First download is blocked until the current asset/license revision is + acknowledged wherever the manifest says `terms_acceptance_required: true`. +- [ ] A changed asset revision or notice/terms version requires a fresh + acknowledgement; unrelated telemetry or marketing consent is separate. +- [ ] Gated-download cancellation, offline behavior, rejection, revoked access, + and expired/invalid credentials produce actionable errors without starting + a partial install. +- [ ] Tokens are redacted from command lines, logs, events, diagnostics, crash + reports, and UI; persistent tokens use the OS credential store and are + never written to plaintext application data. +- [ ] Anonymous optimized artifacts still show the underlying Stability/Gemma + terms; anonymous access is not treated as permission to relicense. + +## LoRA provenance + +- [ ] `bundled_lora_ids`, `official_lora_ids`, and documented references match + the actual application catalog and documentation exactly. +- [ ] Each LSDJ-provided LoRA download displays author, source, exact revision, + adapter license, compatible base, and base-model terms before download. +- [ ] No LoRA is mirrored or bundled without permission for that exact artifact. +- [ ] User imports show the responsibility/provenance notice and do not claim + that LSDJ reviewed the user's rights or private file contents. +- [ ] The Motif Maqam adapter remains a user-directed reference unless its + unresolved license/permission gate is closed. + +## Evidence captured for the release + +- [ ] Record release tag/commit, inventory version, reviewer, review date, and + the exact artifact revisions actually downloaded in the private release + record. +- [ ] Archive generated package file lists proving restricted weights are absent. +- [ ] Record passing acknowledgement-versioning, gated-download blocking, + offline/error, and credential-redaction tests from the follow-up UI/download + implementation. +- [ ] If any item above is not complete, block Linux and Windows publication and + link the unresolved owner decision without exposing sensitive information. diff --git a/docs/third-party-model-notices.md b/docs/third-party-model-notices.md new file mode 100644 index 0000000..a1b4925 --- /dev/null +++ b/docs/third-party-model-notices.md @@ -0,0 +1,132 @@ +# Third-party model and runtime notices + +Audit date: 2026-08-08. Inventory version: `2026-08-08.2`. + +This document is a human-readable projection of +[`compliance/model-assets.json`](../compliance/model-assets.json). The JSON +manifest is authoritative for exact revisions and validation. This document +records upstream statements and open decisions; it is not legal advice and does +not assert that any project use or redistribution path has been approved. + +## Release blockers found by this audit + +1. **LSDJ has no project license file at the audited base revision.** Project + owners must select the LSDJ code license and notice before publishing a + licensed source or binary release. +2. **The PyTorch MRT2 converted weights have conflicting provenance metadata.** + Their cards say Apache-2.0 while also saying the weights are re-keyed, + numerically identical copies of Google's CC-BY-4.0 weights. The effective + redistribution, attribution, and acknowledgement path is an owner-review + gate. Re-keying must not be presented as relicensing. +3. **Current model downloaders are mutable.** `magenta-rt 2.0.2` downloads the + Google model repository without `revision=`; pinned Stable Audio code does the + same for `stabilityai/stable-audio-3-optimized`; the LoRA importer fetches + `resolve/main`. The inventory records audited snapshots, but releases must + make the runtime request those exact revisions and verify technical + provenance. +4. **The documented Maqam LoRA has no identified license grant.** Its card says + `license: other` and supplies no license file. Keep it a user-directed, + upstream reference. Do not mirror, bundle, or label it official until its + author and project owners confirm the path. +5. **Two optimized-model source revisions are not identified upstream.** The + pinned Stable Audio optimized snapshot names the T5Gemma and Stable Audio 3 + Medium source repositories, but does not identify the exact revisions used to + produce the derivative artifacts. The inventory records those revisions as + unresolved rather than substituting the repositories' audit-time heads. + +No reviewed third-party model weights belong in installers while their manifest +entry has `redistribution_confirmed: false`. + +## Inventory summary + +| Asset | Exact revision | Code license | Weight/asset terms | Acquisition | Gate | +| --- | --- | --- | --- | --- | --- | +| LSDJ code | `c9cd822ef6cbb86711e72d35f0f7e50a126d666f` | Unresolved: no project LICENSE/NOTICE found | n/a | Bundled app code | Owners select license and notice | +| Google MRT2 Python runtime (`magenta-rt 2.0.2`) | source `4bf995bdd9c29b818543574e1b3a6e67867c9a58`; wheel SHA-256 in manifest | Apache-2.0 | n/a | Hash-locked bundled sidecar dependency | Package notices not yet wired | +| Google MRT2 weights/resources | `010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc` | n/a | CC-BY-4.0 plus model-card usage statement | Download, not installer | Runtime pin + owner attribution decision | +| Apolinario/multimodalart PyTorch port | `6d076baa3df3b10448876c400521a015a5137c59` | Apache-2.0 | n/a | Planned pinned runtime | #109 hardware and #108 review gates | +| PyTorch MRT2 base | `92087988d05d0fe38b11f021f0b0d00a75afb86b` | Card declares Apache-2.0 remote code | Card says Apache-2.0; underlying Google weights say CC-BY-4.0 | Planned exact download | License ambiguity must be resolved | +| PyTorch MRT2 small | `7037d99551c84ac5c6afb7f1a5e58c65e7233dbb` | Card declares Apache-2.0 remote code | Card says Apache-2.0; underlying Google weights say CC-BY-4.0 | Planned exact download | License ambiguity must be resolved | +| PyTorch MusicCoCa processor | `236c488e38aa98643805514996934d705668298b` | Conversion-code treatment pending | CC-BY-4.0 | Planned exact download | Pin processor; confirm notice path | +| Stable Audio 3 runtime source | `0385302ea26522f00c80392c4b708df5ebf1adf5` | MIT | n/a | Exact source archive download | Carry MIT notice | +| Stable Audio 3 optimized MLX/TFLite assets | `6736003cb57d06b7b1fdc36fad31b2a3709e4774` | n/a | Stability AI Community License plus Gemma Terms for T5Gemma components | Download, not installer | Runtime pin, owner path, acknowledgement | +| Google T5Gemma B-B UL2 source model | Exact conversion-source revision unresolved | n/a here | Gemma Terms of Use | Direct source is manually gated; LSDJ consumes Stability's optimized derivative | Identify source revision; owner derivative/notice decision | +| Stable Audio 3 Medium upstream source family | Exact conversion/training-source revision unresolved | n/a | Stability AI Community License plus Gemma Terms | Provenance reference only; direct source is gated | Identify source revision; base terms follow optimized model/LoRA review | +| Motif Maqam LoRA | `3e1d9aa6fcb72a619b4ced00a240c5039f76daf0` | n/a | Unresolved (`license: other` only); Stable Audio base terms also relevant | User-directed upstream download | No mirroring/bundling; runtime pin required | + +## Notice inputs + +### Google Magenta RealTime 2 + +- Runtime source: [Apache-2.0 at the locked source commit](https://github.com/magenta/magenta-realtime/blob/4bf995bdd9c29b818543574e1b3a6e67867c9a58/LICENSE). +- Weights and shared MusicCoCa/SpectroStream resources: + [CC-BY-4.0 model card at the audited snapshot](https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md). +- Attribution input: Magenta RealTime 2, authors Google DeepMind; copyright + 2026 Google LLC. Link the exact model card and CC-BY-4.0 legal code. +- The card asks users to act responsibly and not generate content that infringes + or violates others' rights. Include that link in the model disclosure rather + than paraphrasing it as a new LSDJ license term. + +### PyTorch MRT2 port and snapshots + +- Port source: [Apache-2.0 at `6d076…`](https://github.com/multimodalart/magenta-realtime-torch/blob/6d076baa3df3b10448876c400521a015a5137c59/LICENSE). +- The [base](https://huggingface.co/magenta-community/magenta-realtime-2/blob/92087988d05d0fe38b11f021f0b0d00a75afb86b/README.md) + and [small](https://huggingface.co/magenta-community/magenta-realtime-2-small/blob/7037d99551c84ac5c6afb7f1a5e58c65e7233dbb/README.md) + cards label the snapshots Apache-2.0 and state that their weights are re-keyed + and numerically identical to the Google checkpoint. +- Google's source weights are [declared CC-BY-4.0](https://huggingface.co/google/magenta-realtime-2/blob/010aa0dcb0dfd27b24f0ad07b4dad63e8f9521cc/README.md). + Show both statements until owners resolve the effective path. +- The [MusicCoCa processor snapshot](https://huggingface.co/magenta-community/magenta-rt-musiccoca-torch/tree/236c488e38aa98643805514996934d705668298b) + is declared CC-BY-4.0 and is also a converted Google artifact. + +### Stable Audio 3 and T5Gemma + +- Stable Audio source is [MIT at the pinned commit](https://github.com/Stability-AI/stable-audio-3/blob/0385302ea26522f00c80392c4b708df5ebf1adf5/LICENSE), + copyright 2026 Stability AI. +- The exact optimized model snapshot carries the + [Stability AI Community License](https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE.md), + [Gemma Terms](https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/LICENSE_GEMMA.md), + and a [NOTICE](https://huggingface.co/stabilityai/stable-audio-3-optimized/blob/6736003cb57d06b7b1fdc36fad31b2a3709e4774/NOTICE). +- Required notice inputs from those upstream files include the Stability AI + Community attribution, a “Powered by Stability AI” display, and the Gemma + terms notice. The application surface must also link Stability's + [acceptable-use policy](https://stability.ai/use-policy) and + [privacy policy](https://stability.ai/privacy-policy). +- The original [T5Gemma repository](https://huggingface.co/google/t5gemma-b-b-ul2) + is manually gated on Hugging Face. It requires an account and explicit Gemma + terms acceptance for direct access. The reviewed evidence does not identify + the exact revision used for Stability's optimized derivative. That derivative + is anonymously downloadable, but its pinned repository says T5Gemma is + redistributed under the Gemma Terms. + +## LoRA inventory and user imports + +At the audited revision, `bundled_lora_ids` and `official_lora_ids` are empty. +LSDJ accepts arbitrary user-supplied safetensors and can download a repository +the user names; that generic capability is not an official catalog. + +The only adapter named in LSDJ documentation/tests is +[`motiftechnologies/stable-audio-3-maqam-lora@3e1d…`](https://huggingface.co/motiftechnologies/stable-audio-3-maqam-lora/tree/3e1d9aa6fcb72a619b4ced00a240c5039f76daf0). +Its card identifies Motif Technologies, a Stable Audio 3 Medium base, and +`license: other`, but contains no license text or redistribution permission. +Show that provenance and unresolved status. Do not mirror or bundle it. + +For every user-imported LoRA, show that LSDJ does not verify the user's rights, +that the adapter may carry independent terms, and that the base-model terms may +still apply. Never infer a license from `.safetensors` format or public access. + +## Distribution and credential rules for follow-up implementation + +- Keep all model weights out of installers until an exact manifest entry records + owner-confirmed redistribution permission. +- Downloads must use `revision=` (or an equivalent immutable URL), + verify expected provenance/hashes, and fail closed rather than falling back to + `main` or another mutable reference. +- Before a download whose terms require acknowledgement, show exact model, + revision, license/terms, notice, privacy, and acceptable-use links. Store the + acknowledgement against the inventory revision and asset revision. +- A gated upstream may require a user token after terms acceptance. Credentials + must be redacted from errors/logs and placed in the OS credential store or kept + intentionally ephemeral—never in plaintext application data. +- The LSDJ license must be presented separately and must say explicitly that it + does not relicense third-party model weights or adapters.