Resolving License Conflicts in Derived Datasets
The moment a pipeline joins two source datasets, it inherits both of their licenses — and if those licenses impose incompatible obligations, the derived dataset cannot be lawfully published under a single license at all. This is the failure that a metadata crosswalk cannot fix, because it is not a formatting problem: combining a share-alike database with an attribution-only table produces a work whose obligations must be reconciled before it is deposited, not after. This page is the concrete decision procedure that sits beneath the broader Open License Configuration guide: given source datasets under Creative Commons Zero 1.0 (CC0-1.0), Creative Commons Attribution 4.0 (CC-BY-4.0), Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA-4.0), and the Open Data Commons Open Database License (ODbL), it determines the single outcome license a merged dataset may carry, propagates the share-alike obligation where one applies, and detects the case where no compatible outcome exists.
It assumes you already assign licenses to individual datasets and are comfortable with Python 3.10+ and the Pydantic V2 API. The task is to turn “what license does the merge take?” from a lawyer’s judgement call into a deterministic function over a fixed table. The mechanics of writing a single resolved license into a deposit — the SPDX identifier, the canonical URI, the DataCite rightsList — are covered in Configuring CC-BY Licenses for Automated Dataset Publishing; here we decide which license that step should receive.
How Combined-Work Obligations Compose
Every one of these licenses grants reuse; they differ only in the obligations they attach. CC0-1.0 attaches none — it is a public-domain dedication, so it never constrains a combination. CC-BY-4.0 attaches attribution: the outcome must credit the source. CC-BY-SA-4.0 attaches attribution plus share-alike, meaning any adaptation that incorporates the material must itself be offered under CC-BY-SA-4.0. ODbL attaches attribution plus a share-alike obligation of its own, scoped to databases and their derived databases. The obligations of a combined work are the union of its sources’ obligations, and the outcome license must be one that carries that whole union.
Two facts make this tractable. First, obligations only ever accumulate: adding a CC0 source can never remove an attribution requirement contributed by a CC-BY source, so the resolver never “downgrades”. Second, share-alike is directional and license-specific — a CC-BY-SA-4.0 obligation is satisfied only by re-licensing the whole adaptation under CC-BY-SA-4.0, and an ODbL obligation only by ODbL. That is exactly why CC-BY-SA-4.0 and ODbL cannot be merged into one outcome: each demands the entire combined work carry its own share-alike license, and no single license is simultaneously CC-BY-SA-4.0 and ODbL. That pair is the canonical conflict, and a correct resolver must refuse it rather than silently pick one.
The Compatibility Matrix
The matrix below is the deterministic core of this operation: for each ordered pair of source licenses it gives the outcome license a merge may carry, or CONFLICT where no single compatible license exists. It is symmetric — combining is commutative — so the diagonal is each license with itself, and the upper and lower triangles mirror. This is the authoritative reference; the resolver code is a direct transcription of it.
| combine ↓ with → | CC0-1.0 | CC-BY-4.0 | CC-BY-SA-4.0 | ODbL |
|---|---|---|---|---|
| CC0-1.0 | CC0-1.0 | CC-BY-4.0 | CC-BY-SA-4.0 | ODbL |
| CC-BY-4.0 | CC-BY-4.0 | CC-BY-4.0 | CC-BY-SA-4.0 | ODbL |
| CC-BY-SA-4.0 | CC-BY-SA-4.0 | CC-BY-SA-4.0 | CC-BY-SA-4.0 | CONFLICT |
| ODbL | ODbL | ODbL | CONFLICT | ODbL |
Three readings of the table matter operationally. CC0-1.0 is the identity element: combined with anything, the outcome is the other license, because a public-domain dedication adds no obligation. A share-alike license absorbs a permissive one — CC-BY-4.0 combined with CC-BY-SA-4.0 yields CC-BY-SA-4.0, because the share-alike obligation propagates to cover the whole adaptation. And the two share-alike families collide: CC-BY-SA-4.0 with ODbL is the only CONFLICT, resolvable only by relicensing one source (where the rights-holder permits) or by keeping the sources as physically separate, separately-licensed components rather than a single merged dataset.
Production Implementation
The resolver stores the matrix once, resolves any pair by lookup, and reduces a whole set of sources by folding pairwise. A Pydantic V2 model carries the result so the propagated obligations are explicit and validated before the outcome is handed to a deposit step. Because the operation is commutative and associative over the compatible subset, folding left-to-right is safe: any ordering of the same sources produces the same outcome or the same conflict.
from __future__ import annotations
from functools import reduce
from itertools import combinations
from typing import Final, Iterable
from pydantic import BaseModel, Field, field_validator
CONFLICT: Final[str] = "CONFLICT"
# Symmetric pairwise outcome table. Only the canonical SPDX ids are valid keys.
_MATRIX: Final[dict[tuple[str, str], str]] = {
("CC0-1.0", "CC0-1.0"): "CC0-1.0",
("CC0-1.0", "CC-BY-4.0"): "CC-BY-4.0",
("CC0-1.0", "CC-BY-SA-4.0"): "CC-BY-SA-4.0",
("CC0-1.0", "ODbL-1.0"): "ODbL-1.0",
("CC-BY-4.0", "CC-BY-4.0"): "CC-BY-4.0",
("CC-BY-4.0", "CC-BY-SA-4.0"): "CC-BY-SA-4.0",
("CC-BY-4.0", "ODbL-1.0"): "ODbL-1.0",
("CC-BY-SA-4.0", "CC-BY-SA-4.0"): "CC-BY-SA-4.0",
("CC-BY-SA-4.0", "ODbL-1.0"): CONFLICT,
("ODbL-1.0", "ODbL-1.0"): "ODbL-1.0",
}
# Obligations each outcome license imposes on the derived dataset.
_OBLIGATIONS: Final[dict[str, list[str]]] = {
"CC0-1.0": [],
"CC-BY-4.0": ["attribution"],
"CC-BY-SA-4.0": ["attribution", "share-alike"],
"ODbL-1.0": ["attribution", "share-alike"],
}
_KNOWN: Final[frozenset[str]] = frozenset(_OBLIGATIONS)
class LicenseConflictError(ValueError):
"""Raised when two source licenses have no compatible outcome license."""
def _pair_outcome(a: str, b: str) -> str:
"""Look up the outcome for an unordered pair, honouring matrix symmetry."""
for key in ((a, b), (b, a)):
if key in _MATRIX:
outcome = _MATRIX[key]
if outcome == CONFLICT:
raise LicenseConflictError(
f"No compatible outcome license for {a} + {b}: "
f"both impose distinct share-alike obligations."
)
return outcome
raise KeyError(f"Unmodelled license pair: {a!r}, {b!r}")
class DerivedLicense(BaseModel):
"""The resolved license for a merged dataset and its obligations."""
outcome: str
obligations: list[str] = Field(default_factory=list)
sources: list[str]
@field_validator("outcome")
@classmethod
def outcome_must_be_known(cls, v: str) -> str:
if v not in _KNOWN:
raise ValueError(f"Unknown outcome license {v!r}")
return v
def resolve_derived_license(sources: Iterable[str]) -> DerivedLicense:
"""Resolve the single license a dataset derived from `sources` may carry.
Raises LicenseConflictError if any pair of sources is incompatible.
"""
unique = sorted({s for s in sources})
if not unique:
raise ValueError("At least one source license is required.")
unknown = [s for s in unique if s not in _KNOWN]
if unknown:
raise ValueError(f"Unknown source license(s): {unknown}")
# Fail fast on any incompatible pair before folding to an outcome.
for a, b in combinations(unique, 2):
_pair_outcome(a, b)
outcome = reduce(_pair_outcome, unique)
return DerivedLicense(
outcome=outcome,
obligations=_OBLIGATIONS[outcome],
sources=unique,
)
The combinations pre-check matters: folding alone would surface a conflict only if it happened to touch the incompatible pair during the reduction, so the resolver validates every pair explicitly first. Once an outcome is produced, its obligations list tells the deposit step what it must enforce — an attribution obligation means the merged record must carry every source’s credit, and a share-alike obligation means the outcome license itself is non-negotiable. The single-license serialization that follows is the same discipline described in Configuring CC-BY Licenses for Automated Dataset Publishing.
Verification
The resolver is fully testable offline because it is a pure function over the matrix. The checks below confirm CC0 acts as the identity, that share-alike propagates over an attribution-only source, and that the CC-BY-SA-4.0 with ODbL pair raises rather than silently choosing a winner.
def test_cc0_is_identity() -> None:
result = resolve_derived_license(["CC0-1.0", "CC-BY-4.0"])
assert result.outcome == "CC-BY-4.0"
assert result.obligations == ["attribution"]
def test_share_alike_propagates() -> None:
result = resolve_derived_license(["CC-BY-4.0", "CC-BY-SA-4.0"])
assert result.outcome == "CC-BY-SA-4.0"
assert "share-alike" in result.obligations
def test_incompatible_share_alike_conflicts() -> None:
import pytest
with pytest.raises(LicenseConflictError):
resolve_derived_license(["CC-BY-SA-4.0", "ODbL-1.0"])
if __name__ == "__main__":
for combo in (["CC0-1.0", "CC-BY-4.0"],
["CC-BY-4.0", "CC-BY-SA-4.0"],
["CC0-1.0", "CC-BY-4.0", "ODbL-1.0"]):
r = resolve_derived_license(combo)
print(f"{combo} -> {r.outcome} {r.obligations}")
Run the tests with pytest -q test_derived_license.py; a passing run prints 3 passed. The __main__ block prints each resolved outcome, and feeding it a CC-BY-SA-4.0/ODbL-1.0 mix raises LicenseConflictError — the structured signal a deposit orchestrator should treat as a hard stop, routing the merge to manual relicensing rather than publishing an unlicensable dataset.
Gotchas
- CC-BY-SA-4.0 and ODbL do not merge. Both demand the whole adaptation carry their own share-alike license, and no license is both, so the only lawful paths are relicensing a source or keeping the components physically separate. Fix: treat this pair as a hard
CONFLICT, never as a “pick the stricter one” choice. - A public-domain source does not launder obligations. Adding a CC0-1.0 table to a CC-BY-SA-4.0 database does not free the result of share-alike; CC0 adds nothing but removes nothing either. Fix: compute the outcome as the union of obligations, so the strictest source always governs.
- Attribution obligations survive the merge and must be carried forward. An outcome of CC-BY-4.0 still requires crediting every attribution-bearing source, not just naming the merged dataset. Fix: propagate each source’s attribution into the derived record’s metadata alongside the resolved license.
Frequently Asked Questions
What outcome license does a dataset take when I merge CC-BY-4.0 and CC-BY-SA-4.0 sources?
The outcome is CC-BY-SA-4.0. The share-alike obligation attached to the CC-BY-SA-4.0 source propagates to cover the entire adaptation, and CC-BY-4.0 imposes no obligation that CC-BY-SA-4.0 fails to satisfy. In general a share-alike license absorbs a permissive one, so any combination that includes CC-BY-SA-4.0 material and only permissive companions resolves to CC-BY-SA-4.0, carrying both attribution and share-alike obligations forward.
Why can’t I combine CC-BY-SA-4.0 and ODbL data into one licensed dataset?
Because each imposes its own distinct share-alike obligation, and share-alike requires the whole combined work to be offered under that same license. No single license is simultaneously CC-BY-SA-4.0 and ODbL, so there is no outcome license that satisfies both. The resolver raises a conflict for this pair. The only lawful resolutions are relicensing one source where the rights-holder allows it, or keeping the two sources as separately-licensed components rather than a single merged dataset.
Does adding a CC0-1.0 source change the obligations of a derived dataset?
No. CC0-1.0 is a public-domain dedication that attaches no obligations, so it acts as an identity element: combined with any other license, the outcome is that other license. It cannot add a requirement and, importantly, it cannot remove one — mixing CC0 data into a CC-BY-SA-4.0 dataset does not strip the share-alike obligation. The strictest source always governs the combined result.
Related Guides
- Open License Configuration — the parent guide defining license identifiers and resolution this conflict procedure builds on.
- Configuring CC-BY Licenses for Automated Dataset Publishing — how the single license this resolver picks is serialized into a deposit’s
rightsList.
See the Open Science Infrastructure Planning overview for how license reconciliation fits the wider governance and deposit workflow.