From 2ecc1fa208384eca7c47c041fb39109668fa6458 Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:08:16 +0200 Subject: [PATCH] Decide the conjunctive licence expression, and name Unicode-3.0 in the set 0103's licence set is an enumeration written for a single term and for a dual offer, and it has no rule for a conjunction. Every conjunction the board had met until now had all its members inside the admitted half, so the gap cost nothing. unicode-ident is offered as (MIT OR Apache-2.0) AND Unicode-3.0, and 0243 could place neither the shape nor the term; it left that collision open by name and left the Android half of the means it decides unlicensed. 0268 decides both halves. A conjunction is admitted only where every member is admitted, and a member the set names in neither half leaves the expression undecided rather than admitted, so the enumeration is total in three states instead of two. Unicode-3.0 is named admitted, on the ground that its three conditions - a notice, a warranty disclaimer, and a bar on promotional use of a name - are each a term AGPL-3.0-or-later enumerates in its own section 7 as one that may supplement it, which is 0103's own test for the refused half answered out of this repository's LICENSE. Admitting the package because it reaches no client binary is refused, because 0103 already applies the licence set without relaxation to a test tree that ships to nobody, and a build-time node of the shipping tree ships further than that. What it prevents: a reader meeting that expression with no rule for a conjunction has two quiet ways out - reading the dual offer and stopping, or reading the conjunction as a choice - and both produce a manifest line citing a clause of 0103 that does not reach the expression. Nothing in this repository reads that line, so the first reader to notice would be whoever assembles the notices for a release, after eleven clients have shipped the graph. No pointer is added to 0103. 0001 permits that edit only for a case the earlier record already names, and a conjunction is not one; the residual that leaves is stated in the record and belongs to #267. Refs #268 Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- .../0268-a-conjunctive-licence-expression.md | 371 ++++++++++++++++++ docs/decisions/README.md | 1 + 2 files changed, 372 insertions(+) create mode 100644 docs/decisions/0268-a-conjunctive-licence-expression.md diff --git a/docs/decisions/0268-a-conjunctive-licence-expression.md b/docs/decisions/0268-a-conjunctive-licence-expression.md new file mode 100644 index 0000000..b1b33eb --- /dev/null +++ b/docs/decisions/0268-a-conjunctive-licence-expression.md @@ -0,0 +1,371 @@ +# 0268. A conjunctive licence expression, and the term the set does not name + +Date: 2026-09-01 + +Status: accepted. Supersedes nothing. Superseded by nothing. + +Issue: #268 + +## The decision + +A conjunctive licence expression is admitted only where every member of it is +admitted, so a conjunction carrying a term +[0103](0103-what-admits-a-dependency-and-what-is-refused.md)'s set names in +neither half is not admitted until the set names it; `Unicode-3.0` is named +admitted here, on the ground that each of its three conditions is a term +`AGPL-3.0-or-later` enumerates as one that may supplement it, so it passes that +record's own test for the refused half and adds no obligation class the admitted +half does not already carry; and what a package reaches decides nothing, because +that record already applies the licence set without relaxation to a test tree +that is distributed to nobody. + +## What was in the way, and it was not whether the licence is acceptable + +The set is an enumeration with an admitted half and a refused half, and a reader +holding an expression is expected to place it in one of the two. That works for +the shapes the set was written for, which its own sentence names - a single term, +and a dual offer that includes an admitted member: + + git show origin/main:docs/decisions/0103-what-admits-a-dependency-and-what-is-refused.md | sed -n '67,71p' + Admitted anywhere in the graph, shipping tree or test tree: `MIT`, `Apache-2.0`, + `BSD-2-Clause`, `BSD-3-Clause`, `ISC`, `Zlib`, `Unlicense`, `CC0-1.0`, `MPL-2.0`, + `LGPL-2.1-or-later`, `LGPL-3.0-or-later`, `GPL-2.0-or-later`, `GPL-3.0-or-later`, + `AGPL-3.0-or-later`. A dual offer that includes one of these is admitted on that + member, which is how the two most common offers in this ecosystem arrive. + +A conjunction is the third shape and the set says nothing about it. That did not +matter while every conjunction in front of the board had all its members inside +the admitted half, which is what +[0243](0243-the-means-a-certificate-is-validated-with.md) found for the two it +could place and recorded as the reason they passed. The one it could not place is +`unicode-ident`, offered as `(MIT OR Apache-2.0) AND Unicode-3.0`, and the term +the set names in neither half is still unnamed at the commit this record is +written against: + + git rev-parse origin/main + 2ed77e65c0c2b144dd3ef4befb7c63aa23c6ac31 + + git show origin/main:docs/decisions/0103-what-admits-a-dependency-and-what-is-refused.md | grep -c 'Unicode-3.0' + 0 + +So there were two questions and only one of them is about a licence. The first is +what an enumerated set does with an expression it has no rule for, which is a +question about the set and would be the same for any term. The second is whether +this particular term belongs in the admitted half. Answering the second alone +would have left the next unnamed term in exactly the state this one was in. + +## The conjunction rule, stated in full + +A conjunction is satisfied only if every member of it is satisfied, so a +conjunction is admitted only where every member is in the admitted half. One +member in the refused half refuses the whole expression, and the other members +being admitted changes nothing, because a conjunction offers no choice between +them. That is the direction a reader gets right without being told. + +The direction that needed writing down is the third state. A member the set names +in neither half leaves the expression UNDECIDED, and undecided is not admitted. +The enumeration is therefore total in the only way an enumeration can be: named +in the admitted half is admitted, named in the refused half is refused, and +unnamed is a question nobody has answered rather than a permission nobody wrote +out. Reading an unnamed term as admitted would make the refused half do all the +work and the admitted half decorative, which is the opposite of what +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) says the set is for - +named explicitly, because a general principle is a thing two readers apply +differently. + +What that costs is one dependency stopped per unnamed term until somebody writes +a record. That is the cost being chosen rather than an oversight: the alternative +is a graph admitted by silence, and a licence obligation is the one class of +mistake in this repository that deleting the dependency later does not repair. + +A dual offer INSIDE a conjunction is read as +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) already reads a dual +offer, on the member that is admitted. `(MIT OR Apache-2.0) AND Unicode-3.0` is +therefore two members: the offer, which is admitted on either of its two, and +`Unicode-3.0`, which is what the rest of this record is about. + +## Why what a package reaches decides nothing here + +The issue names a third answer: admit the package because it reaches no client +binary, on the ground that the distinction is real. It is refused, and the +argument against it is +[0103](0103-what-admits-a-dependency-and-what-is-refused.md)'s own rather than a +new one. + +That record relaxes the worth test for the test tree and states in the same +breath what it does not relax, and the reason it gives is about distribution: + + git show origin/main:docs/decisions/0103-what-admits-a-dependency-and-what-is-refused.md | sed -n '172,175p' + eleven client repositories and its behaviour on an operator's machine are not + costs anybody pays. What still applies without relaxation: the licence set, because + a licence obligation attaches to what is distributed and a test fixture can end up + distributed by accident; and the four behaviours, because a node that starts a + +A test-tree dependency ships to nobody and the licence set still reaches it in +full. A build-time dependency of the shipping tree is in the lockfile every client +resolves, is fetched by every build, and lands in the vendor directory of any +client that vendors its graph. So a reach test admitting the second would be +looser than the rule already in force for the first, which ships less far. There +is no reading of that record under which the ordering comes out the other way. + +The distinction the issue calls real is real, and it is a fact about where an +obligation attaches rather than about whether one exists. `unicode-ident` says so +in its own words - the terms are conjunctive because the artefact is two bodies +of property in one crate: + + gh api repos/dtolnay/unicode-ident/contents/README.md --jq '.content' | base64 -d | sed -n '266,270p' + The **generated** files incorporate tabular data derived from the Unicode + Character Database, together with intellectual property from the original source + code content of the crate. One must comply with the terms of both the Unicode + License Agreement and either of the Apache license or MIT license when those + generated files are involved. + +Compliance with an attribution term is owed by whoever conveys the material, and +eleven clients conveying a graph is exactly the multiplier +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) opens with. A rule +that switched the licence set off for a build-time node would put that obligation +on eleven repositories that never read a record saying it was there. + +## Unicode-3.0 read rather than assumed + +The expression is read from the package's own manifest rather than from a +resolver's summary of it, which is a second route to the fact +[0243](0243-the-means-a-certificate-is-validated-with.md) took with +`cargo metadata`: + + gh api repos/dtolnay/unicode-ident/contents/Cargo.toml --jq '.content' | base64 -d | grep '^license' + license = "(MIT OR Apache-2.0) AND Unicode-3.0" + +The identifier is current rather than deprecated, and it is approved by the body +this ecosystem's manifests are keyed to: + + gh api repos/spdx/license-list-data/contents/json/details/Unicode-3.0.json --jq '.content' \ + | base64 -d | jq -c '{licenseId, name, isOsiApproved, isDeprecatedLicenseId}' + {"licenseId":"Unicode-3.0","name":"Unicode License v3","isOsiApproved":true,"isDeprecatedLicenseId":false} + +Approval by a body is not the test +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) states, so it is +recorded as context and the terms are read. The grant and its condition: + + gh api repos/spdx/license-list-data/contents/text/Unicode-3.0.txt --jq '.content' | base64 -d | sed -n '13,22p' + Permission is hereby granted, free of charge, to any person obtaining a + copy of data files and any associated documentation (the "Data Files") or + software and any associated documentation (the "Software") to deal in the + Data Files or Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, and/or sell + copies of the Data Files or Software, and to permit persons to whom the + Data Files or Software are furnished to do so, provided that either (a) + this copyright and permission notice appear with all copies of the Data + Files or Software, or (b) this copyright and permission notice appear in + associated Documentation. + +and the last of the three things it asks: + + gh api repos/spdx/license-list-data/contents/text/Unicode-3.0.txt --jq '.content' | base64 -d | sed -n '36,39p' + Except as contained in this notice, the name of a copyright holder shall + not be used in advertising or otherwise to promote the sale, use or other + dealings in these Data Files or Software without prior written + authorization of the copyright holder. + +Three conditions and nothing else: a notice carried with the copies or in the +documentation, a warranty and liability disclaimer, and a bar on using a +copyright holder's name to promote. There is no source-availability condition, no +field-of-use restriction, no non-commercial restriction, and no term reaching a +work the material is combined with. + +THE TEST IS THE ONE 0103 WROTE, AND IT IS ANSWERED OUT OF THIS REPOSITORY'S OWN +LICENCE FILE. That record refuses every licence whose terms cannot be satisfied +inside a work distributed under `AGPL-3.0-or-later`, and that licence enumerates +the supplementary terms it tolerates: + + git show origin/main:LICENSE | sed -n '349,365p' + Notwithstanding any other provision of this License, for material you + add to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + +Each of the three lands on one of those. The disclaimer is (a), the notice +condition is (b), and the bar on promotional use of a name is (d). So the terms +are satisfiable inside the work this core is distributed as, by the enumeration +that work's own licence carries, rather than by anybody's judgement about whether +they read as permissive. + +The second half of the ground is that the admitted set already carries both +shapes, so no obligation class arrives with this term. The notice condition is +`MIT`'s: + + gh api repos/spdx/license-list-data/contents/text/MIT.txt --jq '.content' | base64 -d | sed -n '11,12p' + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + +and the name condition is `BSD-3-Clause`'s third clause: + + gh api repos/spdx/license-list-data/contents/text/BSD-3-Clause.txt --jq '.content' | base64 -d | sed -n '9p' + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +Both are in the admitted half already. Admitting `Unicode-3.0` therefore adds a +fourth notice to carry and no obligation an operator or a client author was not +already meeting. + +## What this decides about unicode-ident, and where it stops + +`unicode-ident` is admitted, by the rule above rather than by a reading of the +graph. Its expression is a conjunction of two members; the dual offer is admitted +on `MIT` or on `Apache-2.0`, and `Unicode-3.0` is admitted by the section above. +Every member is in the admitted half, so the conjunction is. + +That closes the collision +[0243](0243-the-means-a-certificate-is-validated-with.md) left open by name. That +record says of the tenth expression it read that it neither admits nor refuses +it, and that until this question is answered the Android half of the means it +decides is a dependency this board has not licensed. It is licensed now, and that +record's reversal condition for it does not fire, because that condition names +the refusal rather than the admission: + + git show origin/main:docs/decisions/0243-the-means-a-certificate-is-validated-with.md | grep -n 'Android licence term is refused' + 537:The Android licence term is refused under #268. The two Android triples then + +Where this stops. Nothing in this repository reads a licence expression, so the +rule above is carried by the review and by whoever writes the line beside a +manifest entry, which is what +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) already says of +itself. And the package is not in this tree's graph today, so nothing here has to +carry a notice yet: + + git grep -n 'unicode-ident' origin/main -- Cargo.lock Cargo.toml ; echo "exit=$?" + exit=1 + +It arrives with the manifest entry +[0243](0243-the-means-a-certificate-is-validated-with.md) leaves to #27 and #29, +and the notice this record admits is one an operator has to be able to find. +Where the assembled notices for a release live is #74's and #87's question, and +this record does not answer it. + +## What this record does to 0103, and what it does not + +It adds a term to the admitted half and a rule for a shape the set had none for. +Both are changes to what that record decided, so neither is written into it: +[0001](0001-decision-records.md) permits three edits to a landed record and a +change to what it decided is not among them. + +NO POINTER IS ADDED TO 0103 EITHER, AND THAT IS A NARROWER CLAIM THAN THE ONE +ABOVE. The pointer edit is permitted for a later record that goes further on a +case the earlier one ALREADY NAMES. +[0243](0243-the-means-a-certificate-is-validated-with.md) took it on the fourth +refused behaviour, which +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) names in its own +words. A conjunction is not a case that record names at all - it names a single +term and a dual offer - so an edit pointing here would be adding the case and the +route in one move, which is the abuse +[0001](0001-decision-records.md) names when it gives the test for that edit. + +THE RESIDUAL IS THE SAME ONE 0243 STATED AND IT IS NOW LARGER RATHER THAN +REPAIRED. A reader who opens +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) reads a licence set +that is one term short and a rule covering two of the three shapes an expression +takes, and nothing routes them here. #267 is where the shape of a partial +supersession is asked for, and this record does not invent one, for the reason +that record already gave: superseding +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) entirely would +discard a worth test, four behaviours and a clause that are all unchanged. + +## Why this is written down before the code + +The graph this decides on is not in the tree - the exit code above says so - which +is the only moment the question can be answered as a rule instead of at a manifest +entry somebody is trying to land. + +The specific failure is narrower than that and this board came within one merge of +it. A reader meeting `(MIT OR Apache-2.0) AND Unicode-3.0` with no rule for a +conjunction has three ways out and two of them are wrong quietly. Reading the dual +offer and stopping admits the package on a term nobody examined. Reading the +conjunction as a choice admits it on `MIT`. Both produce a green manifest line +citing a clause of +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) that does not reach +the expression, nothing in this repository reads that line, and the first reader +to notice is whoever assembles the notices for a release under #74 or #87 - after +eleven clients have shipped the graph. + +Written afterwards, the rule also arrives with a package already resting on it, +which is the shape +[0103](0103-what-admits-a-dependency-and-what-is-refused.md)'s own opening +argument is against: a rule about dependencies written after the first ten exist +is a rule with ten exceptions in it. + +## Alternatives, and what each cost + +**Refuse the term.** The Android triples then carry a package this board may not +carry, and what moves is the means, the licence set, or the triples themselves, +which is +[0243](0243-the-means-a-certificate-is-validated-with.md)'s own reversal +condition and would supersede that record. It costs the widest single entry in +the target set, or a certificate means chosen twice, and it buys nothing an +examination of the terms supports: the three conditions land inside the +enumeration this core's own licence carries. Refusing a licence whose terms the +work tolerates protects nobody, and the cost falls on the platform with the most +people on it. + +**Admit it because the package reaches no client binary.** Refused above on +[0103](0103-what-admits-a-dependency-and-what-is-refused.md)'s own reasoning +about the test tree. Its further cost is that it decides one package and leaves a +rule that has to be applied by measuring a graph per triple, which is the reading +this issue exists because somebody would otherwise have to make at a manifest. + +**Admit any conjunction whose members are individually not refused.** The +cheapest to apply and it needs no record per term. It converts the enumeration +into a deny-list, which is a different decision from the one +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) took, and it would +have admitted this term without anybody reading it. The moment it costs something +is the first proprietary or field-of-use term nobody has thought to name yet, +which is exactly the case an enumeration is chosen over a deny-list for. + +**Rewrite the set as a rule about terms rather than a list of identifiers.** +Admits every future licence whose obligations are of an admitted shape, and no +record is owed per term. +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) states the cost in +its own words where it explains why the set is named explicitly: a general +principle is a thing two readers apply differently. It is also the option that +cannot be checked by a machine later, and a list of identifiers is the one shape +a resolver's output can be compared against. + +**Leave it open and decide it at the manifest entry.** Costs nothing today and is +what happens by default. It moves the decision to whoever is landing #27 or #29, +at the moment they are trying to land something else, which is the most expensive +place to take a licence decision and the one where the cheapest answer is a +clause reference that nothing reads. + +## What would reverse this + +The Unicode licence changes its terms, or a package in this graph carries a +version of it whose conditions are not the three above. The mapping onto the +supplementary terms this core's licence enumerates is what admitted it, and a +fourth condition is a new question rather than a detail. + +A conjunction arrives whose members are each admitted and which is refused +anyway, by a lawyer or by a client author who has to distribute it. That is the +member-by-member rule failing on a composition effect it cannot see, and it is +superseded by a record saying what is read instead of the members. + +The count of unnamed terms this rule stops reaches a number nobody is willing to +write a record for. The rule is then costing more than the graph it protects, and +what replaces it is a rule about terms, with the price +[0103](0103-what-admits-a-dependency-and-what-is-refused.md) already names for +that shape paid deliberately. + +Something in this repository begins to read a licence expression against the set, +under #87 or #19. This record is then superseded by one written as what a machine +refuses, because the three-state rule above is stated for a reader and a checker +needs the unnamed state to have a name it can print. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index d48aaf1..e5d609e 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -62,4 +62,5 @@ allocated, and why a record is superseded rather than edited are in - [0115. Creating the core, stopping it, and a host that suspends it](0115-creating-and-stopping-the-core.md) - [0116. Learning that something cached has changed](0116-learning-that-something-cached-has-changed.md) - [0243. The means a certificate is validated with, and what it costs](0243-the-means-a-certificate-is-validated-with.md) +- [0268. A conjunctive licence expression, and the term the set does not name](0268-a-conjunctive-licence-expression.md) - [0272. The route next up is read from](0272-the-route-next-up-is-read-from.md)