Skip to content

Commit e4e1741

Browse files
committed
feat(docs): third-party licenses list
1 parent dac3cdc commit e4e1741

10 files changed

Lines changed: 1139 additions & 1 deletion

File tree

.claude/settings.local.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(go list:*)",
5+
"Bash(go build:*)",
6+
"Bash(go get:*)",
7+
"Bash(go mod:*)",
8+
"Bash(go test *)",
9+
"Bash(go doc *)",
10+
"Bash(go version *)",
11+
"Bash(grep -E \"\\\\.sql$\")",
12+
"Bash(xargs ls *)",
13+
"Bash(go vet *)",
14+
"Bash(xargs cat *)",
15+
"Bash(npx eslint *)",
16+
"Bash(xargs -I {} sh -c 'echo \"=== {} ===\" && head -5 \"{}\"')",
17+
"Bash(git stash *)",
18+
"Bash(npm run *)",
19+
"Bash(npm --version)",
20+
"Bash(go install *)",
21+
"Bash(npm install *)",
22+
"Bash(go env *)",
23+
"Bash(export PATH=\"$\\(go env GOPATH\\)/bin:$PATH\")",
24+
"Bash(export GOWORK=off)",
25+
"Bash(bash .claude/skills/semaphore-ui-third-party-licenses/scripts/collect_licenses.sh)",
26+
"Bash(python3 *)",
27+
"Bash(echo \"exit=$?\")"
28+
]
29+
}
30+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
name: semaphore-ui-third-party-licenses
3+
description: >
4+
Generate or update the THIRD-PARTY-LICENSES.md file for the Semaphore UI repository. Use this skill whenever the user
5+
asks to create, update, regenerate, audit, or refresh third-party license attributions, OSS notices, SBOM-style
6+
license inventories, or NOTICE files for Semaphore UI. Trigger on phrases like "third-party licenses", "OSS notices",
7+
"license attribution file", "SBOM for licenses", "NOTICE file", "audit dependencies",
8+
"licenses for go.mod / package.json", or any request that mentions compliance with customer agreements requiring an
9+
OSS components list. Also trigger when the user says "we added a new dependency, regenerate licenses". This skill
10+
knows the Semaphore UI layout (Go backend + Vue.js frontend), the project's license policy (allowlist/denylist),
11+
and the canonical output format.
12+
---
13+
14+
# Semaphore UI — Third-Party License Generator
15+
16+
This skill produces `THIRD-PARTY-LICENSES.md` for the Semaphore UI repository. The file lists every open-source
17+
dependency shipped with Semaphore UI by **name**, **version**, **license**, and **source URL**, grouped by component. It
18+
exists to satisfy contractual obligations toward customers (identification of OSS components by name, version,
19+
and license type) and to preserve attribution required by permissive licenses (MIT/BSD/Apache-2.0).
20+
21+
## Repository layout you can expect
22+
23+
Semaphore UI is a self-hosted product. The repository ships:
24+
25+
- **Go backend** — module file at `go.mod`, source under `pkg/`, `services/`, `db/`, etc.
26+
- **Vue.js frontend**`web/package.json` (or `web2/package.json` in some branches), built into static assets and
27+
embedded in the Go binary.
28+
29+
External CLI tools (Ansible, Terraform, OpenTofu, Pulumi, Bash) are invoked at runtime as separate operating-system
30+
processes. They are NOT linked into the binary and NOT redistributed with Semaphore UI, so they are intentionally
31+
**not listed** in `THIRD-PARTY-LICENSES.md` — their license terms apply to the user who installs them on the host,
32+
not to Semaphore UI's distribution. The license policy still discusses why this is contractually safe (see
33+
`references/license_policy.md`, "Subprocess exception").
34+
35+
If the layout differs (monorepo restructure, new submodule, etc.), inspect the repo first with
36+
`find . -name "go.mod" -o -name "package.json" -not -path "*/node_modules/*"` and adapt.
37+
38+
## Workflow
39+
40+
Follow these steps in order. Don't skip the policy check — it's the part that actually protects the project from
41+
accidentally shipping a GPL'd dependency.
42+
43+
### 1. Confirm scope with the user
44+
45+
Before generating anything, ask (briefly) which targets to include if it's not obvious from context:
46+
47+
- Backend only? Frontend only? Both?
48+
- Production dependencies only, or also dev dependencies?
49+
50+
**Default**: both backend and frontend, **production-only** (dev dependencies are not distributed to customers and don't
51+
need attribution). Override only if the user explicitly asks for dev deps too.
52+
53+
### 2. Collect raw license data
54+
55+
Run `scripts/collect_licenses.sh` from the repo root. It does the following:
56+
57+
- Sets `GOWORK=off` and runs `go-licenses report ./... --template scripts/go_template.tpl` for the Go backend, so
58+
workspace siblings (e.g. `pro_impl`) are not pulled in.
59+
- Rolls sub-package rows up to their parent module (uses `go list -m all` for the module → version map) and drops
60+
any module path that belongs to the root module's namespace (first-party packages, including `replace … => ./pro`
61+
targets).
62+
- Applies a small `OVERRIDES` table in the script for modules where `go-licenses` can't auto-detect a license
63+
(e.g. `modernc.org/mathutil` → BSD-3-Clause). Extend the table when new "Unknown" entries appear.
64+
- Runs `license-checker --production --json --excludePrivatePackages` in the frontend directory and strips the
65+
low-confidence `*` suffix from license strings so `MIT*` collapses into `MIT`.
66+
- Writes raw output to `.licenses-cache/go.tsv` and `.licenses-cache/npm.json`.
67+
68+
If the tools aren't installed, the script prints the install commands. Don't try to install them silently — show the
69+
user what needs to be added so they can decide whether to add them to CI.
70+
71+
### 3. Run the policy check
72+
73+
Run `scripts/check_policy.py .licenses-cache/`. This compares every detected license against the project policy in
74+
`references/license_policy.md`:
75+
76+
- **Allowed** (green): MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Zlib, Unlicense, CC0-1.0, MPL-2.0 — proceed.
77+
- **Review** (yellow): LGPL-2.1, LGPL-3.0, EPL-2.0, CDDL-1.0, custom/unknown — pause and ask the user. LGPL is fine for
78+
dynamic linking but Go statically links, so flag it for review.
79+
- **Forbidden** (red): GPL-2.0, GPL-3.0, AGPL-3.0, SSPL-1.0, BUSL-1.1, Commons Clause, "non-commercial" clauses — STOP.
80+
Do not proceed with file generation. Report the offending package and ask the user how to handle it (replace, vendor,
81+
or accept and document the consequence).
82+
83+
**This step is non-negotiable.** A forbidden license slipping into THIRD-PARTY-LICENSES.md isn't just a documentation
84+
problem — it's a contract breach with every customer. If `check_policy.py` returns a non-zero exit code, surface the
85+
failures prominently and stop.
86+
87+
### 4. Generate the markdown
88+
89+
Run `scripts/generate_md.py .licenses-cache/ > THIRD-PARTY-LICENSES.md`. This produces the file using the structure
90+
described in `references/template.md`. Key points the generator handles:
91+
92+
- Sorts entries alphabetically within each section.
93+
- Groups by component category (Go backend / npm frontend).
94+
- Deduplicates packages that appear at multiple versions (lists all versions on one line).
95+
- Renders a summary table at the top with license counts.
96+
- Adds a generation timestamp and a note pointing to `scripts/collect_licenses.sh` so the file is reproducible.
97+
98+
### 5. Verify and commit
99+
100+
After generation:
101+
102+
1. Diff against the previous `THIRD-PARTY-LICENSES.md` (if it exists). New rows = new dependencies — confirm with the
103+
user that they're expected.
104+
2. Verify the file isn't truncated and ends with the closing footer (`<!-- end of generated file -->`).
105+
3. Suggest the commit message: `chore(licenses): regenerate THIRD-PARTY-LICENSES.md for vX.Y.Z`.
106+
4. If a CI pipeline exists (`.github/workflows/`), suggest adding a job that runs `scripts/check_policy.py` on every
107+
PR — that's how the project prevents regressions, not by re-checking manually each release.
108+
109+
## Notes on edge cases
110+
111+
**Vendored dependencies.** If the repo has a `vendor/` directory, `go-licenses` will read from it. That's fine —
112+
vendored code still ships and still needs attribution.
113+
114+
**Dual-licensed packages.** Some packages are offered under "MIT OR Apache-2.0". Pick MIT (the simpler one) and note the
115+
choice in the entry: `License: MIT (also available under Apache-2.0)`.
116+
117+
**Packages with no detectable license.** `go-licenses` flags these as "Unknown". Don't ship the file with Unknowns —
118+
manually inspect the repo, fill in the SPDX identifier, and if there's truly no license, treat the package as
119+
forbidden (no license = "all rights reserved", which is incompatible with redistribution).
120+
121+
**Ansible / Terraform / OpenTofu / Pulumi / Bash.** Do NOT list them in `THIRD-PARTY-LICENSES.md`. They are invoked
122+
as separate OS processes (`os/exec`), not linked, not redistributed. Their licenses bind the user who installs them
123+
on the host, not Semaphore UI's distribution. Listing them would imply otherwise and is an unnecessary signal to a
124+
reviewing customer's legal team. The "Subprocess exception" section in `references/license_policy.md` explains the
125+
reasoning if a reviewer asks.
126+
127+
**First-party packages with no LICENSE.** The root module and any `replace … => ./<dir>` target (e.g. `./pro`) live
128+
inside this repo and don't need to be self-attributed. The collection script filters them out by the root module
129+
prefix from `go.mod`. If you see "Unknown license" rows for `github.com/<this-org>/<this-repo>/…`, the filter is
130+
working correctly — those rows shouldn't appear in the final cache.
131+
132+
**Embedded license texts.** For packages under MIT/BSD/ISC, the license requires preserving the copyright notice. The
133+
generator embeds the full LICENSE file text from each package's source. For Apache-2.0 packages, it embeds the NOTICE
134+
file if one exists.
135+
136+
## What this skill does NOT do
137+
138+
- It does not generate a CycloneDX or SPDX SBOM. If the user wants a machine-readable SBOM (for compliance scanners,
139+
customer attestations, etc.), point them at `syft packages dir:. -o cyclonedx-json` as a separate step.
140+
THIRD-PARTY-LICENSES.md is the human-readable artifact.
141+
- It does not check for CVEs. That's a security concern, not a licensing one — use `govulncheck` and `npm audit` for
142+
that.
143+
- It does not modify go.mod or package.json. The skill is read-only with respect to the dependency manifests; it only
144+
generates the attribution document.
145+
146+
See `references/license_policy.md` for the full policy and `references/template.md` for the output format.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# License Policy for Semaphore UI
2+
3+
This is the source of truth for which open-source licenses are acceptable in
4+
Semaphore UI dependencies. The policy is enforced by
5+
`scripts/check_policy.py` — if you change anything here, mirror the change
6+
in that script.
7+
8+
The policy exists for two reasons:
9+
10+
1. **Customer obligations.** Our MSA commits us to *not* shipping
11+
components on terms that would require customers to license or assign
12+
their IP. That rules out copyleft licenses with strong reciprocity
13+
(GPL family, AGPL, SSPL).
14+
2. **Project sustainability.** Source-available-but-not-OSS licenses
15+
(BUSL, Commons Clause, Elastic 2.0) carry use restrictions that conflict
16+
with our open-source distribution model.
17+
18+
## Allowed (no review needed)
19+
20+
These are permissive licenses with attribution-only obligations. Anything
21+
under one of these can be added without further discussion.
22+
23+
| SPDX ID | Name |
24+
|---------|------|
25+
| MIT, MIT-0 | MIT License |
26+
| Apache-2.0 | Apache License 2.0 |
27+
| BSD-2-Clause | BSD 2-Clause "Simplified" |
28+
| BSD-3-Clause | BSD 3-Clause "New" |
29+
| ISC | ISC License |
30+
| Zlib | zlib License |
31+
| Unlicense | The Unlicense |
32+
| CC0-1.0 | Creative Commons Zero |
33+
| MPL-2.0 | Mozilla Public License 2.0 (file-level copyleft, OK for libraries) |
34+
| 0BSD | BSD Zero Clause |
35+
| BlueOak-1.0.0 | Blue Oak Model License |
36+
| Python-2.0 | Python Software Foundation License |
37+
38+
**Note on MPL-2.0:** MPL is allowed because its copyleft obligation is at
39+
the *file* level, not the project level. We can use MPL-licensed libraries
40+
without making Semaphore UI MPL. If we ever modify an MPL-licensed file,
41+
that file must remain MPL.
42+
43+
## Review required
44+
45+
These licenses are not blanket-forbidden but require a case-by-case
46+
decision. Pause and ask before merging a dependency under one of these.
47+
48+
| SPDX ID | Why review |
49+
|---------|------------|
50+
| LGPL-2.1, LGPL-3.0 (and `-or-later`) | LGPL is fine for **dynamic** linking, but Go statically links by default. An LGPL Go library would force us to provide object files so users can relink. Acceptable only if the library is genuinely irreplaceable and we can document the relinking pathway. |
51+
| EPL-1.0, EPL-2.0 | Eclipse Public License has weak copyleft and an explicit patent clause. Generally workable but worth a legal eyeball. |
52+
| CDDL-1.0, CDDL-1.1 | File-level copyleft like MPL, but with extra requirements. Rare in our ecosystem. |
53+
| EUPL-1.1, EUPL-1.2 | European Union Public License. Compatible with several other licenses; check the specific version. |
54+
| OFL-1.1 | SIL Open Font License. Almost always fine for embedded fonts, but verify the font is being used as intended (not redistributed standalone with a new name). |
55+
56+
## Forbidden
57+
58+
A dependency under one of these licenses **must not be added**. If
59+
`check_policy.py` flags one, the options are: (a) remove it, (b) replace
60+
it with a permissively-licensed alternative, (c) move the functionality
61+
out of process (subprocess invocation breaks the linkage in most cases —
62+
this is why Ansible is acceptable).
63+
64+
| SPDX ID | Why forbidden |
65+
|---------|--------------|
66+
| GPL-2.0, GPL-3.0 (and `-only` / `-or-later`) | Strong copyleft. Linking forces Semaphore UI to be GPL, which conflicts with our distribution model. |
67+
| AGPL-1.0, AGPL-3.0 | Same as GPL plus a network-interaction trigger. Particularly hostile to SaaS/self-hosted server software. |
68+
| SSPL-1.0 | MongoDB's Server Side Public License. Not OSI-approved; imposes obligations on anyone offering the software as a service. |
69+
| BUSL-1.1 | Business Source License. Time-delayed open source with use restrictions during the change window. Not OSS. |
70+
| Commons-Clause | Restricts commercial sale; strips OSS status from any underlying license it's attached to. |
71+
| Elastic-2.0 | Elastic License v2. Use restrictions; not OSS. |
72+
| RSALv2 | Redis Source Available License v2. Same family as BUSL/Elastic. |
73+
| CC-BY-NC-* | Creative Commons "Non-Commercial" variants. Not usable in a commercially-distributable product. |
74+
| Facebook-BSD-Patents (legacy) | The old React patent grant clause. Removed years ago, but flagged here as a defensive measure. |
75+
76+
## Subprocess exception (External Runtime Tools)
77+
78+
Tools that Semaphore UI invokes via `os/exec` (Ansible, Terraform, OpenTofu,
79+
Pulumi, Bash) are NOT linked into our binary and NOT redistributed by us.
80+
Their licenses govern the user's installation of those tools on the host
81+
system, not our distribution.
82+
83+
This is why we can integrate with Ansible (GPL-3.0) and Terraform (BUSL-1.1)
84+
without violating this policy — we never embed their code, we only document
85+
that the user must install them separately.
86+
87+
These tools are intentionally **not listed** in `THIRD-PARTY-LICENSES.md`.
88+
Listing them would wrongly imply that they are distributed with the product
89+
and would invite unnecessary scrutiny from a customer's legal team. Neither
90+
`collect_licenses.sh` nor `generate_md.py` produces or accepts an external-
91+
tools section.
92+
93+
## When the policy itself needs to change
94+
95+
If a dependency we genuinely need is forbidden, the path forward is:
96+
97+
1. Document the technical need (what does it do, what alternatives exist).
98+
2. Get sign-off from a maintainer with the authority to make that call.
99+
3. Update **both** this document and `check_policy.py`, in the same commit.
100+
4. Note the exception in `THIRD-PARTY-LICENSES.md` so customers can see it.
101+
102+
Don't relax the policy silently. The whole point is that customers can
103+
audit us, so the policy must be auditable too.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# THIRD-PARTY-LICENSES.md output format
2+
3+
This is the canonical structure that `scripts/generate_md.py` produces.
4+
Reference for humans reviewing the generator output, and for anyone who
5+
needs to understand what each section is for.
6+
7+
## Structure
8+
9+
```
10+
# Third-Party Licenses
11+
12+
[intro paragraph explaining what this file is and why it exists]
13+
14+
_Generated on YYYY-MM-DD HH:MM UTC by scripts/collect_licenses.sh._
15+
16+
[regeneration instructions — always include these so the file is
17+
reproducible by anyone, not just the original author]
18+
19+
## Summary
20+
21+
[total component count]
22+
23+
| Ecosystem | Components |
24+
|-----------|------------|
25+
| Go (backend) | NN |
26+
| npm (frontend) | NN |
27+
28+
### License distribution
29+
30+
| License | Count |
31+
|---------|-------|
32+
| MIT | NN |
33+
| Apache-2.0 | NN |
34+
| BSD-3-Clause | NN |
35+
| ... | NN |
36+
37+
## Go Backend Dependencies
38+
39+
[one-line description of what these are and where they come from]
40+
41+
| Component | Version(s) | License | Source |
42+
|-----------|------------|---------|--------|
43+
| `github.com/foo/bar` | v1.2.3 | MIT | [link](https://...) |
44+
| `github.com/foo/baz` | v0.4.1, v0.5.0 | Apache-2.0 | [link](https://...) |
45+
| ...
46+
47+
## Frontend Dependencies (npm)
48+
49+
[one-line description]
50+
51+
| Component | Version(s) | License | Source |
52+
|-----------|------------|---------|--------|
53+
| `vue` | 3.4.21 | MIT | [link](https://...) |
54+
| `vuetify` | 3.5.8 | MIT | [link](https://...) |
55+
| ...
56+
57+
---
58+
59+
## License texts
60+
61+
[paragraph explaining where to find full license texts and how to report
62+
missing/incorrect attributions]
63+
64+
<!-- end of generated file -->
65+
```
66+
67+
## Why this structure
68+
69+
**Summary table at the top.** Lets a reader (especially a customer's
70+
compliance team) see the shape of the dependency tree at a glance
71+
without scrolling through hundreds of rows.
72+
73+
**License distribution.** Surfaces concentration risk. If 95% of deps are
74+
MIT/Apache and 1 is MPL-2.0, that 1 deserves attention. It also makes it
75+
easy to spot a forbidden license that somehow slipped through (a single
76+
GPL-3.0 row in the distribution table is a very loud red flag).
77+
78+
**Sorted alphabetically within each section.** Diffs between releases
79+
become useful — a new dependency shows up as an added row in one
80+
predictable place, not scattered across the file.
81+
82+
**Versions collapsed onto one line.** A dependency pinned at multiple
83+
versions (common with transitive Go deps that get MVS-resolved differently
84+
in different parts of the tree) shouldn't take 5 rows. One row, comma-
85+
separated versions.
86+
87+
**External tools deliberately excluded.** Ansible, Terraform, OpenTofu,
88+
Pulumi, and Bash are invoked as subprocesses, not linked, and not
89+
redistributed with Semaphore UI. They are not listed in this file —
90+
including them would wrongly imply distribution. The license policy
91+
document covers them under "Subprocess exception" if a reviewer asks
92+
why they're absent.
93+
94+
**Regeneration instructions in the header.** Means the file is not a
95+
hand-maintained artifact that drifts. Anyone can rebuild it and verify
96+
it matches what's actually in the dependency manifests.
97+
98+
**Footer comment marker.** `<!-- end of generated file -->` lets CI
99+
verify the file wasn't truncated mid-write — useful when generation runs
100+
in a pipeline and might fail partway.
101+
102+
## What NOT to include
103+
104+
- **Full license texts inline.** Don't copy the MIT license text 200
105+
times into one file. Refer readers to the source URL. The packages
106+
themselves carry their LICENSE files; the user has them on disk after
107+
`go mod download` / `npm install`.
108+
- **CVE / vulnerability info.** That belongs in security advisories,
109+
not the license document. Mixing the two confuses the reader and makes
110+
the file go stale faster.
111+
- **Internal-only dependencies.** Build tooling (linters, test
112+
frameworks, code generators) doesn't ship to customers. Production
113+
dependencies only.
114+
- **Hand-edited rows.** If something needs a special note, add it via
115+
the generator (extend `generate_md.py`), not by editing the output
116+
file. Hand edits get destroyed on the next regeneration.

0 commit comments

Comments
 (0)