diff --git a/.vscode/settings.json b/.vscode/settings.json index 035012db2..cbac5697b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "deno.enable": false -} \ No newline at end of file + "deno.enable": true +} diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 3798405cd..fa378424d 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -32,54 +32,18 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@bytecodealliance/jco": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.2.4.tgz", - "integrity": "sha512-uuOm9UkYqWp5uElYDNzlhjbdrAmczEvETgQdI1hFTk79h+mrmHPRV32pgRP5o1eHVwMIpuk4XQkDIhFbksurUw==", - "dev": true, - "workspaces": [ - "packages/preview2-shim" - ], - "dependencies": { - "@bytecodealliance/preview2-shim": "^0.16.2", - "binaryen": "^116.0.0", - "chalk-template": "^1", - "commander": "^12", - "mkdirp": "^3", - "ora": "^8", - "terser": "^5" - }, - "bin": { - "jco": "src/jco.js" - } - }, - "node_modules/@bytecodealliance/jco/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@bytecodealliance/preview2-shim": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.2.tgz", "integrity": "sha512-36MwesmbLSf3Y5/OHcS85iBaF0N92CQ4gpjtDVKSbrjxmrBKCWlWVfoQ03F/cqDg8k5K7pzVaVBH0XBIbTCfTQ==", "dev": true }, - "node_modules/@commontools/runtime-demo": { - "resolved": "packages/runtime-demo", + "node_modules/@commontools/basic-ifc": { + "resolved": "packages/basic-ifc", "link": true }, - "node_modules/@commontools/std": { - "resolved": "packages/std", + "node_modules/@commontools/runtime-demo": { + "resolved": "packages/runtime-demo", "link": true }, "node_modules/@commontools/usuba-api": { @@ -171,6 +135,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -185,6 +151,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -194,6 +162,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -203,6 +173,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -212,13 +184,17 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -399,18 +375,6 @@ "node": ">=0.4.0" } }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -442,16 +406,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/binaryen": { - "version": "116.0.0", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz", - "integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==", - "dev": true, - "bin": { - "wasm-opt": "bin/wasm-opt", - "wasm2js": "bin/wasm2js" - } - }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -468,7 +422,9 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/c12": { "version": "1.10.0", @@ -502,33 +458,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", - "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", - "dev": true, - "dependencies": { - "chalk": "^5.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -571,33 +500,6 @@ "consola": "^3.2.3" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -671,12 +573,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true - }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -715,6 +611,10 @@ "@esbuild/win32-x64": "0.20.2" } }, + "node_modules/example-package": { + "resolved": "packages/grammar-experiments", + "link": true + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -811,18 +711,6 @@ "node": ">=8" } }, - "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -935,18 +823,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -968,18 +844,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", - "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1041,34 +905,6 @@ "@types/trusted-types": "^2.0.2" } }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1282,29 +1118,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", - "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -1441,46 +1254,6 @@ "node": ">=8.10.0" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -1608,55 +1381,13 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -1691,6 +1422,8 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -1708,7 +1441,9 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -1865,6 +1600,16 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "packages/basic-ifc": { + "name": "@commontools/basic-ifc", + "version": "0.0.1", + "license": "UNLICENSED", + "devDependencies": { + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "wireit": "^0.14.4" + } + }, "packages/example-package": { "version": "0.0.1", "extraneous": true, @@ -1876,6 +1621,17 @@ "wireit": "^0.14.4" } }, + "packages/grammar-experiments": { + "name": "example-package", + "version": "0.0.1", + "license": "UNLICENSED", + "devDependencies": { + "@bytecodealliance/preview2-shim": "^0.16.2", + "typescript": "^5.4.5", + "vite": "^5.2.0", + "wireit": "^0.14.4" + } + }, "packages/runtime-demo": { "name": "@commontools/runtime-demo", "version": "0.0.1", @@ -1891,6 +1647,7 @@ "packages/std": { "name": "@commontools/std", "version": "0.0.1", + "extraneous": true, "license": "UNLICENSED", "devDependencies": { "@bytecodealliance/jco": "^1.2.4", @@ -1947,6 +1704,7 @@ "devDependencies": { "@commontools/usuba-api": "^0.0.1", "@commontools/usuba-sw": "^0.0.1", + "tslib": "^2.6.2", "typescript": "^5.2.2", "vite": "^5.2.0", "wireit": "^0.14.4" diff --git a/typescript/packages/basic-ifc/package.json b/typescript/packages/basic-ifc/package.json new file mode 100644 index 000000000..0f891e1c8 --- /dev/null +++ b/typescript/packages/basic-ifc/package.json @@ -0,0 +1,46 @@ +{ + "name": "@commontools/basic-ifc", + "author": "The Common Authors", + "version": "0.0.1", + "description": "A Runtime for managing the invocation of Usuba-produced Modules", + "license": "UNLICENSED", + "private": true, + "type": "module", + "scripts": { + "build": "wireit", + "clean": "wireit" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/commontoolsinc/labs.git" + }, + "bugs": { + "url": "https://github.com/commontoolsinc/labs/issues" + }, + "homepage": "https://github.com/commontoolsinc/labs#readme", + "exports": "./lib/index.js", + "files": [ + "./lib/*.js" + ], + "dependencies": {}, + "devDependencies": { + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "wireit": "^0.14.4" + }, + "wireit": { + "build": { + "dependencies": [], + "files": [ + "./src/**/*" + ], + "output": [ + "./lib/**/*" + ], + "command": "tsc --build -f" + }, + "clean": { + "command": "rm -rf ./lib ./.wireit" + } + } +} diff --git a/typescript/packages/basic-ifc/src/ifc.ts b/typescript/packages/basic-ifc/src/ifc.ts new file mode 100644 index 000000000..e629e9046 --- /dev/null +++ b/typescript/packages/basic-ifc/src/ifc.ts @@ -0,0 +1,570 @@ +type IntegrityOrConfidentiality = "integrity" | "confidentiality"; +type PrincipalVariable = `\$${string}-${IntegrityOrConfidentiality}`; +type Principal = Exclude; + +type PrincipalExpression = + | Principal + | PrincipalVariable + | ["join" | "meet", PrincipalExpression[]] + | undefined; + +function isLatticeVariable( + principal: PrincipalExpression +): principal is PrincipalVariable { + return typeof principal === "string" && principal.startsWith("$"); +} + +function isLatticeGroundedPrincipal( + principal: PrincipalExpression +): principal is Principal { + return typeof principal === "string" && !principal.startsWith("$"); +} + +function isCombinedPrincipal( + principal: PrincipalExpression +): principal is ["join" | "meet", PrincipalExpression[]] { + return Array.isArray(principal) && principal.length === 2; +} + +type Label = { + integrity: PrincipalExpression; + confidentiality: PrincipalExpression; +}; + +type Constraint = [PrincipalVariable, PrincipalExpression]; + +export const $label = Symbol("label"); +type State = { + [$label]?: Label; + [key: string]: State; +}; + +type Node = { + type?: string; // Currently unused + in: string[]; + out: string[]; +}; + +const [TOP, BOTTOM] = ["TOP", "BOTTOM"] as const as Principal[]; + +// Each level lists its parents +// If a key is missing, it's assumed to have TOP as its parent +type LatticeRelationships = { + [label: Principal]: Principal[]; +}; + +type Lattice = { + parents: LatticeRelationships; + children: LatticeRelationships; +}; + +function computeAllParentsForLabel( + label: string, + lattice: LatticeRelationships, + end: PrincipalExpression +): string[] { + if (label === end) { + return []; + } + const parents = lattice[label]; + if (!parents) { + return []; + } + return parents.reduce( + (acc, parent) => [ + ...acc, + ...computeAllParentsForLabel(parent, lattice, end), + ], + parents + ); +} + +// Create a lattice where each key lists all children +function makeLattice(latticeRelationships: LatticeRelationships): Lattice { + const invertedLattice: LatticeRelationships = {}; + + const allKeys = new Set(); + + for (const key in latticeRelationships) { + allKeys.add(key); + for (const parent of latticeRelationships[key]) { + allKeys.add(parent); + if (!invertedLattice[parent]) { + invertedLattice[parent] = []; + } + invertedLattice[parent].push(key); + } + } + + const lattice = { + parents: {} as LatticeRelationships, + children: {} as LatticeRelationships, + }; + + // Compute all parents and children for each key + for (const key of allKeys) { + lattice.parents[key] = [ + key, + ...computeAllParentsForLabel(key, latticeRelationships, TOP), + TOP, + ]; + + lattice.children[key] = [ + key, + ...computeAllParentsForLabel(key, invertedLattice, BOTTOM), + BOTTOM, + ]; + } + + return lattice; +} + +function dedupe(principals: PrincipalExpression[]): PrincipalExpression[] { + const seen = new Set(); + return principals.filter((p) => { + if (p === undefined || seen.has(p)) return false; + seen.add(p); + return true; + }); +} + +// Computes the join of the principals in the lattice +// Only grounded principals can join, if they are variables, we leave them alone +function join( + principals: PrincipalExpression[], + lattice: Lattice +): PrincipalExpression { + const groundedPrincipals = principals.filter(isLatticeGroundedPrincipal); + const otherPrincipals = dedupe( + principals.filter((p) => !isLatticeGroundedPrincipal(p)) + ); + + const commonParents = groundedPrincipals.reduce( + (acc, principal) => + (lattice.parents[principal] ?? []).filter((parent) => + acc.includes(parent) + ), + lattice.parents[groundedPrincipals[0]] ?? [] + ); + + return otherPrincipals.length + ? ([ + "join", + [ + ...otherPrincipals, + ...(commonParents.length ? [commonParents[0]] : []), + ], + ] as PrincipalExpression) + : commonParents[0] ?? TOP; +} + +function meet( + principals: PrincipalExpression[], + lattice: Lattice +): PrincipalExpression { + const groundedPrincipals = principals.filter(isLatticeGroundedPrincipal); + const otherPrincipals = dedupe( + principals.filter((p) => !isLatticeGroundedPrincipal(p)) + ); + + const commonChildren = groundedPrincipals.reduce( + (acc, principal) => + (lattice.children[principal] ?? []).filter((child) => + acc.includes(child) + ), + lattice.children[groundedPrincipals[0]] ?? [] + ); + + return otherPrincipals.length + ? ([ + "meet", + [ + ...otherPrincipals, + ...(commonChildren.length ? [commonChildren[0]] : []), + ], + ] as PrincipalExpression) + : commonChildren[0] ?? BOTTOM; +} + +// Creates an expression from a list of labels combined by join/meet, inlining +// any nested join/meet expressions +function combine( + op: "join" | "meet", + expressions: PrincipalExpression[] +): PrincipalExpression { + const expressionsToJoin = dedupe( + expressions.flatMap((expression) => { + if (Array.isArray(expression) && expression[0] === op) { + return expression[1]; + } + return expression; + }) + ); + + if (expressionsToJoin.length === 0) return undefined; + + if (expressionsToJoin.length === 1) return expressionsToJoin[0]; + + return [op, expressionsToJoin]; +} + +function generatePrincipalVariable( + name: string, + type: IntegrityOrConfidentiality +): PrincipalVariable { + return `$${name}-${type}` as PrincipalVariable; +} + +// Generate initial constraints from the state and bindings +// +// Output integrity is at most the meet of the input integrities Output +// confidentiality is at least the join of the input confidences +// +// "at most" means `x ∧ y = x`, so we can combine all "at most" constraints with +// a meet. Analogous for "at least" and join. +// +// When a label is in the initial state, and it's forcing a specific label. +// Eventually we could support `base-integrity ∧ $other-integrity` to mean to +// compute all other required integrity? Analogous for confidentiality. +function generateConstraints(state: State, bindings: Node[]): Constraint[] { + const constraints: Constraint[] = []; + + // Traverse state and create constraints where labels are set + function traverse(subState: State, path: string[]): void { + if (subState[$label]) { + const label = subState[$label]; + const name = path.join("."); + + if (label.integrity) + constraints.push([ + generatePrincipalVariable(name, "integrity"), + label.integrity, + ]); + + if (label.confidentiality) + constraints.push([ + generatePrincipalVariable(name, "confidentiality"), + label.confidentiality, + ]); + } + for (const key in subState) { + traverse(subState[key], [...path, key]); + } + } + + traverse(state, []); + + for (const binding of bindings) { + // Compute output constraints based on inputs + const combinedInputintegrity = combine( + "meet", + binding.in.map((input) => generatePrincipalVariable(input, "integrity")) + ); + const combinedInputconfidentiality = combine( + "join", + binding.in.map((input) => + generatePrincipalVariable(input, "confidentiality") + ) + ); + + binding.out.forEach((output) => { + if (combinedInputintegrity !== undefined) { + const name = generatePrincipalVariable(output, "integrity"); + constraints.push([ + name, + combine("meet", [name, combinedInputintegrity]), + ]); + } + + if (combinedInputconfidentiality !== undefined) { + const name = generatePrincipalVariable(output, "confidentiality"); + constraints.push([ + name, + combine("join", [name, combinedInputconfidentiality]), + ]); + } + }); + + // Compute input constraints based on outputs + const combinedOutputintegrity = combine( + "join", + binding.out.map((output) => + generatePrincipalVariable(output, "integrity") + ) + ); + const combinedOutputconfidentiality = combine( + "meet", + binding.out.map((output) => + generatePrincipalVariable(output, "confidentiality") + ) + ); + + binding.in.forEach((input) => { + if (combinedOutputintegrity !== undefined) { + const name = generatePrincipalVariable(input, "integrity"); + constraints.push([ + name, + combine("join", [name, combinedOutputintegrity]), + ]); + } + + if (combinedOutputconfidentiality !== undefined) { + const name = generatePrincipalVariable(input, "confidentiality"); + constraints.push([ + name, + combine("meet", [name, combinedOutputconfidentiality]), + ]); + } + }); + } + + return constraints; +} + +type Substitutions = { [key: PrincipalVariable]: PrincipalExpression }; +// Substitution rules, in order of priority: +// - If the variable isn't mentioned on the right side, always substitute it +// - If a subset of an expression is just grounded principals, compute their +// join/meet. +// - If there is only one constraint for a variable, we can substitute it if +// the variable is only mentioned in the first layer. +// - If there are multiple constraints for a variable, and all contain the +// variable, see whether there are more top level joins or meets, and +// substitute the winner together. That is `a = a ∧ b` and `a = a ∧ c` can be +// simplified to `a = a ∧ b ∧ c`. +// +// Function will return the first class of these substitutions it finds. It'll +// return an empty object if no substitutions are found. +function findSubstitutions( + allConstraints: Constraint[], + substitutions: Substitutions +): Substitutions { + const newSubstitutions: Substitutions = {}; + const constraints = allConstraints.filter(([v]) => !substitutions[v]); + + // Return the deepest level the variable is found in, or 0 if not found. + function maxLevelVariableIsContained( + variable: PrincipalVariable, + expression: PrincipalExpression + ): number { + if (isCombinedPrincipal(expression)) + return ( + 1 + + Math.max( + ...expression[1].map((e) => maxLevelVariableIsContained(variable, e)) + ) + ); + else return expression === variable ? 1 : 0; + } + + // If the variable isn't mentioned on the right side, always substitute it + constraints.forEach(([variable, expression]) => { + const level = maxLevelVariableIsContained(variable, expression); + if (level === 0) + newSubstitutions[variable] = simplifyExpression(expression); + }); + if (Object.keys(newSubstitutions).length > 0) return newSubstitutions; + + // Split unique from non-unique constraints + const uniqueConstraints: Constraint[] = []; + const multipleConstraints: Map = + new Map(); + constraints.forEach(([variable, expression]) => { + if (constraints.filter(([v]) => v === variable).length === 1) { + uniqueConstraints.push([variable, expression]); + } else { + if (!multipleConstraints.has(variable)) { + multipleConstraints.set(variable, [expression]); + } else { + multipleConstraints.get(variable)!.push(expression); + } + } + }); + + function substituteWithoutSelfReference([variable, expression]: Constraint): + | PrincipalExpression + | undefined { + const level = maxLevelVariableIsContained(variable, expression); + // level 2 means it's $var = [ "meet"| "join", [$var, ...] ] + if (level == 2) { + const [op, expressions] = expression as [ + "join" | "meet", + PrincipalExpression[] + ]; + return simplifyExpression([ + op, + expressions.filter((e) => e !== variable), + ]); + } + return undefined; + } + + // If there is only one constraint for a variable, we can substitute it if + // the variable is only mentioned in the first layer. + uniqueConstraints.forEach(([v, e]) => { + const substitution = substituteWithoutSelfReference([v, e]); + if (substitution) newSubstitutions[v] = substitution; + }); + if (Object.keys(newSubstitutions).length > 0) return newSubstitutions; + + // If there are multiple constraints for a variable, and one has the variable + // at the top level, substitute all others into it via the same join/meet, + // a = a v (b ^ c), a = a v (d ^ e) => a = a v (d ^ e) v (b ^ c ^ d ^ e) + multipleConstraints.forEach((expressions, variable) => { + const container = expressions.find( + (e) => maxLevelVariableIsContained(variable, e) === 2 + ); + + if (container && isCombinedPrincipal(container)) { + const newExpression: PrincipalExpression = [ + container[0], + [...container[1], ...expressions.filter((e) => e !== container)], + ]; + newSubstitutions[variable] = simplifyExpression( + substituteWithoutSelfReference([variable, newExpression]) ?? + newExpression + ); + } + }); + if (Object.keys(newSubstitutions).length > 0) return newSubstitutions; + + return {}; +} + +function applySubstitutions( + constraints: PrincipalExpression, + substitutions: Substitutions +): PrincipalExpression { + if (isLatticeVariable(constraints)) { + return substitutions[constraints] ?? constraints; + } else if (isLatticeGroundedPrincipal(constraints)) { + return constraints; + } else if (isCombinedPrincipal(constraints)) { + const [op, expressions] = constraints; + return [op, expressions.map((e) => applySubstitutions(e, substitutions))]; + } +} + +// Finds nested join and meet and flattens them into one +function simplifyExpression( + expression: PrincipalExpression +): PrincipalExpression { + if (isCombinedPrincipal(expression)) { + const [op, expressions] = expression; + const simplified: PrincipalExpression[] = []; + expressions.forEach((e) => { + if (isCombinedPrincipal(e)) { + if (e[0] === op) { + const flatten = (e: PrincipalExpression): PrincipalExpression[] => { + if (isCombinedPrincipal(e) && e[0] === op) { + return e[1].flatMap(flatten); + } else { + return [e]; + } + }; + + simplified.push(...flatten(e)); + } else { + // The opposite of `op`, so let's see whether we can swap it. + // Let's handle `B ^ (B v C)` <=> `B v (B ^ C)` for now. + // B AND (B OR C) AND D <=> B OR (B AND C) OR (B AND D) + const single = e[1].find((e) => !isCombinedPrincipal(e)); + const combined = e[1].find( + (e) => isCombinedPrincipal(e) && e[0] === op + ) as ["join" | "meet", PrincipalExpression[]] | undefined; + if ( + e[1].length === 2 && + single && + combined && + single !== combined && + combined[1].includes(single) + ) { + // We can commute the operation and so flatten it by one level + simplified.push(single); + simplified.push([e[0], combined[1]]); + console.log("commuted", single, combined); + } else { + simplified.push(simplifyExpression(e)); + } + } + } else { + simplified.push(e); + } + }); + + const deduped = dedupe(simplified); + if (deduped.length === 1) console.log("single expression", deduped[0]); + return deduped.length === 1 + ? simplifyExpression(deduped[0]) + : [op, deduped]; + } else { + return expression; + } +} + +function unify(constraints: Constraint[], lattice: Lattice): Constraint[] { + function traverse(expression: PrincipalExpression): PrincipalExpression { + if (isLatticeVariable(expression)) { + return expression; + } else if (isLatticeGroundedPrincipal(expression)) { + return expression; + } else if (isCombinedPrincipal(expression)) { + const [op, expressions] = expression; + const newExpression = expressions.map((e) => + isCombinedPrincipal(e) ? traverse(e) : e + ); + return (op === "join" ? join : meet)(newExpression, lattice); + } + } + + let substitutions: { [key: PrincipalVariable]: PrincipalExpression } = {}; + let newSubstitutions: { [key: PrincipalVariable]: PrincipalExpression } = {}; + do { + constraints = constraints + .filter(([v]) => !substitutions[v]) + .map(([v, e]) => [v, traverse(e)]); + newSubstitutions = findSubstitutions(constraints, substitutions); + constraints = constraints.map(([v, e]) => [ + v, + simplifyExpression(applySubstitutions(e, newSubstitutions)), + ]); + substitutions = { ...substitutions, ...newSubstitutions }; + } while (Object.keys(newSubstitutions).length > 0); + + return [...(Object.entries(substitutions) as Constraint[]), ...constraints]; +} + +function inferLabels( + initialState: State, + bindings: Node[], + lattice: Lattice +): State { + const constraints = unify( + generateConstraints(initialState, bindings), + lattice + ); + + // Verify that there are no contradictions + // TODO + + // Apply the constraints to the state and show all inferred labels + // For now, this means turning variable names into paths and writing out the state + const state: State = {}; + for (const [variable, expression] of constraints) { + const [name, type] = variable.slice(1).split("-"); + let current = state; + for (const part of name.split(".")) { + if (!current[part]) current[part] = {}; + current = current[part]; + } + if (!current[$label]) + current[$label] = { integrity: undefined, confidentiality: undefined }; + current[$label][type as IntegrityOrConfidentiality] = expression; + } + + return state; +} + +export { type Node, type State, makeLattice, inferLabels, BOTTOM, TOP }; + +export { join, meet, generateConstraints }; // internals for testing diff --git a/typescript/packages/basic-ifc/src/ifc_test.ts b/typescript/packages/basic-ifc/src/ifc_test.ts new file mode 100644 index 000000000..ad5be061f --- /dev/null +++ b/typescript/packages/basic-ifc/src/ifc_test.ts @@ -0,0 +1,202 @@ +import { + assertEquals, + assertObjectMatch, +} from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { + inferLabels, + generateConstraints, + type State, + $label, + type Node, + makeLattice, + join, + meet, + TOP, + BOTTOM, +} from "./ifc.ts"; + +Deno.test("meet and join", () => { + const lattice = makeLattice({ + public: ["trusted cloud"], + "trusted cloud": ["cc", "openai", "anthropic"], + cc: ["ondevice"], + }); + + assertEquals(meet(["public", "trusted cloud"], lattice), "public"); + assertEquals(meet(["trusted cloud", "cc"], lattice), "trusted cloud"); + assertEquals(meet(["cc", "ondevice"], lattice), "cc"); + assertEquals(meet(["public", "ondevice"], lattice), "public"); + + assertEquals(join(["public", "trusted cloud"], lattice), "trusted cloud"); + assertEquals(join(["trusted cloud", "cc"], lattice), "cc"); + + assertEquals(meet(["openai", "anthropic"], lattice), "trusted cloud"); + assertEquals(join(["openai", "anthropic"], lattice), TOP); + + // Google is not in the lattice + assertEquals(meet(["public", "google"], lattice), BOTTOM); + assertEquals(join(["public", "google"], lattice), TOP); +}); + +Deno.test("meet and join with type variables", () => { + const lattice = makeLattice({ + public: ["trusted cloud"], + "trusted cloud": ["cc", "openai", "anthropic"], + cc: ["ondevice"], + }); + + assertEquals(meet(["$foo", "trusted cloud"], lattice), [ + "meet", + ["$foo", "trusted cloud"], + ]); + + assertEquals(meet(["$foo", "$bar"], lattice), ["meet", ["$foo", "$bar"]]); + + assertEquals(meet(["$foo", "public", "trusted cloud"], lattice), [ + "meet", + ["$foo", "public"], + ]); + + assertEquals(join(["$foo", "$foo"], lattice), ["join", ["$foo"]]); +}); + +Deno.test("generate constraints", () => { + const initialState: State = { + bar: { + baz: { + [$label]: { + integrity: "trusted cloud", + confidentiality: "trusted cloud", + }, + }, + zab: { + [$label]: { integrity: "public", confidentiality: "public" }, + }, + }, + }; + const bindings: Node[] = [{ in: ["bar.baz", "bar.zab"], out: ["foo"] }]; + + const constraints = generateConstraints(initialState, bindings); + + assertEquals(constraints, [ + ["$bar.baz-integrity", "trusted cloud"], + ["$bar.baz-confidentiality", "trusted cloud"], + ["$bar.zab-integrity", "public"], + ["$bar.zab-confidentiality", "public"], + [ + "$foo-integrity", + ["meet", ["$foo-integrity", "$bar.baz-integrity", "$bar.zab-integrity"]], + ], + [ + "$foo-confidentiality", + [ + "join", + [ + "$foo-confidentiality", + "$bar.baz-confidentiality", + "$bar.zab-confidentiality", + ], + ], + ], + ["$bar.baz-integrity", ["join", ["$bar.baz-integrity", "$foo-integrity"]]], + [ + "$bar.baz-confidentiality", + ["meet", ["$bar.baz-confidentiality", "$foo-confidentiality"]], + ], + ["$bar.zab-integrity", ["join", ["$bar.zab-integrity", "$foo-integrity"]]], + [ + "$bar.zab-confidentiality", + ["meet", ["$bar.zab-confidentiality", "$foo-confidentiality"]], + ], + ]); +}); + +Deno.test("infer labels, simple", () => { + // Example lattice: Each line is a principal, listing its parents + const lattice = makeLattice({ + public: ["trusted cloud"], + "trusted cloud": ["cc", "openai", "anthropic"], + cc: ["ondevice"], + "possible prompt injection": ["user authored"], + }); + + // Example inputs to the recipe, with labels at any level in the tree + const inputs = { + bar: { + baz: { + [$label]: { + integrity: "user authored", + confidentiality: "trusted cloud", + }, + }, + zab: { + [$label]: { + integrity: "possible prompt injection", + confidentiality: "public", + }, + }, + }, + }; + + // The recipe + const bindings = [{ type: "foo", in: ["bar.baz", "bar.zab"], out: ["foo"] }]; + + const inferredLabels = inferLabels(inputs, bindings, lattice); + console.log(inferredLabels); + + assertObjectMatch(inferredLabels, { + foo: { + [$label]: { + integrity: "possible prompt injection", + confidentiality: "trusted cloud", + }, + }, + }); +}); + +Deno.test("infer labels, two nodes", () => { + // Example state and bindings + const inputs: State = { + bar: { + baz: { + [$label]: { + integrity: "trusted cloud", + confidentiality: "trusted cloud", + }, + }, + zab: { + [$label]: { integrity: "public", confidentiality: "public" }, + }, + }, + }; + const bindings: Node[] = [ + { in: ["bar.baz", "bar.zab"], out: ["foo"] }, + { in: ["foo", "bar.zab"], out: ["zab"] }, + ]; + + const lattice = makeLattice({ + public: ["trusted cloud"], + "trusted cloud": ["cc", "openai", "anthropic"], + cc: ["ondevice"], + }); + + const inferredState = inferLabels(inputs, bindings, lattice); + + console.log(inferredState); + assertEquals(inferredState, { + bar: { + baz: { + [$label]: { + integrity: "trusted cloud", + confidentiality: "trusted cloud", + }, + }, + zab: { + [$label]: { integrity: "public", confidentiality: "public" }, + }, + }, + foo: { + [$label]: { integrity: "public", confidentiality: "trusted cloud" }, + }, + }); +}); diff --git a/typescript/packages/basic-ifc/src/index.ts b/typescript/packages/basic-ifc/src/index.ts new file mode 100644 index 000000000..800d03cc1 --- /dev/null +++ b/typescript/packages/basic-ifc/src/index.ts @@ -0,0 +1,29 @@ +import { makeLattice, inferState, type Binding, BOTTOM, TOP } from "./ifc.s"; + +// Example state and bindings +const initialState = { + bar: { + baz: { + label: { integrity: "trusted cloud", confidentiality: "trusted cloud" }, + }, + zab: { + label: { integrity: "public", confidentiality: "public" }, + }, + }, +}; + +const bindings: Binding[] = [{ in: ["bar.baz", "bar.zab"], out: ["foo"] }]; + +const lattice = makeLattice({ + [BOTTOM]: ["public"], + public: ["trusted cloud"], + "trusted cloud": ["cc", "openai", "anthropic"], + cc: ["ondevice"], + [TOP]: [], +}); + +// Infer the state +const inferredState = inferState(initialState, bindings, lattice); + +// Accessing the labels +console.log(inferredState); // Output will reflect the inferred labels based on the lattice diff --git a/typescript/packages/basic-ifc/tsconfig.json b/typescript/packages/basic-ifc/tsconfig.json new file mode 100644 index 000000000..f0af7fb66 --- /dev/null +++ b/typescript/packages/basic-ifc/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["es2022", "WebWorker"], + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["src/**/*"] +}