commit fe61cbdbcc10001bada09e796e583b8c86e70525
parent f5daaf2be9febea5963169b54d1739e28e24bfff
Author: triesap <tyson@radroots.org>
Date: Tue, 25 Aug 2026 07:17:38 +0000
security: align package supply-chain evidence
Diffstat:
20 files changed, 130 insertions(+), 18 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -72,6 +72,9 @@ its subtree.
`cargo extbuild run -- ...`.
- `pnpm run contracts:check` validates the exact historical inventory and the
absence of forbidden public roots without requiring a lib checkout.
+- `pnpm run supply-chain:check` validates the exact pnpm toolchain and lock,
+ immutable public Lib source lock, package repository identities, workspace-
+ only internal edges, and byte-identical package license artifacts.
- `pnpm run test:tools` runs standalone tool and boundary tests.
- `pnpm run source:check` and generation/freshness commands require an exact
`RADROOTS_LIB_SOURCE_ROOT` matching the checked-in lock. `pnpm run check` is
diff --git a/README b/README
@@ -9,7 +9,8 @@ capsule. Generated TypeScript, Wasm, Swift, and Kotlin surfaces are produced
through `tools/radroots_sdk_artifact.mjs` from the exact
`radrootslabs/lib` revision recorded in `radroots.lib.source-lock.v1.toml`.
-Use `pnpm run contracts:check` for standalone contract validation and
+Use `pnpm run supply-chain:check` for locked dependency, source, and license
+validation, `pnpm run contracts:check` for standalone contract validation, and
`pnpm run check` with an exact `RADROOTS_LIB_SOURCE_ROOT` for the complete
generated-package lane. See `AGENTS.md` and `CONTRIBUTING.md` for repository
rules and command requirements.
diff --git a/package.json b/package.json
@@ -3,6 +3,7 @@
"private": true,
"packageManager": "pnpm@10.25.0",
"scripts": {
+ "supply-chain:check": "node tools/radroots_sdk_supply_chain.mjs",
"contracts:check": "node tools/radroots_sdk_contract.mjs",
"source:check": "node tools/radroots_sdk_artifact.mjs source-check",
"generate:ts": "node tools/radroots_sdk_artifact.mjs write typescript",
@@ -12,7 +13,7 @@
"generate": "pnpm run generate:ts && pnpm run generate:wasm && pnpm run generate:swift && pnpm run generate:kotlin",
"check:generated": "node tools/radroots_sdk_artifact.mjs check typescript && node tools/radroots_sdk_artifact.mjs check wasm && node tools/radroots_sdk_artifact.mjs check swift && node tools/radroots_sdk_artifact.mjs check kotlin",
"test:tools": "node --test tools/*.test.mjs",
- "check": "pnpm run contracts:check && pnpm run generate && pnpm -r build && pnpm -r typecheck && pnpm run test:tools && pnpm run check:generated",
+ "check": "pnpm run supply-chain:check && pnpm run contracts:check && pnpm run generate && pnpm -r build && pnpm -r typecheck && pnpm run test:tools && pnpm run check:generated",
"build": "pnpm run generate:ts && pnpm run generate:wasm && pnpm -r build",
"typecheck": "pnpm -r typecheck"
},
diff --git a/packages/core-bindings/LICENSE-APACHE b/packages/core-bindings/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/core-bindings/LICENSE-MIT b/packages/core-bindings/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/event-bindings/LICENSE-APACHE b/packages/event-bindings/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/event-bindings/LICENSE-MIT b/packages/event-bindings/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/event-codec-wasm/LICENSE-APACHE b/packages/event-codec-wasm/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/event-codec-wasm/LICENSE-MIT b/packages/event-codec-wasm/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/identity-bindings/LICENSE-APACHE b/packages/identity-bindings/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/identity-bindings/LICENSE-MIT b/packages/identity-bindings/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/replica-schema-bindings/LICENSE-APACHE b/packages/replica-schema-bindings/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/replica-schema-bindings/LICENSE-MIT b/packages/replica-schema-bindings/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/replica-store-wasm/LICENSE-APACHE b/packages/replica-store-wasm/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/replica-store-wasm/LICENSE-MIT b/packages/replica-store-wasm/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/replica-sync-wasm/LICENSE-APACHE b/packages/replica-sync-wasm/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/replica-sync-wasm/LICENSE-MIT b/packages/replica-sync-wasm/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/trade-bindings/LICENSE-APACHE b/packages/trade-bindings/LICENSE-APACHE
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
-Copyright 2025 Tyson Lupul
+Copyright 2026 Tyson Lupul
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/trade-bindings/LICENSE-MIT b/packages/trade-bindings/LICENSE-MIT
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2025 Tyson Lupul
+Copyright (c) 2026 Tyson Lupul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/tools/radroots_sdk_supply_chain.mjs b/tools/radroots_sdk_supply_chain.mjs
@@ -0,0 +1,107 @@
+import { readFileSync, readdirSync } from "node:fs";
+import { basename, dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = dirname(dirname(fileURLToPath(import.meta.url)));
+const expectedLicense = "MIT OR Apache-2.0";
+const expectedRepository = "git+https://github.com/radrootslabs/sdk.git";
+const expectedPackages = [
+ "core-bindings",
+ "event-bindings",
+ "event-codec-wasm",
+ "identity-bindings",
+ "replica-schema-bindings",
+ "replica-store-wasm",
+ "replica-sync-wasm",
+ "trade-bindings",
+];
+
+function fail(message) {
+ throw new Error(`supply_chain_invalid: ${message}`);
+}
+
+function readJson(path) {
+ return JSON.parse(readFileSync(path, "utf8"));
+}
+
+function packageDirectories() {
+ return readdirSync(join(root, "packages"), { withFileTypes: true })
+ .filter((entry) => entry.isDirectory())
+ .map((entry) => join(root, "packages", entry.name))
+ .filter((path) => {
+ try {
+ readFileSync(join(path, "package.json"));
+ return true;
+ } catch {
+ return false;
+ }
+ })
+ .sort();
+}
+
+function verifyManifest(path) {
+ const manifest = readJson(join(path, "package.json"));
+ if (manifest.license !== expectedLicense) fail("package license drifted");
+ if (
+ manifest.repository?.type !== "git" ||
+ manifest.repository?.url !== expectedRepository ||
+ manifest.repository?.directory !== `packages/${basename(path)}`
+ ) {
+ fail("package repository identity drifted");
+ }
+ if (
+ !Array.isArray(manifest.files) ||
+ !manifest.files.includes("LICENSE-MIT") ||
+ !manifest.files.includes("LICENSE-APACHE")
+ ) {
+ fail("package artifact omits governed license files");
+ }
+ for (const section of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
+ for (const value of Object.values(manifest[section] ?? {})) {
+ if (!String(value).startsWith("workspace:")) {
+ fail("package dependency is not workspace-locked");
+ }
+ }
+ }
+ for (const license of ["LICENSE-MIT", "LICENSE-APACHE"]) {
+ if (readFileSync(join(path, license)).compare(readFileSync(join(root, license))) !== 0) {
+ fail("package license bytes differ from the repository authority");
+ }
+ }
+}
+
+const manifest = readJson(join(root, "package.json"));
+if (manifest.packageManager !== "pnpm@10.25.0") fail("package manager drifted");
+if (manifest.devDependencies?.typescript !== "^5.9.0") fail("TypeScript input drifted");
+if (Object.keys(manifest.devDependencies ?? {}).length !== 1) fail("root dependency inventory drifted");
+
+const lock = readFileSync(join(root, "pnpm-lock.yaml"), "utf8");
+for (const required of [
+ "lockfileVersion: '9.0'",
+ "typescript@5.9.3:",
+ "specifier: ^5.9.0",
+ "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+]) {
+ if (!lock.includes(required)) fail("pnpm lock authority drifted");
+}
+for (const forbidden of ["git+", "github:", "http://"]) {
+ if (lock.includes(forbidden)) fail("pnpm lock contains an unapproved source");
+}
+
+const sourceLock = readFileSync(join(root, "radroots.lib.source-lock.v1.toml"), "utf8");
+if (!/^repository = "https:\/\/github\.com\/radrootslabs\/lib"$/m.test(sourceLock)) {
+ fail("Lib repository identity drifted");
+}
+if (!/^revision = "[0-9a-f]{40}"$/m.test(sourceLock)) fail("Lib revision is not immutable");
+if (!/^lockfile_sha256 = "[0-9a-f]{64}"$/m.test(sourceLock)) fail("Cargo lock digest is absent");
+
+const packages = packageDirectories();
+if (
+ packages.length !== expectedPackages.length ||
+ packages.some((path, index) => basename(path) !== expectedPackages[index])
+) {
+ fail("package inventory drifted");
+}
+for (const path of packages) verifyManifest(path);
+
+console.log(`supply-chain ok: ${packages.length} packages`);