From 7b1d2c62616376ad74104fa54f2cd804b8e22667 Mon Sep 17 00:00:00 2001 From: ebjune Date: Fri, 16 Oct 2026 21:12:36 -0700 Subject: [PATCH] ownership.md file added, minor update to spdx header script --- .mailmap | 11 ++++++ OWNERSHIP.md | 73 ++++++++++++++++++++++++++++++++++++ scripts/add-spdx-headers.mjs | 39 ++++++++++++++++--- 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 .mailmap create mode 100644 OWNERSHIP.md diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..429df96 --- /dev/null +++ b/.mailmap @@ -0,0 +1,11 @@ +# Git identity map — P2P Trust Stack +# +# One contributor had two GitHub identities configured on different machines. +# Commits authored as `shannon@iotinventor.com` are the same person as +# `simmunity@gmail.com`. Resolving both to one identity keeps attribution and +# copyright audits correct WITHOUT rewriting history (which would change every +# commit hash). +# +# Check with: git check-mailmap +Shannon ShannonB +Erika Bjune ebjune diff --git a/OWNERSHIP.md b/OWNERSHIP.md new file mode 100644 index 0000000..5b89c89 --- /dev/null +++ b/OWNERSHIP.md @@ -0,0 +1,73 @@ +# Copyright Ownership & Identity Record + +This repository is published by **Trust Stackers** under the license in +[`LICENSE`](LICENSE). See [`NOTICE`](NOTICE) for the dual-license +declaration and third-party attribution. + +This file records *why* Trust Stackers is entitled to license this work, and how +author identities map to people. It exists because a dual license (copyleft + +commercial) is only possible if the licensor owns — or holds sufficient rights +in — **all** of the copyright in the work. + +## Copyright is not the same thing as a Git email + +Ownership is determined by authorship and by the employment / contractor +agreements that assign it, not by the `user.email` configured in a repository. +An author who commits under two addresses is one author. This file keeps that +chain explicit and auditable. + +## Identity map + +| Git identity | Person | Canonical identity | +|---|---|---| +| `ShannonB ` | Shannon | `Shannon ` | +| `ebjune ` | Erika Bjune | `Erika Bjune ` | + +`shannon@iotinventor.com` and `simmunity@gmail.com` are the **same person**, who +had two GitHub identities configured on different machines. The mapping lives in +[`.mailmap`](.mailmap), so `git log`, `git shortlog`, `git blame` and +`git check-mailmap` all report a single identity. + +History is deliberately **not** rewritten. A `git filter-repo --mailmap` pass +would change every commit hash for a cosmetic gain, break existing clones and +invalidate release tags; `.mailmap` achieves the same attribution +non-destructively. + +## Ownership basis + +- Copyright in this repository is held by **Trust Stackers**. +- New contributions are made under the [Contributor License Agreement](CLA.md), + which grants Trust Stackers a perpetual, irrevocable copyright license to + relicense contributions "under any license". That is what keeps the dual + license possible. +- Contributions made **before** the CLA was introduced are covered by the + contributors' employment or contractor IP assignment with Trust Stackers. + +> **Records to keep on file (not a code change):** a signed PIIA, copyright +> assignment, or equivalent for each person in the identity map above. Without +> those records the `Copyright (C) 2026 Trust Stackers` assertion in the SPDX +> headers is not fully supported. + +## Provenance of this repository + +Carved out of the `lp2ptoip` monorepo on 2026-10-15. The last monorepo commit +that touched this repository's source paths (`packages`) was: + +``` +88dcb0bc54a79dd97a2649925b34d7c277692528 2026-10-14 20:26:56 -0700 +added logging management mechanism to stack components +``` + +Verify with: + +```bash +git -C log -1 --format='%H %ad %s' --date=iso -- packages +``` + +## Verifying attribution + +```bash +git log --format='%an <%ae>' | sort -u # every identity in history +git check-mailmap '' # -> Shannon +git shortlog -sne # counts, resolved through .mailmap +``` diff --git a/scripts/add-spdx-headers.mjs b/scripts/add-spdx-headers.mjs index 66c2e9b..0e69aa0 100644 --- a/scripts/add-spdx-headers.mjs +++ b/scripts/add-spdx-headers.mjs @@ -5,14 +5,19 @@ // node scripts/add-spdx-headers.mjs [...] # add headers (idempotent) // node scripts/add-spdx-headers.mjs --check [...] # verify; exit 1 if any missing // +// Options: +// --license= default: GPL-3.0-only OR LicenseRef-TrustStackers-Commercial +// --copyright= default: Copyright (C) 2026 Trust Stackers +// --exclude= skip a directory by name (repeatable), e.g. examples +// // A leading BOM and a leading shebang line are preserved. Generated output // (dist/, node_modules/) is ignored. import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join, extname } from 'node:path'; -const LICENSE_ID = 'GPL-3.0-only OR LicenseRef-TrustStackers-Commercial'; -const COPYRIGHT = 'Copyright (C) 2026 Trust Stackers'; +const DEFAULT_LICENSE_ID = 'GPL-3.0-only OR LicenseRef-TrustStackers-Commercial'; +const DEFAULT_COPYRIGHT = 'Copyright (C) 2026 Trust Stackers'; const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'coverage', '.smoke']); const COMMENT = { @@ -27,10 +32,24 @@ const COMMENT = { const args = process.argv.slice(2); const check = args.includes('--check'); + +function flag(name) { + const hit = args.find((a) => a.startsWith(`${name}=`)); + return hit ? hit.slice(name.length + 1) : undefined; +} + +const LICENSE_ID = flag('--license') || DEFAULT_LICENSE_ID; +const COPYRIGHT = flag('--copyright') || DEFAULT_COPYRIGHT; +const EXCLUDES = args + .filter((a) => a.startsWith('--exclude=')) + .map((a) => a.slice('--exclude='.length)); const roots = args.filter((a) => !a.startsWith('--')); if (roots.length === 0) { - console.error('usage: add-spdx-headers.mjs [--check] [...]'); + console.error( + 'usage: add-spdx-headers.mjs [--check] [--license=] [--copyright=] ' + + '[--exclude=]... [...]', + ); process.exit(2); } @@ -38,6 +57,7 @@ function* walk(dir) { for (const entry of readdirSync(dir, { withFileTypes: true })) { if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue; + if (EXCLUDES.includes(entry.name)) continue; yield* walk(join(dir, entry.name)); } else if (entry.isFile()) { yield join(dir, entry.name); @@ -45,6 +65,15 @@ function* walk(dir) { } } +// A real header is a comment line naming the SPDX identifier near the top of +// the file (after an optional BOM and shebang). A bare substring test is wrong: +// this very script *mentions* the identifier in its own source. +const HEADER_RE = /^(?:\uFEFF)?(?:#![^\n]*\n\s*)?(?:\/\/|--)\s*SPDX-License-Identifier:/; + +function hasHeader(text) { + return HEADER_RE.test(text); +} + let added = 0; let ok = 0; const missing = []; @@ -55,7 +84,7 @@ for (const root of roots) { if (!prefix) continue; const original = readFileSync(file, 'utf8'); - if (original.includes('SPDX-License-Identifier')) { + if (hasHeader(original)) { ok++; continue; } @@ -91,7 +120,7 @@ if (check) { if (missing.length) { console.error(`FAIL: ${missing.length} file(s) are missing an SPDX header:`); for (const file of missing) console.error(` - ${file}`); - console.error('\nFix: node scripts/add-spdx-headers.mjs packages'); + console.error('\nFix: node scripts/add-spdx-headers.mjs '); process.exit(1); } console.log(`OK: all ${ok} source file(s) carry an SPDX header.`);