npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@galaxy-foundry/license-policy

v0.8.1

Published

The shared license → redistribution-policy table for Foundry-pattern instances, and its loader.

Downloads

1,925

Readme

@galaxy-foundry/license-policy

The shared license → redistribution-policy table for Foundry-pattern instances, and the loader for it.

A Foundry's corpus is largely notes derived from other people's work, so every note carries a license and every casting decision has to answer one question: what may we redistribute, and in what form? This package holds the table that answers it.

Why it is a package

Two instances — galaxyproject/foundry and jmchilton/statistical-genomics-foundry — kept byte-identical copies of this 268-line table, differing only in a comment naming the other repo, under a file header instructing everyone to "edit both by hand." The neighbouring file they inherited the same way, reference_contract.yml, shows what that instruction is worth: its key sets still match, but five of its eleven inherited rows had quietly drifted in wording, and nothing detected it.

Installing a table is harder to get wrong than mirroring one.

Install

npm install @galaxy-foundry/license-policy

Use

import {
  bundledPolicy,
  isValidLicenseId,
  resolveLicenseRow,
  declaresVerbatimCarry,
} from '@galaxy-foundry/license-policy';

const policy = bundledPolicy();

isValidLicenseId(policy, 'CC-BY-4.0'); // true
isValidLicenseId(policy, 'LicenseRef-msmb'); // true — the escape hatch
isValidLicenseId(policy, 'Made-Up-1.0'); // false

const row = resolveLicenseRow(policy, 'CC-BY-NC-4.0');
row.policy; // 'own-words-only' — its text may not be carried into a cast at all

// ...but only pass-through content is governed. Your own summary of that paper is your prose.
declaresVerbatimCarry('own-words-summary'); // false — out of scope
declaresVerbatimCarry('faithful-summary-with-quotes'); // true — the row applies

// An unknown or missing id lands on the default row: own-words-only, `defect: true`.
resolveLicenseRow(policy, 'typo').defect; // true
resolveLicenseRow(policy, 'typo').name; // 'unresolved / missing'

// An uncurated LicenseRef keeps every policy field of that answer — its terms are unknown too,
// including whatever YOUR table says to do about unknown terms — but is named after itself,
// because the licence IS resolved. Curating a row overrides this.
const ref = resolveLicenseRow(policy, 'LicenseRef-yale-non-commercial');
ref.defect; // true
ref.obligations; // the table's own default obligations, verbatim
ref.name; // 'yale-non-commercial' — identity is not policy, and only it comes from the id

Functions take the policy as their first argument rather than reading module state, so a kind schema can be tested against a synthetic table instead of the real one.

Reading a table from a repo

Instances migrating away from a vendored copy, or tools operating on a checkout, can read the table off disk instead:

import { loadLicensePolicy, findLicensePolicyPath } from '@galaxy-foundry/license-policy';

const policy = loadLicensePolicy('/path/to/instance-repo');
const file = findLicensePolicyPath(); // walks up from cwd

Keeping a local copy honest

If an instance keeps license-policy.yml at its own repo root — for tools that read the file directly, or during migration — one assertion keeps the copy from drifting:

import { readFileSync } from 'node:fs';
import { bundledPolicyText } from '@galaxy-foundry/license-policy';

it('has not drifted from the shared table', () => {
  expect(readFileSync('license-policy.yml', 'utf8')).toBe(bundledPolicyText());
});

That is the point of the package: "any change here is a cross-repo change" stops being a comment nobody enforces and becomes a failing test in whichever instance has not bumped.

The raw file is also reachable directly, for non-JS consumers:

node_modules/@galaxy-foundry/license-policy/data/license-policy.yml

What this package does not do

It answers "what does this license permit", never "is this note coherent with its license."

Those coherence rules genuinely differ between the two instances today. One keys off a recorded derived posture — rejecting a note that declares verbatim carry under an own-words-only license. The other keys off the row alone — rejecting any own-words-only note that ships a license_file at all. They are related, they are not the same rule, and neither has been shown to be the one both instances want. Abstracting them here would be inventing a shared decision nobody has taken.

When the rules converge, they can move here. Until then they stay where they are honest.

API

| Export | What it does | | --------------------------------------- | --------------------------------------------------------------------- | | bundledPolicy() | The shipped table, parsed. The default source of truth. | | bundledPolicyText() | Its raw bytes — for conformance-testing a local copy. | | bundledPolicyPath() | Absolute path to the shipped license-policy.yml. | | parseLicensePolicy(text, source?) | Parse and fully validate a table. source names the file in errors. | | loadLicensePolicy(repoRoot) | Read and validate <repoRoot>/license-policy.yml. | | findLicensePolicyPath(startDir?) | Walk up until a table is found. | | licenseIds(policy) | The curated SPDX ids — drives an instance's schema grammar. | | isValidLicenseId(policy, id) | Curated id, or LicenseRef-<slug>. | | resolveLicenseRow(policy, id) | Row for an id; unknown/missing → default, an uncurated ref → named. | | declaresVerbatimCarry(derived) | Whether a note's posture reproduces upstream expression. | | LICENSE_POLICY_FILE, LICENSE_REF_RE | The conventional filename and the escape-hatch pattern. |

Types: LicensePolicy, LicenseRow, RedistributionPolicy.

The license texts themselves

A license_file: true row obliges an instance to carry a verbatim copy, conventionally at LICENSES/<source>.LICENSE. These read those copies so a site can render license terms in-app rather than bouncing the reader out to GitHub.

| Export | What it does | | ------------------------------------------------------ | -------------------------------------------------- | | loadLicenseFiles(licenseDirectory) | Every *.LICENSE, filename-sorted, with its text. | | findLicenseFileById(licenseDirectory, licenseFileId) | One by its file id, or undefined. | | licenseFileIdFromPath(path) | LICENSES/nf-schema.LICENSEnf-schema. | | LICENSE_FILE_EXTENSION | .LICENSE. |

The directory is a parameter rather than a resolved ../LICENSES, because the callers are Astro pages whose cwd is a subdirectory — an implicit relative path is the one thing that does not survive being shared.

Types: LicenseFile, LicenseFileId.

Checking that the directory and the notes agree

license_file is a string. A schema can require it and cannot open it, so a typo satisfies every check an instance has until something walks the directory.

import { auditLicenseFiles } from '@galaxy-foundry/license-policy';

const findings = auditLicenseFiles({
  licenseDirectory: 'LICENSES',
  declarations: notes.map((note) => ({ source: note.id, licenseFile: note.data.license_file })),
});
expect(findings).toEqual([]);

Declarations go in rather than content being crawled, because where they live is instance-specific. One instance carries license_file in note frontmatter and nowhere else. The other also authors it once per book in a book.yml and copies it into every chapter with a generator — so both the record and its copies are declarations, and a crawler that found only the notes would leave the record it was generated from unchecked. Findings come back rather than throwing, so the instance decides what fails its build.

| Finding | Is | | ----------------- | ----------------------------------------------------------------------- | | missing-copy | A declaration names a copy the directory does not hold. | | unexpected-path | The copy exists; the declared path does not point into the directory. | | unused-copy | A vendored copy no declaration carries under. | | empty-copy | A copy present but blank, which satisfies existence and grants nothing. |

unexpected-path exists because licenseFileIdFromPath reads the basename: a singular LICENSE/ typo and a bare x.LICENSE both resolve to the right text while sending a reader somewhere there is no file. The check is on by default, matching the licence directory's own name against the last segment of the declared path — LICENSES/x.LICENSE and content/LICENSES/x.LICENSE both pass. directoryName: null turns it off for declarations that carry a bare id.

unused-copy is the reverse direction, and it is there for the reason the tag registries are checked both ways: a vocabulary policed in one direction only accumulates. Licence text outlives the note that was rewritten from quotes to own words, and nothing else would ever say so.

A missing licence directory is audited rather than thrown — that is where an instance stands the moment before it vendors its first copy, and listing the unmet declarations says more than ENOENT does. loadLicenseFiles still throws there.

Types: LicenseFileDeclaration, LicenseFileFinding, LicenseFileFindingCode, LicenseFileAuditOptions.

Two ids, and they are not interchangeable

| Type | Is | Looks like | Comes from | | --------------- | --------------------------------- | ------------------- | ------------------------ | | LicenseId | a licence | MIT, CC-BY-4.0 | a note's license: | | LicenseFileId | a vendored copy of some terms | msmb, nf-schema | a note's license_file: |

A LicenseFileId names the source whose licence text was vendored, never the licence: msmb.LICENSE holds CC-BY-NC-SA-2.0. Two sources under one licence vendor two files and get two ids; a note under MIT that vendors nothing has none at all.

Both were spelled licenseId until a route compared one against the other. Nothing enforces the distinction at runtime — both are strings off a filesystem — so findLicenseFileById handed a LicenseId returns undefined: not an error, not the wrong file, a silent miss that reads as "this source vendored nothing".

The table

version: 1, 25 curated SPDX rows plus a deny-by-default default row, and five global_rules that apply across every row. Each row declares:

| Field | Meaning | | -------------- | ------------------------------------------------------------ | | policy | verbatim-ok or own-words-only | | license_file | Whether a verbatim LICENSES/ copy must accompany the carry | | copyleft | Whether the isolate-in-its-own-file obligation applies | | obligations | What honoring the license actually requires |

Parsing is strict: both enums are closed, every field is checked, and an unknown value is an error rather than a silently-ignored key. A published table is a contract, and a row that is merely well-formed YAML can still authorize a carry nobody intended.

Three invariants are asserted against the shipped table on every run — a verbatim-ok row always demands a notice, an own-words-only row never requires a license_file (nothing is redistributed, so there is no notice to carry), and no row names a casting transform at all. The last is asserted rather than merely deleted, so the column cannot come back one row at a time.

Provenance

The table materializes the decision taken in galaxyproject/foundry-pattern#4, backing the guiding principle "Redistributed Content Carries Its License."

Engineering policy, not legal advice. The rows encode the discipline these projects chose; genuinely novel or high-stakes licenses deserve real review before verbatim redistribution.

License

MIT