Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -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@iotinventor.com>
Shannon <simmunity@gmail.com> ShannonB <shannon@iotinventor.com>
Erika Bjune <erika@simmunity.com> ebjune <erika@simmunity.com>
73 changes: 73 additions & 0 deletions OWNERSHIP.md
Original file line number Diff line number Diff line change
@@ -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@iotinventor.com>` | Shannon | `Shannon <simmunity@gmail.com>` |
| `ebjune <erika@simmunity.com>` | Erika Bjune | `Erika Bjune <erika@simmunity.com>` |

`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 <lp2ptoip> 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@iotinventor.com>' # -> Shannon <simmunity@gmail.com>
git shortlog -sne # counts, resolved through .mailmap
```
39 changes: 34 additions & 5 deletions scripts/add-spdx-headers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
// node scripts/add-spdx-headers.mjs <root-dir> [...] # add headers (idempotent)
// node scripts/add-spdx-headers.mjs --check <root-dir> [...] # verify; exit 1 if any missing
//
// Options:
// --license=<SPDX expression> default: GPL-3.0-only OR LicenseRef-TrustStackers-Commercial
// --copyright=<text> default: Copyright (C) 2026 Trust Stackers
// --exclude=<dir-name> 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 = {
Expand All @@ -27,24 +32,48 @@ 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] <root-dir> [...]');
console.error(
'usage: add-spdx-headers.mjs [--check] [--license=<SPDX>] [--copyright=<text>] ' +
'[--exclude=<dir>]... <root-dir> [...]',
);
process.exit(2);
}

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);
}
}
}

// 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 = [];
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 <root-dir>');
process.exit(1);
}
console.log(`OK: all ${ok} source file(s) carry an SPDX header.`);
Expand Down
Loading