2026-07-30 07:27:13 -07:00
/**
* Generate `THIRD_PARTY_NOTICES.md` from the workspace manifests: every
* external dependency named by a workspace `package.json`, the vendored-package
* manifest in `vendor/README.md`, the Python `pyproject.toml` files, and the
* pnpm patch list. License and repository metadata come from the installed
* store, so the tree must be installed. `--check` verifies the committed
* artifact. Tier policy and ownership live in
* `.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md`.
*/
import { existsSync , globSync , readdirSync , readFileSync , writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import * as yaml from 'js-yaml'
2026-07-31 17:12:41 +08:00
import { parse as parseToml , type TomlTableWithoutBigInt , type TomlValueWithoutBigInt } from 'smol-toml'
2026-07-31 17:16:08 +08:00
import parseSpdx from 'spdx-expression-parse'
2026-07-30 07:27:13 -07:00
const root = resolve ( import . meta . dirname , '..' )
const OUT = 'THIRD_PARTY_NOTICES.md'
/** Dependency-declaration kinds a consumer resolves at runtime. */
const RUNTIME_KINDS = [ 'dependencies' , 'optionalDependencies' ] as const
/** All manifest sections that name an external package this file must disclose. */
const ALL_KINDS = [ 'dependencies' , 'devDependencies' , 'optionalDependencies' , 'peerDependencies' ] as const
/**
* Workspace areas that never reach a user: repository tooling and gates (the
* root manifest), test infrastructure, the documentation site, the runnable
* demo leaves, and the native launcher's build workspace. A runtime
* declaration by anything outside these areas is a disclosure-relevant
* runtime dependency, because `scripts/install.sh` installs the repository
* itself and any plugin package can be mounted from a user's `cordis.yml`.
*/
const DEV_ONLY_AREAS = [
'package.json' ,
'packages/support/' ,
'packages/client/test-runtime/' ,
'website/' ,
'examples/' ,
'native/' ,
] as const
/**
* First-party packages released from sibling repositories under the project's
* own license: reachable from workspace manifests but not third-party.
*/
2026-07-30 09:23:00 -07:00
const FIRST_PARTY = new Set ( [
'node-addon-landlock-run' ,
'node-addon-landlock-run-linux-arm64' ,
'node-addon-landlock-run-linux-x64' ,
] )
2026-07-30 07:27:13 -07:00
/**
* Metadata overrides where the installed manifest is wrong or unreachable.
* Each entry documents why the store cannot answer.
*/
const OVERRIDES : Record < string , { license ? : string ; repo ? : string } > = {
// Rust workspaces publishing npm bins without `license` in package.json.
'oxlint' : { license : 'MIT' , repo : 'https://github.com/oxc-project/oxc' } ,
'oxlint-tsgolint' : { license : 'MIT' , repo : 'https://github.com/oxc-project/tsgolint' } ,
// `license: SEE LICENSE IN LICENSE`: the servers repo is mid MIT→Apache-2.0
// relicensing, so the effective terms are per-contribution.
'@modelcontextprotocol/server-everything' : { license : 'MIT / Apache-2.0' , repo : 'https://github.com/modelcontextprotocol/servers' } ,
'@modelcontextprotocol/server-filesystem' : { license : 'MIT / Apache-2.0' , repo : 'https://github.com/modelcontextprotocol/servers' } ,
// No repository field in the published manifest.
'node-addon-require-builtin' : { repo : 'https://www.npmjs.com/package/node-addon-require-builtin' } ,
}
/**
* Python dependencies are few and named directly in `pyproject.toml` files
* without installed metadata to harvest, so license/repo are recorded here and
* the generator fails when a manifest names a package this map misses.
*/
const PYTHON_METADATA : Record < string , { license : string ; repo : string ; role : string } > = {
pydantic : { license : 'MIT' , repo : 'https://github.com/pydantic/pydantic' , role : 'runtime dependency of `deepseek-harness`' } ,
hatchling : { license : 'MIT' , repo : 'https://github.com/pypa/hatch' , role : 'build backend' } ,
pytest : { license : 'MIT' , repo : 'https://github.com/pytest-dev/pytest' , role : 'test-only' } ,
}
2026-07-31 17:13:50 +08:00
type PythonMetadata = typeof PYTHON_METADATA
2026-07-30 07:27:13 -07:00
/** Tools fetched by scripts at build time, keyed by the pin the script owns. */
const BUILD_TIME_TOOLS = [
{
name : '@yao-pkg/pkg' ,
license : 'MIT' ,
repo : 'https://github.com/yao-pkg/pkg' ,
role : 'invoked by `scripts/build-exe-for-python-sdk.ts` to assemble the single-file SDK runtime executable' ,
pinSource : 'scripts/build-exe-for-python-sdk.ts' ,
} ,
]
/** The `package.json` fields this generator reads. */
export interface Manifest {
name? : string
private ? : boolean
license? : string
dependencies? : Record < string , string >
devDependencies? : Record < string , string >
optionalDependencies? : Record < string , string >
peerDependencies? : Record < string , string >
}
/** One disclosed external npm dependency. */
interface ExternalDep {
name : string
license : string
repo : string
/** True when some shipped workspace consumer reaches it through runtime dependency edges. */
runtime : boolean
}
/** Read and parse a workspace-relative `package.json`. */
function readManifest ( rel : string ) : Manifest {
return JSON . parse ( readFileSync ( resolve ( root , rel ) , 'utf8' ) ) as Manifest
}
2026-07-30 10:28:16 -07:00
/**
* Manifest globs, derived from the workspace declarations rather than listed
* here, so a new member area (`tools/*`) is read the day it is declared.
* @returns one glob per manifest-bearing location, repository-relative.
*/
export function manifestPatterns ( rootMembers : readonly string [ ] , nativeMembers : readonly string [ ] ) : string [ ] {
return [
'package.json' ,
. . . rootMembers . map ( member = > ` ${ member } /package.json ` ) ,
// The demo leaves join the workspace through `examples/package.json`, so
// their own manifests are members of nothing and no glob above reaches them.
'examples/*/package.json' ,
// `native/landlock-run` is a nested workspace with its own lock file.
'native/landlock-run/package.json' ,
. . . nativeMembers . map ( member = > ` native/landlock-run/ ${ member } /package.json ` ) ,
]
}
/** The `packages:` member globs declared by one pnpm workspace file. */
function workspaceMembers ( rel : string ) : string [ ] {
const declared = ( yaml . load ( readFileSync ( resolve ( root , rel ) , 'utf8' ) ) as { packages? : unknown } ) . packages
if ( ! Array . isArray ( declared ) || declared . length === 0 ) {
throw new Error ( ` gen-third-party-notices: ${ rel } declares no workspace members; the manifest set cannot be derived. ` )
}
return declared . map ( member = > String ( member ) )
}
2026-08-01 19:13:42 +08:00
/**
* Every workspace manifest, keyed by repository-relative path, plus the set of
* workspace package names. Paths are normalized to `/` at ingestion: Node's
* `fs.globSync` returns OS-native separators, and the area matching in
* `tierExternalDeps` compares `/`-suffixed prefixes, so Windows backslashes
* would silently push dev-area manifests into the runtime tier.
*/
2026-07-30 07:27:13 -07:00
function loadWorkspaceManifests ( ) : { manifests : Map < string , Manifest > ; names : Set < string > } {
2026-07-30 10:28:16 -07:00
const patterns = manifestPatterns ( workspaceMembers ( 'pnpm-workspace.yaml' ) , workspaceMembers ( 'native/landlock-run/pnpm-workspace.yaml' ) )
2026-07-30 07:27:13 -07:00
const manifests = new Map < string , Manifest > ( )
const names = new Set < string > ( )
for ( const pattern of patterns ) {
for ( const path of globSync ( pattern , { cwd : root } ) ) {
2026-08-01 19:13:42 +08:00
const normalized = path . replaceAll ( '\\' , '/' )
const manifest = readManifest ( normalized )
manifests . set ( normalized , manifest )
2026-07-30 07:27:13 -07:00
if ( manifest . name !== undefined ) names . add ( manifest . name )
}
}
if ( manifests . size < 100 ) throw new Error ( ` gen-third-party-notices: only ${ manifests . size } workspace manifests found; the glob set is stale. ` )
return { manifests , names }
}
2026-08-01 18:04:38 +08:00
type VirtualManifest = Manifest & { license? : string ; repository? : string | { url? : string } ; homepage? : string }
/**
* Resolve one package's manifest inside a pnpm virtual store. The prefix scan
* matches ordinary `@scope+name@version` directory names; pnpm 11 truncates
* long names (a peer-suffixed name past the length limit becomes
* `<prefix>_<hash>`), so a content scan falls back over the whole store when
* the prefix misses.
2026-08-02 01:11:08 +08:00
*
* @param virtual - the `.pnpm` virtual store directory to scan.
* @param name - the external package name, exactly as `node_modules` spells it.
* @returns the parsed manifest, or `undefined` when neither the prefix match
* nor the content scan finds the package's `package.json`.
2026-08-01 18:04:38 +08:00
*/
2026-08-02 01:11:08 +08:00
export function virtualManifest ( virtual : string , name : string ) : VirtualManifest | undefined {
2026-08-01 18:04:38 +08:00
const prefix = ` ${ name . replace ( '/' , '+' ) } @ `
const entry = readdirSync ( virtual ) . find ( dir = > dir . startsWith ( prefix ) )
if ( entry !== undefined ) {
return JSON . parse ( readFileSync ( resolve ( virtual , entry , 'node_modules' , name , 'package.json' ) , 'utf8' ) ) as VirtualManifest
}
for ( const dir of readdirSync ( virtual ) ) {
const candidate = resolve ( virtual , dir , 'node_modules' , name , 'package.json' )
if ( existsSync ( candidate ) ) {
return JSON . parse ( readFileSync ( candidate , 'utf8' ) ) as VirtualManifest
}
}
return undefined
}
2026-07-30 07:27:13 -07:00
/** License and repository URL for an installed external package, from the pnpm store. */
function installedMetadata ( name : string ) : { license : string ; repo : string } {
const override = OVERRIDES [ name ]
let manifest : ( Manifest & { license? : string ; repository? : string | { url? : string } ; homepage? : string } ) | undefined
2026-07-30 10:28:16 -07:00
// The nested Landlock workspace installs into its own store, so a package
// only that workspace depends on is unreachable from the root one.
for ( const store of [ 'node_modules' , 'native/landlock-run/node_modules' ] ) {
const direct = resolve ( root , store , name , 'package.json' )
if ( existsSync ( direct ) ) {
manifest = JSON . parse ( readFileSync ( direct , 'utf8' ) ) as typeof manifest
break
2026-07-30 07:27:13 -07:00
}
2026-07-30 10:28:16 -07:00
const virtual = resolve ( root , store , '.pnpm' )
if ( ! existsSync ( virtual ) ) continue
2026-08-01 18:04:38 +08:00
manifest = virtualManifest ( virtual , name )
if ( manifest !== undefined ) break
2026-07-30 07:27:13 -07:00
}
const license = override ? . license ? ? manifest ? . license
const rawRepo = typeof manifest ? . repository === 'string' ? manifest.repository : manifest?.repository?.url ? ? manifest ? . homepage
const repo = override ? . repo ? ? normalizeRepo ( rawRepo )
if ( license === undefined || repo === undefined ) {
2026-07-30 19:54:27 -07:00
throw new Error ( ` gen-third-party-notices: cannot resolve ${ license === undefined ? 'license' : 'repository' } for ${ name } ; run \` pnpm install \` (or, for a Landlock-only dependency, \` pnpm --dir native/landlock-run install \` ), or add an OVERRIDES entry. ` )
2026-07-30 07:27:13 -07:00
}
return { license , repo }
}
/** Normalize a manifest repository/homepage value to a browsable https URL. */
function normalizeRepo ( raw : string | undefined ) : string | undefined {
if ( raw === undefined || raw === '' ) return undefined
let url = raw
. replace ( /^git\+ssh:\/\/git@/ , 'https://' )
. replace ( /^git\+/ , '' )
. replace ( /^git:\/\// , 'https://' )
. replace ( /^github:/ , 'https://github.com/' )
. replace ( /\.git$/ , '' )
if ( ! url . startsWith ( 'http' ) ) url = ` https://github.com/ ${ url } `
return url
}
/**
* External npm dependencies, tiered by which workspace area declares them at
* runtime: a package is runtime when any manifest outside `DEV_ONLY_AREAS`
* names it in `dependencies`/`optionalDependencies`. A package declared only
* by tooling, test infrastructure, the website, or the demo leaves — whatever
* the declaring section is called — is development-only.
*/
function collectNpmDeps ( ) : ExternalDep [ ] {
const { manifests , names } = loadWorkspaceManifests ( )
return [ . . . tierExternalDeps ( manifests , names ) ]
. filter ( ( [ name ] ) = > ! FIRST_PARTY . has ( name ) )
. sort ( ( [ a ] , [ b ] ) = > a . localeCompare ( b ) )
. map ( ( [ name , runtime ] ) = > ( { name , . . . installedMetadata ( name ) , runtime } ) )
}
/**
* Tier every external dependency the workspace declares.
* @param manifests - workspace manifests keyed by repository-relative path.
* @param names - every workspace package name, which never counts as external.
* @returns each external package mapped to whether it is a runtime dependency.
*/
export function tierExternalDeps ( manifests : Map < string , Manifest > , names : Set < string > ) : Map < string , boolean > {
const tiers = new Map < string , boolean > ( )
// `tsx` is runtime by fiat: `bin/dsh` execs the CLI through its ESM hook.
tiers . set ( 'tsx' , true )
for ( const [ path , manifest ] of manifests ) {
const devOnly = DEV_ONLY_AREAS . some ( area = > ( area . endsWith ( '/' ) ? path . startsWith ( area ) : path === area ) )
for ( const kind of ALL_KINDS ) {
for ( const [ dep , range ] of Object . entries ( manifest [ kind ] ? ? { } ) ) {
if ( names . has ( dep ) || range . startsWith ( 'workspace:' ) ) continue
const runtime = ! devOnly && ( RUNTIME_KINDS as readonly string [ ] ) . includes ( kind )
tiers . set ( dep , ( tiers . get ( dep ) ? ? false ) || runtime )
}
}
}
return tiers
}
/** A vendored package row parsed out of the `vendor/README.md` manifest table. */
export interface VendoredRow {
npmName : string
upstream : string
}
/**
* Parse the vendored-package manifest table out of `vendor/README.md`.
* @param text - the complete `vendor/README.md` contents.
* @returns one row per manifest-table entry, in table order.
*/
export function parseVendoredRows ( text : string ) : VendoredRow [ ] {
const rows : VendoredRow [ ] = [ ]
for ( const line of text . split ( '\n' ) ) {
const match = /^\| \x60\S+\/\x60 \| \x60([^\x60]+)\x60 \| \S+ \| (https:\/\/\S+?)(?: \([^)]*\))? \| \x60[0-9a-f]+\x60 \|$/ . exec ( line )
if ( match === null ) continue
const [ , npmName , upstream ] = match
if ( npmName === undefined || upstream === undefined ) continue
rows . push ( { npmName , upstream } )
}
return rows
}
2026-07-30 09:23:00 -07:00
/**
* Parse the vendored manifest table and confirm it accounts for every vendored
* directory. The `vendor/` tree — not the table — is the set that must be
* disclosed, so a row that stops matching the table format is a hard error
* rather than a package that quietly vanishes from the notices.
*/
2026-07-30 07:27:13 -07:00
function collectVendored ( ) : VendoredRow [ ] {
const rows = parseVendoredRows ( readFileSync ( resolve ( root , 'vendor/README.md' ) , 'utf8' ) )
2026-07-30 09:23:00 -07:00
const onDisk = new Map < string , string > ( )
for ( const entry of readdirSync ( resolve ( root , 'vendor' ) , { withFileTypes : true } ) ) {
if ( ! entry . isDirectory ( ) ) continue
const manifest = readManifest ( ` vendor/ ${ entry . name } /package.json ` )
if ( manifest . name !== undefined ) onDisk . set ( manifest . name , entry . name )
}
const parsed = new Set ( rows . map ( row = > row . npmName ) )
const missing = [ . . . onDisk . keys ( ) ] . filter ( name = > ! parsed . has ( name ) )
if ( missing . length > 0 ) {
throw new Error ( ` gen-third-party-notices: vendor/README.md has no manifest-table row for ${ missing . join ( ', ' ) } ; its table format changed or the sync is incomplete. ` )
}
2026-07-30 07:27:13 -07:00
for ( const row of rows ) {
2026-07-30 09:23:00 -07:00
const dir = onDisk . get ( row . npmName )
if ( dir === undefined ) throw new Error ( ` gen-third-party-notices: vendored package ${ row . npmName } from vendor/README.md has no vendor/ directory. ` )
const license = readManifest ( ` vendor/ ${ dir } /package.json ` ) . license
if ( license !== 'MIT' ) {
throw new Error ( ` gen-third-party-notices: vendored ${ row . npmName } declares license ${ JSON . stringify ( license ) } ; the vendored section assumes MIT throughout. ` )
2026-07-30 07:27:13 -07:00
}
}
return rows
}
2026-07-31 17:12:41 +08:00
/** Whether a parsed TOML value is a table rather than an array or scalar. */
function isTomlTable ( value : TomlValueWithoutBigInt | undefined ) : value is TomlTableWithoutBigInt {
return value !== undefined && typeof value === 'object' && ! Array . isArray ( value )
}
/** Parse one PEP 508 requirement string into its distribution name. */
function parsePythonRequirement ( requirement : string ) : string {
const name = /^\s*([a-zA-Z][a-zA-Z0-9._-]*)\s*(?:\[[^\]]*\])?\s*(?:[<>=!~;@].*)?$/ . exec ( requirement ) ? . [ 1 ]
if ( name === undefined ) {
throw new Error ( ` gen-third-party-notices: cannot read a distribution name from the requirement ${ JSON . stringify ( requirement ) } . ` )
}
return name
}
/** Add the string requirements from one parsed TOML array. */
function collectPythonRequirementArray (
names : string [ ] ,
value : TomlValueWithoutBigInt | undefined ,
location : string ,
allowGroupIncludes = false ,
) : void {
if ( value === undefined ) return
if ( ! Array . isArray ( value ) ) {
throw new Error ( ` gen-third-party-notices: ${ location } must be an array. ` )
}
for ( const item of value ) {
if ( typeof item === 'string' ) {
names . push ( parsePythonRequirement ( item ) )
continue
2026-07-30 19:54:27 -07:00
}
2026-07-31 17:12:41 +08:00
if ( allowGroupIncludes && isTomlTable ( item ) && typeof item [ 'include-group' ] === 'string' && Object . keys ( item ) . length === 1 ) {
continue
}
throw new Error ( ` gen-third-party-notices: ${ location } contains an unsupported requirement entry. ` )
2026-07-30 07:27:13 -07:00
}
2026-07-31 17:12:41 +08:00
}
/** Read an optional TOML table and reject a present value of another shape. */
function optionalTomlTable ( value : TomlValueWithoutBigInt | undefined , location : string ) : TomlTableWithoutBigInt | undefined {
if ( value === undefined || isTomlTable ( value ) ) return value
throw new Error ( ` gen-third-party-notices: ${ location } must be a table. ` )
2026-07-30 07:27:13 -07:00
}
2026-07-30 10:28:16 -07:00
/**
2026-07-31 17:13:50 +08:00
* Parse a `pyproject.toml` project identity and every requirement it declares:
* `requires` under
2026-07-31 17:12:41 +08:00
* `[build-system]`, `dependencies` under `[project]`, and every key under
* `[project.optional-dependencies]` and `[dependency-groups]`. A TOML parser
* owns comments, quoted keys, escapes, and array boundaries; unsupported
* requirement shapes fail instead of disappearing from the notices.
2026-07-30 10:28:16 -07:00
* @param text - the complete `pyproject.toml` contents.
2026-07-31 17:13:50 +08:00
* @returns the local project name and declared requirement names.
2026-07-30 10:28:16 -07:00
*/
2026-07-31 17:13:50 +08:00
function parsePyproject ( text : string ) : { projectName? : string ; requirements : string [ ] } {
2026-07-30 10:28:16 -07:00
const names : string [ ] = [ ]
2026-07-31 17:12:41 +08:00
const document = parseToml ( text , { integersAsBigInt : false } )
const buildSystem = optionalTomlTable ( document [ 'build-system' ] , '[build-system]' )
const project = optionalTomlTable ( document . project , '[project]' )
2026-07-31 17:13:50 +08:00
const projectName = project ? . name
if ( projectName !== undefined && typeof projectName !== 'string' ) {
throw new Error ( 'gen-third-party-notices: [project].name must be a string.' )
}
2026-07-31 17:12:41 +08:00
collectPythonRequirementArray ( names , buildSystem ? . requires , '[build-system].requires' )
collectPythonRequirementArray ( names , project ? . dependencies , '[project].dependencies' )
2026-07-30 10:28:16 -07:00
2026-07-31 17:12:41 +08:00
const optional = optionalTomlTable ( project ? . [ 'optional-dependencies' ] , '[project.optional-dependencies]' )
for ( const [ group , requirements ] of Object . entries ( optional ? ? { } ) ) {
collectPythonRequirementArray ( names , requirements , ` [project.optional-dependencies]. ${ group } ` )
}
const groups = optionalTomlTable ( document [ 'dependency-groups' ] , '[dependency-groups]' )
for ( const [ group , requirements ] of Object . entries ( groups ? ? { } ) ) {
collectPythonRequirementArray ( names , requirements , ` [dependency-groups]. ${ group } ` , true )
2026-07-30 10:28:16 -07:00
}
2026-07-31 17:13:50 +08:00
return projectName === undefined
? { requirements : names }
: { projectName , requirements : names }
}
/**
* Read every requirement name declared by one `pyproject.toml`.
* @param text - the complete `pyproject.toml` contents.
* @returns each declared requirement's distribution name, in file order.
*/
export function parsePyprojectRequirements ( text : string ) : string [ ] {
return parsePyproject ( text ) . requirements
}
/** Normalize a Python distribution name according to the packaging name rule. */
function normalizePythonDistributionName ( name : string ) : string {
return name . toLowerCase ( ) . replace ( /[-_.]+/g , '-' )
}
/**
* Resolve external Python dependencies after excluding local project names.
* @param pyprojects - complete local `pyproject.toml` contents.
* @param metadata - disclosure metadata for every external dependency.
* @returns disclosed dependencies in normalized name order.
*/
export function collectPythonDependencies (
pyprojects : string [ ] ,
metadata : PythonMetadata = PYTHON_METADATA ,
) : { name : string ; license : string ; repo : string ; role : string } [ ] {
const parsed = pyprojects . map ( parsePyproject )
const firstParty = new Set ( parsed . flatMap ( ( { projectName } ) = > (
projectName === undefined ? [ ] : [ normalizePythonDistributionName ( projectName ) ]
) ) )
const found = new Set ( parsed
. flatMap ( ( { requirements } ) = > requirements . map ( normalizePythonDistributionName ) )
. filter ( name = > ! firstParty . has ( name ) ) )
return [ . . . found ] . sort ( ( a , b ) = > a . localeCompare ( b ) ) . map ( ( name ) = > {
const entry = metadata [ name ]
if ( entry === undefined ) throw new Error ( ` gen-third-party-notices: python dependency ${ name } is missing from PYTHON_METADATA. ` )
return { name , . . . entry }
} )
2026-07-30 10:28:16 -07:00
}
2026-07-30 07:27:13 -07:00
/** Direct Python dependencies named by the `pyproject.toml` manifests under `python/`. */
function collectPython ( ) : { name : string ; license : string ; repo : string ; role : string } [ ] {
2026-07-30 19:54:27 -07:00
const manifests = globSync ( 'python/*/pyproject.toml' , { cwd : root } )
if ( manifests . length === 0 ) throw new Error ( 'gen-third-party-notices: no python/*/pyproject.toml found; the Python tree moved.' )
2026-07-31 17:13:50 +08:00
return collectPythonDependencies ( manifests . map ( path = > readFileSync ( resolve ( root , path ) , 'utf8' ) ) )
2026-07-30 07:27:13 -07:00
}
/** pnpm-patched external packages, from `pnpm-workspace.yaml`. */
function collectPatched ( ) : { spec : string ; patch : string } [ ] {
const workspace = yaml . load ( readFileSync ( resolve ( root , 'pnpm-workspace.yaml' ) , 'utf8' ) ) as { patchedDependencies? : Record < string , string > }
return Object . entries ( workspace . patchedDependencies ? ? { } ) . map ( ( [ spec , patch ] ) = > ( { spec , patch } ) )
}
/** Verify each build-time tool pin still appears in its owning script. */
function verifyBuildTimePins ( ) : void {
for ( const tool of BUILD_TIME_TOOLS ) {
const text = readFileSync ( resolve ( root , tool . pinSource ) , 'utf8' )
if ( ! text . includes ( tool . name ) ) {
throw new Error ( ` gen-third-party-notices: ${ tool . pinSource } no longer references ${ tool . name } ; update BUILD_TIME_TOOLS. ` )
}
}
}
2026-07-30 19:54:27 -07:00
/** SPDX identifiers this project may ship without further review. */
const PERMISSIVE_LICENSES = new Set ( [ 'MIT' , 'ISC' , 'BSD-2-Clause' , 'BSD-3-Clause' , 'Apache-2.0' , '0BSD' , 'Unlicense' , 'CC0-1.0' , 'BlueOak-1.0.0' , 'Python-2.0' ] )
2026-07-31 17:16:08 +08:00
/** Evaluate a parsed SPDX expression under the repository's license policy. */
function isPermissiveSpdx ( expression : ReturnType < typeof parseSpdx > ) : boolean {
if ( 'conjunction' in expression ) {
return expression . conjunction === 'and'
? isPermissiveSpdx ( expression . left ) && isPermissiveSpdx ( expression . right )
: isPermissiveSpdx ( expression . left ) || isPermissiveSpdx ( expression . right )
}
return expression . plus !== true
&& expression . exception === undefined
&& PERMISSIVE_LICENSES . has ( expression . license )
}
2026-07-30 10:28:16 -07:00
/**
2026-07-30 19:54:27 -07:00
* Whether an SPDX expression grants terms this project may ship under.
* `OR` needs one permissive alternative, because the consumer chooses; `AND`
* needs all of them, because every obligation applies. Anything that is not a
* recognized permissive identifier — copyleft, an exception clause, or a
* license this list has never seen — evaluates to false, so an unfamiliar
* expression fails closed rather than passing on a partial match.
2026-07-30 10:28:16 -07:00
* @param license - the SPDX expression from the package manifest.
2026-07-30 19:54:27 -07:00
* @returns true when the expression's obligations are all permissive.
2026-07-30 10:28:16 -07:00
*/
export function isPermissive ( license : string ) : boolean {
2026-07-31 17:16:08 +08:00
// Some npm manifests use a slash for a choice despite SPDX requiring `OR`.
const normalized = license . replace ( /\s*\/\s*/g , ' OR ' ) . trim ( )
try {
return isPermissiveSpdx ( parseSpdx ( normalized ) )
} catch {
return false
2026-07-30 19:54:27 -07:00
}
2026-07-30 10:28:16 -07:00
}
/**
* Render the sentence that isolates non-permissive development tooling, or
* nothing at all when every development dependency is permissive.
* @param deps - development dependencies whose license is not permissive.
* @returns the paragraph to place after the development table.
*/
function renderNonPermissiveNote ( deps : ExternalDep [ ] ) : string {
if ( deps . length === 0 ) return ''
const named = deps . map ( dep = > ` \` ${ dep . name } \` ( ${ dep . license } ) ` )
const subject = named . length === 1 ? named [ 0 ] : ` ${ named . slice ( 0 , - 1 ) . join ( ', ' ) } and ${ named . at ( - 1 ) } `
return ` \ n ${ subject } ${ named . length === 1 ? 'runs' : 'run' } only as development tooling; their code is not linked into or distributed with any DeepSeek Harness artifact. \ n `
}
2026-07-30 07:27:13 -07:00
/** Render one npm dependency table. */
function renderNpmTable ( deps : ExternalDep [ ] ) : string {
const lines = [ '| Package | License |' , '| --- | --- |' ]
for ( const dep of deps ) lines . push ( ` | [ \` ${ dep . name } \` ]( ${ dep . repo } ) | ${ dep . license } | ` )
return lines . join ( '\n' )
}
2026-07-30 07:56:13 -07:00
/**
* Render the complete notices document.
* @returns the exact bytes `THIRD_PARTY_NOTICES.md` must hold.
*/
export function render ( ) : string {
2026-07-30 07:27:13 -07:00
verifyBuildTimePins ( )
const npm = collectNpmDeps ( )
const runtimeDeps = npm . filter ( dep = > dep . runtime )
const devDeps = npm . filter ( dep = > ! dep . runtime )
const vendored = collectVendored ( )
const python = collectPython ( )
const patched = collectPatched ( )
2026-07-30 10:28:16 -07:00
const nonPermissiveDev = devDeps . filter ( dep = > ! isPermissive ( dep . license ) )
// A copyleft license reaching a shipped surface is a distribution decision,
// not a rendering detail; the notices cannot quietly absorb it.
const nonPermissiveRuntime = runtimeDeps . filter ( dep = > ! isPermissive ( dep . license ) )
if ( nonPermissiveRuntime . length > 0 ) {
throw new Error ( ` gen-third-party-notices: runtime ${ nonPermissiveRuntime . map ( dep = > ` ${ dep . name } ( ${ dep . license } ) ` ) . join ( ', ' ) } is not a permissive license; review the distribution terms and record the decision before regenerating. ` )
}
2026-07-30 07:27:13 -07:00
const patchedLines = patched . map ( ( { spec , patch } ) = > ` - \` ${ spec } \` — [ \` ${ patch } \` ]( ${ patch } ) ` )
return ` <!-- Generated by scripts/gen-third-party-notices.ts — do not edit by hand.
Run \` pnpm run gen-third-party-notices \` to regenerate. -->
# Third-Party Notices
DeepSeek Harness is licensed under [BSD 3-Clause](LICENSE). It depends on the third-party open-source software listed below. Each project remains under its own license; nothing in this file changes those terms.
2026-07-30 19:54:27 -07:00
This file lists **direct** dependencies declared by the workspace. It is generated from the workspace manifests by \` scripts/gen-third-party-notices.ts \` : a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and \` scripts/gen-third-party-notices.spec.ts \` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run \` pnpm run verify-third-party-notices \` for the standalone check.
2026-07-30 09:23:00 -07:00
The complete npm transitive closure, with exact pinned versions, is recorded in [ \` pnpm-lock.yaml \` ](pnpm-lock.yaml) — inspect it with \` pnpm licenses list \` . The Python closure is recorded in [ \` python/sdk/uv.lock \` ](python/sdk/uv.lock), and the Landlock launcher workspace keeps its own in [ \` native/landlock-run/pnpm-lock.yaml \` ](native/landlock-run/pnpm-lock.yaml).
2026-07-30 07:27:13 -07:00
## Vendored source ( \` vendor/ \` )
The Cordis framework and its foundation libraries are source-vendored into this repository rather than consumed from npm. All are MIT-licensed; each directory preserves its upstream \` LICENSE \` file. Exact upstream commits and local modifications are recorded in [ \` vendor/README.md \` ](vendor/README.md).
| Package | Upstream | License |
| --- | --- | --- |
${ vendored . map ( row = > ` | \` ${ row . npmName } \` | [ ${ row . upstream . replace ( 'https://' , '' ) } ]( ${ row . upstream } ) | MIT | ` ) . join ( '\n' ) }
## Runtime npm dependencies
2026-08-05 12:46:38 +08:00
External packages that a workspace package resolves at runtime. \` scripts/install.sh \` installs this repository itself, so the tier covers every plugin a user can mount from \` cordis.yml \` — not only what the \` dsh \` CLI, Web UI, and Python SDK runtime load by default.
2026-07-30 07:27:13 -07:00
${ renderNpmTable ( runtimeDeps ) }
pnpm applies local patches to the following packages at install time, so shipped artifacts carry modified copies; each patch file is the complete record of the modification:
${ patchedLines . join ( '\n' ) }
## Development-only npm dependencies
2026-07-30 19:54:27 -07:00
External packages **directly declared** only by repository tooling, test infrastructure, the documentation site, the demo leaves, or the native launcher's build workspace. No shipped surface names them itself. A package here may still be pulled in transitively by a runtime dependency — \` pnpm-lock.yaml \` is the authority on the full closure — so this tier records who declares a package, not what a build ultimately bundles.
2026-07-30 07:27:13 -07:00
${ renderNpmTable ( devDeps ) }
2026-07-30 10:28:16 -07:00
${ renderNonPermissiveNote ( nonPermissiveDev ) }
2026-07-30 07:27:13 -07:00
## Python SDK dependencies ( \` python/ \` )
Direct dependencies of the \` pyproject.toml \` manifests, plus \` uv \` as the development workflow tool.
| Package | License | Role |
| --- | --- | --- |
${ python . map ( dep = > ` | [ \` ${ dep . name } \` ]( ${ dep . repo } ) | ${ dep . license } | ${ dep . role } | ` ) . join ( '\n' ) }
| [ \` uv \` ](https://github.com/astral-sh/uv) | MIT / Apache-2.0 | development workflow tool |
## Fetched at build time
| Package | License | Role |
| --- | --- | --- |
${ BUILD_TIME_TOOLS . map ( tool = > ` | [ \` ${ tool . name } \` ]( ${ tool . repo } ) | ${ tool . license } | ${ tool . role } | ` ) . join ( '\n' ) }
## First-party sibling releases
\` node-addon-landlock-run \` (and its platform packages) is released from a DeepSeek Harness sibling repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party.
`
}
/** CLI entry: default writes the notices, `--check` fails if the committed copy
* is stale. Guarded behind an entry-point check so importing this module for
* tests neither regenerates the committed file nor calls process.exit. */
function main ( ) : void {
const content = render ( )
if ( process . argv . includes ( '--check' ) ) {
let committed : string | null = null
try {
committed = readFileSync ( resolve ( root , OUT ) , 'utf8' )
} catch {
// Only ENOENT (not yet generated) is expected; a present-but-unreadable
// file is not a state this repo produces, and the remedy is the same.
committed = null
}
if ( committed === content ) {
console . log ( ` gen-third-party-notices: ${ OUT } is up to date. ` )
process . exit ( 0 )
}
console . error ( ` gen-third-party-notices: ${ OUT } is stale. Run \` pnpm run gen-third-party-notices \` and commit ${ OUT } . ` )
process . exit ( 1 )
}
writeFileSync ( resolve ( root , OUT ) , content )
console . log ( ` gen-third-party-notices: wrote ${ OUT } . ` )
}
// Run only when invoked as a script, not when imported by a test.
if ( process . argv [ 1 ] !== undefined && import . meta . filename === resolve ( process . argv [ 1 ] ) ) {
main ( )
}