From 961597fe6431452a8ba89d513b6ae236abcc2e9f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:58:48 +0900 Subject: [PATCH 1/8] test(supply-chain): define machine-readable license boundary --- src/licenseBoundary.test.ts | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/licenseBoundary.test.ts diff --git a/src/licenseBoundary.test.ts b/src/licenseBoundary.test.ts new file mode 100644 index 000000000..3c805525b --- /dev/null +++ b/src/licenseBoundary.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const CANONICAL_MIT_LICENSE = `MIT License + +Copyright (c) 2026 ContextualWisdomLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +`; + +describe('software and bundled-font license evidence', () => { + it('keeps the root software license as exact canonical MIT text', () => { + expect(readFileSync('LICENSE', 'utf8')).toBe(CANONICAL_MIT_LICENSE); + }); + + it('retains the bundled-font attribution and complete OFL terms separately', () => { + expect(readFileSync('src/fonts/NOTICE', 'utf8')).toContain( + 'SIL Open Font License, Version 1.1', + ); + expect(readFileSync('src/fonts/OFL.txt', 'utf8')).toContain( + 'SIL OPEN FONT LICENSE Version 1.1', + ); + }); +}); From c3ce70a527e19c06e41b7d82d369ee8fd5fa402d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:03:32 +0900 Subject: [PATCH 2/8] fix(supply-chain): separate bundled-font licensing --- LICENSE | 9 --------- 1 file changed, 9 deletions(-) diff --git a/LICENSE b/LICENSE index 84eff9573..591bbf197 100644 --- a/LICENSE +++ b/LICENSE @@ -19,12 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -Bundled fonts (src/fonts/): the Noto Sans font families bundled with this -package are NOT covered by the MIT license above. They are licensed under the -SIL Open Font License, Version 1.1 (OFL-1.1) — a permissive, non-copyleft -license compatible with MIT. See src/fonts/OFL.txt for the full license text -and src/fonts/NOTICE for attribution. Fonts are content/assets, not linked -code; bundling and redistribution under OFL-1.1 is expressly permitted. From b2c6b6eb7b9638051688939b722a1094c97da013 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:09:21 +0900 Subject: [PATCH 3/8] test(supply-chain): bind npm license evidence --- src/licenseBoundary.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/licenseBoundary.test.ts b/src/licenseBoundary.test.ts index 3c805525b..56c75a10e 100644 --- a/src/licenseBoundary.test.ts +++ b/src/licenseBoundary.test.ts @@ -24,6 +24,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. `; +interface PackageLicenseManifest { + readonly files?: readonly string[]; + readonly license?: string; +} + describe('software and bundled-font license evidence', () => { it('keeps the root software license as exact canonical MIT text', () => { expect(readFileSync('LICENSE', 'utf8')).toBe(CANONICAL_MIT_LICENSE); @@ -37,4 +42,15 @@ describe('software and bundled-font license evidence', () => { 'SIL OPEN FONT LICENSE Version 1.1', ); }); + + it('keeps both software and bundled-font license evidence in the npm package', () => { + const packageManifest = JSON.parse( + readFileSync('package.json', 'utf8'), + ) as PackageLicenseManifest; + + expect(packageManifest.license).toBe('MIT'); + expect(packageManifest.files).toEqual( + expect.arrayContaining(['LICENSE', 'src/fonts']), + ); + }); }); From 79c0ca643c012b6666dcbc37cefe16dc3d5575bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 20:28:17 +0900 Subject: [PATCH 4/8] test(supply-chain): verify packed license evidence --- src/licenseBoundary.test.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/licenseBoundary.test.ts b/src/licenseBoundary.test.ts index 56c75a10e..8fcf93b9c 100644 --- a/src/licenseBoundary.test.ts +++ b/src/licenseBoundary.test.ts @@ -1,3 +1,4 @@ +import { execFileSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; @@ -29,6 +30,10 @@ interface PackageLicenseManifest { readonly license?: string; } +interface NpmPackDryRunEntry { + readonly files?: readonly { readonly path?: string }[]; +} + describe('software and bundled-font license evidence', () => { it('keeps the root software license as exact canonical MIT text', () => { expect(readFileSync('LICENSE', 'utf8')).toBe(CANONICAL_MIT_LICENSE); @@ -43,7 +48,7 @@ describe('software and bundled-font license evidence', () => { ); }); - it('keeps both software and bundled-font license evidence in the npm package', () => { + it('keeps both software and bundled-font license evidence in the npm package manifest', () => { const packageManifest = JSON.parse( readFileSync('package.json', 'utf8'), ) as PackageLicenseManifest; @@ -53,4 +58,26 @@ describe('software and bundled-font license evidence', () => { expect.arrayContaining(['LICENSE', 'src/fonts']), ); }); + + it('retains both license families in the actual npm packlist', () => { + const packMetadata = JSON.parse( + execFileSync( + 'npm', + ['pack', '--dry-run', '--json', '--ignore-scripts'], + { encoding: 'utf8' }, + ), + ) as readonly NpmPackDryRunEntry[]; + + expect(packMetadata).toHaveLength(1); + const packedPaths = packMetadata[0]?.files?.flatMap(({ path }) => + path === undefined ? [] : [path], + ) ?? []; + expect(packedPaths).toEqual( + expect.arrayContaining([ + 'LICENSE', + 'src/fonts/NOTICE', + 'src/fonts/OFL.txt', + ]), + ); + }); }); From 7b6688971a4846201dbf97f21acee99d49dd2564 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 09:36:25 -0700 Subject: [PATCH 5/8] test(supply-chain): verify license evidence from actual tarball --- src/licenseBoundary.test.ts | 69 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/src/licenseBoundary.test.ts b/src/licenseBoundary.test.ts index 8fcf93b9c..1c64bc861 100644 --- a/src/licenseBoundary.test.ts +++ b/src/licenseBoundary.test.ts @@ -1,5 +1,7 @@ import { execFileSync } from 'node:child_process'; -import { readFileSync } from 'node:fs'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; const CANONICAL_MIT_LICENSE = `MIT License @@ -30,8 +32,8 @@ interface PackageLicenseManifest { readonly license?: string; } -interface NpmPackDryRunEntry { - readonly files?: readonly { readonly path?: string }[]; +interface NpmPackEntry { + readonly filename?: string; } describe('software and bundled-font license evidence', () => { @@ -59,25 +61,48 @@ describe('software and bundled-font license evidence', () => { ); }); - it('retains both license families in the actual npm packlist', () => { - const packMetadata = JSON.parse( - execFileSync( - 'npm', - ['pack', '--dry-run', '--json', '--ignore-scripts'], + it('retains both license families in the actual npm tarball', () => { + const packRoot = mkdtempSync(join(tmpdir(), 'inkspan-license-pack-')); + + try { + const packMetadata = JSON.parse( + execFileSync( + 'npm', + [ + 'pack', + '--json', + '--ignore-scripts', + '--pack-destination', + packRoot, + ], + { encoding: 'utf8' }, + ), + ) as readonly NpmPackEntry[]; + + expect(packMetadata).toHaveLength(1); + const filename = packMetadata[0]?.filename; + expect(filename).toEqual(expect.any(String)); + if (filename === undefined) { + throw new Error('npm pack did not report the generated tarball filename.'); + } + + const archivePaths = execFileSync( + 'tar', + ['-tzf', join(packRoot, filename)], { encoding: 'utf8' }, - ), - ) as readonly NpmPackDryRunEntry[]; - - expect(packMetadata).toHaveLength(1); - const packedPaths = packMetadata[0]?.files?.flatMap(({ path }) => - path === undefined ? [] : [path], - ) ?? []; - expect(packedPaths).toEqual( - expect.arrayContaining([ - 'LICENSE', - 'src/fonts/NOTICE', - 'src/fonts/OFL.txt', - ]), - ); + ) + .split('\n') + .filter((path) => path.length > 0); + + expect(archivePaths).toEqual( + expect.arrayContaining([ + 'package/LICENSE', + 'package/src/fonts/NOTICE', + 'package/src/fonts/OFL.txt', + ]), + ); + } finally { + rmSync(packRoot, { recursive: true, force: true }); + } }); }); From 530f5e1b5311dad2bbdf0de20d9f50398be1df64 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:26:47 +0900 Subject: [PATCH 6/8] test(ci): cover event-specific Python matrix Signed-off-by: Seongho Bae --- office/tests/test_python_support_contract.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index 7104fd661..209f48454 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,10 +50,14 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search(r'python-version:\s*\[([^\]]+)\]', office_job) + matrix_match = re.search(r"python-version:\s*(.+)", office_job) assert matrix_match is not None - matrix_versions = tuple(re.findall(r'"(3\.\d+)"', matrix_match.group(1))) - assert matrix_versions == SUPPORTED_PYTHON_VERSIONS + pull_request_versions, push_versions = ( + tuple(re.findall(r'"(3\.\d+)"', versions)) + for versions in re.findall(r"fromJSON\('(\[[^']+\])'\)", matrix_match.group(1)) + ) + assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) + assert push_versions == SUPPORTED_PYTHON_VERSIONS def test_python_support_documentation_matches_the_fixed_ci_environment() -> None: From 60f98e3fe9cb8166597aade5658a8f6d48f432d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:32:54 +0900 Subject: [PATCH 7/8] test(ci): bind Python matrix to event Signed-off-by: Seongho Bae --- office/tests/test_python_support_contract.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index 209f48454..a52ddec39 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,11 +50,16 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search(r"python-version:\s*(.+)", office_job) + matrix_match = re.search( + r"python-version:\s*\$\{\{\s*github\.event_name\s*==\s*'pull_request'" + r"\s*&&\s*fromJSON\('(\[[^']+\])'\)\s*\|\|\s*" + r"fromJSON\('(\[[^']+\])'\)\s*\}\}", + office_job, + ) assert matrix_match is not None pull_request_versions, push_versions = ( tuple(re.findall(r'"(3\.\d+)"', versions)) - for versions in re.findall(r"fromJSON\('(\[[^']+\])'\)", matrix_match.group(1)) + for versions in matrix_match.groups() ) assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) assert push_versions == SUPPORTED_PYTHON_VERSIONS From 210284bb30661294a86f413581c26731c25d4b8a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 06:29:31 +0900 Subject: [PATCH 8/8] revert(ci): restore Office contract owner Remove the duplicated Python support contract changes from this license branch. PR #405 remains the single writer while this branch keeps its repository license correction. Signed-off-by: Seongho Bae Commit-Message-Assisted-by: Claude (via Claude Code) --- office/tests/test_python_support_contract.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index a52ddec39..7104fd661 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,19 +50,10 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search( - r"python-version:\s*\$\{\{\s*github\.event_name\s*==\s*'pull_request'" - r"\s*&&\s*fromJSON\('(\[[^']+\])'\)\s*\|\|\s*" - r"fromJSON\('(\[[^']+\])'\)\s*\}\}", - office_job, - ) + matrix_match = re.search(r'python-version:\s*\[([^\]]+)\]', office_job) assert matrix_match is not None - pull_request_versions, push_versions = ( - tuple(re.findall(r'"(3\.\d+)"', versions)) - for versions in matrix_match.groups() - ) - assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) - assert push_versions == SUPPORTED_PYTHON_VERSIONS + matrix_versions = tuple(re.findall(r'"(3\.\d+)"', matrix_match.group(1))) + assert matrix_versions == SUPPORTED_PYTHON_VERSIONS def test_python_support_documentation_matches_the_fixed_ci_environment() -> None: