feat(licence): contributed manifests are CC0 1.0
Contributed manifests were in no declared condition at all, which left §9a replication with no grant flowing through it: peers mirror each other's catalogues wholesale, and every hop of that was unlicensed. CC0 rather than a share-alike licence, because a share-alike works by asserting a right in the data and then conditioning its use. The position in docs/legal-posture.md §3 is that presence timings are facts rather than protectable expression — asserting copyright in them in order to license them would contradict that argument in the same repository, and that contradiction is worth more to an opponent than the licence is worth to us. CC0 also waives the sui generis database right by name, closing the EU-specific residual exposure from the contributor's side. The grant is taken at token issuance, and that is not incidental. There are no accounts, so there is no sign-up to attach terms to, and a manifest arrives over POST /manifests with no channel to negotiate over. Acquiring the contribute capability is the only moment a grant can be made, so POST /tokens now returns the licence and its terms alongside the token — a licence the server publishes but never delivers is one no contributor agreed to. The test pins the scope limit as well as the identifier. Bounding the grant to the manifest is the half that can fail silently: a reworded term reading onto the underlying work would purport to grant what no contributor can. Also records the settled code-licence position across all four repositories in the legal posture, correcting an earlier claim there that the plugin and extraction repos declared nothing. Both already carried LICENSE files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> TRACES: UR-019 | PR-006
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
@@ -10,6 +10,25 @@ point at it. If you are considering **running an instance**, read
|
||||
[`docs/legal-posture.md`](docs/legal-posture.md) first: it states what an
|
||||
instance holds, what it structurally cannot do, and the operator checklist.
|
||||
|
||||
## Licensing — two licences, do not conflate them
|
||||
|
||||
| | Licence | Where |
|
||||
|---|---|---|
|
||||
| **Code** | `GPL-3.0-or-later` | [`LICENSE`](LICENSE), declared in `Cargo.toml` |
|
||||
| **Contributed manifests** | **CC0 1.0 Universal** | [`LICENSE-DATA`](LICENSE-DATA), specified in [SPEC.md](SPEC.md) §5b |
|
||||
|
||||
Both files ship with any distribution. They answer different questions, and a
|
||||
package carrying only `LICENSE` leaves the one that matters for federation
|
||||
unanswered.
|
||||
|
||||
The data licence is the one that matters operationally, because manifests are
|
||||
what replicate between instances (§9a) — a code licence grants nothing over them.
|
||||
`POST /tokens` returns the licence and its terms with every issued token, so the
|
||||
grant is one contributors actually make rather than one the server announces.
|
||||
|
||||
The grant covers **the manifest only** — timings, identifiers, audio signature.
|
||||
It does not, and cannot, license the underlying work.
|
||||
|
||||
The community instance is **`https://jray.tourolle.paris`**. The JRay plugin
|
||||
ships with it pre-configured but **disabled** — §9 requires that no traffic leave
|
||||
an installation until an admin opts in, so the default entry exists to save the
|
||||
|
||||
@@ -951,6 +951,54 @@ this spec, not alongside it: every claim in it is a consequence of a design
|
||||
property recorded here, so **a change that weakens `SR-004` or `SR-005` silently
|
||||
invalidates it.** Its §7 is the list of changes that would.
|
||||
|
||||
---
|
||||
|
||||
## 5b. The licence contributed manifests carry — UR-019
|
||||
|
||||
**Contributed manifests are CC0 1.0 Universal.** The full text is
|
||||
[`LICENSE-DATA`](LICENSE-DATA); the code is separately `GPL-3.0-or-later`, and
|
||||
the two must not be conflated — the code licence says nothing about the data,
|
||||
and the data is the part that replicates between instances.
|
||||
|
||||
This follows the practice of every comparable service (MusicBrainz core data and
|
||||
AcousticBrainz are CC0; AcoustID is CC BY-SA), and it closes a gap rather than
|
||||
adding a feature: until it was stated, contributed manifests were in no declared
|
||||
condition at all, and §9a replication had no grant flowing through it.
|
||||
|
||||
**Why CC0 and not a share-alike licence.** A share-alike licence works by
|
||||
*asserting* a right in the data and then conditioning its use. The position
|
||||
throughout — [`docs/legal-posture.md`](docs/legal-posture.md) §3 — is that
|
||||
presence timings are facts rather than protectable expression. Asserting
|
||||
copyright in them in order to license them would contradict that argument in the
|
||||
same repository, and that contradiction is worth more to an opponent than a
|
||||
share-alike licence is worth to the project. CC0 asserts nothing, which is the
|
||||
position actually taken.
|
||||
|
||||
Two further consequences, both load-bearing:
|
||||
|
||||
- **CC0 waives the sui generis database right by name**, not merely copyright.
|
||||
That closes the EU-specific residual exposure from the contributor's side, in
|
||||
the one jurisdiction where such a right exists to be waived.
|
||||
- **Federation needs no per-peer negotiation.** §9a has independent operators
|
||||
replicating each other's catalogues wholesale; without a grant reaching every
|
||||
peer, each hop is unlicensed. CC0 makes each one a non-event, and means no
|
||||
instance can become a chokepoint by withholding permission to mirror.
|
||||
|
||||
**The grant is taken at token issuance, and this is not incidental.** There are
|
||||
no accounts, so there is no sign-up to attach terms to; and a manifest arrives
|
||||
over `POST /manifests` with no channel to negotiate over. Acquiring the
|
||||
contribute capability is therefore the only moment at which a grant can be made,
|
||||
so `POST /tokens` returns the licence and its terms alongside the token. A
|
||||
licence the server publishes but never delivers is one no contributor agreed to.
|
||||
|
||||
**Scope, stated precisely and repeated in the terms themselves:** the grant
|
||||
covers *the manifest* — timings, identifiers, audio signature. It does not, and
|
||||
cannot, license the underlying work, which is not the contributor's to license
|
||||
and which this server does not hold. That sentence is the whole of §§1–4 of the
|
||||
legal posture restated, and it belongs in the terms in exactly that form.
|
||||
|
||||
---
|
||||
|
||||
### Client-side hardening
|
||||
|
||||
Independent of the server, because a compromised or hostile server must not be
|
||||
|
||||
+44
-17
@@ -333,10 +333,15 @@ inapplicable for the simple reason that there are no links.
|
||||
|
||||
## 6. The licence Jmanifests are published under
|
||||
|
||||
**Recommended: CC0 1.0 Universal.** This matches MusicBrainz core data and
|
||||
**Adopted: CC0 1.0 Universal** — recorded in SPEC §5b, full text in
|
||||
[`LICENSE-DATA`](../LICENSE-DATA). This matches MusicBrainz core data and
|
||||
AcousticBrainz exactly, and it is the one choice consistent with the rest of this
|
||||
document.
|
||||
|
||||
Note the separation, which must not be blurred: the **code** is
|
||||
`GPL-3.0-or-later`, the **data** is CC0. The code licence says nothing about the
|
||||
manifests, and the manifests are the part that replicates between instances.
|
||||
|
||||
**Why CC0 and not a share-alike licence.** AcoustID uses CC BY-SA 3.0, and the
|
||||
temptation is to follow it — but a share-alike licence works by *asserting* a
|
||||
right in the data and then conditioning its use. §3 argues at length that
|
||||
@@ -365,12 +370,13 @@ position §3 actually takes.
|
||||
closed commercially — which is the failure mode this whole ecosystem learned
|
||||
from.
|
||||
|
||||
**The grant must be taken at upload.** A licence the server declares is worth
|
||||
nothing if contributors never granted it. The contribution terms must state that
|
||||
submitting a manifest places its content under CC0, and the JRay plugin's
|
||||
contribution opt-in (SPEC §9) is where a user encounters that — it is already an
|
||||
**The grant is taken at token issuance.** A licence the server declares is worth
|
||||
nothing if contributors never granted it, so `POST /tokens` returns the licence
|
||||
and its terms alongside the token — there are no accounts, so acquiring the
|
||||
contribute capability is the only moment at which a grant can be made. The JRay
|
||||
plugin's contribution opt-in (SPEC §9) is where a user encounters it: already an
|
||||
explicit, off-by-default choice, so this adds a sentence to a decision the user
|
||||
is already making rather than a new one.
|
||||
is making anyway rather than a new one.
|
||||
|
||||
**Scope, stated precisely.** The licence covers *the manifest* — the timings,
|
||||
identifiers and signature contributed. It does not and cannot purport to license
|
||||
@@ -378,10 +384,30 @@ the underlying film, which is not the contributor's to license and which the
|
||||
server does not hold. This distinction is the whole of §§1–4 restated in a
|
||||
sentence, and it belongs in the terms in exactly that form.
|
||||
|
||||
> **Status: recommended, not yet adopted.** No repository currently carries a
|
||||
> `LICENSE` file, and nothing outside this document states a data licence.
|
||||
> Adopting this means a recorded decision, a `LICENSE` file, a line in the
|
||||
> contribution terms, and a sentence in the plugin's contribution opt-in.
|
||||
> **Status: adopted.** Recorded in SPEC §5b as `UR-019`, with the text in
|
||||
> [`LICENSE-DATA`](../LICENSE-DATA) and the grant delivered by `POST /tokens`.
|
||||
|
||||
### Code licences, for completeness
|
||||
|
||||
Distinct from the above, and settled across all three repositories:
|
||||
|
||||
| Repository | Code licence | File |
|
||||
|---|---|---|
|
||||
| `JRay-public-server` | `GPL-3.0-or-later` | `LICENSE` — verified byte-identical to the FSF text |
|
||||
| `jRay` (plugin) | `GPL-3.0` | `LICENSE` |
|
||||
| `scene-actor-extraction` | `MIT`, with a model/third-party addendum | `LICENSE` |
|
||||
|
||||
Two notes an operator or contributor may need:
|
||||
|
||||
- **The MIT/GPL split is directionally fine.** MIT-licensed extraction output and
|
||||
code can be used by the GPL components; the reverse would not hold. Nothing in
|
||||
the current data flow runs the wrong way.
|
||||
- **`scene-actor-extraction`'s addendum is the one to actually read.** It records
|
||||
that some bundled models are licensed for **non-commercial research use only**,
|
||||
which is a restriction on *deploying the extraction pipeline* and has no
|
||||
bearing on the manifests it produces or on this server. The distinction matters
|
||||
precisely because CC0 manifests could otherwise be mistaken for a statement
|
||||
about the tooling that generated them.
|
||||
|
||||
---
|
||||
|
||||
@@ -500,13 +526,14 @@ than as incremental features:
|
||||
6. **Keep the kill switch usable.** SPEC §5a makes delisting a single `UPDATE`:
|
||||
one manifest, every manifest from a token, or every manifest for a title.
|
||||
Delisting is instant and reversible; deletion is a separate, logged action.
|
||||
7. **Adopt a data licence and add a `LICENSE` file.** §6 recommends CC0 1.0.
|
||||
No repository currently carries one, and the server's `GPL-3.0-or-later`
|
||||
covers the *code* only — it says nothing about the manifests, which are the
|
||||
thing that actually replicates between instances. This is the largest
|
||||
outstanding gap in the posture.
|
||||
8. **State the licence in the contribution terms**, so contributors grant it
|
||||
rather than the server merely declaring it (§6).
|
||||
7. **Do not modify the contribution terms without re-reading §6.** The data
|
||||
licence is CC0 1.0 (SPEC §5b), delivered by `POST /tokens`. Its scope limit —
|
||||
the manifest, never the underlying work — is the operative half, and a
|
||||
reworded term that blurs it grants something no contributor can grant.
|
||||
8. **Ship both licence files with any distribution.** `LICENSE` (code,
|
||||
`GPL-3.0-or-later`) and `LICENSE-DATA` (manifests, CC0 1.0) answer different
|
||||
questions, and a package containing only the first leaves the question that
|
||||
actually matters for federation unanswered.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ requirement and no fixture-generation step, unlike `scene-actor-extraction`.
|
||||
| UR-016 | Accept and store `extraction.gallery_scope`; rank on it (§7) | SR-003 | Medium | Done |
|
||||
| UR-017 | Accept per-window belief and identification route; `scenes` are objects | SR-003 | High | Done |
|
||||
| UR-018 | Exclude belief and route from `content_id`, replicating them as attributes | SR-003 | High | Done |
|
||||
| UR-019 | Contributed manifests are CC0 1.0; the grant is delivered with the token, not merely published | PR-006 | High | Done |
|
||||
|
||||
### Notes on status
|
||||
|
||||
@@ -133,6 +134,7 @@ topology is the point, so this is a deliberate choice rather than an oversight.
|
||||
| UR-012 | T2 | No endpoint accepts embeddings or image data | An `embedding` or `crop` field is an unknown-field `400` |
|
||||
| UR-013 | T1 | Stored windows are byte-identical to those submitted | Adjacent windows never merged; a window is never trimmed to a shorter one |
|
||||
| UR-014 | T1 | Unknown `jmanifest_version` rejected | Version `2` and version `0` both refused, naming the field |
|
||||
| UR-019 | T2 | `POST /tokens` returns the licence and its terms | The terms **bound the grant to the manifest** — a reading that covers the underlying work is the failure, not a missing field |
|
||||
| DR-001 | T1 | Unknown field at any nesting depth fails to parse | `movie` and `jellyfin_id` named in the error |
|
||||
| DR-003 | T1 | Concurrent writes serialize rather than returning `SQLITE_BUSY` | Failed transaction rolls back fully |
|
||||
| DR-005 | T1 | Jobs lease once, reschedule with backoff, survive restart | Stranded lease released at startup; future job not leased early |
|
||||
|
||||
+17
-9
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED FILE - do not edit by hand. -->
|
||||
<!-- Regenerate: scripts/traceability/traceability-gate.sh -->
|
||||
|
||||
**Generated:** 2026-07-31T07:49:04+00:00
|
||||
**Generated:** 2026-07-31T08:06:49+00:00
|
||||
|
||||
Denominators are read from [`requirements.md`](requirements.md) at run time, never hardcoded. Coverage counts a requirement only when it is tagged in source **and** has a verification tier this repo's CI host can execute (`T1, T2, static`).
|
||||
|
||||
@@ -14,10 +14,10 @@ Denominators are read from [`requirements.md`](requirements.md) at run time, nev
|
||||
| Source files scanned | 29 |
|
||||
| TRACES tags found | 43 |
|
||||
| EXCEPTION tags found | 0 |
|
||||
| Requirements defined | 32 |
|
||||
| Requirements covered | 25 |
|
||||
| **Coverage** | **78.1%** (25/32) |
|
||||
| Coverage of CI-executable scope | 78.1% (25/32) |
|
||||
| Requirements defined | 33 |
|
||||
| Requirements covered | 26 |
|
||||
| **Coverage** | **78.8%** (26/33) |
|
||||
| Coverage of CI-executable scope | 78.8% (26/33) |
|
||||
| Tagged but unexecuted in CI | 0 |
|
||||
| Orphan tags | 0 |
|
||||
|
||||
@@ -25,7 +25,7 @@ Denominators are read from [`requirements.md`](requirements.md) at run time, nev
|
||||
|
||||
| Type | Covered | Tagged but unexecuted | Defined |
|
||||
|---|---|---|---|
|
||||
| UR | 15 | 0 | 18 |
|
||||
| UR | 16 | 0 | 19 |
|
||||
| DR | 10 | 0 | 14 |
|
||||
|
||||
- **PR** tags present (separate taxonomy, not counted in coverage): PR-004, PR-005, PR-006
|
||||
@@ -77,6 +77,7 @@ _None._
|
||||
| UR-016 | Done | T2 | SR-003 | untagged | - | Accept and store `extraction.gallery_scope`; rank on it (§7) |
|
||||
| UR-017 | Done | T1, T2 | SR-003 | covered | `src/model.rs` | Accept per-window belief and identification route; `scenes` are objec… |
|
||||
| UR-018 | Done | T1 | SR-003 | untagged | - | Exclude belief and route from `content_id`, replicating them as attri… |
|
||||
| UR-019 | Done | T2 | PR-006 | covered | `src/api/upload.rs` | Contributed manifests are CC0 1.0; the grant is delivered with the to… |
|
||||
| DR-001 | Done | T1 | SR-004 | untagged | - | Strict parse boundary: unknown fields rejected structurally, not by v… |
|
||||
| DR-002 | Done | unset | SR-004 | covered | `src/api/fetch.rs`, `src/db/repo.rs` | Fully relational storage — no JSON blob on the write path |
|
||||
| DR-003 | Done | T1 | PR-004 | covered | `src/db/mod.rs` | Single serialized writer connection, with a read pool alongside |
|
||||
@@ -178,7 +179,7 @@ _None._
|
||||
|
||||
### PR-006
|
||||
|
||||
**Locations:** 9
|
||||
**Locations:** 10
|
||||
|
||||
- [`src/api/federation.rs:66`](../src/api/federation.rs#L66) — `pub async fn get_changes(`
|
||||
- [`src/api/federation.rs:108`](../src/api/federation.rs#L108) — `pub async fn get_manifest_by_content_id(`
|
||||
@@ -186,6 +187,7 @@ _None._
|
||||
- [`src/api/fetch.rs:127`](../src/api/fetch.rs#L127) — `pub async fn get_series(`
|
||||
- [`src/api/upload.rs:29`](../src/api/upload.rs#L29) — `pub async fn post_manifest(`
|
||||
- [`src/api/upload.rs:101`](../src/api/upload.rs#L101) — `pub async fn post_bundle(`
|
||||
- [`src/api/upload.rs:245`](../src/api/upload.rs#L245) — `pub async fn post_token(`
|
||||
- [`src/ingest.rs:50`](../src/ingest.rs#L50) — `pub fn persist(`
|
||||
- [`src/validate.rs:572`](../src/validate.rs#L572) — `pub fn validate_bundle_envelope(b: &SeriesBundle) -> VResult<()>`
|
||||
- [`src/worker.rs:107`](../src/worker.rs#L107) — `async fn run_federation_pull(&self, payload: &str) -> Result<(), JobError>`
|
||||
@@ -233,7 +235,7 @@ _None._
|
||||
|
||||
- [`src/api/report.rs:56`](../src/api/report.rs#L56) — `pub async fn post_report(`
|
||||
- [`src/api/upload.rs:29`](../src/api/upload.rs#L29) — `pub async fn post_manifest(`
|
||||
- [`src/api/upload.rs:223`](../src/api/upload.rs#L223) — `pub async fn post_token(`
|
||||
- [`src/api/upload.rs:245`](../src/api/upload.rs#L245) — `pub async fn post_token(`
|
||||
- [`src/app.rs:26`](../src/app.rs#L26) — `pub fn router(state: AppState) -> Router`
|
||||
- [`src/auth.rs:22`](../src/auth.rs#L22) — `pub fn hash_token(token: &str) -> String`
|
||||
- [`src/auth.rs:76`](../src/auth.rs#L76) — `pub fn client_ip(headers: &HeaderMap, peer: Option<IpAddr>, trusted_proxies: &[IpAddr]) -…`
|
||||
@@ -293,7 +295,7 @@ _None._
|
||||
**Locations:** 6
|
||||
|
||||
- [`src/api/report.rs:56`](../src/api/report.rs#L56) — `pub async fn post_report(`
|
||||
- [`src/api/upload.rs:223`](../src/api/upload.rs#L223) — `pub async fn post_token(`
|
||||
- [`src/api/upload.rs:245`](../src/api/upload.rs#L245) — `pub async fn post_token(`
|
||||
- [`src/auth.rs:22`](../src/auth.rs#L22) — `pub fn hash_token(token: &str) -> String`
|
||||
- [`src/castcheck.rs:82`](../src/castcheck.rs#L82) — `pub fn evaluate(submitted: &[SubmittedActor], credits: &[CastMember]) -> CastCheckOutcome`
|
||||
- [`src/castcheck.rs:215`](../src/castcheck.rs#L215) — `pub fn category_guard_violation(matched: &[MatchedActor], title_is_adult: bool) -> Option…`
|
||||
@@ -379,3 +381,9 @@ _None._
|
||||
- [`src/model.rs:191`](../src/model.rs#L191) — `Unknown`
|
||||
- [`src/model.rs:232`](../src/model.rs#L232) — `pub fn from_stored(s: &str) -> Option<Self>`
|
||||
|
||||
### UR-019
|
||||
|
||||
**Locations:** 1
|
||||
|
||||
- [`src/api/upload.rs:245`](../src/api/upload.rs#L245) — `pub async fn post_token(`
|
||||
|
||||
|
||||
+28
-2
@@ -209,9 +209,26 @@ pub async fn post_bundle(
|
||||
Ok(with_quota_headers(resp, quota))
|
||||
}
|
||||
|
||||
/// SPDX identifier of the licence a contributed manifest is placed under (§5b).
|
||||
pub const CONTRIBUTION_LICENSE: &str = "CC0-1.0";
|
||||
|
||||
/// The grant a contributor makes, in the words §5b specifies.
|
||||
///
|
||||
/// Scope is the operative part: it covers *the manifest*, and cannot purport to
|
||||
/// license the underlying work, which is not the contributor's to license and
|
||||
/// which this server does not hold.
|
||||
pub const CONTRIBUTION_TERMS: &str = "Contributing a manifest places its content \
|
||||
— timings, identifiers and audio signature — under CC0 1.0 Universal. This \
|
||||
covers the manifest only. It does not, and cannot, license the underlying \
|
||||
work, which the contributor does not own and this server does not hold.";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TokenIssued {
|
||||
pub token: String,
|
||||
/// Delivered with the capability, not merely published: a licence the server
|
||||
/// declares unilaterally is not one any contributor granted (§5b).
|
||||
pub contribution_license: &'static str,
|
||||
pub contribution_terms: &'static str,
|
||||
}
|
||||
|
||||
/// Issues an anonymous bearer capability (§5a).
|
||||
@@ -220,7 +237,12 @@ pub struct TokenIssued {
|
||||
/// only as a hash, so the server cannot enumerate who holds tokens. Discarding a
|
||||
/// token and requesting another is trivially easy — and that is fine, because the
|
||||
/// token is not the defence; the content checks are.
|
||||
/// TRACES: UR-005 | SR-004
|
||||
///
|
||||
/// The response carries the §5b contribution licence. This is the only moment
|
||||
/// the server can obtain a grant: there are no accounts, so there is no sign-up
|
||||
/// to attach terms to, and a manifest arrives with no channel to negotiate over.
|
||||
/// Acquiring the capability is therefore where the grant has to be made.
|
||||
/// TRACES: UR-005, UR-019 | SR-004 | PR-006
|
||||
pub async fn post_token(
|
||||
State(state): State<AppState>,
|
||||
peer: crate::state::PeerIp,
|
||||
@@ -240,5 +262,9 @@ pub async fn post_token(
|
||||
.await
|
||||
.map_err(ApiError::Internal)?;
|
||||
|
||||
Ok(Json(TokenIssued { token }))
|
||||
Ok(Json(TokenIssued {
|
||||
token,
|
||||
contribution_license: CONTRIBUTION_LICENSE,
|
||||
contribution_terms: CONTRIBUTION_TERMS,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -218,6 +218,25 @@ async fn tokens_are_issued_anonymously_and_are_distinct() {
|
||||
assert!(a.starts_with("jray_"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn the_token_response_carries_the_contribution_licence() {
|
||||
// §5b / UR-019. There are no accounts, so token issuance is the only moment
|
||||
// a grant can be taken — a licence the server publishes but never delivers
|
||||
// is one no contributor agreed to, which is precisely the gap that leaves
|
||||
// federated replication (UR-008) without a grant flowing through it.
|
||||
let s = TestServer::new("token-licence");
|
||||
let (status, body, _) = s.post_json("/api/v1/tokens", &json!({})).await;
|
||||
assert_eq!(status, StatusCode::OK, "body: {body}");
|
||||
|
||||
assert_eq!(body["contribution_license"].as_str(), Some("CC0-1.0"));
|
||||
|
||||
let terms = body["contribution_terms"].as_str().expect("terms in response");
|
||||
assert!(terms.contains("CC0 1.0"), "terms must name the licence: {terms}");
|
||||
// The scope limit is the operative half: it must be impossible to read the
|
||||
// grant as covering the film rather than the manifest.
|
||||
assert!(terms.contains("manifest only"), "terms must bound the grant to the manifest: {terms}");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// §6 — upload validation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user