Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
741e226
feat: legal compliance remediation — SPDX headers, first-run notice, …
rhuanbarreto May 10, 2026
82ed061
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
ceedfce
docs: add LGPD references to telemetry documentation
rhuanbarreto May 10, 2026
8ae4e15
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
1a738a6
feat(adrs): add LEGAL-001 (SPDX headers) and LEGAL-002 (license compa…
rhuanbarreto May 10, 2026
822684b
docs(examples): add spdx-license-headers and license-compatibility ex…
rhuanbarreto May 10, 2026
4f141c2
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
5591279
feat(adrs): LEGAL-002 now scans all transitive dependencies recursively
rhuanbarreto May 10, 2026
53d09c9
refactor: remove redundant license:check script — LEGAL-002 rule cove…
rhuanbarreto May 10, 2026
56c8783
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
6c045b7
fix: remove stale bun run license:check references from ADR and rule
rhuanbarreto May 10, 2026
389187a
refactor: simplify LEGAL-002 to single glob with brace expansion
rhuanbarreto May 10, 2026
24b21aa
docs: update example to use single brace-expansion glob
rhuanbarreto May 10, 2026
71982c7
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
cb78f39
docs(examples): update license-compatibility code to recursive glob i…
rhuanbarreto May 10, 2026
51b174f
Merge branch 'main' into legal/compliance-remediation
rhuanbarreto May 10, 2026
b72e86c
docs: regenerate llms-full.txt
rhuanbarreto May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .archgate/adrs/LEGAL-001-spdx-license-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
id: LEGAL-001
title: SPDX License Headers
domain: legal
rules: true
files: ["src/**/*.ts", "tests/**/*.ts"]
---

## Context

The Archgate CLI is licensed under Apache-2.0. SPDX (Software Package Data Exchange) license identifiers provide a machine-readable, unambiguous way to declare the license of each source file. This eliminates ambiguity about which license applies to any given file and enables automated compliance tooling to verify license declarations at scale.

Without per-file license identifiers, downstream consumers (enterprises, redistributors, compliance scanners) must infer the license from the root LICENSE.md file — a fragile assumption that breaks when files are extracted, copied, or bundled outside the original repository context.

**Alternatives considered:**

- **Root LICENSE.md only** — Covers the project as a whole but provides no per-file signal. Files extracted in isolation (e.g., copy-pasted utilities, bundled snippets) lose their license provenance.
- **Full license header blocks** — Includes the entire license notice in every file. Verbose, creates noise, and adds maintenance burden if the copyright year or holder changes.
- **REUSE 3.0 specification** — A comprehensive approach using `.reuse/dep5` files for bulk declarations. More complex than needed for a single-license project where all files share the same license.

SPDX-License-Identifier comments are the lightest-weight solution that provides per-file legal clarity, is recognized by all major compliance scanners (FOSSA, Snyk, Black Duck, npm license-checker), and is trivial to maintain.

## Decision

Every TypeScript source file in `src/` and `tests/` must begin with an SPDX license identifier and copyright notice:

```typescript
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Archgate
```

For files with a shebang line (`#!/usr/bin/env bun`), the SPDX header must appear immediately after the shebang:

```typescript
#!/usr/bin/env bun
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Archgate
```

## Do's and Don'ts

### Do

- Add the SPDX header as the first two lines of every new `.ts` file in `src/` or `tests/`
- Place the header after the shebang line when one is present (only `src/cli.ts`)
- Use the exact format: `// SPDX-License-Identifier: Apache-2.0` followed by `// Copyright 2026 Archgate`
- Run `bun run scripts/add-spdx-headers.ts` to bulk-add headers to new files

### Don't

- Don't use block comments (`/* */`) for the SPDX header — scanners expect `//` single-line format
- Don't add SPDX headers to non-TypeScript files (JSON, YAML, Markdown) — they are covered by the root LICENSE.md
- Don't modify the copyright year without a project-wide decision
- Don't add additional license identifiers (e.g., dual licensing) without updating this ADR

## Consequences

### Positive

- **Unambiguous per-file licensing** — Every source file declares its license in a machine-readable format
- **Compliance scanner compatibility** — FOSSA, Snyk, and npm license-checker recognize SPDX identifiers automatically
- **Downstream safety** — Files extracted or bundled independently retain their license provenance
- **Apache-2.0 Section 4(a) alignment** — "You must give any other recipients of the Work a copy of this License" — per-file identifiers serve as a lightweight form of notice

### Negative

- **Two extra lines per file** — Minor visual noise; mitigated by being at the very top (above imports)
- **Year maintenance** — If copyright year changes, all files need updating (mitigated by the bulk script)

### Risks

- **Developers forget on new files** — The `LEGAL-001/spdx-header-present` rule catches this automatically in CI.
- **Mitigation:** Automated rule blocks PRs that add `.ts` files without the header.

## Compliance and Enforcement

### Automated Enforcement

- **Archgate rule** `LEGAL-001/spdx-header-present`: Scans all scoped `.ts` files and verifies the SPDX identifier is present in the first 3 lines. Severity: `error` (hard blocker).

### Manual Enforcement

None required — fully automated.

## References

- [SPDX License List](https://spdx.org/licenses/)
- [REUSE Software recommendations](https://reuse.software/spec/)
- [Apache-2.0 License, Section 4](https://www.apache.org/licenses/LICENSE-2.0#redistribution)
38 changes: 38 additions & 0 deletions .archgate/adrs/LEGAL-001-spdx-license-headers.rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// <reference path="../rules.d.ts" />

export default {
rules: {
"spdx-header-present": {
description:
"All TypeScript source files must have an SPDX-License-Identifier header",
async check(ctx) {
const results = await Promise.all(
ctx.scopedFiles.map(async (file) => {
const content = await ctx.readFile(file);
return { file, content };
})
);

for (const { file, content } of results) {
if (content === null) continue;

// Check first 5 lines for the SPDX identifier (allows for shebang)
const lines = content.split("\n").slice(0, 5);
const hasSpdx = lines.some((line) =>
line.includes("SPDX-License-Identifier: Apache-2.0")
);

if (!hasSpdx) {
ctx.report.violation({
message:
"Missing SPDX-License-Identifier header. Add `// SPDX-License-Identifier: Apache-2.0` as the first line.",
file,
line: 1,
fix: 'Add "// SPDX-License-Identifier: Apache-2.0\\n// Copyright 2026 Archgate\\n" at the top of the file',
});
}
}
},
},
},
} satisfies RuleSet;
95 changes: 95 additions & 0 deletions .archgate/adrs/LEGAL-002-dependency-license-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
id: LEGAL-002
title: Dependency License Compatibility
domain: legal
rules: true
files: ["package.json"]
---

## Context

The Archgate CLI is licensed under Apache-2.0, a permissive open-source license. When the CLI is compiled into a binary via `bun build --compile`, all runtime dependencies are bundled into the executable. This means the binary is a combined work that must comply with the license terms of every bundled dependency.

Copyleft licenses (GPL, AGPL, LGPL) impose "share-alike" requirements that could force the entire CLI to be distributed under copyleft terms — incompatible with the project's Apache-2.0 licensing. Even for devDependencies (which are not bundled), copyleft test frameworks or build tools could create legal ambiguity about the project's overall license posture.

**Alternatives considered:**

- **No automated checking** — Rely on manual review during dependency additions. Error-prone; a single copyleft transitive dependency could slip in unnoticed.
- **FOSSA or Snyk integration** — Third-party SaaS license scanners. Adds external dependency, cost, and requires API tokens in CI. Overkill for a project with only 3 production dependencies.
- **npm license-checker package** — Adds a devDependency for something achievable with a simple script. Counter to ARCH-006 (minimize dependencies).

A lightweight, self-contained script that reads `node_modules/*/package.json` license fields provides the same coverage without external dependencies or API tokens.

## Decision

All dependencies (production and development) must use licenses compatible with Apache-2.0. The project maintains an allowlist of approved permissive licenses:

| License | SPDX Identifier |
| ---------------------------- | -------------------- |
| MIT License | MIT |
| Apache License 2.0 | Apache-2.0 |
| ISC License | ISC |
| BSD 2-Clause | BSD-2-Clause |
| BSD 3-Clause | BSD-3-Clause |
| Zero-Clause BSD | 0BSD |
| Creative Commons Zero | CC0-1.0 |
| The Unlicense | Unlicense |
| Blue Oak Model License | BlueOak-1.0.0 |
| Creative Commons Attribution | CC-BY-4.0, CC-BY-3.0 |
| Python Software Foundation | Python-2.0 |

SPDX OR expressions (e.g., `MIT OR Apache-2.0`) are allowed if at least one alternative is on the allowlist.

**Prohibited licenses include:** GPL-2.0, GPL-3.0, AGPL-3.0, LGPL-2.1, LGPL-3.0, SSPL-1.0, and any other copyleft or source-available license.

## Do's and Don'ts

### Do

- Run `archgate check` (or `bun run validate`) before adding any new dependency
- Verify transitive dependencies — a permissively-licensed package may pull in a copyleft transitive
- Add newly-encountered permissive licenses to the allowlist in the LEGAL-002 `.rules.ts` file
- Prefer dependencies with clear SPDX license identifiers in their `package.json`

### Don't

- Don't add dependencies with GPL, AGPL, LGPL, or SSPL licenses
- Don't add dependencies with no license field (`UNLICENSED` or missing) — these are "all rights reserved" by default
- Don't assume a package is permissive based on its README — always check the `license` field in `package.json`
- Don't add packages with `Custom` or proprietary license fields without explicit legal review

## Consequences

### Positive

- **Legal certainty** — Every dependency in the compiled binary is confirmed Apache-2.0-compatible
- **Distribution safety** — Users, enterprises, and downstream redistributors can rely on the project's Apache-2.0 license without hidden copyleft obligations
- **Automated enforcement** — License violations are caught in CI before merge

### Negative

- **May reject useful packages** — Some high-quality libraries use copyleft licenses (e.g., readline-sync is GPL). These cannot be used regardless of utility.
- **Allowlist maintenance** — Rare or exotic permissive licenses require manual addition to the allowlist

### Risks

- **Transitive dependency license change** — A previously-permissive dependency may change its license in a new version (e.g., the Elasticsearch SSPL relicensing).
- **Mitigation:** The `LEGAL-002/no-copyleft-deps` rule scans the installed `node_modules` tree on every `archgate check`, catching license changes on any version update.
- **Missing license field in package.json** — Some packages declare their license only in a LICENSE file, not in the `license` field. The scanner may flag these as "no license."
- **Mitigation:** If a package is clearly permissive (LICENSE file exists) but lacks a `package.json` license field, add it to the allowlist with a comment explaining the override.

## Compliance and Enforcement

### Automated Enforcement

- **Archgate rule** `LEGAL-002/no-copyleft-deps`: Scans **all** packages in `node_modules/` (direct and transitive) via glob, reads each `package.json` license field, and flags any package not on the permissive allowlist. Severity: `error` (hard blocker). Runs as part of `archgate check` (included in `bun run validate`).

### Manual Enforcement

- Dependency additions in PRs should include a note confirming license compatibility

## References

- [SPDX License List](https://spdx.org/licenses/)
- [Apache-2.0 License Compatibility](https://www.apache.org/legal/resolved.html)
- [ARCH-006 — Dependency Policy](./ARCH-006-dependency-policy.md) — Governs which dependencies are allowed; this ADR governs their license compatibility
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/// <reference path="../rules.d.ts" />

const ALLOWED_LICENSES = new Set([
"MIT",
"Apache-2.0",
"ISC",
"BSD-2-Clause",
"BSD-3-Clause",
"0BSD",
"CC0-1.0",
"Unlicense",
"BlueOak-1.0.0",
"CC-BY-4.0",
"CC-BY-3.0",
"Python-2.0",
"(MIT OR Apache-2.0)",
"MIT OR Apache-2.0",
"(MIT AND Zlib)",
"(BSD-2-Clause OR MIT OR Apache-2.0)",
"(MIT OR CC0-1.0)",
"BlueOak-1.0.0 OR MIT OR Apache-2.0",
]);

function isAllowed(license: string | undefined): boolean {
if (!license) return false;
if (ALLOWED_LICENSES.has(license)) return true;

const normalized = license.trim().replace(/^\(/u, "").replace(/\)$/u, "");
if (ALLOWED_LICENSES.has(normalized)) return true;
if (ALLOWED_LICENSES.has(`(${normalized})`)) return true;

// OR expressions: at least one alternative must be allowed
if (normalized.includes(" OR ")) {
return normalized.split(" OR ").some((l) => ALLOWED_LICENSES.has(l.trim()));
}

return false;
}

/**
* Extract the package name from a node_modules package.json path.
* Handles both regular (node_modules/foo/package.json) and scoped
* (node_modules/@scope/foo/package.json) packages.
*/
function extractPackageName(path: string): string {
const parts = path.replaceAll("\\", "/").split("/");
const nmIdx = parts.lastIndexOf("node_modules");
if (nmIdx === -1) return path;
const afterNm = parts.slice(nmIdx + 1);
// Scoped package: @scope/name
if (afterNm[0]?.startsWith("@") && afterNm.length >= 2) {
return `${afterNm[0]}/${afterNm[1]}`;
}
return afterNm[0] ?? path;
}

export default {
rules: {
"no-copyleft-deps": {
description:
"All dependencies (including transitive) must use Apache-2.0-compatible (permissive) licenses",
async check(ctx) {
// Scan ALL packages in node_modules — direct AND transitive.
// Brace expansion covers both regular (zod) and scoped (@sentry/node-core) packages.
const pkgFiles = await ctx.glob("node_modules/{*,@*/*}/package.json");

const depResults = await Promise.all(
pkgFiles.map(async (pkgPath) => {
try {
const depPkg = await ctx.readJSON(pkgPath);
const name = extractPackageName(pkgPath);
return {
dep: name,
license: depPkg.license as string | undefined,
};
} catch {
return null;
}
})
);

for (const result of depResults) {
if (result === null) continue;
if (!isAllowed(result.license)) {
ctx.report.violation({
message: `Dependency "${result.dep}" has disallowed license: "${result.license ?? "(none)"}". Only permissive licenses (MIT, Apache-2.0, ISC, BSD, etc.) are allowed.`,
file: "package.json",
fix: `Remove "${result.dep}" or find an alternative with a permissive license. See LEGAL-002 for the approved license list.`,
});
}
}
},
},
},
} satisfies RuleSet;
Loading
Loading