Hifth's own work is GPL-3.0-or-later (LICENSE). But the repository also carries
data it did not write, under terms it cannot change, so a single blanket statement would
be inaccurate. This file is the map: which terms cover which paths, and why.
SOURCES.md is the authority on every third-party source — its provenance, its verbatim
license text, and where the notice ships. This file only says how those terms compose.
Something leaves this project through three doors, and "may this be distributed?" has a
different answer at each — so the map is organised by door first and by path second. Each row
says what goes out, through which door, under whose terms. The doors and what goes through
each were measured in docs/design/what-we-distribute.md; the numbers below are its.
The whole tree, every vendored input verbatim. This is the door the copyleft obligation is discharged at: the corresponding source for every derived tree below is here, and reachable.
| Paths | Terms | Whose choice |
|---|---|---|
apps/web/, packages/core/, packages/etl/ (scripts), scripts/, docs/, config |
GPL-3.0-or-later | Ours |
packages/etl/data/** — 17 vendored input files, 12.3 MB, verbatim |
Each source's own terms — not ours to relicense | KFGQPC, QAC, quran-tajweed, Tanzil, Waqar144 |
apps/web/public/assets/** — the derived trees, checked in as built |
The site door's terms, below | As below |
Derived assets and the app bundle. No vendored input reaches a browser; what does is built from them, and some of it inherits their terms.
| Paths | Terms | Whose choice |
|---|---|---|
The app bundle, apps/web/dist/, about 120 KB gz |
GPL-3.0-or-later | Ours |
apps/web/public/assets/roots/** |
GPL-3.0 (inherited) | The Quranic Arabic Corpus's |
apps/web/public/assets/skins/** |
CC BY 4.0 (inherited) | quran-tajweed's |
apps/web/public/assets/adj/** |
free use with attribution (inherited) and CC BY (inherited) and Sadaqa-e-Jaria free use (inherited) | Waqar144's and Tanzil's and MushafDatabase's |
apps/web/public/assets/words/** |
GPL-3.0-or-later | Ours — rectangles we measured |
apps/web/public/assets/marks/** |
GPL-3.0-or-later | Ours — rectangles we measured |
apps/web/public/assets/letters/** |
GPL-3.0-or-later | Ours — dividing lines we measured |
apps/web/public/assets/manifest.json |
GPL-3.0-or-later, with one question open (below) | Ours |
apps/web/public/assets/pages/** |
KFGQPC's own terms — not ours to relicense | KFGQPC |
Nothing goes through it. If one is opened it would carry the site door's contents wrapped in
an installable, and that is where two clauses in the inherited grants bite that a static site
never trips — the technological-measures clause of CC BY 4.0, and the GPL's conveying terms on
a binary rather than a page. docs/design/track-b-native.md is the record for that door.
Until 2026-09-01 this file had one table, organised by path, which silently meant the site door: it said nothing about the repository, where every vendored corpus actually is and where the copyleft obligation is discharged, and nothing about a store. A reader got an answer to "may this be distributed?" without being told which distribution it answered for.
Copyleft, chosen so improvements come back. The plain GPL rather than the AGPL, and the reasoning is specific to what Hifth is: a fully static, backend-free PWA hands the browser its entire JS bundle, so anyone deploying a modified Hifth is already conveying the code under §4–6 and owes their users the source. The AGPL's §13 exists to close the network-service hole — the case where the modified code runs on a server the user never receives — and Hifth has no server to hide behind. Taking the AGPL would add real adoption friction to cover a case the architecture does not have.
It also collapses a seam. The derived root shards are GPL-covered whatever we do (below), so licensing our code GPL makes "GPL, except the vendored upstream assets under their own terms" the whole story, instead of a code license and a data license in tension.
apps/web/public/assets/roots/** is generated by packages/etl/scripts/build-roots.mjs
from the Quranic Arabic Corpus morphology. Under a strict reading — the one we design to —
those shards are a derivative work of a GPL-licensed input, so they carry GPL terms
forward. Two consequences we actually have to honor:
- Corresponding source must reach recipients. For the shards that means
packages/etl/scripts/build-roots.mjsplus the pinned input atpackages/etl/data/roots/quranic-corpus-morphology-0.4.txt. Both are in this repository — which discharges the obligation only once the repository is reachable by the people receiving the shards. See the deploy note below. - The notice travels with the data.
assets/roots/<edition>/NOTICE.txtis emitted beside the shards by the build, quoted verbatim from the source header so it cannot drift, and the app shows a visible credit and copyright line on the root lens.
The corpus's terms of use and the GPL contradict each other on modification (GPL §5 grants
it; the terms say changing is not allowed). We satisfy both readings rather than picking
one: the file is vendored verbatim and never edited, and the shards are derived from it
at build time. docs/decisions/loop-5.md records the full analysis.
The app code does not read the corpus data at build time: every asset it touches is
fetched by URL at runtime, and no source file imports or reads anything under
packages/etl/data/. Our code is GPL because we chose it, not because the corpus required it.
That is a statement about one boundary, and it used to be written as though it covered all of
them — "nothing here reaches our source by way of the data". It does not, because content
can arrive by being typed, and once did: twelve verses were transcribed into a source file
and shipped in the bundle for the life of a feature, having crossed no build step at all.
gate:scripture now refuses running scripture anywhere in the tree, which is the check that
sentence was quietly standing in for.
Since 2026-09-26 the corpus is no longer one of them. The spans, and the whole-verse twins,
are now found in the print's own words — MushafDatabase's numbering, the same upstream the word
boxes come from, released as Sadaqa-e-Jaria (free to use, derive and publish) — with each lone
"and" glued onto the word after it, which is the one habit the corpus and the print differ on.
Glued, the print keeps every span the corpus gave, on the same words, and the same 74 twin
clusters; a test holds it to both. The builder no longer imports the corpus reader at all, so
the notice check would fail the day it did. That was decision adjacency-span-source = D. The
three parents are now Waqar144 (the pairings), Tanzil (the juz flag) and MushafDatabase (the
spans and twins). What follows is how the corpus came to be a parent, kept because the way it
arrived — as a feature, with no licence edit — is the lesson.
apps/web/public/assets/adj/** carried one upstream for most of this project's life, and the
row above said so: the mutashabihat pairings are Waqar144's, free to use with attribution.
That stopped being true when the shards started carrying spans — which words of an ayah
a pairing actually shares — and nothing in a licence file moved, because nothing about the
drift was a licence edit. It arrived as a feature.
build-adjacency.mjs imports sharedRuns from morphology.mjs, and that module reads the
same GPL-licensed Quranic Arabic Corpus morphology the root shards are built from. Its result
is written into every pairing it can answer for as span and toSpan. Today that is 2,544
of 3,002 edges across 114 shard files — not an edge case, the common case.
So under the same strict reading of derivative applied to the root shards above, the adjacency shards are a GPL derivative too, and one of their two parents was unnamed. Both were named then. The pairings remain Waqar144's and still owe attribution; the spans are the corpus's and carry GPL terms forward, with the same two consequences the root shards have.
A third parent was named on 2026-09-01, and it arrived the same way the second did — as a
feature, through no licence edit. 510 of 3,002 edges carry a flag saying whether a pairing
stays inside one juz, computed from the juz table in the shared core package; that table is
derived from the Tanzil structural metadata (tanzil-quran-metadata in SOURCES.md, CC BY,
attribution mandatory). No Tanzil byte ships — the flag is a boolean — but attribution is owed
for what was derived, so the row and the notice name Tanzil too. What made this one harder to
see than the second: gate:notices traces what a builder reads through its imports, and by
its own header it does not follow imports into the core package, so a builder can reach a new
upstream through core and the trace stays green. The declaration in the gate names Tanzil by
hand for now; whether the trace should follow core is still open, in
docs/design/what-we-depend-on.md ③.
The asymmetry worth keeping in mind: a static site discharges §4–6 by handing the browser the
whole bundle, which is this file's own argument for plain GPL over AGPL. A wrapped store
binary is the thing that has to be licensed, which is why this matters more to Track B than to
the web build — see docs/design/track-b-native.md.
The notice travels with this data too, as of 2026-08-16. assets/adj/<edition>/NOTICE.txt is
written by the build beside the shards, and it names both parents — the way to get a
two-parent row wrong twice is to fix it once, and a notice that discharged the GPL half while
dropping Waqar144's attribution would have been the same one-parent mistake wearing the other
hat. Since 2026-09-01 it names all three; since 2026-09-26 the three are Waqar144, Tanzil and
MushafDatabase, and the corpus's copyright block is no longer in it, because nothing of the
corpus reaches these shards.
Nothing had noticed any of this, which is its own finding. gate:notices now traces what each
builder actually reads, in imports rather than in prose, and fails the build when a shipped
asset tree reads a vendored input that neither this table nor the tree's notice accounts for.
What this does not yet settle. Building that trace immediately surfaced one more candidate parent: every edge in every shard carries the page it lands on and the distance in pages to its pair, and those come from a table derived from the KFGQPC page corpus — bucketed in the row below under terms neither upstream here grants. Whether a table of which page an ayah falls on is expression that carries terms forward, or a fact about a printing, is a licensing question and not an engineering one. It is recorded rather than guessed, because naming a third parent without grounds would overstate what this app owes in a notice handed to every reader, and leaving it unnamed understates it if the answer runs the other way.
apps/web/public/assets/words/** is 604 files of geometry: for every word of the mus'haf,
a rectangle on our page frame. It shipped for months in no row of the table above and
under no heading in this file — not because anyone decided it needed none, but because
nothing was reading the folder. gate:notices reads it now, and this is the row it asked for.
The rectangles were fitted from a second SVG print of the same mus'haf, set per word rather
than per page (word-geometry-mushafdatabase in SOURCES.md). No byte of that print
ships. 378 MB was read to write 2.2 MB of numbers, and the ink stays the print we already
had. Its grant is a Sadaqa-e-Jaria one — use, copy, modify, publish, distribute and derive,
commercially included, with no prior approval and no attribution obliged — so the tree
inherits nothing that has to travel with it. The colophon credits it anyway, and SOURCES.md
says in as many words that the credit is a courtesy so that a later reader does not mistake it
for a condition, or mistake a condition for a courtesy.
apps/web/public/assets/marks/** is 604 files of the same kind of thing: for every diacritic
mark of the mus'haf, a rectangle on our page frame, at the place and size the app draws it.
It shipped undeclared for the same reason the word rectangles did — nothing read the folder
until gate:notices did — and this is the row it asked for.
Each rectangle was measured from the KFGQPC hafs ligature drawing (hafs-kfqc in SOURCES.md):
the placement search reads the print's own mark ink and records where the mark sits, four
numbers to a mark. No byte of that artwork ships — a mark shard carries page numbers,
indices and offsets, nothing that could redraw a glyph, and gate:scripture and gate:notext
hold that line independently. The Complex's grant behind that print reserves commercial
printing and leaves digital use — websites, software — free, obliging no attribution; Hifth
prints nothing. The 892 marks the reader placed by hand are ours outright. So the tree inherits
nothing that has to travel with it, and the colophon's credit to the Complex is the same
courtesy the word row's is, not a condition.
apps/web/public/assets/letters/** says, for each word, where one letter ends and the next
begins: a few dividing lines across the word, found in the ink of the same KFGQPC hafs
ligature print the mark rectangles were measured from. To know how many letters to look for,
the build reads the word's spelling from the Quranic Arabic Corpus; that spelling is used and
dropped, and no letter of it ships, nor any byte of the print. What ships is lines on our
page frame. So the tree carries the same terms as the marks: ours, with the Complex credited
as a courtesy.
apps/web/public/assets/manifest.json is ours, and it ships one thing that is under a
question: the complete table of which page each of the 6,236 ayahs falls on, byte-identical to
the vendored copy the pipeline reads. That is the same table, and the same question, as the
"what this does not yet settle" note above — whether a page table is expression that carries
its source's terms forward, or a fact about a printing. It reaches three shipped outputs, not
one: the adjacency shards, every root-shard occurrence, and this file. The question is tracked
with all three attached to it rather than answered here, for the reason given above: guessing
either way misinforms every reader who opens the colophon.
Publishing the site is distribution. A static app hands the browser real copies of
assets/roots/**; that is conveying under the GPL, not merely operating a service, and
§13's network provision does not cover it. So from the first public deploy onward the
corresponding source has to be reachable by anyone who loads the page.
Practically: do not publish the site while this repository is private. Either open the repository, or publish the ETL script and its pinned input alongside the deployment.
That trigger has since fired, and it is discharged. The site is published and the
repository is public, so the corresponding source is reachable by anyone who loads the page:
build-roots.mjs and build-adjacency.mjs are in it, along with the pinned
quranic-corpus-morphology-0.4.txt both derive from. The app also offers the source
explicitly rather than leaving a reader to find it — SOURCE_REPO in apps/web/src/provenance.ts,
pinned to the commit the bundle was built from, and scripts/check-source-offer.mjs follows
the link so a moved repository fails a check instead of quietly breaking the offer.
This paragraph said the opposite for a while after it stopped being true, which is the failure worth naming rather than just correcting: an obligation that triggers on an event — a first public deploy — is described in a file nobody edits on the day the event happens. If the repository is ever taken private again, or the site is ever served from somewhere that does not carry the source, this is the paragraph that has to change back.
The mushaf page artwork (assets/pages/**) is KFGQPC's, distributed under the Complex's
own terms. Nothing in LICENSE grants rights to it.
It is not unmodified, which this paragraph used to claim. Three transforms are applied and
each is declared, reproducible and asserted on every build — the artwork is minified at a
fixed coordinate precision, and a short list of malformed shapes and identifiers is repaired.
SOURCES.md names all three and says how many of each there are; the pipeline's own header is
the authority on what they do. Nothing is hand-edited, which is the promise that was actually
being made.
The same holds for every vendored input under packages/etl/data/. When in doubt, the
per-source entry in SOURCES.md governs — not this file, and not LICENSE.