From 533ad3ebbf121bb3b8fc9eb1d1973242a71d1131 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Mon, 21 Sep 2026 22:33:59 -0700 Subject: [PATCH 1/3] build(deps): move @intx/* to 0.4.0 (CL-8797) - @intx/crypto 0.3.0 -> 0.4.0 (dependencies) - @intx/log 0.2.2 -> 0.4.0 (devDependencies), ^0.2.2 -> ^0.4.0 (peerDependencies) - @intx/mailbox, @intx/mime, @intx/types: workspace:* -> 0.4.0 (devDependencies). All three are now published on npm at 0.4.0 for the first time, so the vendored copies under vendor/ can be pinned against a real registry version instead of the local workspace link. - Add semver / @types/semver as explicit devDependencies. Once the vendored @intx/mailbox/@intx/mime/@intx/types packages under vendor/intx-* share their package name with a real npm dependency instead of a workspace:* one, bun stops treating them as workspace members and no longer installs their own devDependencies/dependencies (semver, @types/semver) alongside them. vendor/intx-types imports semver directly, so it has to be declared at the root for build and test to resolve it the way workspace linking used to provide for free. --- bun.lock | 144 ++++++++++++++++++++++----------------------------- package.json | 14 ++--- 2 files changed, 69 insertions(+), 89 deletions(-) diff --git a/bun.lock b/bun.lock index ec141cd..72cfa61 100644 --- a/bun.lock +++ b/bun.lock @@ -6,28 +6,30 @@ "name": "@corbits/mailbox", "dependencies": { "@hono/standard-validator": "0.2.3", - "@intx/crypto": "0.3.0", + "@intx/crypto": "0.4.0", "@standard-community/standard-json": "0.3.5", "@standard-community/standard-openapi": "0.2.9", "arktype": "2.1.29", "hono-openapi": "1.3.1", }, "devDependencies": { - "@intx/log": "0.2.2", - "@intx/mailbox": "workspace:*", - "@intx/mime": "workspace:*", - "@intx/types": "workspace:*", + "@intx/log": "0.4.0", + "@intx/mailbox": "0.4.0", + "@intx/mime": "0.4.0", + "@intx/types": "0.4.0", "@types/bun": "1.1.14", "@types/json-schema": "7.0.15", "@types/node": "22.10.5", + "@types/semver": "^7.7.1", "drizzle-orm": "0.45.2", "hono": "4.12.32", "openapi-types": "12.1.3", "postgres": "3.4.9", + "semver": "^7.7.2", "typescript": "5.7.2", }, "peerDependencies": { - "@intx/log": "^0.2.2", + "@intx/log": "^0.4.0", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0", @@ -49,46 +51,6 @@ "typescript": "5.7.2", }, }, - "vendor/intx-mailbox": { - "name": "@intx/mailbox", - "version": "0.3.0", - "dependencies": { - "@intx/crypto": "0.3.0", - "@intx/mime": "workspace:*", - "@intx/types": "workspace:*", - "arktype": "2.1.29", - }, - "devDependencies": { - "@types/bun": "1.1.14", - "typescript": "5.7.2", - }, - }, - "vendor/intx-mime": { - "name": "@intx/mime", - "version": "0.3.0", - "dependencies": { - "@intx/crypto": "0.3.0", - "@intx/types": "workspace:*", - "arktype": "2.1.29", - }, - "devDependencies": { - "@types/bun": "1.1.14", - "typescript": "5.7.2", - }, - }, - "vendor/intx-types": { - "name": "@intx/types", - "version": "0.3.0", - "dependencies": { - "arktype": "2.1.29", - "semver": "^7.7.2", - }, - "devDependencies": { - "@types/bun": "1.1.14", - "@types/semver": "^7.7.1", - "typescript": "5.7.2", - }, - }, }, "overrides": { "drizzle-orm": "0.45.2", @@ -98,23 +60,23 @@ "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], - "@better-auth/core": ["@better-auth/core@1.6.26", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.7", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-Ud4FqnjIJDvmeo+3bN+OsuVVAiuvFjNERbW7G8/65ictSxCox62gNgTOsJ4YXmbwXzBzyyekStzrupg2qNGEkA=="], + "@better-auth/core": ["@better-auth/core@1.7.5", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-kVlSu4H8OKQfjg4b/Zj5MOaospt83N0JbX38wsDzE58Yw95jzovFkU3pxzB1eUFYc4mkuhUMZD8iT1gpUjNMcQ=="], - "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-SMvAeeUqEsz0BLtWVVp+HdStAVOwcxs4vPkL/AVmFJ0e9dDKItXl881xWeB/Ze4NgBW6NYmUId9qI4LrdQu3hA=="], + "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-9SM7v1735SoaedRDcDbHc5ULgXEd2vUlEJkvRHpMF2Q9qf59TRh1b5A9hryyecyi56bm/0CNUDU3nY0uVWj5/Q=="], - "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-Y0Kdqn8JQMR8dHMtUnbBNqq7Mk8mDf18DwTves2uhjtRfcOWByVTGxsFMDVUbedMuoM+Ab+v04bwCk94sAo8NA=="], + "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-1wE5gvnjW+c1i4GrLtL9HLn3s0Xrq4YneCDan1NO1dpz0mEguLFNlzfnUGykTFrDwvFh2X5rkIbA8ALCj6WzXQ=="], - "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2" } }, "sha512-kb5ahphEp9jyMleXU4T8I/xgWPa8gjOLFKKsnaVqR/BU+h7xqTOsjJwTY3evY+PFYHvjNFUlrjZ1Km8A/w1p/w=="], + "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2" } }, "sha512-YDmnfR9zOXbn5SNYYwfHBPuc19hg1d1C1vUXb+Hm8Q91pTsstFNX5OTlZbo7f28q06FpogAEfGGCN/2QV39cig=="], - "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-jYGhuIQqj48h69ROLPYdc8jgqDaMmRRLLFiRViZFYarNyBuYUV07DywJXvRuw8l9ADx+M1TB+BJZGnLE2nnmwQ=="], + "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-Yq0LfF0VlA9Kfjcjp/v43MSsChv7vKd1ct0Mt15px4zlqaCPIGfPbjw9YNM6jTo0fGpqLR0n15PllSWmLLRp9g=="], - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-ILQoYmnoYyghDP4iN1h/Gkd9Jt7Xs/vxoNAt4AuuJJbqs73u27LlQM4P1YiP7nJ+bM1iVTBI1dDF73myIAtjzQ=="], + "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-QfW6HS9vK0FMcbI/GsQLdplICxOz0EPzYWGONZT4ovL3cSItd4YH/09USbPPVl+VUQCdznAQIk+n+NKvHdmSag=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-pBs69RORUSJHRF9r5PdeY7pzLRga09YSWew8igFYrBvT2tLt7DktrbD5WjryLZpAZOuKAMZ+Xo0DlJbnvLWGpQ=="], + "@better-auth/telemetry": ["@better-auth/telemetry@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2" } }, "sha512-e/REPqMy9Em+gC6G0xWBikiMLRuy532Er7jqdoNkPBa65FbywgWcm1cZbgdW5CnHsrM3OLZsmKn14yeGoDISeg=="], "@better-auth/utils": ["@better-auth/utils@0.4.2", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="], - "@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="], + "@better-fetch/fetch": ["@better-fetch/fetch@1.3.2", "", {}, "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow=="], "@corbits/mailbox": ["@corbits/mailbox@root:", {}], @@ -126,7 +88,7 @@ "@intx/authz": ["@intx/authz@0.2.2", "", { "dependencies": { "@intx/types": "0.2.2" } }, "sha512-LDHF/3u2duVFGOPzbAFP9WuRlouNqVW+os/o7Y2Mj/9vld92DaemDkL1BPO8m6xF2vSctykpLZFnw4fVcC9oQw=="], - "@intx/crypto": ["@intx/crypto@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-NsRzvkFGb0Pcsm9uLFFSXNl6Cbu+ii6VkYAw81qs9IbZzbAFfsZSUVQaX6EZuPNyJ8KK4KmBtqmioq1mhXsJww=="], + "@intx/crypto": ["@intx/crypto@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-4/qWypC90GevbV9u1i/93tn46jsXhj8wE1Pn+awg8/ECpwhHnhPLT5Uy423Arr9eGmRmF6HiMyXcEgH1Z9/Rkw=="], "@intx/db": ["@intx/db@0.2.2", "", { "dependencies": { "@intx/log": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "postgres": "^3.4.8" } }, "sha512-xqYOF8/1Ay1e/eX7GrZV0kzGgdis8SZRaqE77Ncz9hofTnwusPt83Gc1RvtfasKPici4nP6Gq9EBrrf7aRShqg=="], @@ -138,11 +100,11 @@ "@intx/inference": ["@intx/inference@0.2.2", "", { "dependencies": { "@intx/log": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29" } }, "sha512-4xBbz/6WoeDMfOarma4Odm0f2/9h6ICJTtr2D/JiIlGl3Q8EOP8UxGmf9ZY2MisVqznRDFBp/bHh7U2It/1D/g=="], - "@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/log": ["@intx/log@0.4.0", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-f1IPZ/9YpEqlA0I2UZPOlRJOTDoOFEZRwZoUVuw8DpgR1WatOMm4hA0E8o6Mtz+pA0ux7gVdls4y/JZcgypsfQ=="], - "@intx/mailbox": ["@intx/mailbox@workspace:vendor/intx-mailbox"], + "@intx/mailbox": ["@intx/mailbox@0.4.0", "", { "dependencies": { "@intx/crypto": "0.4.0", "@intx/mime": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-u+It1h0NJqodX3wj52H5Ldsn7upkfo3mHWW9fyOzTBaga6vo5ozWVm0zS3b68eb11RaeaA7WlBefxK4aA5eyEg=="], - "@intx/mime": ["@intx/mime@workspace:vendor/intx-mime"], + "@intx/mime": ["@intx/mime@0.4.0", "", { "dependencies": { "@intx/crypto": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-sWrohF2uwVL5AZHbxIEUNiVUBhULff4XB7g6keCmyQWr296GKSvG/HmbihSXxiIvmmDzh9cl+6ZAJz/taxFNog=="], "@intx/pack-transport": ["@intx/pack-transport@0.2.2", "", { "dependencies": { "@intx/types": "0.2.2" } }, "sha512-/bRh5QXj9cZJ3eU9/D7nnWY2Jqr8ega7lCfgKeAkm/xhVZLfUxVFA4eGxfrFkMoqh9oRmKbgdGafYZV4W4BvpQ=="], @@ -150,7 +112,7 @@ "@intx/tool-packaging": ["@intx/tool-packaging@0.2.2", "", { "dependencies": { "@intx/agent": "0.2.2", "@intx/log": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29", "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-registry-fetch": "^19.0.0", "semver": "^7.7.2", "ssri": "^12.0.0", "tar": "^7.5.1" } }, "sha512-luyHVkZSLMN/bWfTD2nLF7l6h7w/NQfFhNX1vfNhl/8DsG0lUFEn5NCjK/07eyF6iyVDMDGl3Cuq9r7B2y7deA=="], - "@intx/types": ["@intx/types@workspace:vendor/intx-types"], + "@intx/types": ["@intx/types@0.4.0", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-nJ2fcmskMOZNMRfbHs5ueKpCANHNttoL+sZgIoFaVUF/QLpFWfVp0YV72CQAaa9bVv9j5kRSwtSOCCGSlwQ6Dw=="], "@intx/workflow": ["@intx/workflow@0.2.2", "", { "dependencies": { "@intx/agent": "0.2.2", "@intx/inference": "0.2.2", "arktype": "^2.1.29" } }, "sha512-/vx8UDtPFjY0K0KoIaHmojg3up0/iOCF9CzTIludHVFWUF/Pc224ZxkqMkL5ncwgUbBQpoIqAAUAmuYMp8JT5Q=="], @@ -158,13 +120,13 @@ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - "@logtape/hono": ["@logtape/hono@2.3.0", "", { "peerDependencies": { "@logtape/logtape": "^2.3.0", "hono": "^4.0.0" } }, "sha512-+aYoPfhEeOVW1hiQwx7Tv2fbNSbOPQhx5EBZ/8JXAXt9j1w0h63X0M/MjyhjBh6Mp22gnOdNLTF6l5Tg2RIcJw=="], + "@logtape/hono": ["@logtape/hono@2.3.6", "", { "peerDependencies": { "@logtape/logtape": "^2.3.6", "hono": "^4.0.0" } }, "sha512-AHVyHBP7i/qTZMZLr2sFi91wz+Dpg6wIZuoFbU7IjkeYF/myOtOLF3GrrbBUd82inmtjSNJEttegwAiUmX2+QQ=="], - "@logtape/logtape": ["@logtape/logtape@2.3.0", "", {}, "sha512-s/pxCgf9Gg75ypTV/bRUq355Dy/JP/zUJWfgJQPO5pTkXC23X2AXBcmBBgFiWH+bSi4yiX/G06qGV8CdAWD3SA=="], + "@logtape/logtape": ["@logtape/logtape@2.3.6", "", {}, "sha512-1ajI6YRkq+gIpgYVhOcJ4Mn5QUarSvWnnuj8SNMjwrjIcOy1ANEIcZyaLN+vPtL2MCa845D8ghXR8xDA+c3Khg=="], - "@noble/ciphers": ["@noble/ciphers@2.3.0", "", {}, "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw=="], + "@noble/ciphers": ["@noble/ciphers@2.4.0", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="], - "@noble/hashes": ["@noble/hashes@2.3.0", "", {}, "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ=="], + "@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="], "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], @@ -206,11 +168,11 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "better-auth": ["better-auth@1.6.26", "", { "dependencies": { "@better-auth/core": "1.6.26", "@better-auth/drizzle-adapter": "1.6.26", "@better-auth/kysely-adapter": "1.6.26", "@better-auth/memory-adapter": "1.6.26", "@better-auth/mongo-adapter": "1.6.26", "@better-auth/prisma-adapter": "1.6.26", "@better-auth/telemetry": "1.6.26", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-nhXWrDDj+EnZsHq1j0z1c6DowOMFZWZHe6LCaXbBfLIgHHZm6dyazBQcbRspM4spUIcUt250Mc1BFOSuP7eniQ=="], + "better-auth": ["better-auth@1.7.5", "", { "dependencies": { "@better-auth/core": "1.7.5", "@better-auth/drizzle-adapter": "1.7.5", "@better-auth/kysely-adapter": "1.7.5", "@better-auth/memory-adapter": "1.7.5", "@better-auth/mongo-adapter": "1.7.5", "@better-auth/prisma-adapter": "1.7.5", "@better-auth/telemetry": "1.7.5", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.0", "defu": "^6.1.4", "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", "zod": "^4.5.4" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-aKE0Zt2EPTpFvmq4/oATNyG/mAfc6JUqWkW9pzGlrVnzUb0lso7GJ9BPxD6JPhLqYV1a9zOAb0uAz1Q5fm+eHA=="], - "better-call": ["better-call@1.3.7", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w=="], + "better-call": ["better-call@1.4.0", "", { "dependencies": { "@better-auth/utils": "^0.5.0", "@better-fetch/fetch": "^1.3.1", "rou3": "^0.9.1", "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA=="], - "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], @@ -228,9 +190,11 @@ "clean-git-ref": ["clean-git-ref@2.0.1", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="], + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], @@ -296,7 +260,7 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="], + "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], @@ -304,13 +268,13 @@ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "isomorphic-git": ["isomorphic-git@1.41.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-YADpOKD/pLemtcyZ9jssNXnPVhfDObGl/BAKMtvmU17svgNzOKTT6AHX68DzFHpie5hAZHRtutC0Cka3lYdmBA=="], + "isomorphic-git": ["isomorphic-git@1.42.2", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-Rb7czWhLO4JYqmg4an01nNomuAgFbWWK2CVfkHGBOvK5t7hrGTKOXPx/csscJ1j9o3uXFzNcC6njgvCafSmIPw=="], - "jose": ["jose@6.2.8", "", {}, "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ=="], + "jose": ["jose@6.2.12", "", {}, "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="], "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], - "kysely": ["kysely@0.29.4", "", {}, "sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA=="], + "kysely": ["kysely@0.29.6", "", {}, "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg=="], "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -342,9 +306,9 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanostores": ["nanostores@1.4.2", "", {}, "sha512-Wxv8Roefr2nqtiRG0bnaFlpYqpIVtOEeJZHaH+4nGgOK1/7n6OHOuHCb/bhqrNQgZM8fyd0s1PqhdrJc9Ib44g=="], + "nanostores": ["nanostores@1.5.3", "", {}, "sha512-rQLB6eV4f2AW/n3L0JmwCROpaisYy9EDEADvEFSd1C/qG8hB6O5TPlh9A791JRbJr4CnMQBzptDcvD9OR1+6WA=="], - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], "npm-install-checks": ["npm-install-checks@7.1.2", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ=="], @@ -360,7 +324,7 @@ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - "p-map": ["p-map@7.0.6", "", {}, "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg=="], + "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], @@ -382,7 +346,7 @@ "reference-host": ["reference-host@workspace:examples/reference-host"], - "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -402,7 +366,7 @@ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="], + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], @@ -422,13 +386,15 @@ "validate-npm-package-name": ["validate-npm-package-name@6.0.2", "", {}, "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ=="], - "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], + "which-typed-array": ["which-typed-array@1.1.24", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], + + "@intx/agent/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], "@intx/agent/@intx/mime": ["@intx/mime@0.2.2", "", { "dependencies": { "@intx/crypto": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29" } }, "sha512-uSrPWqEi8GurKbawtqk3S3KOHi/o1DmxCEeUb3un3fsZC2RkZrhqtqkTpY8a8q5r5gIJCqW/jmi3hyPyjZMakw=="], @@ -436,12 +402,14 @@ "@intx/authz/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], - "@intx/crypto/@intx/types": ["@intx/types@0.3.0", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-PJ+v3IhtfZ4J7ZqJ8E39TteRMnC6Y503Q0JeCJdskkK+jhcjx01z7TJSozucagauAlpyh8Hn/9CyWFl53Ji0DQ=="], + "@intx/db/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], "@intx/db/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], "@intx/hub-api/@intx/crypto": ["@intx/crypto@0.2.2", "", { "dependencies": { "@intx/types": "0.2.2" } }, "sha512-RhqDZ+Yyt24xDmFrBRm58vGyFT20AK/OF5L6GxXUwu/8TRVf406pfpMuRMNzgGxd0mDopdv/Fb+gWc1/PULfFQ=="], + "@intx/hub-api/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/hub-api/@intx/mime": ["@intx/mime@0.2.2", "", { "dependencies": { "@intx/crypto": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29" } }, "sha512-uSrPWqEi8GurKbawtqk3S3KOHi/o1DmxCEeUb3un3fsZC2RkZrhqtqkTpY8a8q5r5gIJCqW/jmi3hyPyjZMakw=="], "@intx/hub-api/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], @@ -450,27 +418,37 @@ "@intx/hub-sessions/@intx/crypto": ["@intx/crypto@0.2.2", "", { "dependencies": { "@intx/types": "0.2.2" } }, "sha512-RhqDZ+Yyt24xDmFrBRm58vGyFT20AK/OF5L6GxXUwu/8TRVf406pfpMuRMNzgGxd0mDopdv/Fb+gWc1/PULfFQ=="], + "@intx/hub-sessions/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/hub-sessions/@intx/mime": ["@intx/mime@0.2.2", "", { "dependencies": { "@intx/crypto": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29" } }, "sha512-uSrPWqEi8GurKbawtqk3S3KOHi/o1DmxCEeUb3un3fsZC2RkZrhqtqkTpY8a8q5r5gIJCqW/jmi3hyPyjZMakw=="], "@intx/hub-sessions/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], + "@intx/inference/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/inference/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], "@intx/pack-transport/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], + "@intx/storage-isogit/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/storage-isogit/@intx/mime": ["@intx/mime@0.2.2", "", { "dependencies": { "@intx/crypto": "0.2.2", "@intx/types": "0.2.2", "arktype": "^2.1.29" } }, "sha512-uSrPWqEi8GurKbawtqk3S3KOHi/o1DmxCEeUb3un3fsZC2RkZrhqtqkTpY8a8q5r5gIJCqW/jmi3hyPyjZMakw=="], "@intx/storage-isogit/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], + "@intx/tool-packaging/@intx/log": ["@intx/log@0.2.2", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-Rlkd4pvyXwlqkm6hui97VwuTytqgOmenvbdaHAFdYr1e6DHHhGu7ZodUEsLiF4993qJmWNHGlNbHEgQY1LNPrQ=="], + "@intx/tool-packaging/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], "@intx/workflow-deploy/@intx/types": ["@intx/types@0.2.2", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-9DYTXLuf6ARsHWOWnRuOaA/+5haM3yJVUhvzlps/eQTH6zjk3CMOlbgKUtBYZ164MhC5IbXCvJTLGyM1grpMqw=="], - "@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "@npmcli/agent/lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + + "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], "bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], - "cacache/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "cacache/lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], "cacache/ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], @@ -486,7 +464,7 @@ "npm-registry-fetch/proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], - "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "path-scurry/lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], "@intx/agent/@intx/mime/@intx/crypto": ["@intx/crypto@0.2.2", "", { "dependencies": { "@intx/types": "0.2.2" } }, "sha512-RhqDZ+Yyt24xDmFrBRm58vGyFT20AK/OF5L6GxXUwu/8TRVf406pfpMuRMNzgGxd0mDopdv/Fb+gWc1/PULfFQ=="], @@ -502,6 +480,6 @@ "npm-registry-fetch/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - "npm-registry-fetch/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "npm-registry-fetch/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], } } diff --git a/package.json b/package.json index 10fb359..ad32cfc 100644 --- a/package.json +++ b/package.json @@ -60,30 +60,32 @@ }, "dependencies": { "@hono/standard-validator": "0.2.3", - "@intx/crypto": "0.3.0", + "@intx/crypto": "0.4.0", "@standard-community/standard-json": "0.3.5", "@standard-community/standard-openapi": "0.2.9", "arktype": "2.1.29", "hono-openapi": "1.3.1" }, "peerDependencies": { - "@intx/log": "^0.2.2", + "@intx/log": "^0.4.0", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0" }, "devDependencies": { - "@intx/log": "0.2.2", - "@intx/mailbox": "workspace:*", - "@intx/mime": "workspace:*", - "@intx/types": "workspace:*", + "@intx/log": "0.4.0", + "@intx/mailbox": "0.4.0", + "@intx/mime": "0.4.0", + "@intx/types": "0.4.0", "@types/bun": "1.1.14", "@types/json-schema": "7.0.15", "@types/node": "22.10.5", + "@types/semver": "^7.7.1", "drizzle-orm": "0.45.2", "hono": "4.12.32", "openapi-types": "12.1.3", "postgres": "3.4.9", + "semver": "^7.7.2", "typescript": "5.7.2" }, "overrides": { From 0b409a7ba1781f5e971e6f675deaf54b45e30f81 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Mon, 21 Sep 2026 23:41:00 -0700 Subject: [PATCH 2/3] refactor(deps): retire vendored @intx copies now that @intx/mailbox is on npm (CL-8797) VENDORED.md's kill condition for all three vendored rows was "first npm publish of @intx/mailbox" -- that publish landed as part of this 0.4.0 wave, so the vendoring reason (nothing to install for @intx/mailbox/@intx/mime/@intx/types) no longer holds. - Delete vendor/intx-mailbox, vendor/intx-mime, vendor/intx-types and VENDORED.md. Delete tsconfig.vendor-build.json (no longer needed). - scripts/build.mjs: drop the vendor-build tsc pass, the copy into dist/vendor/, and the bare-specifier rewrite. The build is now a plain tsc emit of src/, matching corbits-artifacts' build script; @intx/mailbox/@intx/mime/@intx/types stay bare specifiers in dist/ for a consumer's own install to resolve, same as @intx/log already did. - package.json: drop vendor/* from workspaces. @intx/mailbox, @intx/mime, and @intx/types move to peerDependencies (^0.4.0) plus a matching devDependencies pin (0.4.0), the same treatment @intx/log already had and the pattern corbits-artifacts uses for every @intx/* package its src imports (real npm dependencies of that kind are never declared under the plain "dependencies" field in this ecosystem -- see @intx/hub-api and @intx/agent in corbits-artifacts, used at runtime there too but peer+dev only). Drop the semver/@types/semver devDependencies added for the vendor build -- nothing outside vendor/ used them. - Comments and README wording that called these packages "vendored" now name @intx/mailbox directly. - examples/reference-host's own @intx/db, @intx/hub-api, and @intx/hub-sessions deliberately stay at 0.2.2: bumping them to 0.4.0 surfaces real, non-trivial API breaks unrelated to the mailbox/mime/types cutover (SidecarCredentialIdentity is now a discriminated union, SessionService dropped deployInstanceAtHead, SidecarRouterConfig now requires validateSidecarIdentity) -- out of scope here, reported separately rather than improvised. --- README.md | 12 +- VENDORED.md | 49 - bun.lock | 9 +- package.json | 12 +- scripts/build.mjs | 126 +- src/index.ts | 4 +- src/mount.ts | 10 +- src/native-store.test.ts | 2 +- src/native-store.ts | 11 +- tsconfig.vendor-build.json | 22 - vendor/intx-mailbox/LICENSE | 176 - vendor/intx-mailbox/package.json | 35 - vendor/intx-mailbox/src/fetch.ts | 250 -- vendor/intx-mailbox/src/headers.ts | 5 - vendor/intx-mailbox/src/index.ts | 11 - vendor/intx-mailbox/src/mailbox.ts | 192 -- vendor/intx-mailbox/src/search.ts | 208 -- vendor/intx-mailbox/src/thread.ts | 275 -- vendor/intx-mime/LICENSE | 176 - vendor/intx-mime/package.json | 34 - vendor/intx-mime/src/index.ts | 44 - vendor/intx-mime/src/mail-builder.ts | 516 --- vendor/intx-mime/src/mime.ts | 1334 -------- vendor/intx-mime/src/pgp-sign.ts | 29 - vendor/intx-types/LICENSE | 176 - vendor/intx-types/package.json | 78 - vendor/intx-types/src/agent-address.ts | 34 - vendor/intx-types/src/agent-data.ts | 43 - vendor/intx-types/src/approvals.ts | 38 - vendor/intx-types/src/assets.ts | 42 - vendor/intx-types/src/attachments.ts | 66 - vendor/intx-types/src/audit.ts | 49 - vendor/intx-types/src/authz.ts | 49 - vendor/intx-types/src/base64.ts | 22 - vendor/intx-types/src/base64url.ts | 22 - vendor/intx-types/src/capabilities.ts | 59 - vendor/intx-types/src/catalog.ts | 257 -- vendor/intx-types/src/common.ts | 34 - vendor/intx-types/src/concat.ts | 19 - vendor/intx-types/src/content-type.ts | 20 - vendor/intx-types/src/credential-cipher.ts | 55 - vendor/intx-types/src/credentials.ts | 132 - vendor/intx-types/src/grant-snapshot.ts | 37 - vendor/intx-types/src/grant-wire.ts | 29 - vendor/intx-types/src/grants.ts | 114 - vendor/intx-types/src/has-code.ts | 11 - vendor/intx-types/src/hex.ts | 25 - vendor/intx-types/src/index.ts | 38 - vendor/intx-types/src/instances.ts | 77 - vendor/intx-types/src/me.ts | 60 - vendor/intx-types/src/mediated-credential.ts | 117 - vendor/intx-types/src/message-id.ts | 82 - vendor/intx-types/src/models.ts | 39 - vendor/intx-types/src/oauth-clients.ts | 47 - vendor/intx-types/src/observability.ts | 64 - vendor/intx-types/src/offerings.ts | 67 - vendor/intx-types/src/package-json.ts | 98 - vendor/intx-types/src/principals.ts | 57 - vendor/intx-types/src/providers.ts | 56 - vendor/intx-types/src/roles.ts | 21 - vendor/intx-types/src/runtime-capabilities.ts | 135 - vendor/intx-types/src/runtime.ts | 2891 ----------------- vendor/intx-types/src/sessions.ts | 151 - vendor/intx-types/src/sidecar-allocation.ts | 32 - vendor/intx-types/src/sidecar-capabilities.ts | 61 - .../src/sidecar-oauth-login.test.ts | 116 - vendor/intx-types/src/sidecar.ts | 1056 ------ vendor/intx-types/src/signals.ts | 96 - vendor/intx-types/src/signer-identity.ts | 33 - vendor/intx-types/src/tenants.ts | 44 - vendor/intx-types/src/tool-packages.ts | 312 -- vendor/intx-types/src/wallets.ts | 59 - vendor/intx-types/src/wire-definition-hash.ts | 82 - vendor/intx-types/src/wire-workflow.ts | 170 - vendor/intx-types/src/workflow-run-id.ts | 40 - vendor/intx-types/src/workflow-sources.ts | 74 - vendor/intx-types/src/workflows.ts | 48 - 77 files changed, 41 insertions(+), 11135 deletions(-) delete mode 100644 VENDORED.md delete mode 100644 tsconfig.vendor-build.json delete mode 100644 vendor/intx-mailbox/LICENSE delete mode 100644 vendor/intx-mailbox/package.json delete mode 100644 vendor/intx-mailbox/src/fetch.ts delete mode 100644 vendor/intx-mailbox/src/headers.ts delete mode 100644 vendor/intx-mailbox/src/index.ts delete mode 100644 vendor/intx-mailbox/src/mailbox.ts delete mode 100644 vendor/intx-mailbox/src/search.ts delete mode 100644 vendor/intx-mailbox/src/thread.ts delete mode 100644 vendor/intx-mime/LICENSE delete mode 100644 vendor/intx-mime/package.json delete mode 100644 vendor/intx-mime/src/index.ts delete mode 100644 vendor/intx-mime/src/mail-builder.ts delete mode 100644 vendor/intx-mime/src/mime.ts delete mode 100644 vendor/intx-mime/src/pgp-sign.ts delete mode 100644 vendor/intx-types/LICENSE delete mode 100644 vendor/intx-types/package.json delete mode 100644 vendor/intx-types/src/agent-address.ts delete mode 100644 vendor/intx-types/src/agent-data.ts delete mode 100644 vendor/intx-types/src/approvals.ts delete mode 100644 vendor/intx-types/src/assets.ts delete mode 100644 vendor/intx-types/src/attachments.ts delete mode 100644 vendor/intx-types/src/audit.ts delete mode 100644 vendor/intx-types/src/authz.ts delete mode 100644 vendor/intx-types/src/base64.ts delete mode 100644 vendor/intx-types/src/base64url.ts delete mode 100644 vendor/intx-types/src/capabilities.ts delete mode 100644 vendor/intx-types/src/catalog.ts delete mode 100644 vendor/intx-types/src/common.ts delete mode 100644 vendor/intx-types/src/concat.ts delete mode 100644 vendor/intx-types/src/content-type.ts delete mode 100644 vendor/intx-types/src/credential-cipher.ts delete mode 100644 vendor/intx-types/src/credentials.ts delete mode 100644 vendor/intx-types/src/grant-snapshot.ts delete mode 100644 vendor/intx-types/src/grant-wire.ts delete mode 100644 vendor/intx-types/src/grants.ts delete mode 100644 vendor/intx-types/src/has-code.ts delete mode 100644 vendor/intx-types/src/hex.ts delete mode 100644 vendor/intx-types/src/index.ts delete mode 100644 vendor/intx-types/src/instances.ts delete mode 100644 vendor/intx-types/src/me.ts delete mode 100644 vendor/intx-types/src/mediated-credential.ts delete mode 100644 vendor/intx-types/src/message-id.ts delete mode 100644 vendor/intx-types/src/models.ts delete mode 100644 vendor/intx-types/src/oauth-clients.ts delete mode 100644 vendor/intx-types/src/observability.ts delete mode 100644 vendor/intx-types/src/offerings.ts delete mode 100644 vendor/intx-types/src/package-json.ts delete mode 100644 vendor/intx-types/src/principals.ts delete mode 100644 vendor/intx-types/src/providers.ts delete mode 100644 vendor/intx-types/src/roles.ts delete mode 100644 vendor/intx-types/src/runtime-capabilities.ts delete mode 100644 vendor/intx-types/src/runtime.ts delete mode 100644 vendor/intx-types/src/sessions.ts delete mode 100644 vendor/intx-types/src/sidecar-allocation.ts delete mode 100644 vendor/intx-types/src/sidecar-capabilities.ts delete mode 100644 vendor/intx-types/src/sidecar-oauth-login.test.ts delete mode 100644 vendor/intx-types/src/sidecar.ts delete mode 100644 vendor/intx-types/src/signals.ts delete mode 100644 vendor/intx-types/src/signer-identity.ts delete mode 100644 vendor/intx-types/src/tenants.ts delete mode 100644 vendor/intx-types/src/tool-packages.ts delete mode 100644 vendor/intx-types/src/wallets.ts delete mode 100644 vendor/intx-types/src/wire-definition-hash.ts delete mode 100644 vendor/intx-types/src/wire-workflow.ts delete mode 100644 vendor/intx-types/src/workflow-run-id.ts delete mode 100644 vendor/intx-types/src/workflow-sources.ts delete mode 100644 vendor/intx-types/src/workflows.ts diff --git a/README.md b/README.md index 8c3224a..2bbe54b 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ Give a **person** in an Interchange hub an inbox: list, read, flag, send, and li ## Runtime support -Node >= 24 consumes built `dist/`. Bun >= 1.2 runs TypeScript source. Peers: `@intx/log`, `drizzle-orm`, `hono`, `postgres`. +Node >= 24 consumes built `dist/`. Bun >= 1.2 runs TypeScript source. Peers: `@intx/log`, `@intx/mailbox`, `@intx/mime`, `@intx/types`, `drizzle-orm`, `hono`, `postgres`. ## Quickstart ```bash -bun add @corbits/mailbox @intx/log hono postgres drizzle-orm -# or: npm install @corbits/mailbox @intx/log hono postgres drizzle-orm -# or: pnpm add @corbits/mailbox @intx/log hono postgres drizzle-orm -# or: yarn add @corbits/mailbox @intx/log hono postgres drizzle-orm +bun add @corbits/mailbox @intx/log @intx/mailbox @intx/mime @intx/types hono postgres drizzle-orm +# or: npm install @corbits/mailbox @intx/log @intx/mailbox @intx/mime @intx/types hono postgres drizzle-orm +# or: pnpm add @corbits/mailbox @intx/log @intx/mailbox @intx/mime @intx/types hono postgres drizzle-orm +# or: yarn add @corbits/mailbox @intx/log @intx/mailbox @intx/mime @intx/types hono postgres drizzle-orm ``` `mountMailbox(app, opts)` adds the inbox routes to your app. Every field of `opts` is a host responsibility: @@ -117,7 +117,7 @@ export function wrapPersistMail( ## How it works -Writes go through a native `MailboxStore` (uid/modseq always set). Search and threads are vendored `@intx/mailbox` over that store. `POST /me/inbox/send` builds the RFC 5322 message and files a copy in `Sent`, then calls the host's `deliver` exactly once to transmit it. +Writes go through a native `MailboxStore` (uid/modseq always set). Search and threads are `@intx/mailbox` run over that store. `POST /me/inbox/send` builds the RFC 5322 message and files a copy in `Sent`, then calls the host's `deliver` exactly once to transmit it. See [ARCHITECTURE.md](./ARCHITECTURE.md). diff --git a/VENDORED.md b/VENDORED.md deleted file mode 100644 index 81b372b..0000000 --- a/VENDORED.md +++ /dev/null @@ -1,49 +0,0 @@ -# Vendored code - -`@corbits/mailbox` consumes Interchange as published packages wherever a -publish covers the needed capability. `@intx/mailbox` has never been -published, so it is hand-copied here as a sanctioned escape hatch — never a -submodule, never touched upstream. Its two compile-time dependencies that -are published only at an older API surface (`@intx/mime`, `@intx/types`) -are vendored alongside it at the same commit so the tree never mixes pins; -`@intx/crypto`, byte-identical between npm `0.3.0` and this commit, is -consumed as an ordinary npm dependency instead. - -## Rules - -- Vendoring is hand-copied files only — never a git submodule. -- Every vendored path has exactly one row below, with a kill date and a - dated test that fails after it. -- The upstream repository is never modified, committed to, or pushed to. -- Retiring a vendored copy closes the entry: delete the row and the files - together. - -## Ledger - -| Path | Contents | Upstream | Local delta | Kill condition | -| --- | --- | --- | --- | --- | -| `vendor/intx-mailbox` | `@intx/mailbox` source (`src/`, `package.json`, `LICENSE`) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03), copied from Workbench's own `vendor/intx/mailbox` pin | Package-manager pins only: `catalog:` ranges resolved to this repo's fixed versions (`arktype` 2.1.29, `typescript` 5.7.2, `@types/bun` 1.1.14); dependency versions repointed to `workspace:*` for `@intx/mime`/`@intx/types`. No source delta. | First npm publish of `@intx/mailbox` | -| `vendor/intx-mime` | `@intx/mime` source at the `@intx/mailbox` pin | same commit as above | Same pin/catalog cleanup as above. No source delta. Needed because npm `0.3.0` predates `buildMessageHeaders` and other exports `@intx/mailbox` at this pin imports. | Retired when `@intx/mailbox` publishes against a released `@intx/mime` that carries these exports | -| `vendor/intx-types` | `@intx/types` source at the `@intx/mailbox` pin | same commit as above | Same pin/catalog cleanup as above. No source delta. Needed because npm `0.3.0` predates the runtime types (`InterchangeType`, `Thread`, `SearchQuery`, `base64Decode`) `@intx/mailbox`/`@intx/mime` at this pin import. | Retired when `@intx/mailbox` publishes against a released `@intx/types` that carries these exports | - -## Upstream ask - -Publishing `@intx/mailbox` (and refreshing the `@intx/mime`/`@intx/types` -npm releases to the pin it needs) retires all three rows in one move. - -## Published artifact - -None of the three vendored packages appear in the published manifest's -`dependencies`/`peerDependencies` — there is nothing on npm for a consumer -to install against. Instead `scripts/build.mjs` (invoked by `bun run -build`) bundles `vendor/intx-mailbox`, `vendor/intx-mime`, and -`vendor/intx-types` straight into `dist/index.js`, and compiles their -declarations separately into `dist/vendor//`, rewriting the bare -`@intx/mailbox`/`@intx/mime`/`@intx/types` specifiers in every emitted -`.d.ts` to relative paths into that directory. This keeps the tarball -self-contained for both `node --experimental-...`-free runtime use and a -consumer's own `tsc`. It is a build-time workaround, not a vendoring -delta: once `@intx/mailbox` (and the `@intx/mime`/`@intx/types` pins it -needs) are published, the three packages move back to ordinary -`dependencies`/`peerDependencies`, `scripts/build.mjs` goes back to a -plain `tsc` invocation, and this section is deleted. diff --git a/bun.lock b/bun.lock index 72cfa61..517e221 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,5 @@ { - "lockfileVersion": 1, + "lockfileVersion": 2, "configVersion": 1, "workspaces": { "": { @@ -20,16 +20,17 @@ "@types/bun": "1.1.14", "@types/json-schema": "7.0.15", "@types/node": "22.10.5", - "@types/semver": "^7.7.1", "drizzle-orm": "0.45.2", "hono": "4.12.32", "openapi-types": "12.1.3", "postgres": "3.4.9", - "semver": "^7.7.2", "typescript": "5.7.2", }, "peerDependencies": { "@intx/log": "^0.4.0", + "@intx/mailbox": "^0.4.0", + "@intx/mime": "^0.4.0", + "@intx/types": "^0.4.0", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0", @@ -148,8 +149,6 @@ "@types/node": ["@types/node@22.10.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ=="], - "@types/semver": ["@types/semver@7.8.0", "", {}, "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ=="], - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], diff --git a/package.json b/package.json index ad32cfc..1c69ccb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "type": "module", "license": "LGPL-2.1-only", - "description": "A native Interchange mailbox for human principals, mountable onto any Interchange host. Requires @intx 0.2.2 or newer.", + "description": "A native Interchange mailbox for human principals, mountable onto any Interchange host. Requires @intx 0.4.0 or newer.", "keywords": [ "corbits", "interchange", @@ -27,11 +27,10 @@ "node": ">=24" }, "corbits": { - "minimumIntxVersion": "0.2.2" + "minimumIntxVersion": "0.4.0" }, "workspaces": [ - "examples/*", - "vendor/*" + "examples/*" ], "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -68,6 +67,9 @@ }, "peerDependencies": { "@intx/log": "^0.4.0", + "@intx/mailbox": "^0.4.0", + "@intx/mime": "^0.4.0", + "@intx/types": "^0.4.0", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0" @@ -80,12 +82,10 @@ "@types/bun": "1.1.14", "@types/json-schema": "7.0.15", "@types/node": "22.10.5", - "@types/semver": "^7.7.1", "drizzle-orm": "0.45.2", "hono": "4.12.32", "openapi-types": "12.1.3", "postgres": "3.4.9", - "semver": "^7.7.2", "typescript": "5.7.2" }, "overrides": { diff --git a/scripts/build.mjs b/scripts/build.mjs index 3834273..69028ee 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -1,126 +1,22 @@ #!/usr/bin/env node /** - * Builds `dist/`. - * - * `@intx/mailbox` (and its `@intx/mime`/`@intx/types` compile-time - * dependencies at this pin) are vendored under `vendor/` — see - * VENDORED.md — because `@intx/mailbox` has never been published. A - * published `@corbits/mailbox` therefore cannot depend on them: there is - * nothing on npm for a consumer to install. Instead this build emits the - * three vendored packages' own JS (via `tsc`, no bundler) into - * `dist/vendor//`, so a plain `npm install` of the tarball is - * self-contained. - * - * There is no bundler anywhere in this build. `tsc` emits our own `src/` - * as plain JS + `.d.ts` (module-for-module, matching the source layout), - * and separately emits the three vendored packages the same way. Every - * bare `@intx/mailbox` / `@intx/mime` / `@intx/types` import specifier - * (tsc does not rewrite import text just because `paths` resolved it — - * that mapping is compile-time only) is then rewritten, in BOTH the - * emitted `.js` and the emitted `.d.ts`, to a relative path into - * `dist/vendor/`. Every other dependency (the real npm packages in - * `dependencies`/`peerDependencies`, plus `@intx/crypto`/`@intx/log`, - * which are real npm packages too) is left alone for the consumer to - * install. + * Builds `dist/`: plain `tsc` JS emit + declarations, module-for-module, + * matching the source layout. `@intx/mailbox`/`@intx/mime`/`@intx/types` + * are real npm dependencies (see package.json) — this build does not + * bundle them; a consumer's own install resolves the bare `@intx/*` + * specifiers this package's `src/` imports. */ import { execFileSync } from "node:child_process"; -import { - cpSync, - mkdirSync, - readFileSync, - readdirSync, - rmSync, - statSync, - writeFileSync, -} from "node:fs"; -import { dirname, join, relative } from "node:path"; +import { rmSync } from "node:fs"; +import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const root = dirname(dirname(fileURLToPath(import.meta.url))); const dist = join(root, "dist"); -function run(command, args) { - execFileSync(command, args, { cwd: root, stdio: "inherit" }); -} - rmSync(dist, { recursive: true, force: true }); -// 1. Our own src/: plain tsc JS emit + declarations, module-for-module. -run("bun", ["x", "tsc", "-p", "tsconfig.build.json"]); - -// 2. The three vendored packages: same plain tsc JS + declaration emit, -// compiled standalone so they can be relocated under dist/vendor/ and -// referenced by relative path instead of by bare package name. -run("bun", ["x", "tsc", "-p", "tsconfig.vendor-build.json"]); - -const VENDOR_PACKAGES = { - "intx-mailbox": "@intx/mailbox", - "intx-mime": "@intx/mime", - "intx-types": "@intx/types", -}; - -const rawVendorRoot = join(dist, ".vendor-build-raw", "vendor"); -for (const dir of Object.keys(VENDOR_PACKAGES)) { - const from = join(rawVendorRoot, dir, "src"); - const to = join(dist, "vendor", dir); - mkdirSync(to, { recursive: true }); - cpSync(from, to, { recursive: true }); -} -rmSync(join(dist, ".vendor-build-raw"), { recursive: true, force: true }); - -// 3. Rewrite bare `@intx/*` specifiers in every emitted file — `.js` (ours -// and the vendored packages' own runtime code) and `.d.ts` (same, -// cross-referencing each other) — into relative paths under -// dist/vendor/. A package's own `exports` subpaths (e.g. -// `@intx/types/runtime`) map 1:1 onto that package's own src/ file -// names, which is exactly how they land under dist/vendor//. -function listEmittedFiles(dir) { - const out = []; - for (const entry of readdirSync(dir)) { - const full = join(dir, entry); - if (statSync(full).isDirectory()) { - out.push(...listEmittedFiles(full)); - } else if (entry.endsWith(".js") || entry.endsWith(".d.ts")) { - out.push(full); - } - } - return out; -} - -function rewriteImports(file) { - let text = readFileSync(file, "utf8"); - const fileDir = dirname(file); - text = text.replace( - /from\s+"(@intx\/(?:mailbox|mime|types)(?:\/[a-zA-Z0-9-]+)?)"/g, - (match, spec) => { - const [pkgName, ...subpathParts] = spec.split("/").slice(1); - const vendorDir = { mailbox: "intx-mailbox", mime: "intx-mime", types: "intx-types" }[ - pkgName - ]; - if (!vendorDir) return match; - const targetBase = subpathParts.length > 0 ? subpathParts.join("/") : "index"; - const targetFile = join(dist, "vendor", vendorDir, `${targetBase}.js`); - let rel = relative(fileDir, targetFile).split("\\").join("/"); - if (!rel.startsWith(".")) rel = `./${rel}`; - return `from "${rel}"`; - }, - ); - // The vendored packages' own source (unlike ours) writes extensionless - // relative specifiers, resolved at dev time only via `moduleResolution: - // "bundler"`. `tsc` emits import text verbatim — it does not add - // extensions — so a plain `node` ESM resolver (no bundler, no - // resolution-mode help) fails on them. Append `.js` to any relative - // specifier that doesn't already end in a resolvable extension. - text = text.replace( - /from\s+"(\.\.?\/[^"]+)"/g, - (match, spec) => { - if (/\.(js|json|mjs|cjs)$/.test(spec)) return match; - return `from "${spec}.js"`; - }, - ); - writeFileSync(file, text); -} - -for (const file of listEmittedFiles(dist)) { - rewriteImports(file); -} +execFileSync("bun", ["x", "tsc", "-p", "tsconfig.build.json"], { + cwd: root, + stdio: "inherit", +}); diff --git a/src/index.ts b/src/index.ts index 4c377f6..78e025f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,8 +29,8 @@ export { createMailboxDb } from "./db.js"; export type { MailboxDb } from "./db.js"; // The native `MailboxStore` over `mailbox.principal_mail` / -// `mailbox.mailbox_state` — the vendored `executeSearch`/`executeThread` from -// `@intx/mailbox` run over it unmodified. +// `mailbox.mailbox_state` — `@intx/mailbox`'s `executeSearch`/`executeThread` +// run over it unmodified. export { createPrincipalMailboxStore, openNativeMailboxStore, diff --git a/src/mount.ts b/src/mount.ts index 4e6a09f..dba5078 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -122,7 +122,7 @@ const ID_PARAM = { }; /** - * One item of `GET /me/inbox`: the vendored `executeSearch`'s ref, plus the + * One item of `GET /me/inbox`: `@intx/mailbox`'s `executeSearch`'s ref, plus the * envelope and raw bytes read for it. */ type MailboxListItem = { @@ -143,7 +143,7 @@ type MailboxListItem = { // The five single-message mutations that move or flag a message. `op` is the // event op published on success. /** - * One node of `GET /me/inbox/threads(/:rootUid)`: the vendored + * One node of `GET /me/inbox/threads(/:rootUid)`: `@intx/mailbox`'s * `executeThread`'s ref (recursively, as `children`) plus the same envelope * fields `GET /me/inbox` returns for that ref, so a client can render a * thread without an extra fetch per message. @@ -189,8 +189,8 @@ const MOVE_VERBS = [ * * This library exists ONLY to give human principals a native Interchange * mailbox — list, read/unread, archive/trash/restore, and a live SSE stream. - * Every route is a thin wrapper over `NativeMailboxStore` and the vendored - * `@intx/mailbox` `executeSearch`. + * Every route is a thin wrapper over `NativeMailboxStore` and + * `@intx/mailbox`'s `executeSearch`. * * "No-member asymmetry" is intentional, spec'd behavior: when * `resolvePrincipal` yields no principal, list returns an EMPTY result (200) @@ -311,7 +311,7 @@ export function mountMailbox( tags: TAGS, summary: "The caller's inbox as threads", description: - "The vendored `executeThread` (REFERENCES algorithm) run over the " + + "`@intx/mailbox`'s `executeThread` (REFERENCES algorithm) run over the " + "folder's native store — roots plus children, each ref carrying the " + "same envelope fields `GET /me/inbox` returns. With no resolvable " + "principalId this returns an empty list, not a 403.", diff --git a/src/native-store.test.ts b/src/native-store.test.ts index 465889e..279d9d4 100644 --- a/src/native-store.test.ts +++ b/src/native-store.test.ts @@ -132,7 +132,7 @@ describe("native MailboxStore over the principal mailbox tables", () => { expect(secondMove).toBe(2); }); - it("runs the vendored executeSearch and executeThread over the native store", async () => { + it("runs @intx/mailbox's executeSearch and executeThread over the native store", async () => { const inbox = await openNativeMailboxStore(db, { tenantId: TENANT_ID, principalId: PRINCIPAL_ID, diff --git a/src/native-store.ts b/src/native-store.ts index 43a6e05..3e803a2 100644 --- a/src/native-store.ts +++ b/src/native-store.ts @@ -14,12 +14,13 @@ function pgTextArrayLiteral(items: readonly string[]): string { } /** - * A `MailboxStore` (see `vendor/intx-mailbox/src/mailbox.ts`) backed by the + * A `MailboxStore` (see `@intx/mailbox`'s `mailbox.ts`) backed by the * `mailbox.principal_mail` / `mailbox.mailbox_state` tables migration * `0004_native_mailbox_store` added, instead of an in-process array. One * instance is scoped to a single (tenant, principal, folder) mailbox, which is - * the same scope the vendored `executeSearch`/`executeThread` pure functions - * already assume (`mailboxName` names the one mailbox `store.messages` holds). + * the same scope `@intx/mailbox`'s `executeSearch`/`executeThread` pure + * functions already assume (`mailboxName` names the one mailbox + * `store.messages` holds). * * Deliberately reads via plain tagged `sql`, not the `schema.ts` drizzle table * objects: those objects are pinned by `schema-check.ts` and @@ -27,7 +28,7 @@ function pgTextArrayLiteral(items: readonly string[]): string { * on, and this slice must not widen what those assert. * * `MailboxStore`'s mutating methods (`append`/`addFlags`/`removeFlags`/ - * `remove`) are synchronous in the vendored interface — an in-memory backing + * `remove`) are synchronous in `@intx/mailbox`'s interface — an in-memory backing * can satisfy that trivially, a Postgres-backed one cannot make the write * durable before returning. This backing keeps a fully materialized in-memory * mirror (loaded once by `openNativeMailboxStore`) so every synchronous method @@ -319,7 +320,7 @@ export async function openNativeMailboxStore( /** * Move a message from one folder to another for the same (tenant, principal). - * Not part of the vendored `MailboxStore` interface — IMAP MOVE reassigns a + * Not part of `@intx/mailbox`'s `MailboxStore` interface — IMAP MOVE reassigns a * fresh UID in the destination mailbox and bumps ITS counters, which needs * both folders' `mailbox_state` rows, so this operates directly on the * database rather than through two `NativeMailboxStore` instances (each of diff --git a/tsconfig.vendor-build.json b/tsconfig.vendor-build.json deleted file mode 100644 index eee9fcf..0000000 --- a/tsconfig.vendor-build.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "types": ["node"], - "noEmit": false, - "declaration": true, - "declarationMap": false, - "outDir": "dist/.vendor-build-raw", - "rootDir": "." - }, - "include": [ - "vendor/intx-mailbox/src", - "vendor/intx-mime/src", - "vendor/intx-types/src" - ], - "exclude": [ - "**/node_modules", - "**/dist", - "**/*.test.ts", - "**/test-helpers.ts" - ] -} diff --git a/vendor/intx-mailbox/LICENSE b/vendor/intx-mailbox/LICENSE deleted file mode 100644 index c6487f4..0000000 --- a/vendor/intx-mailbox/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE - -Version 2.1, February 1999 - -Copyright (C) 1991, 1999 Free Software Foundation, Inc. -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - -Preamble - -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. - -This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. - -When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. - -To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. - -We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - -Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. - -Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. - -When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. - -We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. - -For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. - -In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. - -Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. - -The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - -GNU LESSER GENERAL PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. - -Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. - -You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. - -(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - -3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. - -5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. - -When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. - -If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: - - a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. - - e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. - -For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - -It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - -7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. - - b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - -11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - -This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - -13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - -NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Libraries - -If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). - -To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - one line to give the library's name and an idea of what it does. - Copyright (C) year name of author - - This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in -the library `Frob' (a library for tweaking knobs) written -by James Random Hacker. - -signature of Ty Coon, 1 April 1990 -Ty Coon, President of Vice -That's all there is to it! diff --git a/vendor/intx-mailbox/package.json b/vendor/intx-mailbox/package.json deleted file mode 100644 index 19bb135..0000000 --- a/vendor/intx-mailbox/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@intx/mailbox", - "description": "Storage-agnostic IMAP mailbox model with search, threading, and fetch projections", - "version": "0.3.0", - "license": "LGPL-2.1-only", - "type": "module", - "exports": { - ".": { - "types": "./src/index.ts", - "default": "./src/index.ts" - } - }, - "scripts": { - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@intx/crypto": "0.3.0", - "@intx/mime": "workspace:*", - "@intx/types": "workspace:*", - "arktype": "2.1.29" - }, - "devDependencies": { - "@types/bun": "1.1.14", - "typescript": "5.7.2" - }, - "files": [ - "src", - "README.md", - "LICENSE" - ], - "sideEffects": false, - "publishConfig": { - "access": "public" - } -} diff --git a/vendor/intx-mailbox/src/fetch.ts b/vendor/intx-mailbox/src/fetch.ts deleted file mode 100644 index 792c7d6..0000000 --- a/vendor/intx-mailbox/src/fetch.ts +++ /dev/null @@ -1,250 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion -- MIME multipart parsing with bounds checks */ -import { type } from "arktype"; -import type { - MessageHeaders, - BodyStructure, - MessagePart, - InboundMessage, - SignatureStatus, - CryptoProvider, - MessageRef, -} from "@intx/types/runtime"; -import { InterchangeType } from "@intx/types/runtime"; -import { base64Decode } from "@intx/types"; -import type { MailboxStore } from "./mailbox"; -import { requireMessage } from "./mailbox"; -import { - parseHeaderSection, - parseMimePart, - extractBoundary, - parseMultipart, - extractPartByPath, - extractAttachments, -} from "@intx/mime"; -import { buildMessageHeaders } from "./headers"; -import { verifyDetachedSignature } from "@intx/crypto"; - -const MessagePayload = type({ - type: InterchangeType, - version: "string", - body: "Record", -}); - -/** - * Parse the full RFC 2822 headers of a stored message. Reads the message's raw - * bytes on demand: the parsed set is a superset of the pre-parsed envelope (it - * carries `cc`, `mimeVersion`, trace headers, ...), so it cannot be served from - * the envelope metadata alone. - */ -export async function fetchHeaders( - ref: MessageRef, - store: MailboxStore, -): Promise { - requireMessage(store, ref.uid, ref.mailbox); - const raw = await store.readRaw(ref.uid); - const { headers } = parseHeaderSection(raw); - return buildMessageHeaders(headers); -} - -/** - * Compute the MIME tree structure (BODYSTRUCTURE) without transferring content. - */ -export async function fetchStructure( - ref: MessageRef, - store: MailboxStore, -): Promise { - requireMessage(store, ref.uid, ref.mailbox); - const raw = await store.readRaw(ref.uid); - const { headers, bodyOffset } = parseHeaderSection(raw); - const body = raw.slice(bodyOffset); - const contentType = headers.get("content-type") ?? "application/octet-stream"; - return buildStructure(body, contentType); -} - -/** - * Fetch a single MIME part by dot-separated path. - */ -export async function fetchPart( - ref: MessageRef, - partPath: string, - store: MailboxStore, -): Promise { - requireMessage(store, ref.uid, ref.mailbox); - const raw = await store.readRaw(ref.uid); - const partBytes = extractPartByPath(raw, partPath); - const part = parseMimePart(partBytes); - - const enc = part.headers.get("content-transfer-encoding") ?? "7bit"; - let content: Uint8Array; - - if (enc.toLowerCase() === "base64") { - const b64 = new TextDecoder().decode(part.body).replace(/\s/g, ""); - content = base64Decode(b64); - } else { - content = part.body; - } - - const result: MessagePart = { - contentType: part.contentType, - content, - }; - if (enc !== "7bit") result.encoding = enc; - return result; -} - -/** - * Fetch a complete message, verify its PGP/MIME signature, and return - * a fully parsed InboundMessage. - */ -export async function fetchFull( - ref: MessageRef, - store: MailboxStore, - getCrypto: (fromAddress: string) => CryptoProvider | undefined, -): Promise { - const msg = requireMessage(store, ref.uid, ref.mailbox); - const raw = await store.readRaw(ref.uid); - const { headers } = parseHeaderSection(raw); - const parsedHeaders = buildMessageHeaders(headers); - - const rawType = parsedHeaders.interchangeType; - const isConversation = - rawType === "conversation.message" || - rawType === "conversation.join" || - rawType === "conversation.leave" || - rawType === undefined; - - const signatureStatus = await verifyMessageSignature( - raw, - parsedHeaders.from, - getCrypto, - ); - - const result: InboundMessage = { - ref, - headers: parsedHeaders, - flags: Array.from(msg.flags), - signatureStatus, - }; - - try { - if (isConversation) { - const part1 = parseMimePart(extractPartByPath(raw, "1")); - const part1Mime = part1.contentType.split(";")[0]!.trim().toLowerCase(); - if (part1Mime.startsWith("multipart/")) { - // Conversation shape: multipart/mixed with the text body at 1.1. - const textPart = parseMimePart(extractPartByPath(raw, "1.1")); - result.content = new TextDecoder("utf-8", { fatal: false }).decode( - textPart.body, - ); - } else { - // A conversation message is "literally a signed email", so a sender - // (e.g. a plain mail client) may sign a bare text/plain part with no - // multipart/mixed wrapper. This branch reads that body directly. Our - // own assembler always emits multipart/mixed; without this branch a - // bare text/plain message would fail the 1.1 lookup and silently lose - // its content to the catch below. - result.content = new TextDecoder("utf-8", { fatal: false }).decode( - part1.body, - ); - } - } else { - // Structured messages carry their JSON payload at 1.1. Attachments on - // structured messages are intentionally not parsed: they have no - // producer today, so parsing them would handle a shape nobody sends. - const part11Bytes = extractPartByPath(raw, "1.1"); - const part11 = parseMimePart(part11Bytes); - const jsonText = new TextDecoder("utf-8", { fatal: false }).decode( - part11.body, - ); - const validated = MessagePayload(JSON.parse(jsonText)); - if (validated instanceof type.errors) { - throw new Error(`invalid message payload: ${validated.summary}`); - } - result.payload = validated; - } - } catch { - // If we can't parse the content, return what we have with the signature status. - } - - // Attachment parsing is deliberately outside the catch above: a malformed - // attachment must surface as a thrown error, not be silently dropped. - if (isConversation) { - const attachments = extractAttachments(raw); - if (attachments.length > 0) { - result.attachments = attachments; - } - } - - return result; -} - -async function verifyMessageSignature( - raw: Uint8Array, - fromAddress: string, - getCrypto: (fromAddress: string) => CryptoProvider | undefined, -): Promise { - const senderCrypto = getCrypto(fromAddress); - if (senderCrypto === undefined) { - return "unknown"; - } - - try { - const { headers, bodyOffset } = parseHeaderSection(raw); - const body = raw.slice(bodyOffset); - const contentType = headers.get("content-type") ?? ""; - - if (!contentType.toLowerCase().includes("multipart/signed")) { - return "missing"; - } - - const boundary = extractBoundary(contentType); - if (boundary === undefined) return "missing"; - - const parts = parseMultipart(body, boundary); - if (parts.length < 2) return "missing"; - - const signedContentBytes = parts[0]!; - const sigPartBytes = parts[1]!; - const sigPart = parseMimePart(sigPartBytes); - - if ( - !sigPart.contentType.toLowerCase().includes("application/pgp-signature") - ) { - return "missing"; - } - - const publicKey = senderCrypto.getPublicKey(); - const valid = await verifyDetachedSignature( - signedContentBytes, - sigPart.body, - publicKey, - ); - - return valid ? "valid" : "invalid"; - } catch { - return "invalid"; - } -} - -function buildStructure(body: Uint8Array, contentType: string): BodyStructure { - const ct = contentType.toLowerCase(); - if (!ct.startsWith("multipart/")) { - return { contentType, size: body.length }; - } - - const boundary = extractBoundary(contentType); - if (boundary === undefined) { - return { contentType, size: body.length }; - } - - const parts = parseMultipart(body, boundary); - const subStructures: BodyStructure[] = parts.map((partBytes) => { - const { headers, bodyOffset } = parseHeaderSection(partBytes); - const partBody = partBytes.slice(bodyOffset); - const partContentType = - headers.get("content-type") ?? "application/octet-stream"; - return buildStructure(partBody, partContentType); - }); - - return { contentType, parts: subStructures }; -} diff --git a/vendor/intx-mailbox/src/headers.ts b/vendor/intx-mailbox/src/headers.ts deleted file mode 100644 index 0e40211..0000000 --- a/vendor/intx-mailbox/src/headers.ts +++ /dev/null @@ -1,5 +0,0 @@ -// `buildMessageHeaders` now lives in `@intx/mime` alongside the rest of the -// MIME/header parsing (it is also what the `decodeMail` decoder builds its -// typed header subset with). Re-exported here so mail-memory's callers keep -// their existing import path. -export { buildMessageHeaders } from "@intx/mime"; diff --git a/vendor/intx-mailbox/src/index.ts b/vendor/intx-mailbox/src/index.ts deleted file mode 100644 index efc103b..0000000 --- a/vendor/intx-mailbox/src/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { - DEFAULT_MAILBOXES, - createInMemoryMailboxStore, - requireMessage, -} from "./mailbox"; -export type { MailboxStore, StoredMessage, StoredEnvelope } from "./mailbox"; - -export { executeSearch } from "./search"; -export { executeThread } from "./thread"; -export { fetchHeaders, fetchStructure, fetchPart, fetchFull } from "./fetch"; -export { buildMessageHeaders } from "./headers"; diff --git a/vendor/intx-mailbox/src/mailbox.ts b/vendor/intx-mailbox/src/mailbox.ts deleted file mode 100644 index 05a3e94..0000000 --- a/vendor/intx-mailbox/src/mailbox.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Pre-parsed envelope extracted from MIME headers at delivery time. - * Avoids re-parsing raw bytes for every search operation. - */ -export type StoredEnvelope = { - messageId: string; - from: string; - to: string[]; - subject: string; - date: Date; - inReplyTo: string | undefined; - references: string[]; - interchangeType: string | undefined; - interchangeCorrelationId: string | undefined; -}; - -/** - * A single stored message's resident model: its uid, the IMAP counters, its - * flags, and the pre-parsed envelope. The complete RFC 2822 bytes are NOT - * resident here; they are read on demand through `MailboxStore.readRaw`, so a - * backing can bound its in-memory footprint to metadata and keep the raw bytes - * on disk (the substrate backing) or retain them itself (the in-memory - * backing). The projections that need the bytes -- `fetchFull`, `fetchPart`, - * `fetchStructure`, `fetchHeaders`, and the raw-scanning search predicates -- - * route through `readRaw`, which returns the verbatim bytes so signature - * verification stays byte-exact. - */ -export type StoredMessage = { - uid: number; - modseq: number; - flags: Set; - envelope: StoredEnvelope; -}; - -/** - * Storage-agnostic per-mailbox model. A backing owns how the message list and - * the uid/modseq/uidValidity counters are stored; the pure query and - * projection functions (search, thread, fetch, bodystructure, headers) read - * the message snapshot the backing exposes through `messages`, and read a - * message's raw bytes on demand through `readRaw`. - * - * The counters follow IMAP semantics: `uidNext` is the UID that the next - * `append` will assign (UIDNEXT), `highestModSeq` is the largest MODSEQ - * currently assigned (HIGHESTMODSEQ), and `uidValidity` is stable for the - * lifetime of the mailbox (UIDVALIDITY). - */ -export interface MailboxStore { - readonly uidValidity: number; - readonly uidNext: number; - readonly highestModSeq: number; - readonly messages: readonly StoredMessage[]; - - /** - * Store a message, assigning it the next UID and MODSEQ. Returns the - * assigned UID. The backing decides whether to retain `raw` in memory or - * persist it and serve it from disk through `readRaw`. - */ - append(raw: Uint8Array, envelope: StoredEnvelope, flags: string[]): number; - - /** - * Read a stored message's verbatim RFC 2822 bytes. Resolves the bytes from - * wherever the backing keeps them (memory or disk). Throws if no message has - * the given UID. - */ - readRaw(uid: number): Promise; - - /** Locate a stored message by UID, or `undefined` if none matches. */ - find(uid: number): StoredMessage | undefined; - - /** - * Add flags to a stored message and advance its MODSEQ. Returns the updated - * message. Throws if no message has the given UID. - */ - addFlags(uid: number, flags: string[]): StoredMessage; - - /** - * Remove flags from a stored message and advance its MODSEQ. Returns the - * updated message. Throws if no message has the given UID. - */ - removeFlags(uid: number, flags: string[]): StoredMessage; - - /** Drop a stored message by UID. Throws if no message has the given UID. */ - remove(uid: number): void; -} - -/** - * The default set of mailboxes created for a freshly registered address. - */ -export const DEFAULT_MAILBOXES = [ - "INBOX", - "Sent", - "Drafts", - "Archive", - "Trash", -] as const; - -/** - * Create an in-memory `MailboxStore` backing. Messages, counters, and - * uidValidity live in process memory for the lifetime of the returned store. - */ -export function createInMemoryMailboxStore(): MailboxStore { - const messages: StoredMessage[] = []; - // The in-memory backing is its own durable store, so it legitimately retains - // every message's raw bytes. `readRaw` returns them; the metadata mirror in - // `messages` stays free of the bytes so the read model matches the - // disk-backed backing. - const rawByUid = new Map(); - let uidCounter = 1; - let modseqCounter = 1; - const uidValidity = Date.now(); - - function find(uid: number): StoredMessage | undefined { - return messages.find((m) => m.uid === uid); - } - - function require(uid: number): StoredMessage { - const msg = find(uid); - if (msg === undefined) { - throw new Error(`Message UID ${uid} not found`); - } - return msg; - } - - return { - uidValidity, - get uidNext() { - return uidCounter; - }, - get highestModSeq() { - return modseqCounter - 1; - }, - get messages() { - return messages; - }, - append(raw, envelope, flags) { - const uid = uidCounter++; - const modseq = modseqCounter++; - messages.push({ uid, modseq, flags: new Set(flags), envelope }); - rawByUid.set(uid, raw); - return uid; - }, - readRaw(uid) { - const raw = rawByUid.get(uid); - if (raw === undefined) { - return Promise.reject(new Error(`Message UID ${uid} not found`)); - } - return Promise.resolve(raw); - }, - find, - addFlags(uid, flags) { - const msg = require(uid); - for (const flag of flags) { - msg.flags.add(flag); - } - msg.modseq = modseqCounter++; - return msg; - }, - removeFlags(uid, flags) { - const msg = require(uid); - for (const flag of flags) { - msg.flags.delete(flag); - } - msg.modseq = modseqCounter++; - return msg; - }, - remove(uid) { - const idx = messages.findIndex((m) => m.uid === uid); - if (idx === -1) { - throw new Error(`Message UID ${uid} not found`); - } - messages.splice(idx, 1); - rawByUid.delete(uid); - }, - }; -} - -/** - * Locate a stored message by UID, throwing a mailbox-qualified error when it - * is absent. Used by the fetch projections, which resolve a `MessageRef` - * against a specific mailbox. - */ -export function requireMessage( - store: MailboxStore, - uid: number, - mailboxName: string, -): StoredMessage { - const msg = store.find(uid); - if (msg === undefined) { - throw new Error(`Message UID ${uid} not found in mailbox "${mailboxName}"`); - } - return msg; -} diff --git a/vendor/intx-mailbox/src/search.ts b/vendor/intx-mailbox/src/search.ts deleted file mode 100644 index 4efc54c..0000000 --- a/vendor/intx-mailbox/src/search.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type { SearchQuery, MessageRef } from "@intx/types/runtime"; -import type { MailboxStore, StoredMessage } from "./mailbox"; -import { parseHeaderSection } from "@intx/mime"; - -/** - * Execute an IMAP SEARCH-equivalent query over a mailbox. - * - * Supports: from, to, cc, bcc, header (field match), before/after/on, - * sentBefore/sentAfter/sentOn, hasFlags, missingFlags, body, text, - * largerThan, smallerThan, and boolean and/or/not composition. - * - * The envelope- and flag-based predicates (from, to, dates, flags, boolean - * composition) resolve from metadata alone. The predicates that inspect - * headers the envelope does not carry (cc, bcc, arbitrary `header`), the body, - * or the raw size (body, text, largerThan, smallerThan) read a message's raw - * bytes on demand through `store.readRaw`, memoized per message so a query that - * touches raw reads each candidate's blob at most once. A query with no - * raw-scanning predicate never reads a blob. - * - * Returns MessageRef[] for all matching messages, ordered by UID. - */ -export async function executeSearch( - mailboxName: string, - store: MailboxStore, - query: SearchQuery, -): Promise { - const results: MessageRef[] = []; - for (const msg of store.messages) { - if (await matchMessage(msg, query, makeRawReader(store, msg.uid))) { - results.push({ uid: msg.uid, mailbox: mailboxName }); - } - } - return results; -} - -/** - * A per-message memoized reader for the raw bytes. The first raw-scanning - * predicate reads the blob through `store.readRaw`; every later predicate on - * the same message reuses the resolved bytes. - */ -function makeRawReader( - store: MailboxStore, - uid: number, -): () => Promise { - let pending: Promise | undefined; - return () => { - if (pending === undefined) pending = store.readRaw(uid); - return pending; - }; -} - -async function matchMessage( - msg: StoredMessage, - query: SearchQuery, - readRaw: () => Promise, -): Promise { - if (query.from !== undefined) { - if (!msg.envelope.from.toLowerCase().includes(query.from.toLowerCase())) { - return false; - } - } - - if (query.to !== undefined) { - const queryTo = query.to; - const toMatch = msg.envelope.to.some((addr) => - addr.toLowerCase().includes(queryTo.toLowerCase()), - ); - if (!toMatch) return false; - } - - if (query.cc !== undefined) { - const headers = await lazyHeaders(msg, readRaw); - const ccHeader = headers.get("cc") ?? ""; - if (!ccHeader.toLowerCase().includes(query.cc.toLowerCase())) { - return false; - } - } - - if (query.bcc !== undefined) { - const headers = await lazyHeaders(msg, readRaw); - const bccHeader = headers.get("bcc") ?? ""; - if (!bccHeader.toLowerCase().includes(query.bcc.toLowerCase())) { - return false; - } - } - - if (query.header !== undefined) { - const { field, contains } = query.header; - const headers = await lazyHeaders(msg, readRaw); - const value = headers.get(field.toLowerCase()) ?? ""; - if (!value.toLowerCase().includes(contains.toLowerCase())) { - return false; - } - } - - if (query.before !== undefined) { - if (msg.envelope.date >= query.before) return false; - } - if (query.after !== undefined) { - if (msg.envelope.date <= query.after) return false; - } - if (query.on !== undefined) { - const d = msg.envelope.date; - const q = query.on; - if ( - d.getUTCFullYear() !== q.getUTCFullYear() || - d.getUTCMonth() !== q.getUTCMonth() || - d.getUTCDate() !== q.getUTCDate() - ) { - return false; - } - } - - // Sent date filters use the Date header (same as envelope date here). - if (query.sentBefore !== undefined) { - if (msg.envelope.date >= query.sentBefore) return false; - } - if (query.sentAfter !== undefined) { - if (msg.envelope.date <= query.sentAfter) return false; - } - if (query.sentOn !== undefined) { - const d = msg.envelope.date; - const q = query.sentOn; - if ( - d.getUTCFullYear() !== q.getUTCFullYear() || - d.getUTCMonth() !== q.getUTCMonth() || - d.getUTCDate() !== q.getUTCDate() - ) { - return false; - } - } - - if (query.hasFlags !== undefined) { - for (const flag of query.hasFlags) { - if (!msg.flags.has(flag)) return false; - } - } - - if (query.missingFlags !== undefined) { - for (const flag of query.missingFlags) { - if (msg.flags.has(flag)) return false; - } - } - - if (query.largerThan !== undefined) { - if ((await readRaw()).length <= query.largerThan) return false; - } - if (query.smallerThan !== undefined) { - if ((await readRaw()).length >= query.smallerThan) return false; - } - - if (query.body !== undefined || query.text !== undefined) { - const raw = await readRaw(); - const rawText = new TextDecoder("utf-8", { fatal: false }).decode(raw); - if (query.body !== undefined) { - const { bodyOffset } = parseHeaderSection(raw); - const bodyText = new TextDecoder("utf-8", { fatal: false }).decode( - raw.slice(bodyOffset), - ); - if (!bodyText.toLowerCase().includes(query.body.toLowerCase())) { - return false; - } - } - if (query.text !== undefined) { - if (!rawText.toLowerCase().includes(query.text.toLowerCase())) { - return false; - } - } - } - - if (query.and !== undefined) { - for (const sub of query.and) { - if (!(await matchMessage(msg, sub, readRaw))) return false; - } - } - - if (query.or !== undefined) { - if (query.or.length > 0) { - let anyMatch = false; - for (const sub of query.or) { - if (await matchMessage(msg, sub, readRaw)) { - anyMatch = true; - break; - } - } - if (!anyMatch) return false; - } - } - - if (query.not !== undefined) { - if (await matchMessage(msg, query.not, readRaw)) return false; - } - - return true; -} - -const headerCache = new WeakMap>(); - -async function lazyHeaders( - msg: StoredMessage, - readRaw: () => Promise, -): Promise> { - const cached = headerCache.get(msg); - if (cached !== undefined) return cached; - const { headers } = parseHeaderSection(await readRaw()); - headerCache.set(msg, headers); - return headers; -} diff --git a/vendor/intx-mailbox/src/thread.ts b/vendor/intx-mailbox/src/thread.ts deleted file mode 100644 index 0245579..0000000 --- a/vendor/intx-mailbox/src/thread.ts +++ /dev/null @@ -1,275 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion -- Map.get()! after has() checks in threading algorithm */ -import type { Thread, SearchQuery } from "@intx/types/runtime"; -import type { MailboxStore, StoredMessage } from "./mailbox"; -import { executeSearch } from "./search"; - -/** - * RFC 5256 REFERENCES threading algorithm. - * - * Builds parent-child relationships from In-Reply-To and References headers. - * The algorithm: - * 1. For each message, collect its References chain (oldest → newest ancestor). - * 2. Link messages into a tree using these chains. - * 3. Create dummy containers for referenced messages not present in the set. - * 4. Prune dummy containers with no children; promote children of childless dummies. - * 5. Gather root-level containers with the same base subject (skipped here — - * we implement only the parent/child linking portion which is what this - * transport needs; subject-based gathering is optional for our use case). - * 6. Sort threads at each level. - * - * Note: RFC 5256 also defines an ORDEREDSUBJECT algorithm. For that, messages - * are sorted by subject and date without reference tracking. - */ - -type Container = { - messageId: string; - message: StoredMessage | null; - parent: Container | null; - children: Container[]; -}; - -export async function executeThread( - mailboxName: string, - store: MailboxStore, - algorithm: "references" | "orderedsubject", - query?: SearchQuery, -): Promise { - let messages: StoredMessage[]; - - if (query !== undefined) { - const refs = await executeSearch(mailboxName, store, query); - const uidSet = new Set(refs.map((r) => r.uid)); - messages = store.messages.filter((m) => uidSet.has(m.uid)); - } else { - messages = [...store.messages]; - } - - if (messages.length === 0) return []; - - if (algorithm === "orderedsubject") { - return orderedSubjectThread(mailboxName, messages); - } - - return referencesThread(mailboxName, messages); -} - -/** - * RFC 5256 ORDEREDSUBJECT: sort by base subject, then date. - * All messages with the same base subject form one thread; the first by date - * is the root, the rest are direct children. - */ -function orderedSubjectThread( - mailboxName: string, - messages: StoredMessage[], -): Thread[] { - const bySubject = new Map(); - - for (const msg of messages) { - const base = baseSubject(msg.envelope.subject); - const bucket = bySubject.get(base); - if (bucket === undefined) { - bySubject.set(base, [msg]); - } else { - bucket.push(msg); - } - } - - const threads: Thread[] = []; - for (const [, msgs] of bySubject) { - const sorted = msgs.sort( - (a, b) => a.envelope.date.getTime() - b.envelope.date.getTime(), - ); - const root = sorted[0]!; - const rootThread: Thread = { - ref: { uid: root.uid, mailbox: mailboxName }, - children: sorted.slice(1).map((m) => ({ - ref: { uid: m.uid, mailbox: mailboxName }, - children: [], - })), - }; - threads.push(rootThread); - } - - return threads.sort((a, b) => { - const aMsg = messages.find((m) => m.uid === a.ref.uid)!; - const bMsg = messages.find((m) => m.uid === b.ref.uid)!; - return aMsg.envelope.date.getTime() - bMsg.envelope.date.getTime(); - }); -} - -/** - * RFC 5256 REFERENCES algorithm. - * - * Step 1: For each message, create a container. Walk its References list - * (and In-Reply-To if not already in References) and link containers - * as parent-child in left-to-right order. - * - * Step 2: Build the id_table mapping Message-IDs to containers. - * - * Step 3: Prune empty containers (those with no message). - * - * Step 4: Collect root containers. - * - * Step 5: Sort each container's children by date. - */ -function referencesThread( - mailboxName: string, - messages: StoredMessage[], -): Thread[] { - const idTable = new Map(); - - function getOrCreate(msgId: string): Container { - const existing = idTable.get(msgId); - if (existing !== undefined) return existing; - const c: Container = { - messageId: msgId, - message: null, - parent: null, - children: [], - }; - idTable.set(msgId, c); - return c; - } - - // Step 1 & 2: Build containers and link parent-child relationships. - for (const msg of messages) { - const container = getOrCreate(msg.envelope.messageId); - container.message = msg; - - // Build the reference list: References + In-Reply-To (deduplicated). - const refs = buildRefList(msg.envelope.references, msg.envelope.inReplyTo); - - // Link: refs[i] is parent of refs[i+1], last ref is parent of this message. - let prevContainer: Container | null = null; - for (const refId of refs) { - const refContainer = getOrCreate(refId); - - if ( - prevContainer !== null && - refContainer.parent === null && - !isAncestor(refContainer, prevContainer) - ) { - prevContainer.children.push(refContainer); - refContainer.parent = prevContainer; - } - - prevContainer = refContainer; - } - - // Link the last reference as parent of this message (if no circular reference). - if ( - prevContainer !== null && - container.parent === null && - !isAncestor(container, prevContainer) - ) { - prevContainer.children.push(container); - container.parent = prevContainer; - } - } - - // Step 3: Find root containers (no parent). - const roots: Container[] = []; - for (const [, c] of idTable) { - if (c.parent === null) { - roots.push(c); - } - } - - // Step 4: Prune dummy containers (containers with no message). - // A dummy with no children is dropped. - // A dummy with children: the children are promoted to the dummy's parent level. - const prunedRoots = pruneContainers(roots); - - // Step 5: Sort and convert to Thread[]. - return containersToThreads(mailboxName, prunedRoots); -} - -function buildRefList(references: string[], inReplyTo?: string): string[] { - const seen = new Set(); - const result: string[] = []; - - for (const ref of references) { - if (ref && !seen.has(ref)) { - seen.add(ref); - result.push(ref); - } - } - - if (inReplyTo !== undefined && inReplyTo !== "" && !seen.has(inReplyTo)) { - result.push(inReplyTo); - } - - return result; -} - -function isAncestor(potentialAncestor: Container, of: Container): boolean { - let cur: Container | null = of; - while (cur !== null) { - if (cur === potentialAncestor) return true; - cur = cur.parent; - } - return false; -} - -function pruneContainers(containers: Container[]): Container[] { - const result: Container[] = []; - for (const c of containers) { - if (c.message === null && c.children.length === 0) { - // Dummy with no children: drop it. - continue; - } - if (c.message === null && c.children.length > 0) { - // Dummy with children: promote children (skip the dummy). - const promotedChildren = pruneContainers(c.children); - result.push(...promotedChildren); - } else { - // Real message: recurse into children. - c.children = pruneContainers(c.children); - result.push(c); - } - } - return result; -} - -function containerDate(c: Container): number { - if (c.message !== null) { - return c.message.envelope.date.getTime(); - } - // For dummy containers, use the earliest child date. - let earliest = Infinity; - for (const child of c.children) { - const d = containerDate(child); - if (d < earliest) earliest = d; - } - return earliest === Infinity ? 0 : earliest; -} - -function containersToThreads( - mailboxName: string, - containers: Container[], -): Thread[] { - // Sort by date of the container (or earliest descendant for dummies). - const sorted = containers.sort((a, b) => containerDate(a) - containerDate(b)); - - return sorted - .filter((c) => c.message !== null) - .map((c) => ({ - ref: { uid: c.message!.uid, mailbox: mailboxName }, - children: containersToThreads(mailboxName, c.children), - })); -} - -function baseSubject(subject: string): string { - // Strip "Re:", "Fwd:", "Fw:" prefixes (case-insensitive) repeatedly. - let s = subject.trim(); - let changed = true; - while (changed) { - changed = false; - const m = s.match(/^(?:re|fwd?)\s*:\s*/i); - if (m !== null) { - s = s.slice(m[0].length).trim(); - changed = true; - } - } - return s; -} diff --git a/vendor/intx-mime/LICENSE b/vendor/intx-mime/LICENSE deleted file mode 100644 index c6487f4..0000000 --- a/vendor/intx-mime/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE - -Version 2.1, February 1999 - -Copyright (C) 1991, 1999 Free Software Foundation, Inc. -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - -Preamble - -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. - -This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. - -When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. - -To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. - -We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - -Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. - -Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. - -When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. - -We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. - -For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. - -In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. - -Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. - -The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - -GNU LESSER GENERAL PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. - -Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. - -You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. - -(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - -3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. - -5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. - -When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. - -If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: - - a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. - - e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. - -For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - -It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - -7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. - - b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - -11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - -This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - -13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - -NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Libraries - -If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). - -To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - one line to give the library's name and an idea of what it does. - Copyright (C) year name of author - - This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in -the library `Frob' (a library for tweaking knobs) written -by James Random Hacker. - -signature of Ty Coon, 1 April 1990 -Ty Coon, President of Vice -That's all there is to it! diff --git a/vendor/intx-mime/package.json b/vendor/intx-mime/package.json deleted file mode 100644 index 603ab68..0000000 --- a/vendor/intx-mime/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@intx/mime", - "description": "RFC 2822 message assembly and parsing with PGP detached signatures", - "version": "0.3.0", - "license": "LGPL-2.1-only", - "type": "module", - "exports": { - ".": { - "types": "./src/index.ts", - "default": "./src/index.ts" - } - }, - "scripts": { - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@intx/crypto": "0.3.0", - "@intx/types": "workspace:*", - "arktype": "2.1.29" - }, - "devDependencies": { - "@types/bun": "1.1.14", - "typescript": "5.7.2" - }, - "files": [ - "src", - "README.md", - "LICENSE" - ], - "sideEffects": false, - "publishConfig": { - "access": "public" - } -} diff --git a/vendor/intx-mime/src/index.ts b/vendor/intx-mime/src/index.ts deleted file mode 100644 index a202956..0000000 --- a/vendor/intx-mime/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -export { - assembleSignedContent, - assembleMessage, - extractAddrSpec, - formatRFC2822Date, - generateMessageId, - parseHeaderSection, - parseMimePart, - parseMultipart, - extractBoundary, - extractPartByPath, - parseMailToEmail, - extractAttachments, - buildMessageHeaders, - decodeMail, -} from "./mime"; - -export type { - MessageHeaders, - ConversationContent, - MimeAssemblyInput, - StructuredContent, - ParsedMimePart, - ParsedMimeMessage, - JMAPEmail, - JMAPAddress, - JMAPBodyValue, - JMAPBodyPart, - JMAPAttachment, -} from "./mime"; - -export { createDetachedSignatureFromProvider } from "./pgp-sign"; - -export { - createInboundMessage, - createOutboundMessage, - isMessageId, -} from "./mail-builder"; - -export type { - CreateInboundMessageOpts, - CreateOutboundMessageOpts, - InboundPayloadInput, -} from "./mail-builder"; diff --git a/vendor/intx-mime/src/mail-builder.ts b/vendor/intx-mime/src/mail-builder.ts deleted file mode 100644 index 65c3ff3..0000000 --- a/vendor/intx-mime/src/mail-builder.ts +++ /dev/null @@ -1,516 +0,0 @@ -/** - * Builders for InboundMessage and OutboundMessage shapes. - * - * Constructing these by hand requires assembling MessageRef, MessageHeaders, - * payload envelopes, signature status, and other mail-shaped fields that the - * transport normally produces after parsing wire bytes. These builders - * collapse that boilerplate behind two factories with sensible defaults. - * - * The builders use the parsed-shape MessageHeaders from - * @intx/types/runtime (where date is an ISO string), NOT the - * wire-shape MessageHeaders local to this package (where date is a Date - * object and headers are serialised to RFC 2822 bytes via assembleMessage). - * - * Consumers import the message types from @intx/types directly; the - * @intx/mime barrel does not re-export them. - */ - -import { type } from "arktype"; -import type { - InboundMessage, - MessageAttachment, - MessageHeaders, - MessageRef, - OutboundMessage, -} from "@intx/types/runtime"; -import { InterchangeType, SignatureStatus } from "@intx/types/runtime"; -import { generateMessageId } from "./mime"; - -/** - * Default schema version for structured payloads. Matches - * docs/MESSAGE.md § Payload Structure, which specifies "version": "1" as - * the current schema version for every Interchange payload type. Audit - * this default whenever the documented schema version increments. - */ -const DEFAULT_PAYLOAD_VERSION = "1"; - -const MESSAGE_ID_RE = /^<[^<>\s@]+@[^<>\s@]+>$/; -const ADDRESS_RE = /^[^@\s]+@[^@\s]+$/; - -const CONVERSATION_TYPE_PREFIX = "conversation."; - -// --------------------------------------------------------------------------- -// InboundMessage builder -// --------------------------------------------------------------------------- - -/** - * Structured payload envelope for an inbound message. `version` defaults to - * the current schema version per docs/MESSAGE.md. - */ -export type InboundPayloadInput = { - type: InterchangeType; - body: Record; - version?: string; -}; - -export type CreateInboundMessageOpts = { - from: string; - to: string | string[]; - - /** Plain-text body. Mutually exclusive with `payload`. */ - content?: string; - - /** Structured JSON envelope. Mutually exclusive with `content`. */ - payload?: InboundPayloadInput; - - cc?: string | string[]; - subject?: string; - - /** - * Defaults to `new Date().toISOString()`. Accepts Date or any string - * parseable by `new Date(...)`; stored as an ISO 8601 string. - */ - date?: Date | string; - - /** Defaults to `generateMessageId(from)`. Must be of the form ``. */ - messageId?: string; - - inReplyTo?: string; - references?: string[]; - listId?: string; - - /** - * Interchange-Type header value. Auto-derived from `payload.type` when a - * payload is supplied; throws if explicitly set to a value that conflicts - * with `payload.type`. - */ - interchangeType?: InterchangeType; - - correlationId?: string; - tenantId?: string; - agentId?: string; - sessionId?: string; - offeringId?: string; - schemaVersion?: string; - traceparent?: string; - tracestate?: string; - - attachments?: MessageAttachment[]; - - /** Merged with `{ uid: 1, mailbox: "INBOX" }`. */ - ref?: Partial; - - flags?: string[]; - - /** Defaults to `"missing"`. */ - signatureStatus?: SignatureStatus; -}; - -export function createInboundMessage( - opts: CreateInboundMessageOpts, -): InboundMessage { - const fn = "createInboundMessage"; - - requireAddress(opts.from, "from", fn); - const to = normalizeAndValidateAddressArray(opts.to, "to", fn); - - validateBodyExclusivity(opts.content, opts.payload, fn); - - if (opts.payload !== undefined) { - validateInterchangeType(opts.payload.type, "payload.type", fn); - if (isConversationType(opts.payload.type)) { - throw new Error( - `${fn}: conversation types must use \`content\` instead of \`payload\`; got \`payload.type\`: ${opts.payload.type}`, - ); - } - validatePayloadBody(opts.payload.body, "payload.body", fn); - if (opts.payload.version !== undefined) { - if ( - typeof opts.payload.version !== "string" || - opts.payload.version.length === 0 - ) { - throw new Error( - `${fn}: \`payload.version\`, when provided, must be a non-empty string`, - ); - } - } - } - - if (opts.interchangeType !== undefined) { - validateInterchangeType(opts.interchangeType, "interchangeType", fn); - if ( - opts.payload !== undefined && - opts.interchangeType !== opts.payload.type - ) { - throw new Error( - `${fn}: \`interchangeType\` (${opts.interchangeType}) conflicts with \`payload.type\` (${opts.payload.type})`, - ); - } - } - - if (opts.messageId !== undefined) { - validateMessageId(opts.messageId, "messageId", fn); - } - if (opts.inReplyTo !== undefined) { - validateMessageId(opts.inReplyTo, "inReplyTo", fn); - } - if (opts.references !== undefined) { - if (opts.references.length === 0) { - throw new Error( - `${fn}: \`references\`, when provided, must contain at least one entry`, - ); - } - opts.references.forEach((ref, i) => { - validateMessageId(ref, `references[${i}]`, fn); - }); - } - - const cc = - opts.cc === undefined - ? undefined - : normalizeAndValidateAddressArray(opts.cc, "cc", fn); - - rejectEmptyStringIfPresent(opts.content, "content", fn); - rejectEmptyStringIfPresent(opts.subject, "subject", fn); - rejectEmptyStringIfPresent(opts.listId, "listId", fn); - rejectEmptyStringIfPresent(opts.correlationId, "correlationId", fn); - rejectEmptyStringIfPresent(opts.tenantId, "tenantId", fn); - rejectEmptyStringIfPresent(opts.agentId, "agentId", fn); - rejectEmptyStringIfPresent(opts.sessionId, "sessionId", fn); - rejectEmptyStringIfPresent(opts.offeringId, "offeringId", fn); - rejectEmptyStringIfPresent(opts.schemaVersion, "schemaVersion", fn); - rejectEmptyStringIfPresent(opts.traceparent, "traceparent", fn); - rejectEmptyStringIfPresent(opts.tracestate, "tracestate", fn); - - if (opts.flags !== undefined) { - opts.flags.forEach((flag, i) => { - if (typeof flag !== "string" || flag.length === 0) { - throw new Error(`${fn}: \`flags[${i}]\` must be a non-empty string`); - } - }); - } - - const signatureStatus = opts.signatureStatus ?? "missing"; - const validatedStatus = SignatureStatus(signatureStatus); - if (validatedStatus instanceof type.errors) { - throw new Error( - `${fn}: \`signatureStatus\` is not a recognised SignatureStatus: ${validatedStatus.summary}`, - ); - } - - const date = normalizeDate(opts.date, "date", fn); - const messageId = opts.messageId ?? generateMessageId(opts.from); - const derivedInterchangeType = opts.interchangeType ?? opts.payload?.type; - - const headers: MessageHeaders = { from: opts.from, to, date, messageId }; - if (cc !== undefined) headers.cc = cc; - if (opts.subject !== undefined) headers.subject = opts.subject; - if (opts.inReplyTo !== undefined) headers.inReplyTo = opts.inReplyTo; - if (opts.references !== undefined) headers.references = opts.references; - if (opts.listId !== undefined) headers.listId = opts.listId; - if (derivedInterchangeType !== undefined) { - headers.interchangeType = derivedInterchangeType; - } - if (opts.correlationId !== undefined) { - headers.interchangeCorrelationId = opts.correlationId; - } - if (opts.tenantId !== undefined) headers.interchangeTenantId = opts.tenantId; - if (opts.agentId !== undefined) headers.interchangeAgentId = opts.agentId; - if (opts.sessionId !== undefined) { - headers.interchangeSessionId = opts.sessionId; - } - if (opts.offeringId !== undefined) { - headers.interchangeOfferingId = opts.offeringId; - } - if (opts.schemaVersion !== undefined) { - headers.interchangeSchemaVersion = opts.schemaVersion; - } - if (opts.traceparent !== undefined) headers.traceparent = opts.traceparent; - if (opts.tracestate !== undefined) headers.tracestate = opts.tracestate; - - if (opts.ref?.uid !== undefined) { - if ( - typeof opts.ref.uid !== "number" || - !Number.isInteger(opts.ref.uid) || - !Number.isFinite(opts.ref.uid) || - opts.ref.uid < 1 - ) { - throw new Error( - `${fn}: \`ref.uid\`, when provided, must be a positive integer (IMAP UID)`, - ); - } - } - const ref: MessageRef = { - uid: opts.ref?.uid ?? 1, - mailbox: opts.ref?.mailbox ?? "INBOX", - }; - if (typeof ref.mailbox !== "string" || ref.mailbox.length === 0) { - throw new Error( - `${fn}: \`ref.mailbox\`, when provided, must be a non-empty string`, - ); - } - - const result: InboundMessage = { - ref, - headers, - flags: opts.flags ?? [], - signatureStatus, - }; - if (opts.content !== undefined) result.content = opts.content; - if (opts.payload !== undefined) { - result.payload = { - type: opts.payload.type, - version: opts.payload.version ?? DEFAULT_PAYLOAD_VERSION, - body: opts.payload.body, - }; - } - if (opts.attachments !== undefined && opts.attachments.length > 0) { - result.attachments = opts.attachments; - } - - return result; -} - -// --------------------------------------------------------------------------- -// OutboundMessage builder -// --------------------------------------------------------------------------- - -export type CreateOutboundMessageOpts = { - to: string | string[]; - - /** Interchange payload type. Determines content vs payload semantics. */ - type: InterchangeType; - - /** Plain-text body. Mutually exclusive with `payload`. */ - content?: string; - - /** Structured JSON envelope body. Mutually exclusive with `content`. */ - payload?: Record; - - cc?: string | string[]; - subject?: string; - - /** Human-readable summary used as the text/plain part for structured types. */ - summary?: string; - - inReplyTo?: string; - references?: string[]; - correlationId?: string; - sessionId?: string; - tenantId?: string; - - attachments?: MessageAttachment[]; -}; - -export function createOutboundMessage( - opts: CreateOutboundMessageOpts, -): OutboundMessage { - const fn = "createOutboundMessage"; - - validateInterchangeType(opts.type, "type", fn); - // Validate addresses without mutating the source shape; the OutboundMessage - // type preserves `string | string[]` and downstream consumers handle both. - normalizeAndValidateAddressArray(opts.to, "to", fn); - if (opts.cc !== undefined) { - normalizeAndValidateAddressArray(opts.cc, "cc", fn); - } - - validateBodyExclusivity(opts.content, opts.payload, fn); - - if (isConversationType(opts.type)) { - if (opts.payload !== undefined) { - throw new Error( - `${fn}: conversation \`type\` ${opts.type} must use \`content\` instead of \`payload\``, - ); - } - if (opts.content === undefined) { - throw new Error( - `${fn}: conversation \`type\` ${opts.type} requires \`content\``, - ); - } - } else { - if (opts.content !== undefined) { - throw new Error( - `${fn}: non-conversation \`type\` ${opts.type} must use \`payload\` instead of \`content\``, - ); - } - if (opts.payload === undefined) { - throw new Error( - `${fn}: non-conversation \`type\` ${opts.type} requires \`payload\``, - ); - } - } - if (opts.payload !== undefined) { - validatePayloadBody(opts.payload, "payload", fn); - } - - if (opts.inReplyTo !== undefined) { - validateMessageId(opts.inReplyTo, "inReplyTo", fn); - } - if (opts.references !== undefined) { - if (opts.references.length === 0) { - throw new Error( - `${fn}: \`references\`, when provided, must contain at least one entry`, - ); - } - opts.references.forEach((ref, i) => { - validateMessageId(ref, `references[${i}]`, fn); - }); - } - rejectEmptyStringIfPresent(opts.content, "content", fn); - rejectEmptyStringIfPresent(opts.subject, "subject", fn); - rejectEmptyStringIfPresent(opts.summary, "summary", fn); - rejectEmptyStringIfPresent(opts.correlationId, "correlationId", fn); - rejectEmptyStringIfPresent(opts.sessionId, "sessionId", fn); - rejectEmptyStringIfPresent(opts.tenantId, "tenantId", fn); - - const result: OutboundMessage = { to: opts.to, type: opts.type }; - if (opts.cc !== undefined) result.cc = opts.cc; - if (opts.subject !== undefined) result.subject = opts.subject; - if (opts.content !== undefined) result.content = opts.content; - if (opts.payload !== undefined) result.payload = opts.payload; - if (opts.summary !== undefined) result.summary = opts.summary; - if (opts.attachments !== undefined && opts.attachments.length > 0) { - result.attachments = opts.attachments; - } - if (opts.inReplyTo !== undefined) result.inReplyTo = opts.inReplyTo; - if (opts.references !== undefined) result.references = opts.references; - if (opts.correlationId !== undefined) { - result.correlationId = opts.correlationId; - } - if (opts.sessionId !== undefined) result.sessionId = opts.sessionId; - if (opts.tenantId !== undefined) result.tenantId = opts.tenantId; - return result; -} - -// --------------------------------------------------------------------------- -// Validation helpers -// --------------------------------------------------------------------------- - -function rejectEmptyStringIfPresent( - value: string | undefined, - field: string, - fn: string, -): void { - if (value !== undefined && value.length === 0) { - throw new Error( - `${fn}: \`${field}\`, when provided, must be a non-empty string`, - ); - } -} - -function requireAddress(value: unknown, field: string, fn: string): void { - if (typeof value !== "string" || value.length === 0) { - throw new Error(`${fn}: \`${field}\` must be a non-empty string`); - } - if (!ADDRESS_RE.test(value)) { - throw new Error( - `${fn}: \`${field}\` must be an RFC 5322 address of the form \`local@domain\`; got: ${value}`, - ); - } -} - -function normalizeAndValidateAddressArray( - input: string | string[], - field: string, - fn: string, -): string[] { - if (typeof input === "string") { - requireAddress(input, field, fn); - return [input]; - } - if (!Array.isArray(input) || input.length === 0) { - throw new Error( - `${fn}: \`${field}\` must contain at least one recipient address`, - ); - } - input.forEach((entry, i) => { - requireAddress(entry, `${field}[${i}]`, fn); - }); - return input; -} - -function validatePayloadBody(value: unknown, field: string, fn: string): void { - if (typeof value !== "object" || value === null || Array.isArray(value)) { - throw new Error( - `${fn}: \`${field}\` must be a plain object (got ${ - value === null ? "null" : Array.isArray(value) ? "array" : typeof value - })`, - ); - } -} - -function isConversationType(t: InterchangeType): boolean { - return t.startsWith(CONVERSATION_TYPE_PREFIX); -} - -function validateInterchangeType( - value: unknown, - field: string, - fn: string, -): void { - const validated = InterchangeType(value); - if (validated instanceof type.errors) { - throw new Error( - `${fn}: \`${field}\` is not a valid InterchangeType: ${validated.summary}`, - ); - } -} - -function validateMessageId(value: string, field: string, fn: string): void { - if (!MESSAGE_ID_RE.test(value)) { - throw new Error( - `${fn}: \`${field}\` must be an RFC 2822 message identifier of the form \`\`; got: ${value}`, - ); - } -} - -/** - * Non-throwing predicate for the RFC 2822 message-identifier form ``. - * A caller forwarding a `messageId`/`inReplyTo`/`references` value into - * `createInboundMessage` (which rejects a malformed identifier) uses this to - * decide whether the value is safe to forward: inbound mail can carry a - * headerless-derived (sha256) or otherwise malformed Message-Id that is a - * valid claim-check key but not a valid RFC identifier. - */ -export function isMessageId(value: string): boolean { - return MESSAGE_ID_RE.test(value); -} - -function normalizeDate( - input: Date | string | undefined, - field: string, - fn: string, -): string { - if (input === undefined) return new Date().toISOString(); - if (input instanceof Date) { - if (Number.isNaN(input.getTime())) { - throw new Error(`${fn}: \`${field}\` is an Invalid Date`); - } - return input.toISOString(); - } - if (typeof input !== "string" || input.length === 0) { - throw new Error( - `${fn}: \`${field}\`, when provided, must be a Date or a non-empty string`, - ); - } - const parsed = new Date(input); - if (Number.isNaN(parsed.getTime())) { - throw new Error( - `${fn}: \`${field}\` is not a parseable date string: ${input}`, - ); - } - return parsed.toISOString(); -} - -function validateBodyExclusivity( - content: unknown, - payload: unknown, - fn: string, -): void { - if (content !== undefined && payload !== undefined) { - throw new Error( - `${fn}: \`content\` and \`payload\` are mutually exclusive; provide at most one`, - ); - } -} diff --git a/vendor/intx-mime/src/mime.ts b/vendor/intx-mime/src/mime.ts deleted file mode 100644 index adb9021..0000000 --- a/vendor/intx-mime/src/mime.ts +++ /dev/null @@ -1,1334 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion -- MIME parser uses bounded array access throughout */ -/** - * MIME byte construction and parsing for Interchange messages. - * - * Implements exactly two message shapes per MESSAGE.md: - * 1. Conversation: multipart/mixed (text/plain plus zero or more - * attachment parts) in multipart/signed - * 2. Structured: application/vnd.interchange+json in multipart/mixed in multipart/signed - * - * Produces real RFC 2822 / RFC 2046 / RFC 3156 bytes. The signed content - * part is produced in MIME canonical form (CRLF line endings) so PGP/MIME - * verification operates on the same bytes regardless of platform. - * - * RFC references verified: - * - RFC 2822 §2.1.1: lines MUST NOT exceed 998 chars; recommended 78 - * - RFC 2046 §5.1.1: boundary MUST be <= 70 chars; CRLF before each boundary - * - RFC 3156 §5: multipart/signed; protocol="application/pgp-signature"; - * micalg=pgp-sha512; first part = signed content; second part = signature - * - Message-IDs: — valid per RFC 2822 §3.6.4 (dot-atom local-part) - */ - -import { type } from "arktype"; -import { base64Decode, base64Encode } from "@intx/types"; -import type { - MessageAttachment, - MessageHeaders as ParsedMessageHeaders, - MessagePart, -} from "@intx/types/runtime"; -import { InterchangeType } from "@intx/types/runtime"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -export type MessageHeaders = { - from: string; - to: string[]; - cc: string[] | undefined; - date: Date; - messageId: string; - subject: string | undefined; - inReplyTo: string | undefined; - references: string[] | undefined; - mimeVersion: "1.0"; - interchangeType: string | undefined; - interchangeCorrelationId: string | undefined; - interchangeTenantId: string | undefined; - interchangeAgentId: string | undefined; - interchangeSessionId: string | undefined; - interchangeOfferingId: string | undefined; - interchangeSchemaVersion: string | undefined; - traceparent: string | undefined; - tracestate: string | undefined; -}; - -export type ConversationContent = { - kind: "conversation"; - text: string; - attachments?: MessageAttachment[]; -}; - -export type StructuredContent = { - kind: "structured"; - json: Record; - summary?: string; -}; - -export type MimeAssemblyInput = { - headers: MessageHeaders; - content: ConversationContent | StructuredContent; -}; - -export type ParsedMimePart = { - contentType: string; - headers: Map; - body: Uint8Array; -}; - -export type ParsedMimeMessage = { - headers: Map; - parts: ParsedMimePart[]; -}; - -// --------------------------------------------------------------------------- -// JMAP Email types (RFC 8621) -// --------------------------------------------------------------------------- - -export type JMAPAddress = { - name: string | null; - email: string; -}; - -export type JMAPBodyValue = { - value: string; - isEncodingProblem: boolean; -}; - -export type JMAPBodyPart = { - partId: string; - type: string; -}; - -export type JMAPAttachment = { - blobId: string; - name: string | null; - type: string; - size: number; -}; - -export type JMAPEmail = { - from: JMAPAddress[]; - to: JMAPAddress[]; - subject: string | null; - sentAt: string | null; - bodyValues: Record; - textBody: JMAPBodyPart[]; - htmlBody: JMAPBodyPart[]; - attachments: JMAPAttachment[]; - headers: Record; -}; - -// --------------------------------------------------------------------------- -// Message-ID generation -// --------------------------------------------------------------------------- - -export function generateMessageId(address: string): string { - const domain = address.includes("@") ? address.split("@")[1]! : "local"; - const uuid = crypto.randomUUID(); - return `<${uuid}@${domain}>`; -} - -// --------------------------------------------------------------------------- -// Address normalization -// --------------------------------------------------------------------------- - -/** - * Extract the bare addr-spec (local-part@domain) from a single RFC 5322 - * address value. Strips any display name and surrounding angle brackets, - * then lowercases the result so case-insensitive comparison falls out - * naturally. - * - * Accepted inputs (single-address only — do not pass comma-separated lists): - * `"Display Name" ` → `user@host` - * `Display Name ` → `user@host` - * `` → `user@host` - * `user@host` → `user@host` - * ` User@Host ` → `user@host` - * - * Rejected (throws) inputs: - * - empty or whitespace-only - * - input with no `@` - * - input that produces an empty local-part or domain - * - quoted local-parts (e.g. `"a@b"@host`) — technically valid per RFC - * 5321 §4.1.2 but rare in practice; the simple split below would - * misinterpret the inner `@`, so we refuse rather than guess - * - content after the closing `>` in an angle-bracketed form - * (e.g. `Name (comment)`) — would silently fall through to a - * misparsed bare-form attempt, so we refuse instead - * - * Per RFC 5321 §2.4 the local-part is technically case-sensitive, but no - * production system honors that; matching case-insensitively is the - * correct call for routing and identity checks. - */ -export function extractAddrSpec(addressLine: string): string { - const trimmed = addressLine.trim(); - if (trimmed === "") { - throw new Error("extractAddrSpec: address is empty"); - } - - let candidate: string; - const angleOpen = trimmed.lastIndexOf("<"); - if (angleOpen !== -1) { - // Angle-bracketed form. Require the `>` to be the trailing - // non-whitespace character so that input like `Name (comment)` - // is refused rather than re-parsed as a bare addr-spec. - if (!trimmed.endsWith(">")) { - throw new Error( - `extractAddrSpec: trailing content after '>' in ${JSON.stringify(addressLine)}`, - ); - } - candidate = trimmed.slice(angleOpen + 1, -1).trim(); - } else { - candidate = trimmed; - } - - // Reject quoted local-parts: the parser below splits on the first `@`, - // which would corrupt a quoted form whose local-part contains `@`. - if (candidate.includes('"')) { - throw new Error( - `extractAddrSpec: quoted local-parts are not supported: ${JSON.stringify(addressLine)}`, - ); - } - - const atIndex = candidate.indexOf("@"); - if (atIndex === -1) { - throw new Error( - `extractAddrSpec: address has no '@': ${JSON.stringify(addressLine)}`, - ); - } - - // Reject any further `@` in the candidate — a well-formed addr-spec - // has exactly one. Multiple `@` is either a quoted form (rejected - // above) or simply malformed. - if (candidate.indexOf("@", atIndex + 1) !== -1) { - throw new Error( - `extractAddrSpec: multiple '@' in ${JSON.stringify(addressLine)}`, - ); - } - - const local = candidate.slice(0, atIndex); - const domain = candidate.slice(atIndex + 1); - if (local === "" || domain === "") { - throw new Error( - `extractAddrSpec: empty local-part or domain in ${JSON.stringify(addressLine)}`, - ); - } - - return `${local.toLowerCase()}@${domain.toLowerCase()}`; -} - -// --------------------------------------------------------------------------- -// RFC 2822 date formatting -// --------------------------------------------------------------------------- - -const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const; -const MONTHS = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", -] as const; - -export function formatRFC2822Date(date: Date): string { - const day = DAYS[date.getUTCDay()]!; - const d = String(date.getUTCDate()).padStart(2, "0"); - const mon = MONTHS[date.getUTCMonth()]!; - const year = date.getUTCFullYear(); - const h = String(date.getUTCHours()).padStart(2, "0"); - const m = String(date.getUTCMinutes()).padStart(2, "0"); - const s = String(date.getUTCSeconds()).padStart(2, "0"); - return `${day}, ${d} ${mon} ${year} ${h}:${m}:${s} +0000`; -} - -// --------------------------------------------------------------------------- -// Boundary generation -// --------------------------------------------------------------------------- - -function generateBoundary(): string { - const bytes = new Uint8Array(18); - crypto.getRandomValues(bytes); - return ( - "----=_Part_" + - Array.from(bytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - ); -} - -// --------------------------------------------------------------------------- -// Header serialization (RFC 2822) -// --------------------------------------------------------------------------- - -const CRLF = "\r\n"; - -function hdr(name: string, value: string): string { - return `${name}: ${value}${CRLF}`; -} - -function serializeMessageHeaders( - h: MessageHeaders, - contentType: string, -): string { - let out = ""; - out += hdr("From", h.from); - out += hdr("To", Array.isArray(h.to) ? h.to.join(", ") : (h.to as string)); - if (h.cc && h.cc.length > 0) { - out += hdr("Cc", h.cc.join(", ")); - } - out += hdr("Date", formatRFC2822Date(h.date)); - out += hdr("Message-ID", h.messageId); - if (h.subject !== undefined) { - out += hdr("Subject", h.subject); - } - if (h.inReplyTo !== undefined) { - out += hdr("In-Reply-To", h.inReplyTo); - } - if (h.references !== undefined && h.references.length > 0) { - out += hdr("References", h.references.join(" ")); - } - out += hdr("MIME-Version", "1.0"); - out += hdr("Content-Type", contentType); - - // Interchange headers - if (h.interchangeType !== undefined) { - out += hdr("Interchange-Type", h.interchangeType); - } - if (h.interchangeCorrelationId !== undefined) { - out += hdr("Interchange-Correlation-ID", h.interchangeCorrelationId); - } - if (h.interchangeTenantId !== undefined) { - out += hdr("Interchange-Tenant-ID", h.interchangeTenantId); - } - if (h.interchangeAgentId !== undefined) { - out += hdr("Interchange-Agent-ID", h.interchangeAgentId); - } - if (h.interchangeSessionId !== undefined) { - out += hdr("Interchange-Session-ID", h.interchangeSessionId); - } - if (h.interchangeOfferingId !== undefined) { - out += hdr("Interchange-Offering-ID", h.interchangeOfferingId); - } - if (h.interchangeSchemaVersion !== undefined) { - out += hdr("Interchange-Schema-Version", h.interchangeSchemaVersion); - } - if (h.traceparent !== undefined) { - out += hdr("traceparent", h.traceparent); - } - if (h.tracestate !== undefined) { - out += hdr("tracestate", h.tracestate); - } - - return out; -} - -// --------------------------------------------------------------------------- -// MIME part assembly -// --------------------------------------------------------------------------- - -/** - * Reject values that would break out of a MIME header. CR/LF in a header - * value is a header-injection vector; a double quote breaks the quoted - * `filename="..."` / `name="..."` forms the parser relies on. The MIME - * layer owns header well-formedness, so it fails loudly here rather than - * emitting a corrupt envelope. - */ -function assertHeaderSafe(value: string, field: string): void { - if (/[\r\n]/.test(value)) { - throw new Error( - `${field} must not contain CR or LF: ${JSON.stringify(value)}`, - ); - } - if (value.includes('"')) { - throw new Error( - `${field} must not contain a double quote: ${JSON.stringify(value)}`, - ); - } -} - -/** - * Encode bytes as base64, wrapped at 76 columns per RFC 2045. Returns the - * empty string for empty input. - */ -function base64Lines(bytes: Uint8Array): string { - const b64 = base64Encode(bytes); - const lines: string[] = []; - for (let i = 0; i < b64.length; i += 76) { - lines.push(b64.slice(i, i + 76)); - } - return lines.join(CRLF); -} - -/** - * Assemble the signed content for a conversation message. - * - * The shape is always multipart/mixed: one text/plain part (BODY[1.1]) - * followed by zero or more binary attachment parts (BODY[1.2..N]). The - * shape is unconditional — there is no bare text/plain branch — so the - * writer, the parser, and the signed-bytes contract have one form each. - * - * This is the exact bytes that will be hashed for the PGP/MIME signature. - */ -function assembleConversationSignedPart( - text: string, - attachments: readonly MessageAttachment[] = [], -): Uint8Array { - const boundary = generateBoundary(); - - // Canonicalize the text part: CRLF line endings, strip trailing - // whitespace per line. - const lines = text.split(/\r\n|\r|\n/); - const canonLines = lines.map((l) => l.replace(/[ \t]+$/, "")); - const canonical = canonLines.join(CRLF); - - let body = `Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}`; - - // Text part (BODY[1.1]) - body += `--${boundary}${CRLF}`; - body += `Content-Type: text/plain; charset=utf-8${CRLF}`; - body += `Content-Transfer-Encoding: 7bit${CRLF}`; - body += `${CRLF}`; - body += `${canonical}${CRLF}`; - - // Attachment parts (BODY[1.2..N]) - for (const att of attachments) { - assertHeaderSafe(att.contentType, "attachment contentType"); - assertHeaderSafe(att.name, "attachment name"); - body += `--${boundary}${CRLF}`; - body += `Content-Type: ${att.contentType}${CRLF}`; - body += `Content-Transfer-Encoding: base64${CRLF}`; - body += `Content-Disposition: attachment; filename="${att.name}"${CRLF}`; - body += `${CRLF}`; - body += `${base64Lines(att.data)}${CRLF}`; - } - - body += `--${boundary}--${CRLF}`; - return new TextEncoder().encode(body); -} - -/** - * Assemble the signed content for a structured message (multipart/mixed). - * - * This is the exact bytes that will be hashed for the PGP/MIME signature. - */ -function assembleStructuredSignedPart( - json: Record, - summary?: string, -): Uint8Array { - const boundary = generateBoundary(); - const jsonStr = JSON.stringify(json); - - let body = `Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}`; - - // JSON payload part - body += `--${boundary}${CRLF}`; - body += `Content-Type: application/vnd.interchange+json; charset=utf-8${CRLF}`; - body += `Content-Transfer-Encoding: 7bit${CRLF}`; - body += `${CRLF}`; - body += `${jsonStr}${CRLF}`; - - // Optional human-readable summary - if (summary !== undefined) { - body += `--${boundary}${CRLF}`; - body += `Content-Type: text/plain; charset=utf-8${CRLF}`; - body += `Content-Transfer-Encoding: 7bit${CRLF}`; - body += `${CRLF}`; - const lines = summary.split(/\r\n|\r|\n/); - const canonLines = lines.map((l) => l.replace(/[ \t]+$/, "")); - body += `${canonLines.join(CRLF)}${CRLF}`; - } - - body += `--${boundary}--${CRLF}`; - return new TextEncoder().encode(body); -} - -/** - * Wrap content part and PGP signature into multipart/signed per RFC 3156. - * - * RFC 3156 §5: The multipart/signed body MUST consist of exactly two parts. - * The first part contains the signed data. The second part contains the - * detached PGP signature in application/pgp-signature. - * - * The boundary delimiter lines use CRLF as required by RFC 2046. - */ -function wrapInMultipartSigned( - signedContentBytes: Uint8Array, - signatureBytes: Uint8Array, - boundary: string, -): Uint8Array { - const signedContent = new TextDecoder().decode(signedContentBytes); - const signature = new TextDecoder().decode(signatureBytes); - - const enc = new TextEncoder(); - - // Per RFC 2046: boundary delimiter = "--" + boundary parameter. - // The CRLF preceding the boundary belongs to the boundary, not the part. - // Each part is preceded by: CRLF + "--" + boundary + CRLF - // The closing delimiter: CRLF + "--" + boundary + "--" + CRLF - const body = - `--${boundary}${CRLF}` + - `${signedContent}` + - `${CRLF}--${boundary}${CRLF}` + - `Content-Type: application/pgp-signature${CRLF}` + - `${CRLF}` + - `${signature}${CRLF}` + - `--${boundary}--${CRLF}`; - - return enc.encode(body); -} - -// --------------------------------------------------------------------------- -// Full message assembly -// --------------------------------------------------------------------------- - -/** - * Assemble a complete RFC 2822 message from headers, content, and signature - * bytes. Returns the raw message bytes for storage. - * - * The signature bytes must be produced by signing the signed content part - * bytes (the result of assembleSignedContentPart below). - */ -export function assembleMessage( - headers: MessageHeaders, - signedContentBytes: Uint8Array, - signatureBytes: Uint8Array, -): Uint8Array { - const outerBoundary = generateBoundary(); - - const contentType = - `multipart/signed; protocol="application/pgp-signature"; ` + - `micalg=pgp-sha512; boundary="${outerBoundary}"`; - - const headerSection = serializeMessageHeaders(headers, contentType); - const bodyBytes = wrapInMultipartSigned( - signedContentBytes, - signatureBytes, - outerBoundary, - ); - - const enc = new TextEncoder(); - const headerBytes = enc.encode(headerSection + CRLF); - - const result = new Uint8Array(headerBytes.length + bodyBytes.length); - result.set(headerBytes, 0); - result.set(bodyBytes, headerBytes.length); - return result; -} - -/** - * Build the signed content bytes for a message. These exact bytes are - * what the CryptoProvider signs. The transport calls this, then signs, - * then calls assembleMessage with both. - */ -export function assembleSignedContent( - content: ConversationContent | StructuredContent, -): Uint8Array { - if (content.kind === "conversation") { - return assembleConversationSignedPart(content.text, content.attachments); - } - return assembleStructuredSignedPart(content.json, content.summary); -} - -// --------------------------------------------------------------------------- -// MIME parsing (for fetchHeaders, fetchStructure, fetchPart, fetchFull) -// --------------------------------------------------------------------------- - -const CRLF_CRLF = new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]); -const LF_LF = new Uint8Array([0x0a, 0x0a]); - -function findByteSequence(haystack: Uint8Array, needle: Uint8Array): number { - if (needle.length === 0) return 0; - const limit = haystack.length - needle.length; - outer: for (let i = 0; i <= limit; i++) { - for (let j = 0; j < needle.length; j++) { - if (haystack[i + j] !== needle[j]) continue outer; - } - return i; - } - return -1; -} - -/** - * Parse the header section of a raw RFC 2822 message. - * Returns a map of lowercase header names to their values, and the - * byte offset where the body starts. - */ -export function parseHeaderSection(raw: Uint8Array): { - headers: Map; - bodyOffset: number; - headerEnd: number; -} { - const headers = new Map(); - - // Search for the blank line separator in byte space so the returned - // offset is valid for Uint8Array.slice() even when headers contain - // multi-byte UTF-8 characters. - const crlfIdx = findByteSequence(raw, CRLF_CRLF); - const lfIdx = findByteSequence(raw, LF_LF); - - let bodyOffset = raw.length; - let headerEnd = raw.length; - - if (crlfIdx !== -1 && (lfIdx === -1 || crlfIdx <= lfIdx)) { - headerEnd = crlfIdx; - bodyOffset = crlfIdx + 4; - } else if (lfIdx !== -1) { - headerEnd = lfIdx; - bodyOffset = lfIdx + 2; - } - - const headerText = new TextDecoder("utf-8", { fatal: false }).decode( - raw.subarray(0, headerEnd), - ); - parseHeaders(headerText, headers); - - return { headers, bodyOffset, headerEnd }; -} - -function parseHeaders(headerSection: string, out: Map): void { - // Unfold continuation lines (lines starting with whitespace per RFC 2822). - const unfolded = headerSection - .replace(/\r\n[ \t]+/g, " ") - .replace(/\n[ \t]+/g, " "); - const lines = unfolded.split(/\r\n|\n/); - for (const line of lines) { - if (line.trim() === "") continue; - const colon = line.indexOf(":"); - if (colon === -1) continue; - const name = line.slice(0, colon).trim().toLowerCase(); - const value = line.slice(colon + 1).trim(); - // For repeated headers (like Received), keep the first value. - if (!out.has(name)) { - out.set(name, value); - } - } -} - -/** - * Extract the boundary parameter from a Content-Type header value. - */ -export function extractBoundary(contentTypeValue: string): string | undefined { - const match = - contentTypeValue.match(/boundary="([^"]+)"/i) ?? - contentTypeValue.match(/boundary=([^\s;]+)/i); - return match?.[1]; -} - -/** - * Parse a multipart body into individual parts. - * - * Each part is returned as raw bytes (headers + blank line + body) for - * further parsing. - */ -export function parseMultipart( - body: Uint8Array, - boundary: string, -): Uint8Array[] { - const text = new TextDecoder("utf-8", { fatal: false }).decode(body); - const delimiter = `--${boundary}`; - const parts: Uint8Array[] = []; - const enc = new TextEncoder(); - - let pos = 0; - while (pos < text.length) { - // Find next delimiter. - const delimIdx = text.indexOf(delimiter, pos); - if (delimIdx === -1) break; - - // Check if it's the closing delimiter. - const afterDelim = delimIdx + delimiter.length; - if (text.slice(afterDelim, afterDelim + 2) === "--") break; - - // Skip past the delimiter line (to end of CRLF or LF). - let partStart = afterDelim; - if (text[partStart] === "\r") partStart++; - if (text[partStart] === "\n") partStart++; - - // Find the next delimiter to know where this part ends. - const nextDelimIdx = text.indexOf("\n" + delimiter, partStart); - if (nextDelimIdx === -1) break; - - // Part body excludes the trailing CRLF before the next boundary. - let partEnd = nextDelimIdx; - // Account for the \n we searched for. - // We want to include only up to (but not including) the CRLF before "--boundary". - // nextDelimIdx points to the \n before the delimiter. The part ends before - // the preceding \r\n (or just \n). - if (partEnd > partStart && text[partEnd - 1] === "\r") { - partEnd--; - } - - const partText = text.slice(partStart, partEnd); - parts.push(enc.encode(partText)); - - pos = nextDelimIdx + 1; - } - - return parts; -} - -/** - * Parse a single MIME part into its headers and body. - */ -export function parseMimePart(partBytes: Uint8Array): ParsedMimePart { - const { headers, bodyOffset } = parseHeaderSection(partBytes); - const contentType = headers.get("content-type") ?? "application/octet-stream"; - const body = partBytes.slice(bodyOffset); - return { contentType, headers, body }; -} - -/** - * Extract a MIME part by dot-separated path from a multipart/signed message. - * - * Path "1" returns the signed content part (text/plain or multipart/mixed). - * Path "1.1" returns the first sub-part of the signed content (JSON payload). - * Path "2" returns the application/pgp-signature part. - * - * This follows IMAP FETCH section specifier semantics (RFC 9051). - */ -export function extractPartByPath( - raw: Uint8Array, - partPath: string, -): Uint8Array { - const { headers, bodyOffset } = parseHeaderSection(raw); - const body = raw.slice(bodyOffset); - const contentType = headers.get("content-type") ?? ""; - - const steps = partPath.split(".").map((s) => { - const n = parseInt(s, 10); - if (isNaN(n) || n < 1) { - throw new Error(`Invalid part path segment: "${s}"`); - } - return n; - }); - - return walkParts(body, contentType, steps, 0); -} - -function walkParts( - body: Uint8Array, - contentType: string, - steps: number[], - depth: number, -): Uint8Array { - const step = steps[depth]; - if (step === undefined) { - throw new Error("Part path has no more segments"); - } - - if (!contentType.toLowerCase().startsWith("multipart/")) { - throw new Error( - `Cannot index into non-multipart content type: ${contentType}`, - ); - } - - const boundary = extractBoundary(contentType); - if (boundary === undefined) { - throw new Error(`No boundary found in Content-Type: ${contentType}`); - } - - const parts = parseMultipart(body, boundary); - if (step > parts.length) { - throw new Error(`Part ${step} does not exist (only ${parts.length} parts)`); - } - - const partBytes = parts[step - 1]!; - - if (depth + 1 === steps.length) { - return partBytes; - } - - // Need to descend further. - const part = parseMimePart(partBytes); - return walkParts(part.body, part.contentType, steps, depth + 1); -} - -// --------------------------------------------------------------------------- -// JMAP Email parsing -// --------------------------------------------------------------------------- - -/** - * Parse a RFC 2822 address value into structured JMAP address objects. - * - * Handles both "Display Name" and bare email@example.com - * forms, as well as comma-separated address lists. - */ -function parseAddressList(value: string): JMAPAddress[] { - const results: JMAPAddress[] = []; - // Split on commas that are not inside quoted strings or angle brackets. - // We handle the two common forms: - // 1. "Display Name" - // 2. Display Name - // 3. - // 4. email - const segments = splitAddressList(value); - for (const segment of segments) { - const addr = parseOneAddress(segment.trim()); - if (addr !== null) { - results.push(addr); - } - } - return results; -} - -function splitAddressList(value: string): string[] { - const segments: string[] = []; - let current = ""; - let depth = 0; - let inQuote = false; - - for (const ch of value) { - if (ch === '"' && !inQuote) { - inQuote = true; - current += ch; - } else if (ch === '"' && inQuote) { - inQuote = false; - current += ch; - } else if (ch === "<" && !inQuote) { - depth++; - current += ch; - } else if (ch === ">" && !inQuote) { - depth--; - current += ch; - } else if (ch === "," && depth === 0 && !inQuote) { - segments.push(current); - current = ""; - } else { - current += ch; - } - } - if (current.trim() !== "") { - segments.push(current); - } - return segments; -} - -function parseOneAddress(segment: string): JMAPAddress | null { - if (segment === "") return null; - - // "Display Name" or Display Name - const angleMatch = segment.match(/^(.*?)<([^>]+)>\s*$/); - if (angleMatch !== null) { - const rawName = angleMatch[1]!.trim(); - const email = angleMatch[2]!.trim(); - // Strip surrounding quotes from display name if present - const name = - rawName === "" ? null : rawName.replace(/^"(.*)"$/, "$1").trim() || null; - return { name, email }; - } - - // Bare email address - const bare = segment.trim(); - if (bare !== "") { - return { name: null, email: bare }; - } - - return null; -} - -/** - * Parse the MIME Date header into an ISO 8601 string. - * - * Returns null if the header is missing or the value cannot be parsed. - */ -function parseDateHeader(value: string | undefined): string | null { - if (value === undefined) return null; - const date = new Date(value); - if (isNaN(date.getTime())) return null; - return date.toISOString(); -} - -/** - * Decode a MIME body part, handling Content-Transfer-Encoding. - */ -function decodeBodyBytes( - body: Uint8Array, - headers: Map, -): { value: string; isEncodingProblem: boolean } { - const cte = (headers.get("content-transfer-encoding") ?? "7bit") - .trim() - .toLowerCase(); - - if (cte === "base64") { - try { - const raw = new TextDecoder("utf-8", { fatal: false }).decode(body); - const cleaned = raw.replace(/\s+/g, ""); - const binaryStr = atob(cleaned); - return { value: binaryStr, isEncodingProblem: false }; - } catch { - return { - value: new TextDecoder("utf-8", { fatal: false }).decode(body), - isEncodingProblem: true, - }; - } - } - - if (cte === "quoted-printable") { - const raw = new TextDecoder("utf-8", { fatal: false }).decode(body); - return { value: decodeQuotedPrintable(raw), isEncodingProblem: false }; - } - - // 7bit, 8bit, binary — decode as UTF-8 - return { - value: new TextDecoder("utf-8", { fatal: false }).decode(body), - isEncodingProblem: false, - }; -} - -function decodeQuotedPrintable(text: string): string { - return text - .replace(/=\r\n/g, "") - .replace(/=\n/g, "") - .replace(/=([0-9A-Fa-f]{2})/g, (_match, hex: string) => - String.fromCharCode(parseInt(hex, 16)), - ); -} - -/** - * Determine whether a MIME part is an attachment based on Content-Disposition - * and content type. - */ -function isAttachmentPart( - contentType: string, - headers: Map, -): boolean { - const disposition = headers.get("content-disposition") ?? ""; - if (disposition.toLowerCase().startsWith("attachment")) return true; - - const ct = contentType.toLowerCase().split(";")[0]!.trim(); - if (ct === "text/plain" || ct === "text/html") return false; - - // Non-text types are treated as attachments unless they are multipart. - if (ct.startsWith("multipart/")) return false; - - return true; -} - -function extractContentTypeMime(contentType: string): string { - return contentType.split(";")[0]!.trim().toLowerCase(); -} - -function extractFilename(headers: Map): string | null { - const disposition = headers.get("content-disposition") ?? ""; - const nameMatch = - disposition.match(/filename="([^"]+)"/i) ?? - disposition.match(/filename=([^\s;]+)/i); - if (nameMatch !== null) return nameMatch[1]!; - - const ct = headers.get("content-type") ?? ""; - const ctNameMatch = - ct.match(/name="([^"]+)"/i) ?? ct.match(/name=([^\s;]+)/i); - if (ctNameMatch !== null) return ctNameMatch[1]!; - - return null; -} - -type WalkContext = { - mailId: string; - bodyValues: Record; - textBody: JMAPBodyPart[]; - htmlBody: JMAPBodyPart[]; - attachments: JMAPAttachment[]; -}; - -/** - * Recursively walk MIME parts, populating body values and attachment lists. - * - * partPath uses IMAP-style dot-separated numbering (e.g., "1", "1.1", "2.3"). - */ -function walkMimePart( - partBytes: Uint8Array, - partPath: string, - ctx: WalkContext, -): void { - const part = parseMimePart(partBytes); - const mime = extractContentTypeMime(part.contentType); - - if (mime.startsWith("multipart/")) { - const boundary = extractBoundary(part.contentType); - if (boundary === undefined) return; - const subParts = parseMultipart(part.body, boundary); - subParts.forEach((subPartBytes, idx) => { - walkMimePart(subPartBytes, `${partPath}.${idx + 1}`, ctx); - }); - return; - } - - if (isAttachmentPart(part.contentType, part.headers)) { - const blobId = `blob_${ctx.mailId}_${partPath}`; - ctx.attachments.push({ - blobId, - name: extractFilename(part.headers), - type: mime, - size: part.body.length, - }); - return; - } - - const decoded = decodeBodyBytes(part.body, part.headers); - ctx.bodyValues[partPath] = decoded; - - if (mime === "text/plain") { - ctx.textBody.push({ partId: partPath, type: mime }); - } else if (mime === "text/html") { - ctx.htmlBody.push({ partId: partPath, type: mime }); - } -} - -/** - * Convert raw MIME bytes into a JMAP Email-shaped object. - * - * Handles text/plain, multipart/mixed, and multipart/signed message shapes. - * For multipart/signed (RFC 3156), the signed content part (part 1) is - * parsed for body and attachments. Signature verification is not performed. - * - * @param raw - Raw RFC 2822 message bytes - * @param mailId - Opaque mail record ID used to generate blob IDs - */ -export function parseMailToEmail(raw: Uint8Array, mailId: string): JMAPEmail { - const { headers: msgHeaders, bodyOffset } = parseHeaderSection(raw); - const body = raw.slice(bodyOffset); - const contentType = msgHeaders.get("content-type") ?? "text/plain"; - const mime = extractContentTypeMime(contentType); - - const ctx: WalkContext = { - mailId, - bodyValues: {}, - textBody: [], - htmlBody: [], - attachments: [], - }; - - if (mime === "multipart/signed") { - // RFC 3156: part 1 is the signed content, part 2 is the signature. - // Parse the content part through to extract body and attachments. - const boundary = extractBoundary(contentType); - if (boundary !== undefined) { - const outerParts = parseMultipart(body, boundary); - const contentPart = outerParts[0]; - if (contentPart !== undefined) { - // The content part may itself be text/plain or multipart/mixed. - // We assign it path "1" and walk it. - walkMimePart(contentPart, "1", ctx); - } - } - } else if (mime.startsWith("multipart/")) { - const boundary = extractBoundary(contentType); - if (boundary !== undefined) { - const parts = parseMultipart(body, boundary); - parts.forEach((partBytes, idx) => { - walkMimePart(partBytes, `${idx + 1}`, ctx); - }); - } - } else { - // Single-part message (e.g. text/plain). - // Reconstruct minimal part bytes with content-type header so parseMimePart works. - const enc = new TextEncoder(); - const ctHeader = `Content-Type: ${contentType}\r\n\r\n`; - const partBytes = new Uint8Array(enc.encode(ctHeader).length + body.length); - partBytes.set(enc.encode(ctHeader), 0); - partBytes.set(body, enc.encode(ctHeader).length); - walkMimePart(partBytes, "1", ctx); - } - - // Extract Interchange-specific headers. - const interchangeHeaders: Record = {}; - for (const [name, value] of msgHeaders) { - if (name.startsWith("interchange-")) { - interchangeHeaders[name] = value; - } - } - - return { - from: parseAddressList(msgHeaders.get("from") ?? ""), - to: parseAddressList(msgHeaders.get("to") ?? ""), - subject: msgHeaders.get("subject") ?? null, - sentAt: parseDateHeader(msgHeaders.get("date")), - bodyValues: ctx.bodyValues, - textBody: ctx.textBody, - htmlBody: ctx.htmlBody, - attachments: ctx.attachments, - headers: interchangeHeaders, - }; -} - -/** - * Decode a MIME part body into raw bytes, honoring Content-Transfer-Encoding. - * - * Unlike `decodeBodyBytes` (which produces a JMAP string value), this returns - * the actual bytes for reconstructing a `MessageAttachment`. A malformed - * base64 body surfaces as a thrown error rather than a silent best-effort - * decode — attachment integrity is load-bearing. - */ -function decodeAttachmentBytes( - body: Uint8Array, - headers: Map, -): Uint8Array { - const cte = (headers.get("content-transfer-encoding") ?? "7bit") - .trim() - .toLowerCase(); - - if (cte === "base64") { - const raw = new TextDecoder("utf-8", { fatal: false }).decode(body); - return base64Decode(raw.replace(/\s+/g, "")); - } - - if (cte === "quoted-printable") { - const raw = new TextDecoder("utf-8", { fatal: false }).decode(body); - const decoded = decodeQuotedPrintable(raw); - const out = new Uint8Array(decoded.length); - for (let i = 0; i < decoded.length; i++) { - out[i] = decoded.charCodeAt(i); - } - return out; - } - - if (cte === "7bit" || cte === "8bit" || cte === "binary") { - return body; - } - - throw new Error( - `decodeAttachmentBytes: unsupported content-transfer-encoding "${cte}"`, - ); -} - -/** - * Extract conversation attachments from raw message bytes as - * `MessageAttachment[]` with decoded payloads. - * - * The conversation signed content is a multipart/mixed whose first part is - * the text body and whose remaining attachment parts (Content-Disposition: - * attachment) carry the binary payloads. Returns an empty array for any - * shape without attachment parts — a bare text/plain signed part, a - * non-multipart/signed message, or a multipart/mixed with only the text - * part — so callers can use it unconditionally. - * - * Counterpart to `assembleConversationSignedPart`: assemble then extract - * round-trips a `MessageAttachment[]`. - */ -export function extractAttachments(raw: Uint8Array): MessageAttachment[] { - const { headers, bodyOffset } = parseHeaderSection(raw); - const body = raw.slice(bodyOffset); - const mime = extractContentTypeMime(headers.get("content-type") ?? ""); - - if (mime !== "multipart/signed") return []; - const outerBoundary = extractBoundary(headers.get("content-type") ?? ""); - if (outerBoundary === undefined) return []; - - const contentPart = parseMultipart(body, outerBoundary)[0]; - if (contentPart === undefined) return []; - - const signed = parseMimePart(contentPart); - if (!extractContentTypeMime(signed.contentType).startsWith("multipart/")) { - return []; - } - const innerBoundary = extractBoundary(signed.contentType); - if (innerBoundary === undefined) return []; - - const attachments: MessageAttachment[] = []; - for (const subPartBytes of parseMultipart(signed.body, innerBoundary)) { - const subPart = parseMimePart(subPartBytes); - if (!isAttachmentPart(subPart.contentType, subPart.headers)) continue; - attachments.push({ - name: extractFilename(subPart.headers) ?? "attachment", - contentType: extractContentTypeMime(subPart.contentType), - data: decodeAttachmentBytes(subPart.body, subPart.headers), - }); - } - return attachments; -} - -// --------------------------------------------------------------------------- -// Decoded-mail model (Mail / MessagePart) — lossless inbound decoding -// --------------------------------------------------------------------------- - -function isInterchangeType(s: string): s is InterchangeType { - return !(InterchangeType(s) instanceof type.errors); -} - -/** - * Build the typed, ergonomic `MessageHeaders` subset from a parsed header map. - * Optional fields are included only when present (exactOptionalPropertyTypes- - * safe). The full, lossless header set is carried separately as `rawHeaders`. - */ -export function buildMessageHeaders( - headers: Map, -): ParsedMessageHeaders { - const from = headers.get("from") ?? ""; - const toRaw = headers.get("to") ?? ""; - const to = toRaw - ? toRaw - .split(",") - .map((s) => s.trim()) - .filter(Boolean) - : []; - - const date = headers.get("date") ?? ""; - const messageId = headers.get("message-id") ?? ""; - - const result: ParsedMessageHeaders = { from, to, date, messageId }; - - const ccRaw = headers.get("cc"); - if (ccRaw !== undefined) { - const cc = ccRaw - .split(",") - .map((s) => s.trim()) - .filter(Boolean); - if (cc.length > 0) result.cc = cc; - } - - const refsRaw = headers.get("references"); - if (refsRaw !== undefined) { - const refs = refsRaw.split(/\s+/).filter(Boolean); - if (refs.length > 0) result.references = refs; - } - - const inReplyTo = headers.get("in-reply-to"); - if (inReplyTo !== undefined) result.inReplyTo = inReplyTo; - - const subject = headers.get("subject"); - if (subject !== undefined) result.subject = subject; - - const listId = headers.get("list-id"); - if (listId !== undefined) result.listId = listId; - - const rawType = headers.get("interchange-type"); - if (rawType !== undefined && isInterchangeType(rawType)) { - result.interchangeType = rawType; - } - - const corrId = headers.get("interchange-correlation-id"); - if (corrId !== undefined) result.interchangeCorrelationId = corrId; - - const tenantId = headers.get("interchange-tenant-id"); - if (tenantId !== undefined) result.interchangeTenantId = tenantId; - - const agentId = headers.get("interchange-agent-id"); - if (agentId !== undefined) result.interchangeAgentId = agentId; - - const sessionId = headers.get("interchange-session-id"); - if (sessionId !== undefined) result.interchangeSessionId = sessionId; - - const offeringId = headers.get("interchange-offering-id"); - if (offeringId !== undefined) result.interchangeOfferingId = offeringId; - - const schemaVersion = headers.get("interchange-schema-version"); - if (schemaVersion !== undefined) - result.interchangeSchemaVersion = schemaVersion; - - const traceparent = headers.get("traceparent"); - if (traceparent !== undefined) result.traceparent = traceparent; - - const tracestate = headers.get("tracestate"); - if (tracestate !== undefined) result.tracestate = tracestate; - - return result; -} - -/** - * Parse every header line in the message's header section into a raw, - * lossless map of lowercased name to its ordered values. Repeated headers - * (e.g. `Received`) keep all occurrences; folded continuation lines are - * unfolded onto the preceding header. Bounded to the header section via - * `headerEnd` so the whole message body is never decoded here. - */ -function parseRawHeaders( - raw: Uint8Array, - headerEnd: number, -): Record { - const text = new TextDecoder("utf-8", { fatal: false }).decode( - raw.subarray(0, headerEnd), - ); - const out: Record = {}; - let current: { name: string; value: string } | null = null; - const flush = (): void => { - if (current === null) return; - const key = current.name.trim().toLowerCase(); - (out[key] ??= []).push(current.value.trim()); - current = null; - }; - for (const line of text.split(/\r\n|\n/)) { - if (line === "") break; - if ((line.startsWith(" ") || line.startsWith("\t")) && current !== null) { - current.value += ` ${line.trim()}`; - continue; - } - const idx = line.indexOf(":"); - if (idx === -1) continue; - flush(); - current = { name: line.slice(0, idx), value: line.slice(idx + 1) }; - } - flush(); - return out; -} - -function parseDisposition( - headers: Map, -): "inline" | "attachment" | undefined { - const d = (headers.get("content-disposition") ?? "").trim().toLowerCase(); - if (d.startsWith("attachment")) return "attachment"; - if (d.startsWith("inline")) return "inline"; - return undefined; -} - -/** - * Recursively collect the decoded leaf parts of a MIME part. A multipart part - * recurses into its children; a leaf part is decoded (transfer-encoding undone) - * into a `MessagePart`. The PGP/MIME signature part is transport plumbing, not - * content, so it is skipped -- which unwraps the `multipart/signed` envelope - * (its two children are the signed content and the signature) for free. - */ -function collectLeafParts(partBytes: Uint8Array): MessagePart[] { - const part = parseMimePart(partBytes); - const mime = extractContentTypeMime(part.contentType); - if (mime === "application/pgp-signature") return []; - if (mime.startsWith("multipart/")) { - const boundary = extractBoundary(part.contentType); - // A multipart part with no boundary is undecodable: its children cannot - // be located. Silently returning [] would drop that content and break the - // lossless contract, so surface it as a decode failure the caller drops. - if (boundary === undefined) { - throw new Error( - `decodeMail: ${mime} part has no boundary parameter; cannot decode its children`, - ); - } - return parseMultipart(part.body, boundary).flatMap(collectLeafParts); - } - const result: MessagePart = { - contentType: mime, - content: decodeAttachmentBytes(part.body, part.headers), - }; - const filename = extractFilename(part.headers); - if (filename !== null) result.filename = filename; - const disposition = parseDisposition(part.headers); - if (disposition !== undefined) result.disposition = disposition; - return [result]; -} - -/** - * Decode a raw inbound MIME message into its lossless parts: the typed header - * subset, the full raw header map, and the flat list of decoded leaf parts - * (the PGP/MIME signature and multipart wrappers removed). This is the - * in-memory form; a caller commits each part's bytes to durable storage to - * produce a JSON-safe `Mail`. Reused across the standalone and deployed - * ingest paths so both see the same decoding. - */ -export function decodeMail(raw: Uint8Array): { - headers: ParsedMessageHeaders; - rawHeaders: Record; - parts: MessagePart[]; -} { - const { headers: singleMap, headerEnd } = parseHeaderSection(raw); - const rawHeaders = parseRawHeaders(raw, headerEnd); - const headers = buildMessageHeaders(singleMap); - const parts = collectLeafParts(raw); - return { headers, rawHeaders, parts }; -} diff --git a/vendor/intx-mime/src/pgp-sign.ts b/vendor/intx-mime/src/pgp-sign.ts deleted file mode 100644 index bb89b48..0000000 --- a/vendor/intx-mime/src/pgp-sign.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * PGP/MIME signing via CryptoProvider. - * - * createDetachedSignature in @intx/crypto signs with raw private key bytes, - * but callers that only hold a CryptoProvider (which does not expose the - * private key) need this variant. It delegates to the crypto package's - * signer-function primitive, handing it the provider's raw Ed25519 sign - * operation. The OpenPGP packet assembly lives entirely in @intx/crypto; - * this module only adapts a CryptoProvider into the signer the primitive - * expects. - */ - -import { createDetachedSignatureWithSigner } from "@intx/crypto"; -import type { CryptoProvider } from "@intx/types/runtime"; - -/** - * Produce a PGP/MIME detached signature using a CryptoProvider. - * - * Mirrors createDetachedSignature from @intx/crypto but accepts a - * CryptoProvider instead of raw private key bytes. - */ -export async function createDetachedSignatureFromProvider( - content: Uint8Array, - provider: CryptoProvider, -): Promise { - return createDetachedSignatureWithSigner(content, (input) => - provider.sign(input), - ); -} diff --git a/vendor/intx-types/LICENSE b/vendor/intx-types/LICENSE deleted file mode 100644 index c6487f4..0000000 --- a/vendor/intx-types/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE - -Version 2.1, February 1999 - -Copyright (C) 1991, 1999 Free Software Foundation, Inc. -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - -Preamble - -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. - -This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. - -When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. - -To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. - -We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - -Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. - -Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. - -When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. - -We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. - -For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. - -In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. - -Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. - -The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - -GNU LESSER GENERAL PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. - -Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. - -You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. - -(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - -3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library into a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. - -5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. - -When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. - -If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: - - a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. - - e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. - -For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - -It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - -7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. - - b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - -11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - -This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - -13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - -NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Libraries - -If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). - -To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - one line to give the library's name and an idea of what it does. - Copyright (C) year name of author - - This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: - -Yoyodyne, Inc., hereby disclaims all copyright interest in -the library `Frob' (a library for tweaking knobs) written -by James Random Hacker. - -signature of Ty Coon, 1 April 1990 -Ty Coon, President of Vice -That's all there is to it! diff --git a/vendor/intx-types/package.json b/vendor/intx-types/package.json deleted file mode 100644 index c560849..0000000 --- a/vendor/intx-types/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "@intx/types", - "description": "Runtime validators, API contract types, runtime interfaces, and sidecar wire frames for Interchange", - "version": "0.3.0", - "license": "LGPL-2.1-only", - "type": "module", - "exports": { - ".": { - "types": "./src/index.ts", - "default": "./src/index.ts" - }, - "./authz": { - "types": "./src/authz.ts", - "default": "./src/authz.ts" - }, - "./audit": { - "types": "./src/audit.ts", - "default": "./src/audit.ts" - }, - "./content-type": { - "types": "./src/content-type.ts", - "default": "./src/content-type.ts" - }, - "./runtime": { - "types": "./src/runtime.ts", - "default": "./src/runtime.ts" - }, - "./runtime-capabilities": { - "types": "./src/runtime-capabilities.ts", - "default": "./src/runtime-capabilities.ts" - }, - "./sidecar": { - "types": "./src/sidecar.ts", - "default": "./src/sidecar.ts" - }, - "./grant-wire": { - "types": "./src/grant-wire.ts", - "default": "./src/grant-wire.ts" - }, - "./tool-packages": { - "types": "./src/tool-packages.ts", - "default": "./src/tool-packages.ts" - }, - "./package-json": { - "types": "./src/package-json.ts", - "default": "./src/package-json.ts" - }, - "./wire-definition-hash": { - "types": "./src/wire-definition-hash.ts", - "default": "./src/wire-definition-hash.ts" - }, - "./workflow-sources": { - "types": "./src/workflow-sources.ts", - "default": "./src/workflow-sources.ts" - } - }, - "scripts": { - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "arktype": "2.1.29", - "semver": "^7.7.2" - }, - "devDependencies": { - "@types/bun": "1.1.14", - "@types/semver": "^7.7.1", - "typescript": "5.7.2" - }, - "files": [ - "src", - "README.md", - "LICENSE" - ], - "sideEffects": false, - "publishConfig": { - "access": "public" - } -} diff --git a/vendor/intx-types/src/agent-address.ts b/vendor/intx-types/src/agent-address.ts deleted file mode 100644 index 2af45e5..0000000 --- a/vendor/intx-types/src/agent-address.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Run addresses are "@" where runId is the local part: the -// `run_`-prefixed identifier that names the run. These helpers are the single -// source of truth for that format. -// -// The shape of the right-hand side of the "@" is not validated beyond the -// requirement that it be non-empty: tightening the contract (DNS-ish -// validation, normalisation, etc.) is a separate follow-up. -// -// `@intx/hub-sessions`'s `parseAgentId` is the canonical throwing wrapper -// over `parseRunAddress` — call it when a `null` return would -// propagate as a silent bug, and keep this parser's `null` return -// reserved for callers that already have a structured fallback. - -const RUN_PREFIX = "run_"; - -export function formatRunAddress(runId: string, domain: string): string { - return `${runId}@${domain}`; -} - -export function parseRunAddress( - address: string, -): { runId: string; domain: string } | null { - const atIdx = address.indexOf("@"); - if (atIdx <= 0) return null; - const runId = address.slice(0, atIdx); - const domain = address.slice(atIdx + 1); - if (!runId.startsWith(RUN_PREFIX)) return null; - if (domain.length === 0) return null; - return { runId, domain }; -} - -export function isRunAddress(address: string): boolean { - return parseRunAddress(address) !== null; -} diff --git a/vendor/intx-types/src/agent-data.ts b/vendor/intx-types/src/agent-data.ts deleted file mode 100644 index 7788119..0000000 --- a/vendor/intx-types/src/agent-data.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { type } from "arktype"; - -export const FileEntry = type({ - path: "string", - type: "'file' | 'directory'", - "size?": "number | null", - "modifiedAt?": "string | null", -}); - -export const FileContent = type({ - path: "string", - content: "string", - "encoding?": "'utf-8' | 'base64'", -}); - -export const HistoryEntry = type({ - ref: "string", - message: "string", - author: "string", - timestamp: "string", - "filesChanged?": "number", -}); - -export const CommitDetail = type({ - ref: "string", - message: "string", - author: "string", - timestamp: "string", - changes: type({ - path: "string", - status: "'added' | 'modified' | 'deleted'", - "additions?": "number", - "deletions?": "number", - }).array(), -}); - -export const BranchInfo = type({ - name: "string", - "isCurrent?": "boolean", - "lastCommitRef?": "string | null", - "lastCommitMessage?": "string | null", - "lastCommitAt?": "string | null", -}); diff --git a/vendor/intx-types/src/approvals.ts b/vendor/intx-types/src/approvals.ts deleted file mode 100644 index a7f6a0e..0000000 --- a/vendor/intx-types/src/approvals.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { type } from "arktype"; - -export const ApprovalResponse = type({ - id: "string", - tenantId: "string", - anchorRunId: type("string").describe( - "The anchor run the approval originates from. Every approval is raised during a workflow run; there is no launched single agent or agent-definition row behind it.", - ), - runId: "string", - agentAddress: "string", - correlationId: type("string").describe( - "Ties the approval to the suspension it resolves. The parked run awaits the control signal keyed by this id.", - ), - toolDefinition: type("Record").describe( - "The approver-facing tool snapshot (name, description, input schema) captured at suspend time.", - ), - toolArguments: "Record", - scope: "'once' | 'always' | null", - status: "'pending' | 'approved' | 'rejected' | 'timeout' | 'expired'", - timeoutAt: type("string | null").describe( - "Deadline after which the approval expires. Null records a hold-indefinitely approval with no deadline.", - ), - resolvedAt: "string | null", - createdAt: "string", - updatedAt: "string", -}); - -export const ApproveAction = type({ - scope: "'once' | 'always'", -}); - -export const RejectAction = type({ - // Optional so a plain one-time rejection (the default) stays a bare body. - // Scope 'always' records a standing rejection: the tool's ask gate is set to - // a standing deny for the run, so it is blocked without asking again. - "scope?": "'once' | 'always'", - "message?": "string", -}); diff --git a/vendor/intx-types/src/assets.ts b/vendor/intx-types/src/assets.ts deleted file mode 100644 index 9f713cf..0000000 --- a/vendor/intx-types/src/assets.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { type } from "arktype"; - -const assetKindDescription = - "Category of the asset, used together with `name` to address it. The (kind, name) pair is what callers resolve against, and it is unique within a tenant."; - -export const AssetResponse = type({ - id: "string", - tenantId: "string", - kind: type("string").describe(assetKindDescription), - name: "string", - displayName: "string | null", - creatorPrincipalId: "string | null", - createdAt: "string", - updatedAt: "string", -}); - -/** - * `AssetResponse` extended with the tenant that supplied the row. The - * inherited-list endpoint stamps every row with this tag so callers can - * distinguish locally-defined assets from inherited ones without - * issuing a second round-trip per row. - */ -export const AssetWithOriginResponse = type({ - id: "string", - tenantId: "string", - kind: type("string").describe(assetKindDescription), - name: "string", - displayName: "string | null", - creatorPrincipalId: "string | null", - createdAt: "string", - updatedAt: "string", - origin: type({ - tenantId: type("string").describe( - "The tenant that supplied this row -- either the queried tenant itself or an ancestor it inherits from.", - ), - direct: type("boolean").describe( - "True when the asset is declared on the queried tenant itself; false when it is inherited from an ancestor tenant.", - ), - }).describe( - "Which tenant in the hierarchy this asset row came from, distinguishing locally-defined assets from inherited ones.", - ), -}); diff --git a/vendor/intx-types/src/attachments.ts b/vendor/intx-types/src/attachments.ts deleted file mode 100644 index ffae817..0000000 --- a/vendor/intx-types/src/attachments.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Attachment allowlist — the system-level source of truth for which MIME -// types the hub accepts as conversation attachments and which ContentBlock -// category each maps to. Adding a MIME type is a one-line change here. -// -// This is the hard capability ceiling: a type is only useful if the pipeline -// can produce the right ContentBlock and an adapter can marshal it. Per-agent -// or per-workflow narrowing rides on top of this ceiling — it narrows the -// accepted set, it never widens past what the adapters support. - -export const ATTACHMENT_CATEGORIES = [ - "image", - "video", - "audio", - "document", -] as const; -export type AttachmentCategory = (typeof ATTACHMENT_CATEGORIES)[number]; - -export const ATTACHMENT_ALLOWLIST = { - "image/png": "image", - "image/jpeg": "image", - "image/gif": "image", - "image/webp": "image", - "image/heic": "image", - "image/heif": "image", - "video/mp4": "video", - "video/webm": "video", - "video/quicktime": "video", - "audio/mpeg": "audio", - "audio/wav": "audio", - "audio/ogg": "audio", - "audio/webm": "audio", - "application/pdf": "document", - "application/json": "document", - "text/plain": "document", - "text/csv": "document", - "text/markdown": "document", -} as const satisfies Record; - -export type AllowedMimeType = keyof typeof ATTACHMENT_ALLOWLIST; - -export function isAllowedMimeType( - mimeType: string, -): mimeType is AllowedMimeType { - return mimeType in ATTACHMENT_ALLOWLIST; -} - -/** - * The ContentBlock category for an allowlisted MIME type, or `undefined` - * when the type is not on the allowlist. Callers decide how to treat an - * unknown type (the route rejects it at the boundary; turn construction - * surfaces it as a text marker). - */ -export function attachmentCategory( - mimeType: string, -): AttachmentCategory | undefined { - if (isAllowedMimeType(mimeType)) { - return ATTACHMENT_ALLOWLIST[mimeType]; - } - return undefined; -} - -// Default size limits, on decoded bytes. These are the system-level -// ceiling; a future per-agent/per-workflow policy resolves an effective -// limit that defaults to these. -export const PER_ATTACHMENT_LIMIT_BYTES = 10 * 1024 * 1024; -export const PER_MESSAGE_TOTAL_LIMIT_BYTES = 30 * 1024 * 1024; diff --git a/vendor/intx-types/src/audit.ts b/vendor/intx-types/src/audit.ts deleted file mode 100644 index 42cbad1..0000000 --- a/vendor/intx-types/src/audit.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { type } from "arktype"; - -import { MatchedGrant, grantEffects } from "./grants"; - -const Effect = type.enumerated(...grantEffects); - -export const AuditAuthz = type({ - effect: Effect.or("null").describe( - "The authorization outcome the runtime resolved for this tool call: `allow`, `deny`, or `ask`, or `null` when no grant matched.", - ), - "resolvedBy?": MatchedGrant.or("null").describe( - "The single grant whose effect determined the outcome (the most specific match), or `null` when nothing matched.", - ), - matchingGrants: MatchedGrant.array(), - blocked: type("boolean").describe( - "True when the runtime prevented the tool call from executing because authorization did not resolve to `allow`.", - ), - "blockReason?": "string", -}); -export type AuditAuthz = typeof AuditAuthz.infer; - -export const AuditRecord = type({ - callId: "string", - tool: "string", - arguments: "Record", - authz: AuditAuthz.or("null"), - result: type({ - content: "string | Record", - isError: "boolean", - }), - timestamp: "string", - sessionId: "string", - // Monotonic sequence number from the reactor's tool.done event. - // Supplied by the caller; the reactor owns the sequence. - seq: "number.integer >= 0", -}); -export type AuditRecord = typeof AuditRecord.infer; - -export const ErrorRecord = type({ - source: "'inference' | 'reactor'", - category: "string", - message: "string", - "statusCode?": "number.integer", - fatal: "boolean", - timestamp: "string", - sessionId: "string", - seq: "number.integer >= 0", -}); -export type ErrorRecord = typeof ErrorRecord.infer; diff --git a/vendor/intx-types/src/authz.ts b/vendor/intx-types/src/authz.ts deleted file mode 100644 index 9f0d6bd..0000000 --- a/vendor/intx-types/src/authz.ts +++ /dev/null @@ -1,49 +0,0 @@ -export type Effect = "allow" | "deny" | "ask"; - -export type GrantRule = { - id: string; - resource: string; - action: string; - effect: Effect; - origin: "system" | "role" | "creator" | "invoker"; - conditions: Record | null; - expiresAt: Date | null; - roleId: string | null; - principalId: string | null; -}; - -export type GrantStore = { - collectGrants(principalId: string, tenantId: string): Promise; - /** - * Like `collectGrants`, but unions the principal's grants across the tenant - * ancestor chain (the acting tenant plus every ancestor up to the root) - * rather than a single tenant. Only the source-resolution credential-use - * check uses this: it mirrors the ancestor-chain reach of credential - * resolution so a `credential:{id}` / `use` grant stamped with an inherited - * credential's own (ancestor) tenant still authorizes use. The general RBAC - * path stays on the single-tenant `collectGrants`. - */ - collectGrantsInChain( - principalId: string, - tenantId: string, - ): Promise; -}; - -export type ConditionContext = { - now: Date; - resource: string; - action: string; - principalId: string; - tenantId: string; - // Identity of the capability consumer the decision is being made for - // (e.g. a `tool:` consumer). Empty when no consumer is in scope; - // a consumer-scoped condition fails closed against an empty consumer. - consumer: string; -}; - -export type ConditionEvaluator = ( - value: unknown, - ctx: ConditionContext, -) => boolean | Promise; - -export type ConditionRegistry = Record; diff --git a/vendor/intx-types/src/base64.ts b/vendor/intx-types/src/base64.ts deleted file mode 100644 index 5158372..0000000 --- a/vendor/intx-types/src/base64.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Base64 codec for byte strings. -// -// Used to ship binary mail bodies over text-only WebSocket frames. -// Centralizing here keeps the encoding stable across the sidecar/hub -// boundary and any other consumer that needs the same wire shape. - -export function base64Encode(bytes: Uint8Array): string { - let binary = ""; - for (const byte of bytes) { - binary += String.fromCharCode(byte); - } - return btoa(binary); -} - -export function base64Decode(base64: string): Uint8Array { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; -} diff --git a/vendor/intx-types/src/base64url.ts b/vendor/intx-types/src/base64url.ts deleted file mode 100644 index fdfacb3..0000000 --- a/vendor/intx-types/src/base64url.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Base64url codec for byte strings (RFC 4648 section 5). -// -// URL- and filename-safe base64: standard base64 with `+`/`/` replaced by -// `-`/`_` and trailing `=` padding stripped. Used for opaque pagination -// cursors and git PAT secrets that ride in URLs and HTTP basic-auth headers, -// where the standard `+`, `/`, and `=` characters are unsafe. Reuses the -// base64 core so the two encodings stay byte-compatible. - -import { base64Decode, base64Encode } from "./base64"; - -export function base64urlEncode(bytes: Uint8Array): string { - return base64Encode(bytes) - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=+$/, ""); -} - -export function base64urlDecode(s: string): Uint8Array { - const translated = s.replace(/-/g, "+").replace(/_/g, "/"); - const padLength = (4 - (translated.length % 4)) % 4; - return base64Decode(translated + "=".repeat(padLength)); -} diff --git a/vendor/intx-types/src/capabilities.ts b/vendor/intx-types/src/capabilities.ts deleted file mode 100644 index 933819d..0000000 --- a/vendor/intx-types/src/capabilities.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { type } from "arktype"; - -// The capabilities the production inference runtime demonstrates on the wire, -// and that the discovery rig probes for. This is the single source of truth for -// the shared capability vocabulary: @intx/inference-discovery imports this list -// and extends it, so production code never has to depend on the discovery -// package. Each capability that has a streaming wire flow distinct from its -// buffered one carries a paired `-streaming` variant; `function-calling` is the -// sole base with no streaming pair (a bare tool call has no delta flow to -// capture). -export const WIRE_CAPABILITIES = [ - "plain-text", - "plain-text-streaming", - "function-calling", - "function-calling-multi-turn", - "function-calling-multi-turn-streaming", - "function-calling-with-thinking", - "function-calling-with-thinking-streaming", - "vision-input", - "vision-input-streaming", - "audio-input", - "audio-input-streaming", - "video-input", - "video-input-streaming", - "document-input", - "document-input-streaming", - "image-output", - "image-output-streaming", - "code-execution", - "code-execution-streaming", - "reasoning-content", - "reasoning-content-streaming", - "grounding", - "grounding-streaming", - "files-api-reference", - "files-api-reference-streaming", - "redacted-thinking", - "redacted-thinking-streaming", - "structured-output", - "structured-output-streaming", -] as const; - -// Capabilities a model has that are not observable on the wire and cannot be -// proven by a discovery fixture. `long-context` denotes a model advertising a -// context window of at least ~200k tokens (a curation criterion, not a stored -// limit); `prompt-caching` denotes provider-side prompt caching. The discovery -// rig has no probe that could prove either, so operators curate them by hand. -export const CURATED_CAPABILITIES = ["long-context", "prompt-caching"] as const; - -export const CAPABILITIES = [ - ...WIRE_CAPABILITIES, - ...CURATED_CAPABILITIES, -] as const; -export type Capability = (typeof CAPABILITIES)[number]; -export const Capability = type - .enumerated(...CAPABILITIES) - .describe( - "A capability a provider advertises for a model: a wire capability the inference runtime supports, or one of the curated tags `long-context` and `prompt-caching`.", - ); diff --git a/vendor/intx-types/src/catalog.ts b/vendor/intx-types/src/catalog.ts deleted file mode 100644 index 222ccb0..0000000 --- a/vendor/intx-types/src/catalog.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { type } from "arktype"; - -import { Capability } from "./capabilities"; - -export const modelProviderPlugins = [ - "anthropic", - "openai", - "openai-compatible", - "google-genai", - // Local delta (CL-7510): the workbench's loopback-OAuth providers (Codex, - // xai-oauth) speak OpenAI's Responses protocol and ride this plugin id. - "openai-responses", -] as const; -export type ModelProviderPlugin = (typeof modelProviderPlugins)[number]; -export const ModelProviderPlugin = type - .enumerated(...modelProviderPlugins) - .describe( - "The inference adapter that serves this provider's models, dispatched by the runtime provider registry.", - ); - -export const providerPreferenceModes = ["pin", "prefer"] as const; -export type ProviderPreferenceMode = (typeof providerPreferenceModes)[number]; - -export const ProviderPreference = type({ - mode: type - .enumerated(...providerPreferenceModes) - .describe( - "`pin` restricts resolution to the listed providers and fails over only among them; `prefer` orders the listed providers first but keeps the rest of the tenant's providers as fallback.", - ), - order: type("string[]").describe( - "Model-provider names in preferred order, most preferred first.", - ), -}); -export type ProviderPreference = typeof ProviderPreference.infer; - -export const ModelRequirement = type({ - model: type("string").describe( - "Canonical model name the agent requires for inference.", - ), - "capabilities?": Capability.array().describe( - "An offering must advertise every one of these capabilities to be eligible to serve this requirement.", - ), - "providers?": ProviderPreference.describe( - "The definition author's provider preference for this model. Resolution applies it over the tenant-visible providers; it cannot introduce a provider the tenant catalog does not contain.", - ), -}); -export type ModelRequirement = typeof ModelRequirement.infer; - -// A definition declares at most one requirement per canonical model: two -// requirements for the same model would resolve the same offering twice and -// produce duplicate inference-source ids. Reject the ambiguity here, at the -// boundary, rather than letting it surface deep in source resolution. -export const ModelRequirements = ModelRequirement.array().narrow( - (reqs, ctx) => { - const seen = new Set(); - for (const req of reqs) { - if (seen.has(req.model)) { - return ctx.mustBe( - `an array with no duplicate model requirements; "${req.model}" appears more than once`, - ); - } - seen.add(req.model); - } - return true; - }, -); -export type ModelRequirements = typeof ModelRequirements.infer; - -export const InvokerModelPreference = type({ - model: type("string").describe( - "Canonical model name this launch-time preference applies to.", - ), - providers: ProviderPreference, -}); -export type InvokerModelPreference = typeof InvokerModelPreference.infer; - -export const InvokerModelPreferences = InvokerModelPreference.array(); -export type InvokerModelPreferences = typeof InvokerModelPreferences.infer; - -export const CreateModel = type({ - canonicalName: type("string").describe( - "Tenant-unique canonical model name agents match their requirements against.", - ), - "displayName?": "string | null", - "description?": "string | null", -}); - -export const UpdateModel = type({ - "displayName?": "string | null", - "description?": "string | null", - "disabled?": "boolean", -}); - -export const CreateModelProvider = type({ - name: type("string").describe("Tenant-unique model-provider name."), - plugin: ModelProviderPlugin, - baseURL: "string", - // Exactly one of these must be set; the route rejects a body that sets - // both or neither before touching the database. - "credentialId?": "string | null", - "walletId?": "string | null", -}); - -export const UpdateModelProvider = type({ - "name?": "string", - "baseURL?": "string", - "disabled?": "boolean", -}); - -export const CreateModelOffering = type({ - modelId: type("string").describe( - "Catalog id of a model owned by this tenant.", - ), - providerId: type("string").describe( - "Catalog id of a model-provider owned by this tenant.", - ), - "priority?": type("number").describe( - "Ordering hint for source resolution; lower values are preferred first. Defaults to 0.", - ), - "deploymentTags?": "string[]", - "capabilities?": Capability.array(), - "quirks?": type("Record").describe( - "Opaque per-deployment adapter accommodations for this offering; the adapter factory validates the provider-specific shape. Omit when the deployment needs none.", - ), -}); - -export const UpdateModelOffering = type({ - "priority?": "number", - "deploymentTags?": "string[]", - "capabilities?": Capability.array(), - "quirks?": type("Record | null").describe( - "Replacement quirks bag, or null to clear it back to the adapter's default behavior. Omit to leave unchanged.", - ), - "disabled?": "boolean", -}); - -const createPriceDescription = (axis: string): string => - `${axis} as a decimal string in this row's \`currency\`, or null if this provider does not charge for it.`; - -export const CreatePricingRow = type({ - currency: type("string").describe( - "Fiat currency code or opaque credit unit this row prices in.", - ), - "effectiveFrom?": type("string").describe( - "ISO-8601 timestamp from which this price applies. Defaults to the time of the request.", - ), - "inputTokenPrice?": type("string | null").describe( - createPriceDescription("Cost per input token"), - ), - "outputTokenPrice?": type("string | null").describe( - createPriceDescription("Cost per output token"), - ), - "cacheReadTokenPrice?": type("string | null").describe( - createPriceDescription("Cost per cached-read token"), - ), - "cacheWriteTokenPrice?": type("string | null").describe( - createPriceDescription("Cost per cached-write token"), - ), - "thinkingTokenPrice?": type("string | null").describe( - createPriceDescription("Cost per thinking token"), - ), - "perRequestFee?": type("string | null").describe( - createPriceDescription("Flat fee per request"), - ), - "perImageFee?": type("string | null").describe( - createPriceDescription("Fee per image"), - ), - "perAudioFee?": type("string | null").describe( - createPriceDescription("Fee per audio unit"), - ), -}); - -export const ModelResponse = type({ - id: "string", - tenantId: "string", - canonicalName: "string", - "displayName?": "string | null", - "description?": "string | null", - disabled: "boolean", - createdAt: "string", - updatedAt: "string", -}); - -export const ModelProviderResponse = type({ - id: "string", - tenantId: "string", - name: "string", - plugin: ModelProviderPlugin, - baseURL: "string", - // Exactly one of these is set (enforced at the database). They are opaque - // references to a credential or wallet row, not secret material. - "credentialId?": "string | null", - "walletId?": "string | null", - disabled: "boolean", - createdAt: "string", - updatedAt: "string", -}); - -export const ModelOfferingResponse = type({ - id: "string", - tenantId: "string", - modelId: "string", - providerId: "string", - priority: type("number").describe( - "Ordering hint for source resolution; lower values are preferred first.", - ), - deploymentTags: "string[]", - capabilities: Capability.array().describe( - "Curated capability tags this provider advertises for this model.", - ), - quirks: type("Record | null").describe( - "Opaque per-deployment adapter accommodations, or null when the deployment needs none.", - ), - disabled: "boolean", - createdAt: "string", - updatedAt: "string", -}); - -const priceDescription = (axis: string): string => - `${axis} as a decimal string in the row's \`currency\`, or null if this provider does not charge for it.`; - -export const PricingRowResponse = type({ - id: "string", - tenantId: "string", - offeringId: "string", - currency: type("string").describe( - "Fiat currency code or opaque credit unit this row prices in.", - ), - "inputTokenPrice?": type("string | null").describe( - priceDescription("Cost per input token"), - ), - "outputTokenPrice?": type("string | null").describe( - priceDescription("Cost per output token"), - ), - "cacheReadTokenPrice?": type("string | null").describe( - priceDescription("Cost per cached-read token"), - ), - "cacheWriteTokenPrice?": type("string | null").describe( - priceDescription("Cost per cached-write token"), - ), - "thinkingTokenPrice?": type("string | null").describe( - priceDescription("Cost per thinking token"), - ), - "perRequestFee?": type("string | null").describe( - priceDescription("Flat fee per request"), - ), - "perImageFee?": type("string | null").describe( - priceDescription("Fee per image"), - ), - "perAudioFee?": type("string | null").describe( - priceDescription("Fee per audio unit"), - ), - effectiveFrom: type("string").describe( - "ISO-8601 timestamp from which this price applies. Cost attribution at a past time uses the latest row whose effectiveFrom is at or before that time.", - ), - createdAt: "string", -}); diff --git a/vendor/intx-types/src/common.ts b/vendor/intx-types/src/common.ts deleted file mode 100644 index 3033801..0000000 --- a/vendor/intx-types/src/common.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { type, type Type } from "arktype"; - -export const ErrorResponse = type({ - error: { - code: "string", - message: "string", - }, -}); - -export const PaginationParams = type({ - "cursor?": "string", - "limit?": "string", -}); - -export const PaginatedList = type({ - data: "unknown[]", - nextCursor: "string | null", -}); - -/** - * Creates a typed paginated response schema for use with OpenAPI. - * Wraps an item array schema in `{ data: T[], nextCursor: string | null }`. - */ -export function paginatedSchema(itemSchema: Type) { - return type({ - data: itemSchema.array(), - nextCursor: "string | null", - }); -} - -export const Timestamps = type({ - createdAt: "string", - updatedAt: "string", -}); diff --git a/vendor/intx-types/src/concat.ts b/vendor/intx-types/src/concat.ts deleted file mode 100644 index cd3c016..0000000 --- a/vendor/intx-types/src/concat.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Concatenate byte arrays into a single Uint8Array. -// -// A Web-standard replacement for Node's `Buffer.concat`: sum the chunk -// lengths, allocate the result once, and copy each chunk in at its -// running offset so the bytes land in input order. - -export function concatBytes(chunks: Uint8Array[]): Uint8Array { - let total = 0; - for (const chunk of chunks) { - total += chunk.length; - } - const out = new Uint8Array(total); - let offset = 0; - for (const chunk of chunks) { - out.set(chunk, offset); - offset += chunk.length; - } - return out; -} diff --git a/vendor/intx-types/src/content-type.ts b/vendor/intx-types/src/content-type.ts deleted file mode 100644 index 1dd9e52..0000000 --- a/vendor/intx-types/src/content-type.ts +++ /dev/null @@ -1,20 +0,0 @@ -export type ResponseKind = "sse" | "json"; - -export function detectResponseKind(headers: Headers): ResponseKind { - const raw = headers.get("content-type"); - if (raw === null) { - throw new Error( - "Cannot detect response kind: response has no Content-Type header", - ); - } - const normalized = raw.trim().toLowerCase(); - if (normalized.startsWith("text/event-stream")) { - return "sse"; - } - if (normalized.startsWith("application/json")) { - return "json"; - } - throw new Error( - `Unsupported response Content-Type: ${raw}. Expected text/event-stream or application/json.`, - ); -} diff --git a/vendor/intx-types/src/credential-cipher.ts b/vendor/intx-types/src/credential-cipher.ts deleted file mode 100644 index 9f475c7..0000000 --- a/vendor/intx-types/src/credential-cipher.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * The pluggable seam for encrypting credential secrets at rest. - * - * Every write site (credential / oauth-client create and update) encrypts - * through this interface; the single read-for-use site decrypts through it. Both - * depend only on the interface, so the concrete implementation is chosen once at - * the composition root and swapped without touching any call site. - * - * The one basic implementation today is `createEnvKeyCredentialCipher` - * (@intx/crypto): AES-256-GCM under a single operator-provided key. A future KMS - * or envelope-encryption plugin implements this same interface and can keep key - * material inside the KMS, because the seam abstracts the whole encrypt/decrypt - * operation rather than just supplying key bytes. - * - * `aad` (additional authenticated data) binds a ciphertext to its context -- the - * row id and column -- so a blob cannot be transplanted between rows (or between - * a row's columns) and still decrypt. Every site builds the `aad` with - * `credentialAad(id, column)` so the binding is identical on write and read. - * - * `decrypt` is strict: it throws on a value that is not a ciphertext produced by - * `encrypt` rather than returning it as plaintext. A plaintext value reaching - * decrypt means a write path failed to encrypt or the row was never re-keyed -- - * a failure that must surface, not be silently served. - */ -export interface CredentialCipher { - encrypt(plaintext: string, aad: string): Promise; - decrypt(blob: string, aad: string): Promise; -} - -/** - * Build the additional-authenticated-data string binding a credential-secret - * ciphertext to the row and column it belongs to. The encoding is injective in - * `(id, column)` -- distinct pairs always produce distinct strings -- so a - * ciphertext cannot be transplanted to a row/column it was not sealed for even - * if an id contained the delimiter of a naive `id:column` scheme. The - * `"credential-secret"` tag domain-separates this use of the AEAD primitive from - * any other. Both the write and read sites (and the re-key script) MUST build - * the `aad` through this one function so the value matches. - */ -export function credentialAad(id: string, column: string): string { - return JSON.stringify(["credential-secret", id, column]); -} - -/** - * Build the additional-authenticated-data string binding a principal signing - * key's sealed private material to the `principal_key` row and column it belongs - * to. Shares the AEAD primitive and encoding rules with `credentialAad` but uses - * a distinct `"principal-key"` tag domain, so a credential-secret ciphertext and - * a principal-key ciphertext are never interchangeable even under the same key. - * The mint (write) and sign (read) sites MUST build the `aad` through this one - * function so the value matches. - */ -export function principalKeyAad(id: string, column: string): string { - return JSON.stringify(["principal-key", id, column]); -} diff --git a/vendor/intx-types/src/credentials.ts b/vendor/intx-types/src/credentials.ts deleted file mode 100644 index aad64b7..0000000 --- a/vendor/intx-types/src/credentials.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { type } from "arktype"; - -import { ToolCredentialHandle } from "./package-json"; - -export const credentialTypes = [ - "api_key", - "oauth_token", - "certificate", - "other", -] as const; -export type CredentialType = (typeof credentialTypes)[number]; - -export const credentialStatuses = [ - "active", - "expired", - "revoked", - "error", -] as const; -export type CredentialStatus = (typeof credentialStatuses)[number]; - -export const credentialRequirementSources = [ - "tenant", - "creator", - "invoker", -] as const; -export type CredentialRequirementSource = - (typeof credentialRequirementSources)[number]; - -const CredType = type.enumerated(...credentialTypes); -const CredStatus = type.enumerated(...credentialStatuses); -const CredentialSourceType = type.enumerated(...credentialRequirementSources); - -// A credential binding on a workflow definition maps a tool package's declared -// credential handle -- keyed `(package, handle)` against the tool-package -// declaration -- to a concrete credential resolved fresh at launch. `locator` -// is which credential namespace the name is resolved in; today only `tenant` -// exists (a tenant-owned credential, authorized by ownership). A second locator -// that resolves a principal-owned credential -- and the delegation authority -// axis it would need -- is future work, added with the code that consumes it. -export const credentialBindingLocators = ["tenant"] as const; -export type CredentialBindingLocator = - (typeof credentialBindingLocators)[number]; - -const BindingLocator = type.enumerated(...credentialBindingLocators); - -export const CredentialBinding = type({ - package: type("string").describe( - "The tool package the declared handle belongs to; matches the resolved manifest's top-level package name.", - ), - handle: ToolCredentialHandle.describe( - "The credential handle the tool package declared; unique within its package.", - ), - provider: type("string").describe( - "The provider the bound credential resolves against.", - ), - "name?": type("string").describe( - "Optional credential name, a tiebreaker when several credentials match the provider and locator.", - ), - locator: BindingLocator.describe( - "Which credential namespace the binding resolves the credential in. `tenant` resolves a tenant-owned credential by provider/name through the tenant walk-up; its use is authorized by tenant ownership.", - ), -}); -export type CredentialBinding = typeof CredentialBinding.infer; - -const credentialTypeDescription = - "Kind of secret material this credential holds: `api_key`, `oauth_token`, `certificate`, or `other`. Determines how `secret` (and `refreshSecret` for OAuth) is interpreted when the credential is used."; - -const credentialStatusDescription = - "Usability state of the credential: `active` (usable), `expired` (past its `expiresAt`), `revoked` (deliberately invalidated), or `error` (last use failed, e.g. rejected by the provider)."; - -const credentialScopesDescription = - "Permissions granted to this credential by the provider (for example OAuth scopes). Informational on the credential record; the provider is the authority on what the secret can actually do."; - -const credentialMetadataDescription = - "Free-form provider- or integration-specific data attached to the credential. Not interpreted by the hub."; - -export const CreateCredential = type({ - providerId: "string", - name: "string", - type: CredType.describe(credentialTypeDescription), - "principalId?": "string", - "oauthClientId?": "string", - "description?": "string", - secret: "string", - "refreshSecret?": "string", - "scopes?": type("string[]").describe(credentialScopesDescription), - "expiresAt?": "string", - "metadata?": type("Record").describe( - credentialMetadataDescription, - ), -}); - -export const UpdateCredential = type({ - "name?": "string", - "description?": "string", - "secret?": "string", - "refreshSecret?": "string | null", - "scopes?": type("string[] | null").describe(credentialScopesDescription), - "expiresAt?": "string | null", - "status?": CredStatus.describe(credentialStatusDescription), - "metadata?": type("Record").describe( - credentialMetadataDescription, - ), -}); - -export const CredentialResponse = type({ - id: "string", - tenantId: "string", - providerId: "string", - "principalId?": "string | null", - "oauthClientId?": "string | null", - name: "string", - type: CredType.describe(credentialTypeDescription), - "description?": "string | null", - "scopes?": type("string[] | null").describe(credentialScopesDescription), - "expiresAt?": "string | null", - status: CredStatus.describe(credentialStatusDescription), - "metadata?": type("Record | null").describe( - credentialMetadataDescription, - ), - createdAt: "string", - updatedAt: "string", -}); - -export const CredentialRequirement = type({ - providerName: "string", - "scopes?": "string[]", - source: CredentialSourceType.describe( - "Whose credential satisfies this requirement at launch: `tenant` (a credential owned by the tenant), `creator` (the definition author's), or `invoker` (whoever launched the workflow run).", - ), - "name?": "string", -}); diff --git a/vendor/intx-types/src/grant-snapshot.ts b/vendor/intx-types/src/grant-snapshot.ts deleted file mode 100644 index d8b304f..0000000 --- a/vendor/intx-types/src/grant-snapshot.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Serializable projection of the deploy-time capability walk. -// -// The capability walk produces per-step grant declarations keyed by two -// `Map`s (grant strings plus a tool-grant-to-effect map) alongside the -// definition's grant requirements. Persisting that walk so a run can -// materialize grants without re-reading and re-walking a `workflow.json` -// blob needs a plain-data shape: the `Map`s flatten to arrays and records -// so the whole thing survives a JSON round-trip. -// -// `perStep[i].grantEffects` covers TOOL grants only, mirroring the walk's -// `GrantDeclarations.grantEffects`; director/capability/inference.source/ -// mail.* grants live in `grants` and carry no effect entry. -// -// `grantRequirements` is the full, unfiltered requirement list (both -// creator- and invoker-sourced). Consumers filter it by source themselves; -// the snapshot does not filter here. - -import { type } from "arktype"; - -import { grantEffects, GrantRequirement } from "./grants"; - -const Effect = type.enumerated(...grantEffects); - -const GrantWalkStepSnapshot = type({ - stepId: "string", - grants: "string[]", - grantEffects: { - "[string]": Effect, - }, -}); - -export const GrantWalkSnapshot = type({ - perStep: GrantWalkStepSnapshot.array(), - grantRequirements: GrantRequirement.array(), -}); - -export type GrantWalkSnapshot = typeof GrantWalkSnapshot.infer; diff --git a/vendor/intx-types/src/grant-wire.ts b/vendor/intx-types/src/grant-wire.ts deleted file mode 100644 index a5e5f38..0000000 --- a/vendor/intx-types/src/grant-wire.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Arktype validators for GrantRule wire serialization. -// -// GrantRule.expiresAt is a Date | null at runtime, but JSON round-trips -// turn it into a string | null. This validator accepts either form and -// coerces strings back to Date instances, making it safe to use when -// deserializing grants that have round-tripped through JSON. - -import { type } from "arktype"; - -import { grantEffects, grantOrigins } from "./grants"; - -const Effect = type.enumerated(...grantEffects); -const Origin = type.enumerated(...grantOrigins); - -const DateOrNull = type("Date | null").or(type("string.date.parse")); - -export const WireGrantRule = type({ - id: "string", - resource: "string", - action: "string", - effect: Effect, - origin: Origin, - conditions: "Record | null", - expiresAt: DateOrNull, - roleId: "string | null", - principalId: "string | null", -}); - -export type WireGrantRule = typeof WireGrantRule.infer; diff --git a/vendor/intx-types/src/grants.ts b/vendor/intx-types/src/grants.ts deleted file mode 100644 index 99f64a6..0000000 --- a/vendor/intx-types/src/grants.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { type } from "arktype"; - -export const grantEffects = ["allow", "deny", "ask"] as const; -export type GrantEffect = (typeof grantEffects)[number]; - -export const grantOrigins = ["system", "role", "creator", "invoker"] as const; -export type GrantOrigin = (typeof grantOrigins)[number]; - -export const grantRequirementSources = ["creator", "invoker"] as const; -export type GrantRequirementSource = (typeof grantRequirementSources)[number]; - -const Effect = type.enumerated(...grantEffects); -const Origin = type.enumerated(...grantOrigins); -const GrantSourceType = type.enumerated(...grantRequirementSources); - -const effectDescription = - "Outcome when this grant is the one resolved for a request: `allow` permits the action, `deny` blocks it, `ask` requires interactive approval before proceeding. When several grants match, the most specific wins, and at equal specificity the strongest effect wins (`deny` over `ask` over `allow`)."; - -const originDescription = - "Records where the grant came from: `system` (built-in), `role` (granted via a role), `creator` (from the workflow definition author), or `invoker` (delegated by whoever launched the workflow run). Origin is provenance only; it does not affect evaluation precedence."; - -const conditionsDescription = - "Optional map of named conditions that must all pass for the grant to apply, evaluated against a condition registry at authorization time. A grant with conditions is skipped (fails closed) when no registry is available to evaluate them."; - -const specificityDescription = - "Computed match-strength score used to rank grants: the count of non-wildcard characters in the resource and action patterns, with exact (wildcard-free) patterns scored far above prefix globs. Higher wins; ties are broken by effect priority."; - -export const CreateGrant = type({ - "roleId?": "string | null", - "principalId?": "string | null", - resource: "string", - action: "string", - effect: Effect.describe(effectDescription), - "conditions?": type("Record | null").describe( - conditionsDescription, - ), - origin: Origin.describe(originDescription), - "expiresAt?": "string | null", -}).narrow((g, ctx) => { - // A grant targets exactly one of a role or a principal -- the same invariant - // the `grant_target_exactly_one` DB CHECK enforces. Rejecting both/neither - // here surfaces a malformed request as a 400 rather than a database 500. - const targets = (g.roleId != null ? 1 : 0) + (g.principalId != null ? 1 : 0); - if (targets !== 1) { - return ctx.mustBe( - "a grant with exactly one target: set roleId or principalId, not both and not neither", - ); - } - return true; -}); - -export const UpdateGrant = type({ - "effect?": Effect.describe(effectDescription), - "conditions?": type("Record | null").describe( - conditionsDescription, - ), - "expiresAt?": "string | null", -}); - -export const GrantResponse = type({ - id: "string", - tenantId: "string", - "roleId?": "string | null", - "roleName?": "string | null", - "principalId?": "string | null", - "principalName?": "string | null", - resource: "string", - action: "string", - effect: Effect.describe(effectDescription), - "conditions?": type("Record | null").describe( - conditionsDescription, - ), - origin: Origin.describe(originDescription), - "expiresAt?": "string | null", - createdAt: "string", - updatedAt: "string", -}); - -export const EvaluateRequest = type({ - resource: "string", - action: "string", -}); - -export const MatchedGrant = type({ - id: "string", - resource: "string", - action: "string", - effect: Effect.describe(effectDescription), - origin: Origin.describe(originDescription), - "specificity?": type("number").describe(specificityDescription), -}); -export type MatchedGrant = typeof MatchedGrant.infer; - -export const EvaluateResult = type({ - effect: Effect.describe( - "The resolved outcome for the query: the effect of the winning grant, or `deny` when no grant matched (authorization fails closed).", - ), - matchingGrants: MatchedGrant.array().describe( - "Every grant that matched the requested resource and action, including the one that won. Useful for debugging why a request was allowed, denied, or required approval.", - ), -}); - -export const GrantRequirement = type({ - resource: "string", - action: "string", - "effect?": Effect.describe( - "Effect to assign the materialized grant: `allow`, `deny`, or `ask`. Defaults to `allow` when omitted.", - ), - source: GrantSourceType.describe( - "Whose authority the grant is resolved against at launch: `creator` (the definition author) or `invoker` (whoever launched the workflow run) -- satisfied only if that party actually holds the requested capability. Tenant-owned credential use is not a grant requirement: it is authorized by ownership at resolution and its consumer-scoping grant is stamped directly (see CREDENTIALS.md).", - ), - "conditions?": "Record | null", -}); -export type GrantRequirement = typeof GrantRequirement.infer; diff --git a/vendor/intx-types/src/has-code.ts b/vendor/intx-types/src/has-code.ts deleted file mode 100644 index 718dfa3..0000000 --- a/vendor/intx-types/src/has-code.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Type guard for errors with a Node-style `{ code: string }` shape, -// as thrown by Node.js (POSIX errno), isomorphic-git, and similar. - -export function hasCode(err: unknown): err is { code: string } { - return ( - typeof err === "object" && - err !== null && - "code" in err && - typeof (err as { code: unknown }).code === "string" - ); -} diff --git a/vendor/intx-types/src/hex.ts b/vendor/intx-types/src/hex.ts deleted file mode 100644 index cf1187c..0000000 --- a/vendor/intx-types/src/hex.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Hex codec for byte strings. -// -// Used across the codebase for Ed25519 key and signature serialization on the -// wire. Centralizing here keeps the encoding stable and the error wording -// consistent. - -export function hexEncode(bytes: Uint8Array): string { - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -} - -export function hexDecode(hex: string): Uint8Array { - if (hex.length % 2 !== 0) { - throw new Error(`hexDecode: odd-length input (${hex.length} chars)`); - } - if (!/^[0-9a-fA-F]*$/.test(hex)) { - throw new Error("hexDecode: input contains non-hex characters"); - } - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < bytes.length; i++) { - bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); - } - return bytes; -} diff --git a/vendor/intx-types/src/index.ts b/vendor/intx-types/src/index.ts deleted file mode 100644 index 755229d..0000000 --- a/vendor/intx-types/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -export * from "./common"; -export * from "./me"; -export * from "./tenants"; -export * from "./principals"; -export * from "./roles"; -export * from "./grants"; -export * from "./grant-snapshot"; -export * from "./signals"; -export * from "./instances"; -export * from "./workflows"; -export * from "./attachments"; -export * from "./sessions"; -export * from "./approvals"; -export * from "./wallets"; -export * from "./providers"; -export * from "./oauth-clients"; -export * from "./credentials"; -export * from "./credential-cipher"; -export * from "./signer-identity"; -export * from "./mediated-credential"; -export * from "./assets"; -export * from "./offerings"; -export * from "./models"; -export * from "./capabilities"; -export * from "./catalog"; -export * from "./observability"; -export * from "./agent-address"; -export * from "./agent-data"; -export * from "./hex"; -export * from "./message-id"; -export * from "./workflow-run-id"; -export * from "./base64"; -export * from "./base64url"; -export * from "./concat"; -export * from "./has-code"; -export * from "./audit"; -export * from "./sidecar-allocation"; -export * from "./sidecar-capabilities"; diff --git a/vendor/intx-types/src/instances.ts b/vendor/intx-types/src/instances.ts deleted file mode 100644 index 9f2786c..0000000 --- a/vendor/intx-types/src/instances.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { type } from "arktype"; -import { InvokerModelPreferences } from "./catalog"; -import { grantEffects } from "./grants"; -import { ApprovalResponse } from "./approvals"; - -const Effect = type.enumerated(...grantEffects); - -export const workflowRunStatuses = [ - "deployed", - "running", - "updating", - "error", - "stopped", -] as const; -export type WorkflowRunStatus = (typeof workflowRunStatuses)[number]; - -const WorkflowRunStatusType = type.enumerated(...workflowRunStatuses); - -export const CreateWorkflowRun = type({ - definitionId: "string", - "modelPreferences?": InvokerModelPreferences.describe( - "The invoker's per-model provider preferences for this launch. Applied over the tenant-visible providers after the definition's preferences; it can only reorder or restrict, never introduce a provider the tenant catalog lacks. Persisted on the run so re-resolution reuses it.", - ), - "invokerGrants?": type({ - resource: "string", - action: "string", - "effect?": Effect, - "conditions?": "Record | null", - }) - .array() - .describe( - "Capabilities the invoker is willing to delegate to the run, resolved against the invoker's own authority at launch. These are materialized as grants on the run principal in addition to any grants from the definition's own requirements.", - ), -}); - -export const WorkflowRunResponse = type({ - id: "string", - definitionId: "string", - definitionName: "string", - tenantId: "string", - address: "string", - status: WorkflowRunStatusType.describe( - "Lifecycle state of this run: `deployed` (provisioned on a sidecar, not yet started), `running` (started and serving), `updating` (rolling to a new definition version), `error` (launch or runtime failure), or `stopped` (undeployed).", - ), - "publicKey?": "string | null", - "kernelId?": "string | null", - "sidecarId?": "string | null", - createdAt: "string", - updatedAt: "string", - "endedAt?": "string | null", -}); - -export const WorkflowRunHealth = type({ - liveness: "'ok' | 'unhealthy'", - readiness: "'ok' | 'not_ready' | 'unhealthy'", - "lastCheckedAt?": "string | null", -}); - -export const RunAuthorizationGrant = type({ - resource: "string", - action: "string", - effect: Effect, -}); - -export const RunAuthorizationResponse = type({ - runId: "string", - grants: RunAuthorizationGrant.array().describe( - "The run's effective authorization floor: each capability the run's principal holds with its resolved effect. A standing 'always' approval mutates the tool's committed grant in place at resolve time (approve-always sets 'allow', reject-always sets 'deny'), so a standing-resolved tool reads that effect directly. Read straight from the run's committed grants; complete for the source-ref deploy lineage (the shipping pipeline), whose committed grants carry every tool's effect. A pinned-tool deploy's ask floor is injected sidecar-side and is not reflected here.", - ), -}); - -export const RunApprovalsResponse = type({ - runId: "string", - approvals: ApprovalResponse.array().describe( - "The run's approval decisions, newest first, across every status. A tool an operator turned into a standing allow appears here with scope 'always' and status 'approved'.", - ), -}); diff --git a/vendor/intx-types/src/me.ts b/vendor/intx-types/src/me.ts deleted file mode 100644 index e5091b2..0000000 --- a/vendor/intx-types/src/me.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { type } from "arktype"; - -export const UserProfile = type({ - id: "string", - name: "string", - email: "string", - emailVerified: "boolean", - "image?": "string | null", - createdAt: "string", - updatedAt: "string", -}); - -export const PrincipalSummary = type({ - principalId: "string", - tenantId: "string", - tenantName: "string", - tenantSlug: "string", - kind: "'user' | 'agent'", - status: "'active' | 'suspended' | 'invited' | 'deactivated'", - roles: type({ - id: "string", - name: "string", - }).array(), -}); - -export const WorkflowRunSummary = type({ - id: "string", - tenantId: "string", - tenantName: "string", - definitionId: "string", - definitionName: "string", - address: "string", - status: "'deployed' | 'running' | 'updating' | 'error' | 'stopped'", - createdAt: "string", -}); - -export const SessionSummary = type({ - id: "string", - tenantId: "string", - tenantName: "string", - definitionId: "string", - definitionName: "string", - status: "'idle' | 'ending' | 'ended'", - createdAt: "string", - "lastActivityAt?": "string | null", -}); - -export const ApprovalSummary = type({ - id: "string", - tenantId: "string", - tenantName: "string", - definitionId: "string", - definitionName: "string", - sessionId: type("string").describe( - "Internal FK to the session channel. The run ID can be resolved via the session relationship.", - ), - resource: "string", - action: "string", - createdAt: "string", -}); diff --git a/vendor/intx-types/src/mediated-credential.ts b/vendor/intx-types/src/mediated-credential.ts deleted file mode 100644 index 6346166..0000000 --- a/vendor/intx-types/src/mediated-credential.ts +++ /dev/null @@ -1,117 +0,0 @@ -// The runtime mediated-credential surface: how a resolved provider-backed -// credential reaches the consumer that uses it (a tool, or the built-in -// reactor) WITHOUT handing over the raw secret. -// -// A consumer declares a credential handle (see `ToolCredentialHandle`) and, at -// handler-init, resolves a *mediated credential* -- a handle that lets it -// authenticate against the provider without holding the secret on its own API. -// An HTTP credential mediates by exposing an authed `fetch` pinned to the -// credential's provider origin; the bearer token is injected per request and -// never surfaced. -// -// Honest scope of the mediation: it is NOT containment against hostile tool -// code. A tool that legitimately receives an http mediated credential can read -// the Authorization header the fetch sends. What mediation buys is (a) the -// secret is off the tool's declared API surface, (b) a single rotation point -- -// material is read fresh per use, so a rotation reaches every holder without -// re-shaping the handle -- and (c) consumer-scoped resolution. Confidentiality -// from the receiving tool needs process/VM isolation, a different boundary. -// -// The provider plugin owns how a handle is shaped; the acquisition of material -// (resolve a credential row, authorize, decrypt) lives on the delivery side and -// is never the plugin's decision. - -/** The current secret material behind a credential, read fresh at each use. */ -export interface CredentialMaterial { - readonly secret: string; -} - -/** - * Reads the current material for one credential. A provider handle calls this - * per use rather than capturing a snapshot, so a rotation that updates the - * underlying cell is picked up without rebuilding the handle. - */ -export type CredentialMaterialSource = () => CredentialMaterial; - -/** - * Resolves the current material for a credential BY id from the run's credential - * cell. Inference uses this to fill a request's credential from - * `InferenceSource.credentialId` at send time -- the same cell tool credentials - * resolve from, so neither rail holds an inline secret. Keyed by `credentialId` - * (not bound to one, unlike `CredentialMaterialSource`) because a source's - * forward-only failover chain carries a distinct credential per entry. Reads - * live, so a rotation of the cell is picked up on the next call; fails closed - * when the credential is absent (revoked or never delivered). This is the single - * seam a future mode swaps to keep the raw secret out of the child entirely. - */ -export type CredentialMaterialResolver = ( - credentialId: string, -) => CredentialMaterial; - -/** What a provider plugin is given to shape a mediated credential. */ -export interface CredentialShapeContext { - /** - * The provider origin the credential authenticates to (e.g. - * `https://api.github.com`). An http handle pins its requests to this origin. - */ - readonly origin: string; - /** Reads the current secret material at each use (rotation indirection). */ - readCurrentMaterial: CredentialMaterialSource; -} - -/** Fields shared by every mediated-credential variant. */ -export interface MediatedCredentialBase { - /** Discriminates the variant a consumer narrows on. */ - readonly kind: string; - /** - * Release resources the handle allocated. An http handle allocates none; a - * future key-file/socket handle would. Idempotent; run on teardown. - */ - dispose(): void | Promise; -} - -/** - * An HTTP-authenticated mediated credential: an authed `fetch` pinned to the - * credential's provider origin. A request whose resolved origin is not the - * pinned one is refused, and redirects are not followed (a 3xx is returned to - * the caller), so the bearer token is only ever sent to the pinned origin and a - * holder cannot redirect it to an attacker-chosen host. - */ -export interface HttpMediatedCredential extends MediatedCredentialBase { - readonly kind: "http"; - fetch(input: string | URL | Request, init?: RequestInit): Promise; -} - -/** - * A mediated credential handed to a consumer at resolve time. A discriminated - * union on `kind`; `http` is the only variant today. Future provider kinds - * (e.g. an ssh key-file + agent socket) extend the union with their own `kind`. - */ -export type MediatedCredential = HttpMediatedCredential; - -/** - * A provider plugin: the seam that owns how a mediated credential is shaped for - * its provider. Registered under `key`, matched against a resolved provider's - * plugin identifier. The plugin shapes a handle from a material source; it does - * not acquire material and never decides authorization -- both happen upstream, - * at the delivery boundary, before a plugin is ever consulted. - */ -export interface CredentialProvider { - readonly key: string; - shape(context: CredentialShapeContext): MediatedCredential; -} - -/** - * The runtime `credentials` capability: a sub-registry a consumer queries by - * the credential handle it declared, receiving a mediated credential. It is the - * dynamic (per-binding) axis that lives under the fixed, statically-typed - * capability map. - * - * Resolution is consumer-scoped and fail-closed: it yields a handle only for a - * credential the calling consumer is authorized to use. An unbound handle, or - * one the consumer lacks a `credential:{id}` / `use` grant for, throws. `resolve` - * is async because the authorization check is. - */ -export interface CredentialCapability { - resolve(handle: string): Promise; -} diff --git a/vendor/intx-types/src/message-id.ts b/vendor/intx-types/src/message-id.ts deleted file mode 100644 index 523afff..0000000 --- a/vendor/intx-types/src/message-id.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Canonical Message-ID derivation for a raw RFC 2822 message. -// -// This id identifies the MESSAGE, not the run it triggers. It is the -// claim-check dedup key the inbox pipeline keys on (the same bytes -// delivered twice consume once), and it must be derived identically -// wherever a message is fingerprinted, or a redelivery would be treated -// as a fresh message. This module is the single source of truth those -// call sites import. -// -// A workflow run's id is NOT this value -- a deployment's one addressable -// top-level run uses the local part of its mail address as its stable runId -// (see `deriveWorkflowRunId`). The two ids are distinct: this one is -// per-message, while the top-level runId is per-deployment. -// -// The identifier is the `Message-ID` header value when the message -// carries one, and a sha256 of the raw bytes otherwise -- so a message -// from a non-RFC 2822 transport still receives a deterministic id. - -import { hexEncode } from "./hex"; - -/** - * Derive the canonical Message-ID for a raw message. Returns the parsed - * `Message-ID` header when present, else the hex-encoded sha256 of the - * raw bytes. - */ -export async function deriveMessageId(rawMessage: Uint8Array): Promise { - const messageIdFromHeader = parseMessageIdHeader(rawMessage); - if (messageIdFromHeader !== null) { - return messageIdFromHeader; - } - const digest = await crypto.subtle.digest( - "SHA-256", - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ArrayBuffer-backed at the call site; Web Crypto's BufferSource type rejects Uint8Array under TS 5.9 (microsoft/TypeScript#62240) - rawMessage as Uint8Array, - ); - return hexEncode(new Uint8Array(digest)); -} - -/** - * Parse the `Message-ID` header value from a raw message, or `null` when - * the message carries no such header. - * - * The parser walks the message until the headers/body separator - * (`CRLF CRLF` per RFC 2822 §2.1, with the lone-`LF` variant tolerated to - * match common in-memory senders). Header-field unfolding follows RFC - * 2822 §2.2.3: a continuation line begins with whitespace and appends to - * the prior line. Header-name comparison is case-insensitive per RFC 2822 - * §1.2.2. - */ -export function parseMessageIdHeader(rawMessage: Uint8Array): string | null { - const text = new TextDecoder("utf-8", { fatal: false }).decode(rawMessage); - // Headers end at the first blank line. RFC 2822 mandates `CRLF CRLF` - // but tolerate `LF LF` for callers that normalize line endings. - let headerSection = text; - const crlfBoundary = text.indexOf("\r\n\r\n"); - const lfBoundary = text.indexOf("\n\n"); - if (crlfBoundary >= 0 && (lfBoundary < 0 || crlfBoundary < lfBoundary)) { - headerSection = text.slice(0, crlfBoundary); - } else if (lfBoundary >= 0) { - headerSection = text.slice(0, lfBoundary); - } - // Unfold continuation lines (a line starting with WSP belongs to - // the prior header field). - const lines = headerSection.split(/\r?\n/); - const unfolded: string[] = []; - for (const line of lines) { - if (line.length > 0 && (line[0] === " " || line[0] === "\t")) { - if (unfolded.length === 0) continue; - unfolded[unfolded.length - 1] += " " + line.trim(); - continue; - } - unfolded.push(line); - } - for (const line of unfolded) { - const colon = line.indexOf(":"); - if (colon < 0) continue; - const name = line.slice(0, colon).trim().toLowerCase(); - if (name !== "message-id") continue; - return line.slice(colon + 1).trim(); - } - return null; -} diff --git a/vendor/intx-types/src/models.ts b/vendor/intx-types/src/models.ts deleted file mode 100644 index 3262753..0000000 --- a/vendor/intx-types/src/models.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type } from "arktype"; - -import { Capability } from "./capabilities"; -import { ModelProviderPlugin, PricingRowResponse } from "./catalog"; - -export const ModelOfferingInfo = type({ - offeringId: type("string").describe( - "Catalog primary key of the model-provider offering this entry describes.", - ), - providerId: "string", - providerName: type("string").describe( - "The model-provider's catalog name, as shown to operators.", - ), - plugin: ModelProviderPlugin, - priority: type("number").describe( - "Source-resolution ordering hint for this offering; lower values are preferred first.", - ), - deploymentTags: "string[]", - capabilities: Capability.array().describe( - "Curated capability tags this provider advertises for this model.", - ), - pricing: PricingRowResponse.array().describe( - "The active price per currency for this offering: for each currency, the latest pricing row in effect at the time of the discovery request.", - ), -}); -export type ModelOfferingInfo = typeof ModelOfferingInfo.infer; - -export const ModelInfo = type({ - id: "string", - canonicalName: type("string").describe( - "The model's tenant-unique canonical name, matched against an agent's model requirements.", - ), - "displayName?": "string | null", - "description?": "string | null", - offerings: ModelOfferingInfo.array().describe( - "One entry per provider that offers this model in the tenant's resolved catalog, ordered by resolution priority.", - ), -}); -export type ModelInfo = typeof ModelInfo.infer; diff --git a/vendor/intx-types/src/oauth-clients.ts b/vendor/intx-types/src/oauth-clients.ts deleted file mode 100644 index 38ddc63..0000000 --- a/vendor/intx-types/src/oauth-clients.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { type } from "arktype"; - -const redirectUrisDescription = - "Allowed OAuth redirect URIs for this client. The authorization callback must match one of these."; - -const defaultScopesDescription = - "Scopes requested by default when initiating an authorization flow with this client."; - -const oauthClientMetadataDescription = - "Free-form client-specific configuration not covered by the typed fields. Not interpreted by the hub."; - -export const CreateOAuthClient = type({ - providerId: "string", - name: "string", - clientId: "string", - clientSecret: "string", - "redirectUris?": type("string[]").describe(redirectUrisDescription), - "defaultScopes?": type("string[]").describe(defaultScopesDescription), - "metadata?": type("Record").describe( - oauthClientMetadataDescription, - ), -}); - -export const UpdateOAuthClient = type({ - "name?": "string", - "clientId?": "string", - "clientSecret?": "string", - "redirectUris?": type("string[] | null").describe(redirectUrisDescription), - "defaultScopes?": type("string[] | null").describe(defaultScopesDescription), - "metadata?": type("Record | null").describe( - oauthClientMetadataDescription, - ), -}); - -export const OAuthClientResponse = type({ - id: "string", - tenantId: "string", - providerId: "string", - name: "string", - "redirectUris?": type("string[] | null").describe(redirectUrisDescription), - "defaultScopes?": type("string[] | null").describe(defaultScopesDescription), - "metadata?": type("Record | null").describe( - oauthClientMetadataDescription, - ), - createdAt: "string", - updatedAt: "string", -}); diff --git a/vendor/intx-types/src/observability.ts b/vendor/intx-types/src/observability.ts deleted file mode 100644 index 61027ba..0000000 --- a/vendor/intx-types/src/observability.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type } from "arktype"; - -export const LogEntry = type({ - timestamp: "string", - level: "'debug' | 'info' | 'warn' | 'error'", - message: "string", - "metadata?": "Record | null", -}); - -export const LogQuery = type({ - "level?": "'debug' | 'info' | 'warn' | 'error'", - "startTime?": "string", - "endTime?": "string", -}); - -export const MetricsResponse = type({ - agentId: "string", - "messageCount?": "number", - "tokenUsage?": { - "input?": "number", - "output?": "number", - "total?": "number", - }, - "cost?": "string", - "avgLatencyMs?": "number", - "errorRate?": "number", -}); - -export const TraceQuery = type({ - "agentId?": "string", - "sessionId?": "string", - "traceId?": "string", - "startTime?": "string", - "endTime?": "string", -}); - -export const SpanResponse = type({ - spanId: "string", - traceId: "string", - "parentSpanId?": "string | null", - name: "string", - "agentId?": "string | null", - startTime: "string", - "endTime?": "string | null", - "durationMs?": "number | null", - "status?": "'ok' | 'error'", - "attributes?": "Record | null", -}); - -export const TraceResponse = type({ - traceId: "string", - spans: type({ - spanId: "string", - traceId: "string", - "parentSpanId?": "string | null", - name: "string", - "agentId?": "string | null", - startTime: "string", - "endTime?": "string | null", - "durationMs?": "number | null", - "status?": "'ok' | 'error'", - "attributes?": "Record | null", - }).array(), -}); diff --git a/vendor/intx-types/src/offerings.ts b/vendor/intx-types/src/offerings.ts deleted file mode 100644 index 2bf7ac6..0000000 --- a/vendor/intx-types/src/offerings.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { type } from "arktype"; - -export const CreateOffering = type({ - agentId: "string", - name: "string", - "description?": "string", - "pricing?": { - "base?": { - amount: "string", - currency: "string", - }, - "methods?": "string[]", - "negotiable?": "boolean", - "bounds?": { - "min?": "string", - "max?": "string", - }, - }, - "schema?": "Record", -}); - -export const UpdateOffering = type({ - "name?": "string", - "description?": "string", - "pricing?": { - "base?": { - amount: "string", - currency: "string", - }, - "methods?": "string[]", - "negotiable?": "boolean", - "bounds?": { - "min?": "string", - "max?": "string", - }, - }, - "schema?": "Record", -}); - -export const OfferingSearch = type({ - "name?": "string", - "minPrice?": "string", - "maxPrice?": "string", - "paymentMethod?": "string", -}); - -export const OfferingDetail = type({ - id: "string", - agentId: "string", - agentName: "string", - tenantId: "string", - name: "string", - "description?": "string | null", - "pricing?": { - "base?": { - amount: "string", - currency: "string", - }, - "methods?": "string[]", - "negotiable?": "boolean", - "bounds?": { - "min?": "string", - "max?": "string", - }, - }, - "schema?": "Record | null", -}); diff --git a/vendor/intx-types/src/package-json.ts b/vendor/intx-types/src/package-json.ts deleted file mode 100644 index 80fe2fb..0000000 --- a/vendor/intx-types/src/package-json.ts +++ /dev/null @@ -1,98 +0,0 @@ -// Schema for the subset of `package.json` fields the asset substrate -// and tool-package builders read. -// -// Promoted here so the package-registry kind handler (in -// `@intx/hub-sessions`) and the workspace builtin-packing script -// (`bin/build-builtins.ts`) share one definition: the asset -// substrate's validation of an uploaded tarball must match the field -// set the build path emits, otherwise a freshly-packed builtin would -// be rejected for shape reasons the build did not anticipate. - -import path from "node:path"; - -import { type } from "arktype"; - -/** - * A tool package's static declaration of one provider-backed credential it - * needs: an abstract handle plus optional scopes. Advisory only -- a request - * the workflow definition later binds to a concrete credential and the launch-time - * grant gate authorizes; a declaration consents to nothing on its own. The - * handle is the key the binding and the runtime delivery use. - */ -export const ToolCredentialHandle = type(/^[a-z0-9][a-z0-9._-]*$/); - -export const ToolCredentialDeclaration = type({ - handle: ToolCredentialHandle, - "scopes?": "string[]", -}); -export type ToolCredentialDeclaration = typeof ToolCredentialDeclaration.infer; - -/** - * The credential declarations for one package, with the unique-handle - * invariant enforced at parse time: a handle is the binding/delivery key, so a - * duplicate within a single package is a defect the upload boundary must - * reject rather than let collapse silently downstream. - */ -export const ToolCredentialDeclarationArray = - ToolCredentialDeclaration.array().narrow((decls, ctx) => { - const seen = new Set(); - for (const decl of decls) { - if (seen.has(decl.handle)) { - return ctx.mustBe( - `an array with no duplicate credential handles; "${decl.handle}" appears more than once`, - ); - } - seen.add(decl.handle); - } - return true; - }); -export type ToolCredentialDeclarationArray = - typeof ToolCredentialDeclarationArray.infer; - -/** - * Required fields plus the `interchange` extensions used to identify - * interchange packages: `tools` names the sidecar-bundle entry, `credentials` - * statically declares the provider-backed credentials the package's tools may - * need, `workflow` names the module whose evaluation produces a workflow - * package's `WorkflowDefinition`, `directors` names the module whose exports - * are the package's custom `defineDirector` factories, `loops` names the module - * whose exports are the package's `loop` `while`/`carry` functions, and - * `actions` names the module whose exports are the package's `action` handlers. - * `loops` and `actions` refs are resolved by export name at establish. - * `onUndeclaredKey("ignore")` lets the arbitrary upstream npm fields pass - * through without listing them. - */ -export const PackageJSON = type({ - name: "string", - version: "string", - "interchange?": type({ - "tools?": "string", - "credentials?": ToolCredentialDeclarationArray, - "workflow?": "string", - "directors?": "string", - "loops?": "string", - "actions?": "string", - }).onUndeclaredKey("ignore"), -}).onUndeclaredKey("ignore"); -export type PackageJSON = typeof PackageJSON.infer; - -/** - * True when `entry` -- an `interchange.workflow`/`interchange.directors` - * module path relative to its package -- stays inside the package directory. - * An absolute path or a `..` traversal escapes and returns false. - * - * This is the string-level half of the loader's containment rule. The - * load-time loader (`resolveContainedEntry`) pairs it with a realpath-based - * symlink-escape check that only a materialized directory can run; the - * push-time asset validator, which has no filesystem, relies on this string - * half alone. Both boundaries call this one predicate so they cannot diverge - * on what "contained" means. The check uses POSIX path semantics so the - * result does not depend on the host's separator or cwd. - */ -export function isContainedEntryPath(entry: string): boolean { - if (path.posix.isAbsolute(entry)) { - return false; - } - const normalized = path.posix.normalize(entry); - return normalized !== ".." && !normalized.startsWith(`..${path.posix.sep}`); -} diff --git a/vendor/intx-types/src/principals.ts b/vendor/intx-types/src/principals.ts deleted file mode 100644 index d70860b..0000000 --- a/vendor/intx-types/src/principals.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { type } from "arktype"; - -export const principalKinds = ["user", "agent", "workflow"] as const; -export type PrincipalKind = (typeof principalKinds)[number]; - -export const principalStatuses = [ - "active", - "suspended", - "invited", - "deactivated", -] as const; -export type PrincipalStatus = (typeof principalStatuses)[number]; - -export const updatablePrincipalStatuses = [ - "active", - "suspended", - "deactivated", -] as const; -export type UpdatablePrincipalStatus = - (typeof updatablePrincipalStatuses)[number]; - -const Kind = type.enumerated(...principalKinds); -const Status = type.enumerated(...principalStatuses); -const UpdatableStatus = type.enumerated(...updatablePrincipalStatuses); - -export const PrincipalResponse = type({ - id: "string", - tenantId: "string", - kind: Kind.describe( - "Whether this principal represents a `user` (a human account), an `agent`, or a `workflow` (a workflow run).", - ), - refId: type("string").describe( - "Identifier of the underlying entity this principal stands for: the auth user id when `kind` is `user`, an agent-instance id when `kind` is `agent`, or a workflow run (`run_...`) or workflow definition (`wfd_...`) id when `kind` is `workflow`. Unique per tenant and kind.", - ), - displayName: "string", - "email?": "string", - status: Status.describe( - "Account state of the principal: `active`, `suspended`, `invited` (membership pending acceptance), or `deactivated`.", - ), - roles: type({ - id: "string", - name: "string", - }).array(), - createdAt: "string", - updatedAt: "string", -}); - -export const UpdatePrincipal = type({ - status: UpdatableStatus.describe( - "New account state for the principal. Only `active`, `suspended`, and `deactivated` are settable; `invited` is reached only through the invitation flow.", - ), -}); - -export const InviteMember = type({ - email: "string", - "roleId?": "string", -}); diff --git a/vendor/intx-types/src/providers.ts b/vendor/intx-types/src/providers.ts deleted file mode 100644 index f3c0a46..0000000 --- a/vendor/intx-types/src/providers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { type } from "arktype"; - -const pluginDescription = - "Identifier of the integration this provider drives (for example the inference backend). Used to dispatch to the matching plugin and as the prefix when forming fully-qualified model ids (`plugin:model`)."; - -const providerScopesDescription = - "OAuth scopes associated with this provider integration."; - -const providerMetadataDescription = - "Free-form provider-specific configuration not covered by the typed fields. Not interpreted by the hub."; - -const apiBaseUrlDescription = - "The API origin a credential from this provider authenticates to (for example https://api.github.com). A provider that backs an origin-pinned credential must set it; OAuth-login-only providers may omit it."; - -export const CreateProvider = type({ - name: "string", - plugin: type("string").describe(pluginDescription), - "apiBaseUrl?": type("string").describe(apiBaseUrlDescription), - "authorizationUrl?": "string", - "tokenUrl?": "string", - "userInfoUrl?": "string", - "scopes?": type("string[]").describe(providerScopesDescription), - "metadata?": type("Record").describe( - providerMetadataDescription, - ), -}); - -export const UpdateProvider = type({ - "name?": "string", - "plugin?": type("string").describe(pluginDescription), - "apiBaseUrl?": type("string | null").describe(apiBaseUrlDescription), - "authorizationUrl?": "string | null", - "tokenUrl?": "string | null", - "userInfoUrl?": "string | null", - "scopes?": type("string[] | null").describe(providerScopesDescription), - "metadata?": type("Record | null").describe( - providerMetadataDescription, - ), -}); - -export const ProviderResponse = type({ - id: "string", - tenantId: "string", - name: "string", - plugin: type("string").describe(pluginDescription), - "apiBaseUrl?": type("string | null").describe(apiBaseUrlDescription), - "authorizationUrl?": "string | null", - "tokenUrl?": "string | null", - "userInfoUrl?": "string | null", - "scopes?": type("string[] | null").describe(providerScopesDescription), - "metadata?": type("Record | null").describe( - providerMetadataDescription, - ), - createdAt: "string", - updatedAt: "string", -}); diff --git a/vendor/intx-types/src/roles.ts b/vendor/intx-types/src/roles.ts deleted file mode 100644 index 201e952..0000000 --- a/vendor/intx-types/src/roles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type } from "arktype"; - -export const CreateRole = type({ - name: "string", - "description?": "string", -}); - -export const UpdateRole = type({ - "name?": "string", - "description?": "string", -}); - -export const RoleResponse = type({ - id: "string", - tenantId: "string", - name: "string", - "description?": "string | null", - isSystem: "boolean", - createdAt: "string", - updatedAt: "string", -}); diff --git a/vendor/intx-types/src/runtime-capabilities.ts b/vendor/intx-types/src/runtime-capabilities.ts deleted file mode 100644 index 374f9df..0000000 --- a/vendor/intx-types/src/runtime-capabilities.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Typed registry of host-provided capabilities that tool packages request at -// handler-init. The host (sidecar harness, or an alternate runtime) builds a -// RuntimeCapabilities instance and hands it to each tool package's factory; -// the package calls `resolve` to obtain typed handles to host services. -// -// The map is the extension point: new capabilities are added by extending -// RuntimeCapabilityMap inside this file. TypeScript permits module -// augmentation of the interface from any consumer, but augmentation from -// outside @intx/types is not the supported extension path; contribute -// keys here so every host sees the same canonical map. - -import type { MessageTransport } from "./runtime"; -import type { CredentialCapability } from "./mediated-credential"; - -/** - * Registry of capability keys to the value types they resolve to. Keys are - * dotted strings scoped by subsystem (e.g. `mail.transport`). - * - * Adding a capability: extend this interface with the new key and its value - * type, then have a host populate it when constructing a - * `RuntimeCapabilities`. - */ -export interface RuntimeCapabilityMap { - /** - * The bound agent's message transport — the SMTP/IMAP-equivalent handle - * for sending and receiving mail. - */ - "mail.transport": MessageTransport; - - /** - * Provider-backed credentials the agent's tools resolve by their declared - * handle. Unlike the other keys, its value is itself a sub-registry: the set - * of bound handles is per-deploy runtime data, not known at compile time, so - * the dynamic axis lives inside `CredentialCapability` while this outer map - * stays fixed and typed. Resolution is consumer-scoped and fail-closed. - */ - credentials: CredentialCapability; -} - -export type RuntimeCapabilityKey = keyof RuntimeCapabilityMap; - -/** - * Host-provided capability registry. Tool packages receive an instance at - * construction; `resolve` is intended to be called once per key at - * handler-init, with the returned handle held for the deploy lifetime. - * `resolve` throws naming the key when the host did not provide a value - * for it. - */ -export interface RuntimeCapabilities { - resolve(key: K): RuntimeCapabilityMap[K]; -} - -/** - * Build a resolver from a partial map of capability values. The map is - * snapshotted at construction — later mutation of the input is not visible - * to `resolve`. Keys absent from the snapshot throw at resolve-time with a - * message naming the key. - * - * Use this from any host (harness, test harness, alternate runtime) that - * wants the standard resolver semantics without re-implementing the - * throw-on-missing plumbing. - */ -export function createRuntimeCapabilities( - values: Partial, -): RuntimeCapabilities { - // Snapshot the input. The resolver's lifecycle contract is "resolved - // once at handler-init, held for the deploy lifetime" — later mutation - // of the input map by the host must not be observable here. - const snapshot: Partial = { ...values }; - - return { - resolve(key: K): RuntimeCapabilityMap[K] { - // Object.hasOwn distinguishes "host did not provide" from "host - // provided undefined". Both are distinct failures the host - // should hear about separately. No capability in - // RuntimeCapabilityMap currently resolves to undefined, so the - // second check is a defensive guard against a host accidentally - // wiring an undefined value to a non-nullable capability slot; - // adding a nullable capability in the future means revisiting - // this branch. - if (!Object.hasOwn(snapshot, key)) { - throw new Error( - `Runtime capability "${String(key)}" was requested but not provided by the host`, - ); - } - const value = snapshot[key]; - if (value === undefined) { - throw new Error( - `Runtime capability "${String(key)}" was provided as undefined; no current capability resolves to undefined`, - ); - } - return value; - }, - }; -} - -/** - * Compose a resolver that answers the keys in `overrides` from the override - * map and delegates every other key to `base`. - * - * The host uses this to add a per-bundle capability -- the consumer-scoped - * `credentials` handle, one instance per tool package -- onto a shared - * per-step base bag without re-plumbing the base's keys (`mail.transport` - * and any future shared key stay owned by the step bag). Each tool package's - * bundle receives the same base layered with ITS OWN credentials capability, - * so a package cannot resolve a handle scoped to a different package. - * - * `overrides` is snapshotted at construction, mirroring - * `createRuntimeCapabilities`, so later mutation of the input is not - * observable through `resolve`. An overridden key wired to `undefined` - * throws with the same guard as the base resolver rather than silently - * shadowing `base` with a hole -- a host that layers an undefined value - * has a wiring bug and must hear about it. - */ -export function layerRuntimeCapabilities( - base: RuntimeCapabilities, - overrides: Partial, -): RuntimeCapabilities { - const snapshot: Partial = { ...overrides }; - - return { - resolve(key: K): RuntimeCapabilityMap[K] { - if (Object.hasOwn(snapshot, key)) { - const value = snapshot[key]; - if (value === undefined) { - throw new Error( - `Runtime capability "${String(key)}" was layered as undefined; no current capability resolves to undefined`, - ); - } - return value; - } - return base.resolve(key); - }, - }; -} diff --git a/vendor/intx-types/src/runtime.ts b/vendor/intx-types/src/runtime.ts deleted file mode 100644 index deb3239..0000000 --- a/vendor/intx-types/src/runtime.ts +++ /dev/null @@ -1,2891 +0,0 @@ -// Runtime definitions for the Interchange agent harness. -// -// Wire-facing data types (AbortReason, InferenceSource, ToolDefinition, -// HarnessConfig) are arktype validators so they can be composed into -// WebSocket frame validators and used for runtime validation at parse -// boundaries. Behavioral interfaces (ContextStore, MessageTransport, -// ToolRunner, etc.) remain plain TypeScript. - -import { type } from "arktype"; -import type { AuditRecord, ErrorRecord } from "./audit"; -import { WireGrantRule } from "./grant-wire"; -import type { SignalKind } from "./signals"; - -// --------------------------------------------------------------------------- -// Cryptographic Identity (ARCHITECTURE.md § Cryptographic Identity, -// IMPLEMENTATION.md § Cryptographic Identity: Key Formats) -// --------------------------------------------------------------------------- - -/** - * An Ed25519 key pair as raw bytes. The private key is 32 bytes; the public - * key is the corresponding 32-byte compressed point. - * - * Key material is represented as Uint8Array throughout so it stays - * runtime-agnostic (Bun, Node, browser) and never accidentally leaks through - * JSON serialization. - */ -export type KeyPair = { - privateKey: Uint8Array; - publicKey: Uint8Array; -}; - -/** - * A key-bound cryptographic provider. Each instance is constructed with a - * specific agent's Ed25519 key pair and holds the private key internally. - * - * `sign` uses the instance's own private key — no key parameter is accepted. - * `verify` accepts a public key parameter so the holder can verify messages - * from arbitrary senders without constructing a new provider instance. - * - * The in-memory transport stores one CryptoProvider per registered agent and - * calls `crypto.sign(content)` during `send()` without passing keys around. - * - * Key formats (IMPLEMENTATION.md): - * - Ed25519 in SSH format — control plane interactions - * - Ed25519 in PGP format — message-level signatures over SMTP/IMAP - * - Ed25519 in X.509 format — TLS mutual auth certificates - * - * `getPublicKey` returns the raw public key bytes so callers can publish - * them to the control plane or embed them in discovery metadata. - */ -export interface CryptoProvider { - /** - * Sign `content` with the instance's private key. Returns the Ed25519 - * detached signature as raw bytes. - */ - sign(content: Uint8Array): Promise; - - /** - * Sign `payload` with the instance's private key using the SSH signature - * envelope (sshsig). Returns an ASCII-armored SSH SIGNATURE block suitable - * for the `gpgsig` header of a git commit or any other site that consumes - * `git verify-commit`-compatible signatures. The framing differs from - * `sign`'s raw output; callers that need either format should pick the - * matching method rather than reframing the result themselves. - */ - signSSH(payload: string): Promise; - - /** - * Verify that `signature` over `content` was produced by `publicKey`. - * Returns true if the signature is valid; false otherwise. - */ - verify( - content: Uint8Array, - signature: Uint8Array, - publicKey: Uint8Array, - ): Promise; - - /** The public key for this instance, as raw bytes. */ - getPublicKey(): Uint8Array; -} - -/** - * Generate a fresh Ed25519 key pair. The returned pair is used to construct - * a CryptoProvider instance. - */ -export type GenerateKeyPair = () => Promise; - -// --------------------------------------------------------------------------- -// Message Transport (MESSAGE.md § Transport Interface) -// --------------------------------------------------------------------------- - -/** - * Opaque reference to a message in a specific mailbox. Carries the IMAP UID - * and the mailbox name. Passed to fetch, flag, and move operations without - * requiring re-search. - */ -export type MessageRef = { - uid: number; - mailbox: string; -}; - -/** - * Interchange payload types as defined in MESSAGE.md § Payload Types. - * The type field in structured messages matches the Interchange-Type header. - * - * Exposed as both an arktype validator (for runtime validation at parse - * boundaries and tool-argument schemas) and a derived TypeScript union. - */ -export const InterchangeType = type.enumerated( - "conversation.message", - "conversation.join", - "conversation.leave", - "offering.request", - "offering.response", - "offering.error", - "offering.discover", - "offering.catalog", - "payment.required", - "payment.receipt", - "payment.verified", - "approval.request", - "approval.granted", - "approval.denied", - "system.health", - "system.register", - "system.deregister", - "system.credential.refresh", -); -export type InterchangeType = typeof InterchangeType.infer; - -/** - * Attachment for an outbound message. Content is raw bytes; the transport - * handles Content-Transfer-Encoding (base64 for binary, quoted-printable - * for 8-bit text). - */ -export type MessageAttachment = { - name: string; - contentType: string; - data: Uint8Array; -}; - -/** - * A message the harness submits for delivery via SMTP. The transport - * assembles the PGP/MIME multipart structure, signs it with the agent's - * CryptoProvider, and submits it. - * - * Conversation types (conversation.*) carry `content` as text/plain. - * Structured types carry `payload` as application/vnd.interchange+json. - * Providing both is an error. - * - * (MESSAGE.md § Transport Interface › Outbound) - */ -export type OutboundMessage = { - to: string | string[]; - cc?: string | string[]; - subject?: string; - - type: InterchangeType; - - /** Plain text body — used when type is a conversation.* type. */ - content?: string; - - /** Structured JSON body — used when type is a non-conversation type. */ - payload?: Record; - - /** Human-readable summary for structured messages (the text/plain part). */ - summary?: string; - - attachments?: MessageAttachment[]; - - /** Message-ID of the message being replied to. */ - inReplyTo?: string; - - /** - * The RFC 5322 References chain for a threaded reply: the parent's own - * References plus the parent's Message-ID, in order. When present the - * transport ships it verbatim (after appending `inReplyTo` if it is not - * already the tail) rather than deriving a single-element `[inReplyTo]` - * chain, so a reply carries the full conversational ancestry. Absent for a - * non-reply or a reply whose parent could not be located. - */ - references?: string[]; - - /** Correlation ID linking this message to a pending async request. */ - correlationId?: string; - - /** Reactor session ID from the Interchange-Session-ID header. */ - sessionId?: string; - - /** Tenant ID for the Interchange-Tenant-ID header. */ - tenantId?: string; -}; - -/** - * Receipt returned by `send()`. Contains the assigned Message-ID and - * delivery status. - * - * (MESSAGE.md § Transport Interface › Outbound) - */ -export type SendReceipt = { - messageId: string; - status: "delivered" | "queued"; -}; - -/** - * Parsed headers from an inbound message. Field names follow RFC 5322 and - * the Interchange-specific header conventions from MESSAGE.md § Headers. - */ -export type MessageHeaders = { - from: string; - to: string[]; - cc?: string[]; - date: string; - messageId: string; - inReplyTo?: string; - references?: string[]; - subject?: string; - listId?: string; - - interchangeType?: InterchangeType; - interchangeCorrelationId?: string; - interchangeTenantId?: string; - interchangeAgentId?: string; - interchangeSessionId?: string; - interchangeOfferingId?: string; - interchangeSchemaVersion?: string; - - traceparent?: string; - tracestate?: string; -}; - -/** - * Signature verification status of an inbound message. - * - * - `valid` — signature verified against the sender's public key - * - `invalid` — signature check failed (tampering or wrong key) - * - `unknown` — public key not available for verification - * - `missing` — message was not signed - * - * (MESSAGE.md § Transport Interface › fetchFull) - */ -export const SignatureStatus = type.enumerated( - "valid", - "invalid", - "unknown", - "missing", -); -export type SignatureStatus = typeof SignatureStatus.infer; - -/** - * A parsed MIME part. `content` is the DECODED bytes in memory (the - * transfer-encoding has already been undone). `filename` and `disposition` are - * surfaced from the part's `Content-Disposition` / `Content-Type` so a consumer - * can distinguish an inline part from a named attachment without re-parsing - * headers. - */ -export type MessagePart = { - contentType: string; - content: Uint8Array; - filename?: string; - disposition?: "inline" | "attachment"; - /** - * Original Content-Transfer-Encoding, when a producer chooses to record it. - * Not set for a decoded mail part -- `content` is already decoded, so the - * wire encoding is spent transport metadata. - */ - encoding?: string; -}; - -/** - * A single part of a persisted `Mail`. The bytes live in the durable store; - * this descriptor carries the part's metadata plus an opaque, relocation-stable - * `ref` a `MailPartReader` resolves to the part's bytes. Small UTF-8 text parts - * also carry their decoded `text` inline so a selector can read them without - * resolving. - */ -export type MailPart = { - contentType: string; - filename?: string; - disposition?: "inline" | "attachment"; - ref: string; - text?: string; -}; - -/** - * The single, environment-agnostic interface for reading a persisted mail - * part's bytes. Modeled on `BlobReader`: a consumer -- a workflow step, an - * agent tool, the agent's content-block projection -- resolves a - * `MailPart.ref` to its bytes without knowing or caring where the bytes live - * (a committed file on the sidecar, a blob in a browser runtime). The `ref` - * is opaque; the reader owns its scheme. Threaded to consumers through the - * runtime so a browser runtime can supply its own implementation. - */ -export interface MailPartReader { - /** Resolve a `MailPart.ref` to the part's decoded bytes. Throws if the ref - * is unrecognized or its bytes are missing. */ - read(ref: string): Promise; -} - -/** - * A fully decoded mail message: every header (both the typed, ergonomic subset - * and a raw catch-all with nothing dropped) plus the flat list of decoded leaf - * parts. This is the lossless representation a deployed workflow receives as - * its trigger input; a workflow programs against it directly (select headers, - * walk parts, route on content type), and an agent step projects the parts into - * model content blocks. It is JSON-safe: each part carries a `ref` (not raw - * bytes), so binary content never enters the event log; the runtime resolves a - * `ref` into a loadable `MessagePart` on demand. - */ -export type Mail = { - headers: MessageHeaders; - /** Every header, lowercased name to its ordered values; nothing dropped. */ - rawHeaders: Record; - parts: MailPart[]; -}; - -const MailShape = type({ - // Require the header fields a consumer dereferences unconditionally (the - // sender/recipient a projection reads); other header fields stay optional - // and are carried losslessly in `rawHeaders`. - headers: { - from: "string", - to: "string[]", - }, - rawHeaders: "object", - parts: type({ - contentType: "string", - ref: "string", - "filename?": "string", - "disposition?": "'inline' | 'attachment'", - "text?": "string", - }) - .onUndeclaredKey("reject") - .array(), -}).onUndeclaredKey("reject"); - -/** - * Narrow an opaque value (a workflow step input) to a `Mail`. Used at the - * `agent.send` boundary to decide whether the input is a mail-derived message - * whose parts must be projected into content blocks, or an arbitrary value - * delivered as synthesized text. The strict undeclared-key rejection keeps an - * arbitrary step value that merely carries a `parts` field from matching. - */ -export function isMail(value: unknown): value is Mail { - return !(MailShape(value) instanceof type.errors); -} - -/** - * MIME tree metadata returned by `fetchStructure()`. Describes content types, - * sizes, and dispositions without transferring content. - * - * (MESSAGE.md § Partial Fetch) - */ -export type BodyStructure = { - contentType: string; - size?: number; - disposition?: string; - parts?: BodyStructure[]; -}; - -/** - * A fully parsed inbound message including structured payload, headers, - * attachments, and signature verification status. - * - * (MESSAGE.md § Transport Interface › fetchFull) - */ -export type InboundMessage = { - ref: MessageRef; - headers: MessageHeaders; - flags: string[]; - - /** Plain text body for conversation.* types. */ - content?: string; - - /** Parsed JSON payload for structured types. */ - payload?: { - type: InterchangeType; - version: string; - body: Record; - }; - - attachments?: MessageAttachment[]; - signatureStatus: SignatureStatus; -}; - -/** - * IMAP mailbox descriptor. - * - * (MESSAGE.md § Inbox Management) - */ -export type Mailbox = { - name: string; - role?: string; - delimiter?: string; -}; - -/** - * Current status of an IMAP mailbox, including QRESYNC identifiers. - * - * (MESSAGE.md § Inbox Management) - */ -export type MailboxStatus = { - total: number; - unseen: number; - recent: number; - uidNext: number; - uidValidity: number; - highestModSeq: number; -}; - -/** - * Structured IMAP search query. Maps the IMAP SEARCH grammar to a typed - * object. Supports recursive boolean composition via `and`, `or`, `not`. - * - * (MESSAGE.md § Search) - */ -export type SearchQuery = { - from?: string; - to?: string; - cc?: string; - bcc?: string; - header?: { field: string; contains: string }; - before?: Date; - after?: Date; - on?: Date; - sentBefore?: Date; - sentAfter?: Date; - sentOn?: Date; - hasFlags?: string[]; - missingFlags?: string[]; - body?: string; - text?: string; - largerThan?: number; - smallerThan?: number; - and?: SearchQuery[]; - or?: SearchQuery[]; - not?: SearchQuery; -}; - -/** - * A thread node returned by `thread()`. Carries a message reference and - * child threads representing replies. Implements the RFC 5256 REFERENCES - * threading algorithm. - * - * (MESSAGE.md § Thread Retrieval) - */ -export type Thread = { - ref: MessageRef; - children: Thread[]; -}; - -/** - * QRESYNC state the harness provides when reconnecting to the transport. - * - * (MESSAGE.md § Synchronization) - */ -export type SyncState = { - uidValidity: number; - uidNext: number; - highestModSeq: number; - knownUids?: number[]; -}; - -/** - * Result of a QRESYNC-style sync operation. - * - * (MESSAGE.md § Synchronization) - */ -export type SyncResult = { - vanished: number[]; - changed: { uid: number; flags: string[] }[]; - newMessages: MessageRef[]; - fullResyncRequired: boolean; -}; - -/** - * Distribution list metadata returned by `createList()`. - * - * (MESSAGE.md § Message Topologies) - */ -export type ListInfo = { - address: string; - name: string; - memberCount: number; - createdAt: string; -}; - -/** - * Event emitted by the mailbox watcher callback. Corresponds to IMAP IDLE - * notifications. - * - * (MESSAGE.md § Real-Time Notification) - */ -export type MailboxEvent = - | { type: "exists"; uid: number; headers: MessageHeaders } - | { type: "flagsChanged"; uid: number; flags: string[] } - | { type: "expunged"; uid: number }; - -/** Unsubscribe function returned by `watch()`. */ -export type Unsubscribe = () => void; - -/** - * The message transport interface. Abstracts SMTP and IMAP behind a - * TypeScript API. Implementations range from real SMTP/IMAP servers to - * in-process stubs that route messages through memory. - * - * All long-running operations accept an AbortSignal for cooperative - * cancellation. - * - * (MESSAGE.md § Transport Interface) - */ -export interface MessageTransport { - // --- Outbound --- - - /** Compose, sign, and deliver a message via SMTP. */ - send(message: OutboundMessage, signal?: AbortSignal): Promise; - - /** Append a raw message to a mailbox (IMAP APPEND). */ - append( - mailbox: string, - message: InboundMessage, - flags?: string[], - signal?: AbortSignal, - ): Promise; - - // --- Mailbox management --- - - listMailboxes(signal?: AbortSignal): Promise; - createMailbox(name: string, signal?: AbortSignal): Promise; - deleteMailbox(name: string, signal?: AbortSignal): Promise; - getMailboxStatus(name: string, signal?: AbortSignal): Promise; - - // --- Message search and retrieval --- - - search( - mailbox: string, - query: SearchQuery, - signal?: AbortSignal, - ): Promise; - - thread( - mailbox: string, - algorithm: "references" | "orderedsubject", - query?: SearchQuery, - signal?: AbortSignal, - ): Promise; - - fetchHeaders(ref: MessageRef, signal?: AbortSignal): Promise; - fetchStructure(ref: MessageRef, signal?: AbortSignal): Promise; - fetchPart( - ref: MessageRef, - partPath: string, - signal?: AbortSignal, - ): Promise; - fetchFull(ref: MessageRef, signal?: AbortSignal): Promise; - - // --- Flag management --- - - setFlags( - ref: MessageRef, - flags: string[], - signal?: AbortSignal, - ): Promise; - - clearFlags( - ref: MessageRef, - flags: string[], - signal?: AbortSignal, - ): Promise; - - // --- Message organization --- - - move(ref: MessageRef, toMailbox: string, signal?: AbortSignal): Promise; - - copy(ref: MessageRef, toMailbox: string, signal?: AbortSignal): Promise; - - /** - * Permanently remove every `\Deleted` message from the mailbox. Returns the - * uids that were expunged, so a caller can report how many messages it - * consumed and which ones. - */ - expunge( - mailbox: string, - signal?: AbortSignal, - ): Promise<{ expungedUids: number[] }>; - - // --- Real-time notification --- - - /** Monitor a mailbox for new messages and flag changes (IMAP IDLE). */ - watch(mailbox: string, callback: (event: MailboxEvent) => void): Unsubscribe; - - // --- Synchronization --- - - /** Efficient reconnection using QRESYNC semantics. */ - sync( - mailbox: string, - knownState: SyncState, - signal?: AbortSignal, - ): Promise; - - // --- Distribution lists --- - - createList( - address: string, - name: string, - signal?: AbortSignal, - ): Promise; - - listMembers(address: string, signal?: AbortSignal): Promise; - - subscribe( - listAddress: string, - subscriberAddress: string, - signal?: AbortSignal, - ): Promise; - - unsubscribe( - listAddress: string, - subscriberAddress: string, - signal?: AbortSignal, - ): Promise; -} - -// --------------------------------------------------------------------------- -// Tool Execution (ARCHITECTURE.md § Tools, INFERENCE.md § Tool Execution) -// --------------------------------------------------------------------------- - -/** - * A tool call as requested by the model. Carries the provider-assigned call - * ID, the tool name, and the parsed arguments. - * - * (INFERENCE.md § Message Format › Content Types) - */ -export const ToolCall = type({ - id: "string", - name: "string", - arguments: "Record", -}); -export type ToolCall = typeof ToolCall.infer; - -/** - * Approver-facing snapshot of the tool call awaiting approval. Built at the - * authz `ask` branch from the tool's definition and the live call, then - * threaded unchanged from the reactor's pending operation through every - * suspend hop to the hub co-write that records it on the approval row. - * - * `name`, `description`, and `inputSchema` mirror the {@link ToolDefinition}; - * `arguments` is the live call's arguments. Carried as a sibling of the pending - * operation's `suspendedCall`, never folded into {@link ToolCall}, so the - * re-dispatch artifact and the approval snapshot stay separate concerns. - */ -export const ApprovalSnapshot = type({ - name: "string", - description: "string", - inputSchema: "Record", - arguments: "Record", -}); -export type ApprovalSnapshot = typeof ApprovalSnapshot.infer; - -/** - * The kind of a control-plane park: a step suspended awaiting an external - * event. `"approval"` and `"input"` park on a reserved - * `signalName(correlationId)` channel; `"signal-relay"` parks on an - * author-chosen name. - * - * - `"approval"` -- the step parked on a tool/authz gate and REQUIRES an - * {@link ApprovalSnapshot}; the runtime notifies the host (`env.onPark`) so - * the sidecar co-writes the approval/correlation rows the hub registers. - * - `"input"` -- the step parked awaiting its next input (e.g. a long-lived - * agent run awaiting the next mail so it can take another turn). It carries - * NO snapshot and does NOT notify the host: the run's owner delivers the - * input on the same channel and the step re-arms. It is a runtime-local - * concept -- deliberately NOT a {@link SignalKind}, so it never touches the - * approval-routing machinery (IPC register frames, the hub co-write, the - * approval columns). - * - `"signal-relay"` -- an onTrigger section container parked on an - * author-named signal so a body child's `awaitSignal` on that name is - * serviced through the deployment run: the external signal is delivered to - * the parent run and the runtime relays it down into the live body child. - * The channel name is the author's free-form signal name, NOT a reserved - * `signalName(correlationId)`, so recovery must branch on this kind BEFORE - * assuming the awaited name is a reserved control-plane channel. Carries no - * snapshot and is not hub-registered. - * - * The kinds are distinguished by an EXPLICIT discriminant everywhere the kind - * flows -- never inferred from the presence or absence of a snapshot, which - * would silently reclassify a malformed snapshot-less approval as another - * park kind rather than failing loud. - */ -export const ControlParkKind = type.enumerated( - "approval", - "input", - "signal-relay", -); -export type ControlParkKind = typeof ControlParkKind.infer; - -/** - * Maximum serialized size, in UTF-8 bytes, of an {@link ApprovalSnapshot} that - * crosses a trust boundary. A tool `inputSchema` is normally single-digit KB; - * a snapshot approaching this bound is malformed or hostile and is rejected at - * the parse boundary rather than co-written onto an approval row. - */ -export const APPROVAL_SNAPSHOT_MAX_BYTES = 131072; - -/** - * {@link ApprovalSnapshot} bounded to {@link APPROVAL_SNAPSHOT_MAX_BYTES}. - * Parse the snapshot through this validator where it crosses a trust boundary - * (the `park.notify` IPC frame, the `parked-correlations.response` IPC frame, - * and the sidecar→hub register frame); internal hops use the unbounded - * {@link ApprovalSnapshot}. `.narrow` bounds the runtime check only — its - * inferred type is identical to {@link ApprovalSnapshot} — so the cap holds only - * where a frame is actually parsed, not merely typed. - */ -export const BoundedApprovalSnapshot = ApprovalSnapshot.narrow( - (snapshot, ctx) => { - const bytes = Buffer.byteLength(JSON.stringify(snapshot), "utf8"); - return ( - bytes <= APPROVAL_SNAPSHOT_MAX_BYTES || - ctx.mustBe(`at most ${APPROVAL_SNAPSHOT_MAX_BYTES} bytes when serialized`) - ); - }, -); -export type BoundedApprovalSnapshot = typeof BoundedApprovalSnapshot.infer; - -/** - * Result of a tool execution. `content` is text or structured data the model - * sees as the tool result. `detail` is additional data that the harness may - * use (e.g., for validation or audit) but that is not shown to the model. - * - * When `isError` is true the model sees the result as an error. When - * `pendingMarker` is present the tool is async — the reactor registers the - * correlation ID and waits for a matching inbound message. - * - * (INFERENCE.md § Tool Execution Semantics) - */ -export const ToolResult = type({ - callId: "string", - content: "string | Record", - "detail?": "unknown", - "isError?": "boolean", - "pendingMarker?": { - status: "'pending'", - correlationId: "string", - "expectedFrom?": "string", - }, -}); -export type ToolResult = typeof ToolResult.infer; - -/** - * The tool runner interface. The harness implements this; the reactor calls - * it when the director requests tool execution. - * - * Parallel execution is modeled by calling `run` concurrently for each call - * in a batch — the interface is per-call, not per-batch. - * - * (ARCHITECTURE.md § Agent Harness › Tools) - */ -export interface ToolRunner { - /** - * Execute a single tool call. Resolves with the result. Must not throw — - * errors are returned as `ToolResult` with `isError: true`. - */ - run(call: ToolCall, signal: AbortSignal): Promise; -} - -// --------------------------------------------------------------------------- -// Inference Event Building Blocks (INFERENCE.md § Event Protocol) -// --------------------------------------------------------------------------- - -/** - * Partial assistant message accumulated during streaming. Carries all - * content blocks seen so far so late-joining subscribers receive current - * state without replaying deltas. - * - * `text` and `thinking` are cumulative across every emitted delta of - * that kind in the current turn — intentionally flat, even when the - * harness's per-index block tracking has split the stream into - * multiple ThinkingBlocks or TextBlocks. Consumers that need per-block - * structure walk the finalized inference.done turn's content[]; this - * snapshot is the live "what bytes has the assistant streamed so - * far" view. - * - * (INFERENCE.md § Event Protocol › Partial State) - */ -export const PartialMessage = type({ - text: "string", - "thinking?": "string", - "toolCalls?": type({ - id: "string", - name: "string", - partialArguments: "string", - }).array(), -}); -export type PartialMessage = typeof PartialMessage.infer; - -/** - * Token usage for a single inference call. Cache read/write counts are - * provider-specific and may be zero when the provider does not report them. - * - * (INFERENCE.md § Token Accounting) - */ -export const TokenUsage = type({ - input: "number", - output: "number", - cacheRead: "number", - cacheWrite: "number", - thinking: "number", -}); -export type TokenUsage = typeof TokenUsage.infer; - -/** - * Slim source descriptor stamped onto `inference.usage` / `inference.done` - * events and onto `ReactorState.lastCycleSource`. - * - * Carries enough identity for state-aware policies (cost gating, budget - * caps, governance triggers, audit) to attribute usage to a specific - * inference source without re-reading the live, mutable `InferenceSource` - * the harness owns. - * - * Deliberately a strict subset of `InferenceSource` — `apiKey` and - * `baseURL` are intentionally excluded. Credentials and endpoints must - * not leak to director-side policy code or to external event consumers. - * Any code path that needs the full source obtains it through the - * harness's source registry, not through this descriptor. - * - * `sourceId` aliases `InferenceSource.id` to disambiguate from message - * ids, turn ids, and session ids in director-side code where `id` alone - * would be ambiguous. - */ -export const LastCycleSource = type({ - sourceId: "string", - provider: "string", - model: "string", -}); -export type LastCycleSource = typeof LastCycleSource.infer; - -// --------------------------------------------------------------------------- -// Internal Turn Format (INFERENCE.md § Message Format) -// --------------------------------------------------------------------------- - -/** - * A single content block within a conversation turn. Provider-agnostic. - * - * (INFERENCE.md § Message Format › Content Types) - */ -const TextBlock = type({ - type: "'text'", - text: "string", - // Opaque provider signature authenticating this block, echoed back - // verbatim on follow-up turns. Gemini attaches a `thoughtSignature` to - // output parts (including plain text); absent for providers that do not - // sign this block kind. - "signature?": "string", -}); - -/** - * How a media payload is carried by a content block. One of three - * variants: inline as a base64-encoded string, by reference to an - * opaque provider-native handle (e.g. a Gemini fileUri, an Anthropic - * file_id), or by public URL the provider fetches itself. The wire - * shape each provider expects is built by the provider adapter; - * MediaSource is the internal, provider-agnostic representation. - * - * (INFERENCE.md § Generalized Multimodal Taxonomy) - */ -const MediaSourceBase64 = type({ - kind: "'base64'", - mimeType: "string", - data: "string", -}); - -const MediaSourceFileReference = type({ - kind: "'file-reference'", - mimeType: "string", - reference: "string", -}); - -const MediaSourceUrl = type({ - kind: "'url'", - mimeType: "string", - url: "string", -}); - -export const MediaSource = MediaSourceBase64.or(MediaSourceFileReference).or( - MediaSourceUrl, -); -export type MediaSource = typeof MediaSource.infer; - -// Exported because `inference.image_output` events reference it by -// name, following the same pattern as `CitationBlock`, -// `CodeExecutionRequestBlock`, and `RedactedThinkingBlock`. -export const ImageBlock = type({ - type: "'image'", - source: MediaSource, - // Opaque provider signature authenticating this block, echoed back - // verbatim on follow-up turns. Gemini rides a `thoughtSignature` on the - // inlineData part; absent otherwise. - "signature?": "string", -}); -export type ImageBlock = typeof ImageBlock.infer; - -const AudioBlock = type({ - type: "'audio'", - source: MediaSource, -}); - -const VideoBlock = type({ - type: "'video'", - source: MediaSource, -}); - -const DocumentBlock = type({ - type: "'document'", - source: MediaSource, - "title?": "string", - "context?": "string", -}); - -const ThinkingBlock = type({ - type: "'thinking'", - thinking: "string", - "signature?": "string", -}); - -/** - * A thinking block whose content the provider has filtered. The - * opaque `data` blob must echo back verbatim on every follow-up turn - * — Anthropic 400s the request if it changes or goes missing. Treat - * the bytes as opaque: do not log them and do not render them to - * users. - * - * Exported because `inference.thinking.redacted` events reference it - * by name. - */ -export const RedactedThinkingBlock = type({ - type: "'redacted_thinking'", - data: "string", -}); -export type RedactedThinkingBlock = typeof RedactedThinkingBlock.infer; - -/** - * A model-emitted refusal. Produced when a provider's strict-mode - * structured-outputs path declines to satisfy the requested schema — - * OpenAI's `delta.refusal` / `message.refusal` field is the canonical - * wire shape. The `reason` is the accumulated human-readable text the - * model emitted in lieu of conformant output. - * - * Refusal is semantically distinct from `inference.error`: the HTTP - * call succeeded and the model produced a coherent response, but that - * response is "I will not satisfy this schema" rather than schema- - * conformant content. Callers that distinguish policy declines from - * transport/protocol failures should branch on the block type rather - * than treat the assistant turn as an error. - * - * Exported because `inference.refusal.delta` events reference it by - * name and adapters construct RefusalBlocks in the finalized - * AssistantTurn from accumulated delta fragments. - */ -export const RefusalBlock = type({ - type: "'refusal'", - // Refusals must carry text — a zero-length reason corrupts the - // "human-readable text the model emitted in lieu of conformant - // output" contract and would round-trip indistinguishably from a - // refusal block whose payload was lost. The arktype constraint is - // belt-and-braces alongside the adapter's wire-boundary filter on - // empty `delta.refusal` chunks: synthetic fixtures or future - // adapters without that filter still cannot construct a vacuous - // refusal. - reason: "string > 0", -}); -export type RefusalBlock = typeof RefusalBlock.infer; -const ToolCallBlock = type({ - type: "'tool_call'", - id: "string", - name: "string", - arguments: "Record", - // Opaque provider signature authenticating this block, echoed back - // verbatim on follow-up turns. Gemini rides a `thoughtSignature` on the - // functionCall part; absent otherwise. - "signature?": "string", -}); -/** - * Location of a citation's cited span within its source document. - * The unit of `start` and `end` varies by `kind`: - * - "page": 1-indexed page numbers (Anthropic `page_location`). - * - "char": UTF-16 character offsets, matching JS string semantics - * (Anthropic `char_location`; Gemini `groundingSupports[].segment`). - * - "content-block": index into a structured source's content blocks - * (Anthropic `content_block_location`). - */ -const CitationLocation = type({ - kind: "'page' | 'char' | 'content-block'", - start: "number", - end: "number", -}); - -const CitationSource = type({ - "title?": "string", - // Self-contained dereferenceable URL — populated by providers whose - // citations carry URLs directly (Gemini `groundingChunks[].web.uri`). - "uri?": "string", - // Back-pointer into the request's `documents` array, populated by - // providers that cite uploaded documents by position (Anthropic - // `document_index`). - "documentRef?": type({ index: "number" }), -}); - -/** - * A citation that supports a span of assistant text. Consumers - * receiving a CitationBlock without a paired source-block index MUST - * attribute it by adjacency to the nearest preceding TextBlock in the - * same turn. - * - * Citations are deliberately excluded from ToolResultBlock.content - * — they annotate model output, not tool output. - * - * Exported because `inference.citation` events reference it by name, - * following the same pattern as `AssistantTurn`, `ToolCall`, and - * `ToolResult`. See the `inference.citation` event docstring for how - * a paired source-block index is carried on the wire and consumed by - * the harness. - */ -export const CitationBlock = type({ - type: "'citation'", - // The exact substring of the preceding TextBlock this citation - // supports. Both providers emit it; required for inspection and - // for fallback offset reconstruction. - citedText: "string", - source: CitationSource, - "location?": CitationLocation, - // UTF-16 character offsets into the preceding TextBlock's text. - // Providers that emit offsets natively populate these directly; - // adapters that derive offsets from a cited substring populate - // them only when the substring appears unambiguously in the - // preceding text. Omitted when the offset cannot be determined. - "textOffset?": type({ start: "number", end: "number" }), -}); -export type CitationBlock = typeof CitationBlock.infer; - -/** - * A structured safety signal on model output or request filtering. - * - * The name `SafetyRatingBlock` follows the issue vocabulary; the - * payload is derived from the first real Gemini capture that engaged - * the structured classifier (2026-07-28). That wire shape is - * prompt-level only: - * - * `promptFeedback: { blockReason: "PROHIBITED_CONTENT" }` - * - * with no candidates and no per-category `safetyRatings` arrays. So - * this block carries `blockReason` and does **not** invent category / - * probability / blocked fields. When a future capture surfaces - * candidate-level ratings, extend the type from those bytes rather - * than from the API reference. - * - * Deliberately excluded from ToolResultBlock.content — safety - * signals annotate model/request filtering, not tool output. - * - * Exported because `inference.safety_rating` events reference it by - * name. - */ -export const SafetyRatingBlock = type({ - type: "'safety_rating'", - // Provider-native block reason string (observed: "PROHIBITED_CONTENT"). - // Open string so a new reason token does not force a type bump. - blockReason: "string > 0", -}); -export type SafetyRatingBlock = typeof SafetyRatingBlock.infer; - -/** - * Human-readable rendering of a SafetyRatingBlock for reply text, - * timeline summaries, and request-history rewrites when a provider - * has no input wire shape for safety_rating. Single owner of the - * display string so reply / history / transform stay in lockstep. - */ -export function formatSafetyRatingText(block: SafetyRatingBlock): string { - return `Request blocked: ${block.blockReason}`; -} - -/** - * The model's request to execute code via a server-side execution tool. - * Paired with a CodeExecutionResultBlock carrying the same `id` as the - * result's `requestId`. Streaming order within a single execution is - * `inference.code_execution.start` → zero or more - * `inference.code_execution.delta` → `inference.code_execution.result`, - * uninterrupted by other events that share the same `requestId`; events - * with different `requestId`s or for other block kinds at distinct - * `index`es may interleave. - * - * Exported because `inference.code_execution.start` references it by - * name. - */ -export const CodeExecutionRequestBlock = type({ - type: "'code_execution_request'", - // Identifier for the execution request. Populated from the - // provider's call id where one exists (Anthropic - // `srvtoolu_...`); synthesized by the adapter for providers that - // don't emit one (Gemini), using a deterministic per-response - // position-based scheme so replays match. - id: "string", - // Source code the model is asking to execute. - code: "string", - // Language hint. Absent when the provider does not emit one; - // adapters MUST NOT default this — callers narrow on its - // presence rather than fall through to a guessed language. - "language?": "string", - // Opaque provider signature authenticating this block, echoed back - // verbatim on follow-up turns. Gemini rides a `thoughtSignature` on the - // executableCode part; absent otherwise. - "signature?": "string", -}); -export type CodeExecutionRequestBlock = typeof CodeExecutionRequestBlock.infer; - -/** - * The result of executing a CodeExecutionRequestBlock. The `requestId` - * back-points to the request block's `id`. Status is normalized across - * providers; raw provider signals (return code, native outcome string, - * abort reason) are preserved on optional fields for callers that need - * them. - * - * File outputs from code execution (e.g. generated plots that - * Anthropic returns in `code_execution_tool_result.content`) are NOT - * modeled by this block today. The block carries no field for them; - * surfacing file outputs is a separate concern. - * - * Exported because `inference.code_execution.result` references it by - * name. - */ -export const CodeExecutionResultBlock = type({ - type: "'code_execution_result'", - // Back-pointer to the originating CodeExecutionRequestBlock.id. - requestId: "string", - // Normalized outcome. Translated from provider-specific signals: - // - Anthropic: derived from `return_code` (0 → "ok", non-zero → - // "error") and `abort_reason` (non-null → "aborted" or - // "timeout" per the reason). - // - Gemini: derived from the `outcome` enum - // (OUTCOME_OK → "ok", OUTCOME_FAILED → "error", - // OUTCOME_DEADLINE_EXCEEDED → "timeout", etc.). - status: "'ok' | 'error' | 'aborted' | 'timeout'", - // Standard output. Providers that don't split stdout from stderr - // (Gemini) map their combined `output` here and leave `stderr` empty. - "stdout?": "string", - // Standard error. Empty for providers that don't split. - "stderr?": "string", - // Provider-native numeric return code when available - // (Anthropic `return_code`). Absent for providers whose outcome - // is enum-only (Gemini). - "returnCode?": "number", - // Provider-native outcome string preserved verbatim for callers - // that need the raw signal (Gemini `OUTCOME_OK` / - // `OUTCOME_FAILED` / `OUTCOME_DEADLINE_EXCEEDED` / ...). Absent - // when the provider does not emit one (Anthropic). - "providerOutcome?": "string", - // Human-readable reason populated when status is "aborted" - // (Anthropic `abort_reason`). Absent otherwise. - "abortReason?": "string", -}); -export type CodeExecutionResultBlock = typeof CodeExecutionResultBlock.infer; - -const ToolResultBlock = type({ - type: "'tool_result'", - callId: "string", - // Deliberately narrow: tool results carry user-facing media, not - // CitationBlocks (citations annotate the model's text output), not - // SafetyRatingBlocks (safety signals annotate model/request - // filtering), and not CodeExecution blocks (server-side code - // execution is a distinct lifecycle from the user-tool round-trip). - content: TextBlock.or(ImageBlock) - .or(AudioBlock) - .or(VideoBlock) - .or(DocumentBlock) - .array(), - "detail?": "unknown", - "isError?": "boolean", -}); - -export const ContentBlock = TextBlock.or(ThinkingBlock) - .or(RedactedThinkingBlock) - .or(RefusalBlock) - .or(ImageBlock) - .or(AudioBlock) - .or(VideoBlock) - .or(DocumentBlock) - .or(CitationBlock) - .or(SafetyRatingBlock) - .or(CodeExecutionRequestBlock) - .or(CodeExecutionResultBlock) - .or(ToolCallBlock) - .or(ToolResultBlock); -export type ContentBlock = typeof ContentBlock.infer; - -/** - * A turn in the internal conversation history. The `model` field records - * which provider model produced this turn (present only on assistant - * turns). Used by cross-provider transformation to strip or preserve - * thinking blocks. - * - * (INFERENCE.md § Message Format) - */ -export type ConversationTurn = { - role: "user" | "assistant" | "system"; - content: ContentBlock[]; - model?: string; - timestamp: number; -}; - -/** - * A completed assistant turn returned in `inference.done`. Narrower type - * than ConversationTurn to make the inference boundary explicit. - */ -export const AssistantTurn = type({ - role: "'assistant'", - content: ContentBlock.array(), - model: "string", - timestamp: "number", -}); -export type AssistantTurn = typeof AssistantTurn.infer; - -// --------------------------------------------------------------------------- -// Error Classification (INFERENCE.md § Error Classification) -// --------------------------------------------------------------------------- - -/** - * Classified inference error. The category determines the reactor's default - * response; the director can override per its policy. - * - * (INFERENCE.md § Error Classification) - */ -export const InferenceError = type({ - category: type.enumerated( - "retryable", - "context_overflow", - "credential_failure", - "quota_exhausted", - "fatal", - "aborted", - "timeout", - "protocol_mismatch", - ), - message: "string", - "statusCode?": "number", - "retryAfterMs?": "number", - "raw?": "unknown", -}); -export type InferenceError = typeof InferenceError.infer; - -// --------------------------------------------------------------------------- -// Agent Reactor (INFERENCE.md § Agent Reactor) -// --------------------------------------------------------------------------- - -/** - * Gate types that can block the reactor. - * - * (INFERENCE.md § Gates) - */ -export const GateType = type.enumerated( - "approval", - "payment", - "credential", - "budget", - "child_completion", - "message_response", -); -export type GateType = typeof GateType.infer; - -/** - * Fork mode. `independent` creates a divergent reactor with its own context. - * `child` creates a reactor that reports results back to the parent. - * - * (INFERENCE.md § Forking) - */ -export const ForkMode = type.enumerated("independent", "child"); -export type ForkMode = typeof ForkMode.infer; - -// --------------------------------------------------------------------------- -// Inference Event Protocol (INFERENCE.md § Event Protocol) -// --------------------------------------------------------------------------- - -/** - * Wire-safe representation of InboundMessage for use in InferenceEvent - * variants. The runtime InboundMessage type contains Uint8Array fields - * (MessageAttachment.data) that cannot survive JSON serialization, so the - * wire validator uses `unknown` for attachment data and accepts whatever - * JSON.parse produces. - */ -const WireInboundMessage = type({ - ref: { uid: "number", mailbox: "string" }, - headers: "Record", - flags: "string[]", - "content?": "string", - "payload?": "object", - "attachments?": "unknown[]", - signatureStatus: type.enumerated("valid", "invalid", "unknown", "missing"), -}); - -/** - * A single event in the inference event protocol. Every event carries a - * monotonic session-scoped sequence number. - * - * Event types are namespaced: `inference.*`, `tool.*`, `reactor.*`, - * `fork.*`, `message.*`, `custom.*`. - * - * (INFERENCE.md § Event Protocol) - */ -export const InferenceEvent = type({ - type: "'inference.start'", - seq: "number", - data: { model: "string" }, -}) - .or({ - type: "'inference.thinking.delta'", - seq: "number", - data: { - token: "string", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.block.signature'", - seq: "number", - data: { signature: "string", "index?": "number" }, - }) - .or({ - type: "'inference.thinking.redacted'", - seq: "number", - data: { redactedThinking: RedactedThinkingBlock, "index?": "number" }, - }) - .or({ - type: "'inference.text.delta'", - seq: "number", - data: { - token: "string", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.refusal.delta'", - seq: "number", - data: { - token: "string", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.tool_call.start'", - seq: "number", - data: { - callId: "string", - name: "string", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.tool_call.delta'", - seq: "number", - data: { - callId: "string", - argumentFragment: "string", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.tool_call.end'", - seq: "number", - data: { - callId: "string", - name: "string", - arguments: "Record", - partial: PartialMessage, - "index?": "number", - }, - }) - .or({ - type: "'inference.usage'", - seq: "number", - data: { usage: TokenUsage, source: LastCycleSource }, - }) - .or({ - type: "'inference.done'", - seq: "number", - data: { - turn: AssistantTurn, - usage: TokenUsage, - source: LastCycleSource, - "pacingDelayMs?": "number", - }, - }) - .or({ - type: "'inference.error'", - seq: "number", - data: { error: InferenceError, partial: PartialMessage }, - }) - .or({ - type: "'inference.retry'", - seq: "number", - data: { - attempt: "number", - delayMs: "number", - previousError: InferenceError, - }, - }) - .or({ - type: "'inference.citation'", - seq: "number", - // `index`, when present, names the source content block (typically - // a TextBlock) the citation annotates. The harness uses it to - // interleave the citation into the finalized turn's `content[]` - // immediately after the matching block. Adapters whose wire - // protocol does not carry per-citation block indices omit the - // field; the harness then appends those citations at the end of - // `content[]` and consumers attribute them to the nearest - // preceding TextBlock per the CitationBlock docstring. - data: { citation: CitationBlock, "index?": "number" }, - }) - .or({ - type: "'inference.safety_rating'", - seq: "number", - // Prompt-level structured safety signal (observed Gemini - // `promptFeedback.blockReason`). No candidate index: the first - // capture has zero candidates. Harness appends the block to the - // finalized turn's `content[]`. - data: { safetyRating: SafetyRatingBlock }, - }) - .or({ - type: "'inference.code_execution.start'", - seq: "number", - data: { request: CodeExecutionRequestBlock, "index?": "number" }, - }) - .or({ - type: "'inference.code_execution.delta'", - seq: "number", - // requestId correlates fragments back to the originating - // CodeExecutionRequestBlock; index is the positional hint into - // the response's content-block stream. They are independent: a - // single response may stream code execution for multiple - // requests interleaved, distinguished by requestId; index lets - // the harness's per-block accumulator route the fragment to - // the correct block when the array isn't yet finalized. - data: { - requestId: "string", - codeFragment: "string", - "index?": "number", - }, - }) - .or({ - type: "'inference.code_execution.result'", - seq: "number", - data: { result: CodeExecutionResultBlock, "index?": "number" }, - }) - .or({ - type: "'inference.image_output'", - seq: "number", - // Fires mid-stream when an adapter finalizes an image-output - // block, signaling that the image is ready for downstream - // handoff before the full inference.done lands. The wrapped - // ImageBlock typically carries a base64 MediaSource — the - // payload can be large (Gemini's image-output captures show - // ~1MB inline blobs); consumers that subscribe to this event - // should treat it as a non-trivial transport size. - data: { image: ImageBlock, "index?": "number" }, - }) - .or({ - type: "'tool.start'", - seq: "number", - data: { call: ToolCall }, - }) - .or({ - type: "'tool.update'", - seq: "number", - data: { callId: "string", partial: "string" }, - }) - .or({ - type: "'tool.done'", - seq: "number", - data: { result: ToolResult }, - }) - .or({ - type: "'message.queued'", - seq: "number", - data: { message: WireInboundMessage }, - }) - .or({ - type: "'message.run.started'", - seq: "number", - data: { - messageId: "string", - messageRunId: "string", - receivedAt: "number", - }, - }) - .or({ - type: "'message.run.ended'", - seq: "number", - data: { - messageRunId: "string", - messageId: "string", - status: type.enumerated("completed", "failed"), - "error?": { - message: "string", - "kind?": "string", - }, - }, - }) - .or({ - type: "'message.correlated'", - seq: "number", - data: { message: WireInboundMessage, correlationId: "string" }, - }) - .or({ - type: "'connector.reply'", - seq: "number", - data: { content: "string", "checkpointHash?": "string" }, - }) - .or({ - type: "'reactor.start'", - seq: "number", - data: "object", - }) - .or({ - type: "'reactor.gate.blocked'", - seq: "number", - data: { - reason: GateType, - gateId: "string", - "correlationId?": "string", - "approvalSnapshot?": ApprovalSnapshot, - }, - }) - .or({ - type: "'reactor.gate.cleared'", - seq: "number", - data: { - gateId: "string", - reason: type.enumerated("resolved", "timeout", "shutdown"), - }, - }) - .or({ - type: "'reactor.done'", - seq: "number", - data: "object", - }) - .or({ - type: "'reactor.error'", - seq: "number", - data: { error: "string", fatal: "boolean" }, - }) - .or({ - type: "'fork.created'", - seq: "number", - data: { forkId: "string", parentId: "string", mode: ForkMode }, - }) - .or({ - type: "'fork.done'", - seq: "number", - data: { forkId: "string", "result?": "unknown" }, - }) - .or({ - type: "'fork.error'", - seq: "number", - data: { forkId: "string", error: "string" }, - }) - .or({ - type: "'fork.aborted'", - seq: "number", - data: { forkId: "string" }, - }) - .or({ - type: /^custom\./, - seq: "number", - data: "Record", - }); -// The TypeScript type is defined manually rather than inferred from the -// validator because the `custom.*` variant uses a regex pattern which -// arktype infers as `string`. A bare `string` in the discriminant position -// prevents TypeScript from narrowing the union in switch statements. -// The manually defined type uses a `custom.${string}` template literal -// for that variant, preserving the narrowing behavior downstream code -// relies on. -export type InferenceEvent = - | { type: "inference.start"; seq: number; data: { model: string } } - | { - type: "inference.thinking.delta"; - seq: number; - data: { token: string; partial: PartialMessage; index?: number }; - } - | { - type: "inference.block.signature"; - seq: number; - data: { signature: string; index?: number }; - } - | { - type: "inference.thinking.redacted"; - seq: number; - data: { redactedThinking: RedactedThinkingBlock; index?: number }; - } - | { - type: "inference.text.delta"; - seq: number; - data: { token: string; partial: PartialMessage; index?: number }; - } - | { - type: "inference.refusal.delta"; - seq: number; - data: { token: string; partial: PartialMessage; index?: number }; - } - | { - type: "inference.tool_call.start"; - seq: number; - data: { - callId: string; - name: string; - partial: PartialMessage; - index?: number; - }; - } - | { - type: "inference.tool_call.delta"; - seq: number; - data: { - callId: string; - argumentFragment: string; - partial: PartialMessage; - index?: number; - }; - } - | { - type: "inference.tool_call.end"; - seq: number; - data: { - callId: string; - name: string; - arguments: Record; - partial: PartialMessage; - index?: number; - }; - } - | { - type: "inference.usage"; - seq: number; - data: { usage: TokenUsage; source: LastCycleSource }; - } - | { - type: "inference.done"; - seq: number; - data: { - turn: AssistantTurn; - usage: TokenUsage; - source: LastCycleSource; - pacingDelayMs?: number; - }; - } - | { - type: "inference.error"; - seq: number; - data: { error: InferenceError; partial: PartialMessage }; - } - | { - /** - * Emitted between attempts when the per-call retry policy decides - * to retry after an error. `attempt` is the 1-indexed number of - * the attempt that just **failed** — the same value the policy - * saw on its `RetrySituation.attempt` reading. `delayMs` is the - * delay the wrapper will apply before the next attempt starts; - * `previousError` carries the classified error that triggered - * the retry. The event is not emitted when the policy aborts. - */ - type: "inference.retry"; - seq: number; - data: { - attempt: number; - delayMs: number; - previousError: InferenceError; - }; - } - | { - type: "inference.citation"; - seq: number; - data: { citation: CitationBlock; index?: number }; - } - | { - type: "inference.safety_rating"; - seq: number; - data: { safetyRating: SafetyRatingBlock }; - } - | { - type: "inference.code_execution.start"; - seq: number; - data: { request: CodeExecutionRequestBlock; index?: number }; - } - | { - type: "inference.code_execution.delta"; - seq: number; - data: { requestId: string; codeFragment: string; index?: number }; - } - | { - type: "inference.code_execution.result"; - seq: number; - data: { result: CodeExecutionResultBlock; index?: number }; - } - | { - type: "inference.image_output"; - seq: number; - data: { image: ImageBlock; index?: number }; - } - | { type: "tool.start"; seq: number; data: { call: ToolCall } } - | { - type: "tool.update"; - seq: number; - data: { callId: string; partial: string }; - } - | { type: "tool.done"; seq: number; data: { result: ToolResult } } - | { - type: "message.queued"; - seq: number; - data: { message: InboundMessage }; - } - | { - /** - * Per-message run-bracket open. Emitted by the reactor when it - * dequeues an inbound mail message and begins per-message work. - * - * `messageRunId` is reactor-minted, unique per dequeue. It is - * non-negotiable for crash-replay correlation: the reactor can - * legitimately dequeue the same `messageId` more than once across - * a crash + replay cycle, so two bracket-open events with the - * same `messageId` and no run-id cannot be unambiguously paired - * with their `message.run.ended` counterparts. - */ - type: "message.run.started"; - seq: number; - data: { - messageId: string; - messageRunId: string; - receivedAt: number; - }; - } - | { - /** - * Per-message run-bracket close. Pairs with `message.run.started` - * by `messageRunId`. `messageId` is carried redundantly so log - * readers can correlate without a join against the open event. - * - * The `status` enum is `"completed" | "failed"` only. - * Cancellation lives in the workflow-runtime's - * `CancelRequested` -> `RunFailed` vocabulary, not on the - * reactor's bracket: the reactor does not run a state machine - * and what it observes when cancellation arrives is a harness - * abort, which is structurally `"failed"` with a specific - * `error.kind`. - * - * `error.kind` is documented as one of - * `"inference_error" | "tool_error" | "reactor_fatal" | - * "harness_aborted" | "doom_loop"` initially, extensible as new - * failure categories surface. `"doom_loop"` marks a protective - * break the reactor took on the agent's behalf when the agent - * repeated an identical tool batch past the configured threshold; - * unlike `"reactor_fatal"` it is not an internal fault. - */ - type: "message.run.ended"; - seq: number; - data: { - messageRunId: string; - messageId: string; - status: "completed" | "failed"; - error?: { - message: string; - kind?: string; - }; - }; - } - | { - type: "message.correlated"; - seq: number; - data: { message: InboundMessage; correlationId: string }; - } - | { - type: "connector.reply"; - seq: number; - data: { content: string; checkpointHash?: string }; - } - | { type: "reactor.start"; seq: number; data: Record } - | { - type: "reactor.gate.blocked"; - seq: number; - data: { - reason: GateType; - gateId: string; - correlationId?: string; - approvalSnapshot?: ApprovalSnapshot; - }; - } - | { - type: "reactor.gate.cleared"; - seq: number; - data: { - gateId: string; - reason: "resolved" | "timeout" | "shutdown"; - }; - } - | { type: "reactor.done"; seq: number; data: Record } - | { - type: "reactor.error"; - seq: number; - data: { error: string; fatal: boolean }; - } - | { - type: "fork.created"; - seq: number; - data: { forkId: string; parentId: string; mode: ForkMode }; - } - | { - type: "fork.done"; - seq: number; - data: { forkId: string; result?: unknown }; - } - | { - type: "fork.error"; - seq: number; - data: { forkId: string; error: string }; - } - | { type: "fork.aborted"; seq: number; data: { forkId: string } } - | { - type: `custom.${string}`; - seq: number; - data: Record; - }; - -// Load-bearing drift guards for the dual-maintained `reactor.gate.blocked` -// event. The arktype `InferenceEvent` validator and the hand-written -// `InferenceEvent` type are kept in lockstep by hand (the `custom.*` regex -// variant forces the manual mirror). arktype passes undeclared keys through at -// runtime, so a schema that dropped `approvalSnapshot` would not fail at -// runtime. Projecting the field off each inferred shape makes it load-bearing: -// `tsc` errors if either mirror stops carrying it, mirroring the -// `_persistedSuspendedCall` guard in storage-isogit. -const _arkGateBlockedApprovalSnapshot = ( - data: Extract< - typeof InferenceEvent.infer, - { type: "reactor.gate.blocked" } - >["data"], -): ApprovalSnapshot | undefined => data.approvalSnapshot; -void _arkGateBlockedApprovalSnapshot; - -const _tsGateBlockedApprovalSnapshot = ( - data: Extract["data"], -): ApprovalSnapshot | undefined => data.approvalSnapshot; -void _tsGateBlockedApprovalSnapshot; - -/** - * Validate unknown data as an InferenceEvent. ArkType's regex-based validator - * infers `custom.*` event types as `string`, but the manual InferenceEvent type - * uses a `custom.${string}` template literal for switch narrowing. This function - * centralizes that single unavoidable cast. - */ -export function parseInferenceEvent( - data: unknown, -): InferenceEvent | type.errors { - const result = InferenceEvent(data); - if (result instanceof type.errors) return result; - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- arktype regex infers as string; manual type uses template literal - return result as InferenceEvent; -} - -/** - * A pending async operation registered in the reactor's async state. - * Correlates an outbound message (or payment/approval request) to the - * expected inbound response. - * - * (INFERENCE.md § Correlation) - */ -export type PendingOperation = { - correlationId: string; - kind: SignalKind; - expectedFrom?: string; - registeredAt: number; - gateId: string; - /** - * Absolute deadline (epoch ms) for the gate that parks this operation. - * Persisted so that rehydration after a restart re-arms the gate with the - * remaining time against the original deadline rather than restarting the - * countdown. Absent for operations parked with no deadline. - */ - timeoutAt?: number; - /** - * The tool call that was suspended when this operation parked. Captured for - * `kind: "approval"` operations minted from the ask flow so the approved - * call can be re-run on resume. Absent for operations parked by the - * director path (async-tool pending markers), which carry no tool call. - */ - suspendedCall?: ToolCall; - /** - * Approver-facing snapshot of `suspendedCall`, built at the authz `ask` - * branch from the tool definition and the live arguments. A sibling of - * `suspendedCall`, not a widening of it: `suspendedCall` is the re-dispatch - * artifact, this is what the approver decides on. Present only for ask-rail - * operations that carry a `suspendedCall`; absent for async-tool pending - * markers. Threaded through the suspend hops to the hub co-write. - */ - approvalSnapshot?: ApprovalSnapshot; -}; - -/** - * Complete reactor state visible to the director decision function. - * - * `tokenUsage` is the cumulative usage across the session. - * - * `lastCycleUsage` and `lastCycleSource` describe the most recent - * *successful* inference call. They move together: both null before the - * first completion; every `inference.done` sets both atomically. - * `inference.error` does not clear either — the pair always reflects the - * last cycle that produced a well-defined turn and usage. The director's - * `afterInferenceDone` hook fires only on `inference.done`, so policy - * code never observes a torn or stale-vs-fresh window. - * - * The per-cycle values support compaction triggers that key off recent - * input cost rather than session totals, and state-aware policies (cost - * gating, budget caps, governance triggers) that need to attribute - * usage to the source that produced it. - * - * (INFERENCE.md § Agent Reactor › Director Decision Function) - */ -export type ReactorState = { - turns: ConversationTurn[]; - activeForks: { forkId: string; mode: ForkMode }[]; - pendingOperations: PendingOperation[]; - activeGates: { gateId: string; type: GateType; timeoutAt: number }[]; - tokenUsage: TokenUsage; - lastCycleUsage: TokenUsage | null; - lastCycleSource: LastCycleSource | null; - sessionId: string; -}; - -/** - * Actions the director can direct the reactor to take. - * - * (INFERENCE.md § Agent Reactor › Actions) - */ -export type ReactorAction = - | { - type: "infer"; - options?: InferenceOptions; - } - | { - type: "execute_tools"; - calls: ToolCall[]; - parallel?: boolean; - addToHistory?: boolean; - } - | { - type: "suspend"; - gate: { - type: GateType; - gateId: string; - timeoutMs: number; - correlationId?: string; - }; - } - | { - type: "fork"; - mode: ForkMode; - forkId: string; - } - | { - type: "emit"; - eventType: `custom.${string}`; - data: Record; - } - | { - type: "reply"; - content: string; - } - | { type: "checkpoint"; message: string } - | { type: "compact"; compactor: string; reason: string } - | { type: "wait" } - | { type: "done" }; - -/** - * The capabilities object passed to the director. Mirrors the `ReactorAction` - * union — provides a type-safe way for the director to construct actions. - * - * (INFERENCE.md § Agent Reactor › Director Decision Function) - */ -export type ReactorCapabilities = { - infer(options?: InferenceOptions): ReactorAction; - executeTools( - calls: ToolCall[], - parallel?: boolean, - addToHistory?: boolean, - ): ReactorAction; - suspend(gate: { - type: GateType; - gateId: string; - timeoutMs: number; - correlationId?: string; - }): ReactorAction; - fork(mode: ForkMode, forkId: string): ReactorAction; - emit( - eventType: `custom.${string}`, - data: Record, - ): ReactorAction; - reply(content: string): ReactorAction; - checkpoint(message?: string): ReactorAction; - compact(compactor: string, reason: string): ReactorAction; - wait(): ReactorAction; - done(): ReactorAction; -}; - -/** - * The inbound events delivered to the director decision function. - * - * `resume.execute_tools` is raised by the reactor when an approval resolves - * and a parked tool call must be re-run on resume. It carries the calls the - * reactor is about to dispatch so the director can seed its outstanding - * tool-result count before those calls' `tool.done` events arrive — the - * reactor drives the execution, the director counts the results. Without this - * seed the count would sit at zero and the first `tool.done` would drive an - * accidental re-inference off a negative count. - * - * `resume.tool_result` is raised by the reactor when a parked approval ends - * without running its tool — a rejected decision or a gate timeout. It carries - * a synthetic error tool result that answers the parked call so history stays - * well-formed; the director appends it and re-infers exactly once. No tool - * runs, so it seeds no outstanding-result count. - * - * (INFERENCE.md § Agent Reactor › Reactor Structure) - */ -export type ReactorInboundEvent = - | { type: "message.received"; message: InboundMessage } - | { - type: "inference.done"; - turn: AssistantTurn; - usage: TokenUsage; - source: LastCycleSource; - } - | { type: "inference.error"; error: InferenceError; partial: PartialMessage } - | { type: "tool.done"; result: ToolResult } - | { - type: "reactor.gate.cleared"; - gateId: string; - reason: "resolved" | "timeout" | "shutdown"; - } - | { type: "resume.execute_tools"; calls: ToolCall[] } - | { type: "resume.tool_result"; result: ToolResult } - | { type: "abort"; reason: AbortReason }; - -/** - * The core director is a single decision function: given an event and the - * current reactor state, return one or more actions. - * - * If the director throws, the reactor catches the exception, emits - * `reactor.error`, and initiates graceful shutdown. - * - * (INFERENCE.md § Reactor Director › Core Director) - */ -export interface ReactorDirector { - decide( - event: ReactorInboundEvent, - state: ReactorState, - capabilities: ReactorCapabilities, - ): Promise; -} - -// --------------------------------------------------------------------------- -// Director Extension Hooks (INFERENCE.md § Reactor Director › Extension Hooks) -// --------------------------------------------------------------------------- - -/** - * Decision returned by a `BeforeToolExtension`. - * - * - `allow` — the tool proceeds. - * - `block` — the tool is answered with an error result carrying `reason`; - * the call is done. - * - `suspend` — the call is parked awaiting an external decision. The reactor - * registers `gate`, persists `pendingOp`, and does not answer the call: it - * is neither run nor error-completed. `gate.timeoutAt` is the absolute - * deadline (epoch ms) so the reactor can compute the remaining time; the - * `correlationId` on both `gate` and `pendingOp` ties an inbound resolution - * back to the suspension. - */ -export type BeforeToolDecision = - | { type: "allow" } - | { type: "block"; reason: string } - | { - type: "suspend"; - gate: { - type: GateType; - gateId: string; - correlationId: string; - timeoutAt: number; - }; - pendingOp: PendingOperation; - }; - -/** - * Extension that runs before a tool call is executed. Returns a - * `BeforeToolDecision`: `allow` lets the call run, `block` answers it with an - * error result, `suspend` parks it awaiting an external decision. - * - * `grantOneShot` registers a within-cycle bypass token keyed on a - * `ToolCall.id`: the next `beforeTool` for that id skips a suspension it would - * otherwise raise, consuming the token as it does so. It is optional because - * only extensions that can suspend a call have anything to bypass; extensions - * that never suspend omit it. - */ -export interface BeforeToolExtension { - beforeTool( - call: ToolCall, - state: ReactorState, - signal: AbortSignal, - ): Promise; - grantOneShot?(id: string): void; -} - -/** - * Extension that runs after a tool result is produced. Can modify the result - * (redaction, enrichment, audit logging). Extensions run in order. - */ -export interface AfterToolExtension { - afterTool( - result: ToolResult, - call: ToolCall, - state: ReactorState, - signal: AbortSignal, - ): Promise; -} - -// --------------------------------------------------------------------------- -// Context Strategies: Transforms and Compactors -// (INFERENCE.md § Context Management, § Tool Result Lifecycle) -// --------------------------------------------------------------------------- - -/** - * Durable description of a single strategy invocation. Written to the - * per-cycle manifest in the context store so that future operators can - * reconstruct exactly which strategy made which change, with what - * parameters, and why. - * - * - `strategy` is the implementation name (e.g. `"size-cap"`). - * - `version` is the implementation version. Changes to the strategy's - * behavior bump the version so old manifest entries remain unambiguous. - * - `parameters` records the configuration the strategy ran with. - * - `reason` is a short machine-readable cause label - * (e.g. `"exceeded-cap"`, `"overflow-recovery"`). - * - `decisions` records strategy-specific details about what was actually - * done (e.g. the keep count, the spill key, the original byte size). - */ -export const TransformRecord = type({ - strategy: "string", - version: "string", - parameters: "Record", - reason: "string", - decisions: "Record", -}); -export type TransformRecord = typeof TransformRecord.infer; - -/** - * Per-invocation context passed to every `ContextStrategy.apply` call. - * `state` is the reactor's snapshot at the moment the strategy runs; - * `trigger` is a short label describing why the strategy was invoked - * (e.g. `"tool-result-ingest"`, `"pre-inference"`, `"director-request"`). - */ -export interface StrategyContext { - readonly state: ReactorState; - readonly trigger: string; -} - -/** - * Optional blob attachment emitted by a strategy. The reactor writes each - * blob to the context store's working tree via `ContextStore.writeBlob` - * (Phase 2) so the data is durable and migrates with the conversation. - */ -export type StrategyBlob = { - key: string; - bytes: Uint8Array; - contentType?: string; -}; - -/** - * Result returned by `ContextStrategy.apply`. Carries the transformed - * output, a `TransformRecord` describing what happened, and any blobs - * that should be persisted in the context store. - */ -export interface StrategyResult { - output: O; - record: TransformRecord; - blobs?: StrategyBlob[]; -} - -/** - * Generic base interface for content-mutating strategies. The role-specific - * aliases below specialize `I` and `O` for tool-result ingestion, pre- - * inference context shaping, and explicit compaction. - * - * Strategies are pure with respect to the context store: they describe what - * should change via their return value. The reactor decides where to write - * the result (history, prompt, manifest) and which blobs to persist. - */ -export interface ContextStrategy { - readonly name: string; - readonly version: string; - apply(input: I, ctx: StrategyContext): Promise>; -} - -/** - * Runs on each tool result entering history. Output is appended to the - * conversation; any emitted blobs are written to the context store's - * `tool-output/` directory. - */ -export type ToolResultTransform = ContextStrategy< - { call: ToolCall; result: ToolResult }, - ToolResult ->; - -/** - * Runs in order before every inference call, producing the materialized - * prompt. Output is written to `prompt.jsonl` for that cycle; the durable - * history in `turns.jsonl` is left untouched. - * - * (INFERENCE.md § Async State Awareness › Pending Status Injection) - */ -export type ContextTransform = ContextStrategy< - ConversationTurn[], - ConversationTurn[] ->; - -/** - * Named compaction strategy. Registered in a registry on the reactor and - * invoked explicitly via the director's `compact` action. Output overwrites - * `turns.jsonl`; a `TransformRecord` is appended to the manifest. - */ -export type Compactor = ContextStrategy; - -// --------------------------------------------------------------------------- -// Blob Reader (INFERENCE.md § Tool Result Lifecycle) -// --------------------------------------------------------------------------- - -/** - * Read-only capability for resolving `tool-output:///{callId}` URIs to the - * underlying blob bytes. A `ToolResultTransform` that spills oversized tool - * output writes a blob via `ContextStore.writeBlob` and returns a pointer of - * the form `tool-output:///{callId}`; the agent's read tool reaches the spill - * by calling `BlobReader.read(uri)`. - * - * The URI scheme is deliberately rigid: - * - * - Scheme: `tool-output` - * - Authority: empty (the `///` makes pathname carry the callId) - * - Path: `/{callId}` — preserves case so provider-assigned callIds with - * uppercase letters survive parsing - * - Query and fragment: rejected - * - * Any deviation (different scheme, missing or non-empty hostname, extra path - * segments, search string, or fragment) throws. Missing blobs throw. - * `BlobReader` never accepts a filesystem path; the agent has no direct view - * of the context store's working tree. - */ -export interface BlobReader { - /** - * Resolve `uri` to the underlying blob bytes. Throws if the URI is not a - * well-formed `tool-output:///{callId}` reference or if no blob exists for - * the extracted callId. - */ - read(uri: string): Promise; -} - -/** Source for blob bytes used by `createBlobReader`. */ -export interface BlobSource { - readBlob(key: string, signal?: AbortSignal): Promise; -} - -/** - * Parse a `tool-output:///{callId}` URI and return the callId. Throws on any - * deviation from the documented shape: wrong scheme, non-empty authority, - * missing or extra path components, search string, or fragment. - * - * The two-slash form `tool-output://abc` is rejected because the URL parser - * lowercases the hostname, which silently corrupts provider-assigned callIds - * that contain uppercase letters. The three-slash form puts the callId in - * `pathname`, where case is preserved. - */ -export function parseToolOutputURI(uri: string): string { - let parsed: URL; - try { - parsed = new URL(uri); - } catch (cause) { - throw new Error(`invalid tool-output URI: ${uri}`, { cause }); - } - if (parsed.protocol !== "tool-output:") { - throw new Error( - `invalid tool-output URI scheme: expected "tool-output:", got "${parsed.protocol}"`, - ); - } - if (parsed.hostname !== "") { - throw new Error( - `invalid tool-output URI: authority must be empty (use the form tool-output:///{callId}), got "${parsed.hostname}"`, - ); - } - if (parsed.search !== "") { - throw new Error( - `invalid tool-output URI: query string is not allowed, got "${parsed.search}"`, - ); - } - if (parsed.hash !== "") { - throw new Error( - `invalid tool-output URI: fragment is not allowed, got "${parsed.hash}"`, - ); - } - const path = parsed.pathname; - if (!path.startsWith("/")) { - throw new Error(`invalid tool-output URI: empty path: ${uri}`); - } - const callId = path.slice(1); - if (callId === "") { - throw new Error(`invalid tool-output URI: missing callId: ${uri}`); - } - if (callId.includes("/")) { - throw new Error( - `invalid tool-output URI: path must contain a single callId segment, got "${callId}"`, - ); - } - return callId; -} - -/** - * Construct a `BlobReader` that resolves `tool-output:///{callId}` URIs by - * delegating to `source.readBlob(callId)`. The most common source is a - * `ContextStore` (Phase 2 added `readBlob` to that interface), but any object - * implementing `BlobSource` works — this keeps tests trivial. - * - * URI parsing is performed in this layer; the source only ever sees the - * extracted callId. Missing blobs surface as whatever error the source - * raises (`ContextStore.readBlob` already throws for unknown keys). - */ -export function createBlobReader(source: BlobSource): BlobReader { - return { - async read(uri: string): Promise { - const callId = parseToolOutputURI(uri); - return source.readBlob(callId); - }, - }; -} - -// --------------------------------------------------------------------------- -// Abort Reasons (INFERENCE.md § Abort Handling) -// --------------------------------------------------------------------------- - -/** - * Reason codes for the `abort` reactor event. The reason determines the - * appropriate cleanup action. - * - * (INFERENCE.md § Abort Handling › Abort Reasons) - */ -export const AbortReason = type.enumerated( - "user_disconnect", - "wallet_exhaustion", - "admin_kill", - "session_timeout", - "credential_revocation", -); -export type AbortReason = typeof AbortReason.infer; - -// --------------------------------------------------------------------------- -// Inference Source (INFERENCE.md § Providers) -// --------------------------------------------------------------------------- - -/** - * Model-bound default knobs for an inference source. Per-call - * `InferenceOptions.X` overrides `defaults.X`; the merge happens once at - * the top of `runInference` before the adapter sees anything. New fields - * land here as separately-scoped issues. - */ -export const InferenceSourceDefaults = type({ - "maxTokens?": "number", - // A bag of provider-native knobs the caller wants merged into the - // outbound request body (Anthropic's `metadata.user_id`, - // OpenAI's `user`, Gemini's `safetySettings`, etc.). Adapters that - // recognize keys translate; unrecognized keys are passed through or - // dropped per the adapter's documented behavior. The merge into - // per-call `InferenceOptions.providerOptions` is shallow — a per- - // call providerOptions object wholesale replaces the source-bound - // one, it does not deep-merge per key. - "providerOptions?": "Record", -}); -export type InferenceSourceDefaults = typeof InferenceSourceDefaults.infer; - -/** - * A specific (provider, model) bundle the agent runtime can route to. - * Carries wire reachability, credentials, the model identity at the - * provider, and the model-bound default knobs. - * - * `id` is the catalog offering's primary key, set by the resolver from the - * matched offering. It is the routing key used by `AgentConfig.defaultSource` - * and `Agent.setSource`. - * - * Multi-model providers become multiple sources — `model` is part of the - * identity, not an optional override. - * - * `capabilities` is carried for the selection-policy layer (the model - * selector consumes it). The runtime ignores it; populating the field - * later is not a wire-format change. - * - * `quirks` is the opaque per-deployment bag of provider-specific adapter - * accommodations. The harness reads it once, at adapter instantiation, and - * passes it to `AdapterRegistry.resolve` as a sibling of the slim - * `LastCycleSource` — quirks are deliberately kept off `LastCycleSource`, - * which rides on every usage event. The field is present-and-populated or - * absent; it is never `null`. A source row with no quirks stores SQL `NULL`, - * and the catalog resolver translates that absence into an omitted key here, - * so downstream code sees `undefined`, never `null`. - * - * (INFERENCE.md § Providers) - */ -export const InferenceSource = type({ - id: "string", - provider: "string", - baseURL: "string", - // Reference into the run's credential-material cell. The provider's secret - // (formerly an inline `apiKey`) is resolved from that cell by `credentialId` - // at call time, so the source config carries no secret and the child never - // holds the key inline. The same cell backs tool credentials. - credentialId: "string", - model: "string", - "defaults?": InferenceSourceDefaults, - "capabilities?": "string[]", - "quirks?": "Record", -}); -export type InferenceSource = typeof InferenceSource.infer; - -/** - * Replace every field on `active` with the corresponding field from - * `next`, in place. Optional fields (`defaults`, `capabilities`, - * `quirks`) are `delete`d from `active` when absent on `next` so the - * swap is exact — no stale value from a previous rotation can survive. - * - * Used by both the agent's source registry and the harness's source - * hot-swap path to mutate the single shared `InferenceSource` object the - * reactor reads lazily at the start of each inference call. Putting the - * field list in one place means the next field added to - * `InferenceSource` only has to be remembered here. - */ -export function applyInferenceSourceFields( - active: InferenceSource, - next: InferenceSource, -): void { - active.id = next.id; - active.provider = next.provider; - active.baseURL = next.baseURL; - active.credentialId = next.credentialId; - active.model = next.model; - if (next.defaults !== undefined) { - active.defaults = next.defaults; - } else { - delete active.defaults; - } - if (next.capabilities !== undefined) { - active.capabilities = next.capabilities; - } else { - delete active.capabilities; - } - if (next.quirks !== undefined) { - active.quirks = next.quirks; - } else { - delete active.quirks; - } - - // Compile-time exhaustiveness check. `Required<>` forces optional - // keys to also be required in the guard — so a future optional field - // (e.g. `region?: string`) added to `InferenceSource` without being - // handled above is flagged by TypeScript, not silently dropped. - const _handled: { readonly [K in keyof Required]: true } = { - id: true, - provider: true, - baseURL: true, - credentialId: true, - model: true, - defaults: true, - capabilities: true, - quirks: true, - }; - void _handled; -} - -/** - * Outcome of a `RetryPolicy` consultation. Either abort the call - * (surface the most recent `inference.error` to the caller), or retry - * after `delayMs` milliseconds, measured against the harness Scheduler. - * - * (INFERENCE.md § Providers › Streaming Harness) - */ -export type RetryDecision = - | { kind: "abort" } - | { kind: "retry"; delayMs: number }; - -/** - * Context supplied to a `RetryPolicy` each time an attempt produces an - * `inference.error`. - * - * (INFERENCE.md § Providers › Streaming Harness) - */ -export type RetrySituation = { - /** The classified error the most recent attempt produced. */ - readonly error: InferenceError; - /** - * 1-indexed attempt counter. The first failure has `attempt: 1`; - * the second failure (after one retry) has `attempt: 2`; and so on. - */ - readonly attempt: number; - /** - * Milliseconds since the *first* attempt of this call started, - * measured via the harness `Scheduler.now()`. The default Scheduler - * uses `performance.now()` (sub-millisecond resolution), so the - * value may be fractional; virtual-clock test schedulers report - * integer virtual time. Both are valid; policies that compare - * against integer thresholds should `Math.floor` if they need that. - */ - readonly elapsedMs: number; -}; - -/** - * Per-call retry policy. The harness invokes the policy once per - * `inference.error` an attempt produces, in 1-indexed attempt order. - * Returning `{ kind: "abort" }` ends the call by surfacing the most - * recent error to the caller; returning `{ kind: "retry", delayMs }` - * causes the harness to discard the failed attempt's events, sleep - * `delayMs` milliseconds against the Scheduler, and re-issue the - * underlying HTTP request with the identical body. The policy may be - * async; the harness awaits the returned `Promise` if - * it is a thenable. - * - * (INFERENCE.md § Providers › Streaming Harness) - */ -export type RetryPolicy = ( - situation: RetrySituation, -) => RetryDecision | Promise; - -/** - * Options for a single inference call. Override the defaults from the agent - * configuration on a per-call basis. - * - * (INFERENCE.md § Providers › Streaming Harness) - */ -export type InferenceOptions = { - maxTokens?: number; - temperature?: number; - thinking?: { enabled: boolean; budgetTokens?: number }; - systemPrompt?: string; - tools?: ToolDefinition[]; - /** - * Modalities the caller wants the model to emit. Adapters translate - * to the provider-native shape (Gemini's - * `generationConfig.responseModalities` accepts `"TEXT"` / `"IMAGE"` - * uppercase; see `packages/inference-discovery-google-genai/sessions/ - * google-genai/gemini-2.5-flash-image/image-output/exchanges/0/request.json` - * for the captured shape). Providers that do not expose a modality - * switch ignore the - * field. When omitted the provider's default modalities apply. - */ - responseModalities?: ("text" | "image" | "audio")[]; - /** - * Structured-output constraint. Asks the model to produce text, free- - * form JSON, or JSON conforming to a specific schema. Adapters - * translate to the provider-native wire shape: - * - * - **OpenAI** (`response_format`): - * - `text` → `{ type: "text" }` - * - `json` → `{ type: "json_object" }` - * - `json-schema` → `{ type: "json_schema", json_schema: { name, schema, strict } }` - * When the model declines in strict mode, the wire emits - * `delta.refusal` chunks; the adapter surfaces them as - * `inference.refusal.delta` events and a final `RefusalBlock` in - * the assistant turn's `content[]`. - * - **Google GenAI** (`generationConfig`): - * - `text` → no constraint (default). - * - `json` → `{ responseMimeType: "application/json" }`. - * - `json-schema` → `{ responseMimeType: "application/json", responseSchema: }`. - * `name` and `strict` are OpenAI-specific and have no Gemini - * counterpart; adapters ignore them. Gemini enforces a subset of - * JSON Schema (no `oneOf`, limited `pattern`, no `$ref`, etc.) — - * the adapter forwards the schema verbatim and surfaces Gemini's - * HTTP error if the subset is violated. - * - **Anthropic**: no native structured-output API. - * - `text` is a no-op (the default). - * - `json` and `json-schema` throw at the adapter boundary; there - * is no shim that synthesizes a tool to extract structured - * output. - * - * When omitted the provider's default applies (typically free-form - * text). - */ - responseFormat?: - | { kind: "text" } - | { kind: "json" } - | { - kind: "json-schema"; - name: string; - schema: unknown; - strict?: boolean; - }; - /** - * A bag of provider-native knobs the adapter merges into the outbound - * request body. Primary home is `InferenceSourceDefaults.providerOptions` - * (model-bound); this field exists for per-call overrides through the - * standard merge precedence at the top of `runInference`. The merge is - * shallow: a per-call providerOptions object wholesale replaces the - * source-bound one, it does not deep-merge per key. - */ - providerOptions?: Record; - /** - * Per-call inactivity timeout in milliseconds. If the harness yields no - * event (other than `inference.start`) for this many ms, the underlying - * fetch is aborted and the call ends with `inference.error` of category - * `"timeout"`. Default 120_000 (2 min). Tune higher for reasoning models - * that exhibit long silent-thinking stretches between token bursts; tune - * lower to fail fast. `0` arms the timer to fire on the next tick (a - * "fail-fast even if the fetch is instant" mode useful in tests). - */ - inactivityTimeoutMs?: number; - /** - * Per-call total wall-clock cap in milliseconds. Starts at fetch. - * Default 600_000 (10 min). Backstop for streams that keep emitting - * forever without terminating. Same error category as `inactivityTimeoutMs`. - * `0` arms the timer to fire on the next tick. - */ - totalTimeoutMs?: number; - /** - * Per-call mechanical retry policy. Consulted once per attempt that - * ends in `inference.error`; see `RetryPolicy` for the contract. If - * omitted, a built-in default policy is applied. - */ - retryPolicy?: RetryPolicy; -}; - -// --------------------------------------------------------------------------- -// Context Store (INFERENCE.md § Context Management › Context Store, -// ARCHITECTURE.md § Change History) -// --------------------------------------------------------------------------- - -/** - * A named commit point in the context store. Corresponds to a git commit. - * - * (ARCHITECTURE.md § Change History › Named Checkpoints) - */ -export type ContextCommit = { - hash: string; - message: string; - timestamp: number; - parentHash?: string; -}; - -/** - * The state of an active connector thread. The connector is one durable - * thread per agent; participants accumulate as they speak. Persisted - * alongside the conversation context so the thread survives sidecar - * restarts. - * - * `replyTo` is the most recent speaker — the primary recipient (`to`) - * on the next outbound reply. `cc` is every other participant who has - * spoken on the thread, deduplicated, in arrival order — they ride as - * `cc` on the next outbound reply so everyone stays in the loop. - * `subject` is set when the thread starts and preserved for its life. - * - * Defined as an arktype so the wire layer (sidecar↔hub frames) and - * other parsing boundaries can validate snapshots without - * re-declaring the shape. - */ -export const ConnectorThreadState = type({ - threadRoot: "string", - lastMessageId: "string", - replyTo: "string", - cc: "string[]", - "subject?": "string", -}); -export type ConnectorThreadState = typeof ConnectorThreadState.infer; - -/** - * The context store interface. Implementations back the store with git - * (filesystem, in-memory, or virtual) depending on the execution environment. - * The reactor accepts any implementation that satisfies this interface. - * - * The store holds the turn history and reactor metadata. Forking creates - * a git branch. Compaction commits the compacted history. - * - * (INFERENCE.md § Context Management › Context Store) - */ -export interface ContextStore { - /** - * Load the current turn history and reactor metadata from the store. - * Called during reactor initialization. - */ - load(signal?: AbortSignal): Promise<{ - turns: ConversationTurn[]; - pendingOperations: PendingOperation[]; - tokenUsage: TokenUsage; - connectorState: ConnectorThreadState | null; - }>; - - /** - * Buffer connector thread state for the next commit. The harness calls - * this before each checkpoint so that connector state is persisted - * atomically with the conversation context. - */ - setConnectorState(state: ConnectorThreadState | null): void; - - /** - * Commit whatever currently lives in the working tree, using the supplied - * commit message. The reactor's per-cycle checkpoint routes through this - * overload after writing the per-cycle files via `writeTurns`, - * `writePrompt`, `writeResponse`, `writeManifest`, and any `writeBlob` - * calls produced by transforms. - */ - commit( - options: { message: string }, - signal?: AbortSignal, - ): Promise; - - /** - * Create a branch for a fork operation. The branch starts from the current - * HEAD commit. - */ - branch(name: string, signal?: AbortSignal): Promise; - - /** - * List recent commits. Used by the agent's history query tools. - */ - log(limit?: number, signal?: AbortSignal): Promise; - - /** - * Read the turn history at a specific commit hash. Used for history - * inspection and rollback. - */ - readAt(hash: string, signal?: AbortSignal): Promise; - - /** - * Write an opaque blob to the working tree under `tool-output/`. Used by - * `ToolResultTransform`s that spill oversized payloads out of the inline - * conversation. The file is staged at the next `commit({ message })`. - * - * `key` is sanitized for filesystem safety; callers should pass the tool - * call id. `contentType` selects a file extension when known. - */ - writeBlob( - key: string, - bytes: Uint8Array, - contentType?: string, - signal?: AbortSignal, - ): Promise; - - /** - * Read a blob previously written via `writeBlob`. Throws if no blob with - * that key exists. - */ - readBlob(key: string, signal?: AbortSignal): Promise; - - /** - * Overwrite `prompt.jsonl` with the materialized prompt for the current - * inference cycle. One `ConversationTurn` per line. Staged at the next - * `commit({ message })`. - */ - writePrompt(turns: ConversationTurn[], signal?: AbortSignal): Promise; - - /** - * Overwrite `response.jsonl` with the assistant turn returned for the - * current cycle. Single-line JSONL for consistency with the per-cycle file - * conventions. Staged at the next `commit({ message })`. - */ - writeResponse(turn: AssistantTurn, signal?: AbortSignal): Promise; - - /** - * Overwrite `manifest.jsonl` with the ordered transform records produced - * for the current cycle. One `TransformRecord` per line. Staged at the - * next `commit({ message })`. - */ - writeManifest( - records: TransformRecord[], - signal?: AbortSignal, - ): Promise; - - /** - * Overwrite `turns.jsonl` with the durable conversation history. One - * `ConversationTurn` per line. Staged at the next `commit({ message })`. - */ - writeTurns(turns: ConversationTurn[], signal?: AbortSignal): Promise; - - /** - * Overwrite `metadata.json` with non-turn-shaped reactor state needed for - * restart: pending async operations and cumulative token usage. The store - * combines this with the most recently buffered connector state (from - * `setConnectorState`) and writes the merged payload. Staged at the next - * `commit({ message })`. - */ - writeMetadata( - metadata: { - pendingOperations: PendingOperation[]; - tokenUsage: TokenUsage; - }, - signal?: AbortSignal, - ): Promise; - - /** - * Read manifest entries from the most recent `limit` commits that contain - * a `manifest.jsonl`. Newest commit first; records within a commit are - * returned in their natural in-file order (chronological per-cycle). - */ - readManifestHistory( - limit: number, - signal?: AbortSignal, - ): Promise; -} - -// --------------------------------------------------------------------------- -// Audit Store (INTR-4 § Audit Trail) -// --------------------------------------------------------------------------- - -/** - * Persistent store for tool invocation audit records. Separated from - * ContextStore so the audit capability is opt-in at the composition - * layer. The isogit implementation writes audit records as individual - * JSON files in the same git repo used for context storage. - */ -export interface AuditStore { - /** - * Persist a batch of audit records. Called at checkpoint boundaries - * with all records accumulated since the last checkpoint. - */ - commitAudit(records: AuditRecord[], signal?: AbortSignal): Promise; - - /** - * Load audit records for a session. Returns all records matching - * the given sessionId, ordered by seq. - */ - loadAudit(sessionId: string, signal?: AbortSignal): Promise; - - /** - * Persist a batch of error records. Called at checkpoint boundaries - * and shutdown with all error records accumulated since the last flush. - */ - commitErrors(records: ErrorRecord[], signal?: AbortSignal): Promise; -} - -// --------------------------------------------------------------------------- -// Agent / Harness Configuration (ARCHITECTURE.md § Agent Harness) -// --------------------------------------------------------------------------- - -/** - * Configured tool definition exposed to the model. The harness registers - * available tools; the reactor passes this list to the inference provider as - * part of each request. - * - * (ARCHITECTURE.md § Agent Harness › Tools) - */ -export const ToolDefinition = type({ - name: "string", - description: "string", - inputSchema: "Record", -}); -export type ToolDefinition = typeof ToolDefinition.infer; - -/** - * Agent harness configuration. Assembled from the agent definition package - * and capability grants during harness initialization. - * - * `principalId` is the agent's principal in the hub's authorization model. - * The sidecar needs it to reconstruct the in-memory grant store on restart - * (the store's `collectGrants` filters by principal). - * - * `grants` uses `WireGrantRule` because this type arrives over JSON where - * `GrantRule.expiresAt` is serialized as a string. The wire validator - * coerces strings back to Date instances. - * - * (ARCHITECTURE.md § Agent Harness) - */ -export const HarnessConfig = type({ - sessionId: "string", - agentId: "string", - tenantId: "string", - principalId: "string", - agentAddress: "string", - systemPrompt: "string", - tools: ToolDefinition.array(), - grants: WireGrantRule.array(), - sources: InferenceSource.array(), - defaultSource: "string", - "sessionChannelEnabled?": "boolean", -}); -export type HarnessConfig = typeof HarnessConfig.infer; diff --git a/vendor/intx-types/src/sessions.ts b/vendor/intx-types/src/sessions.ts deleted file mode 100644 index 1917f67..0000000 --- a/vendor/intx-types/src/sessions.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { type } from "arktype"; - -export const CreateSession = type({ - agentId: "string", - "invokerCapabilities?": type({ - resource: "string", - action: "string", - "conditions?": "Record | null", - }).array(), -}); - -export const SessionResponse = type({ - id: "string", - tenantId: "string", - agentId: "string", - principalId: "string", - status: type("'idle' | 'ending' | 'ended'").describe( - "Persisted lifecycle state of the session: `idle` (open, awaiting work), `ending` (teardown in progress), or `ended` (closed).", - ), - createdAt: "string", - updatedAt: "string", - "lastActivityAt?": "string | null", -}); - -// Runtime operational status of an active session. The harness retries -// internally and does not surface retry state to the hub, so the retry -// variant is omitted until the event protocol supports it. -export const SessionStatus = type({ - status: type("'idle' | 'busy' | 'waiting_approval'").describe( - "Runtime operational state of an active session, distinct from its persisted lifecycle state: `idle` (ready), `busy` (processing a turn), or `waiting_approval` (blocked on an interactive approval before a tool call can proceed).", - ), -}); -export type SessionStatus = typeof SessionStatus.infer; - -// The schema validates structure only: a required mimeType, a required -// string `data` carrying base64-encoded bytes, an optional name, and no -// other keys. base64 validity, the MIME allowlist, and size limits are -// enforced at the route boundary so it can emit ordered, per-index -// structured errors (malformed_base64, disallowed_mime_type, oversize_*) -// that an all-or-nothing schema validator cannot produce. -export const SendMessage = type({ - content: "string", - "attachments?": type({ - mimeType: "string", - data: "string", - "name?": "string", - }) - .onUndeclaredKey("reject") - .array(), -}); - -export const MailResponse = type({ - id: "string", - sessionId: type("string").describe( - "Internal session channel identifier, not a user-facing session resource.", - ), - runId: "string | null", - direction: type("'inbound' | 'outbound'").describe( - "Whether the message was sent to the agent (`inbound`) or emitted by the agent (`outbound`).", - ), - status: type("'pending' | 'delivered'").describe( - "Delivery state of the mail: `pending` (accepted, not yet dispatched to the running agent) or `delivered`.", - ), - receivedAt: "string", - from: type({ - name: "string | null", - email: "string", - }).array(), - to: type({ - name: "string | null", - email: "string", - }).array(), - subject: "string | null", - sentAt: "string | null", - bodyValues: "Record", - textBody: type({ - partId: "string", - type: "string", - }).array(), - htmlBody: type({ - partId: "string", - type: "string", - }).array(), - attachments: type({ - blobId: "string", - name: "string | null", - type: "string", - size: "number", - }).array(), - headers: "Record", -}); -export type MailResponse = typeof MailResponse.infer; - -// Structured attachment-rejection errors returned by POST /:runId/mail. -// Each variant carries a machine-actionable `code` plus the fields a client -// needs to locate and explain the rejection, alongside a human-readable -// `message`. This is the wire contract for the route's attachment 400s; the -// route handler is the single producer. -export const AttachmentError = type({ - code: "'oversize_attachment'", - message: "string", - attachmentIndex: "number", - byteLength: "number", - limitBytes: "number", -}) - .or({ - code: "'disallowed_mime_type'", - message: "string", - attachmentIndex: "number", - mimeType: "string", - }) - .or({ - code: "'invalid_attachment_name'", - message: "string", - attachmentIndex: "number", - }) - .or({ - code: "'malformed_base64'", - message: "string", - attachmentIndex: "number", - }) - .or({ - code: "'oversize_total'", - message: "string", - totalBytes: "number", - limitBytes: "number", - }); -export type AttachmentError = typeof AttachmentError.infer; - -export const AttachmentErrorResponse = type({ error: AttachmentError }); -export type AttachmentErrorResponse = typeof AttachmentErrorResponse.infer; - -export const InferenceTurnResponse = type({ - id: "string", - sessionId: type("string").describe( - "Internal session channel identifier, not a user-facing session resource.", - ), - runId: "string", - model: "string", - status: "'running' | 'completed' | 'failed'", - startedAt: "string", - endedAt: "string | null", - parts: type({ - id: "string", - type: "'text' | 'reasoning' | 'tool' | 'file' | 'error' | 'step-start' | 'step-finish' | 'snapshot' | 'patch'", - "content?": "string | null", - "metadata?": "Record | null", - ordinal: "number", - }).array(), -}); -export type InferenceTurnResponse = typeof InferenceTurnResponse.infer; diff --git a/vendor/intx-types/src/sidecar-allocation.ts b/vendor/intx-types/src/sidecar-allocation.ts deleted file mode 100644 index ccbf59c..0000000 --- a/vendor/intx-types/src/sidecar-allocation.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const sidecarAllocationStatuses = [ - "pending", - "provisioning", - "allocated", - "replacing", - "releasing", - "released", - "failed", -] as const; - -export type SidecarAllocationStatus = - (typeof sidecarAllocationStatuses)[number]; - -export function isSidecarAllocationDispatchable( - status: SidecarAllocationStatus, -): boolean { - switch (status) { - case "pending": - case "provisioning": - case "allocated": - case "replacing": - return true; - case "releasing": - case "released": - case "failed": - return false; - default: { - const exhaustive: never = status; - return exhaustive; - } - } -} diff --git a/vendor/intx-types/src/sidecar-capabilities.ts b/vendor/intx-types/src/sidecar-capabilities.ts deleted file mode 100644 index d39196d..0000000 --- a/vendor/intx-types/src/sidecar-capabilities.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { type } from "arktype"; - -export type ParsedSidecarCapabilitySelector = { - readonly kind: "exact" | "prefix"; - readonly segments: readonly string[]; -}; - -export function parseSidecarCapabilitySelector( - value: string, -): ParsedSidecarCapabilitySelector | null { - if (value.length === 0) return null; - if (value === "*") return { kind: "prefix", segments: [] }; - if (!value.includes("*")) { - const segments = value.split(":"); - return segments.some((segment) => segment.length === 0) - ? null - : { kind: "exact", segments }; - } - - if (!value.endsWith(":*") || value.indexOf("*") !== value.length - 1) { - return null; - } - const segments = value.slice(0, -2).split(":"); - if (segments.some((segment) => segment.length === 0)) return null; - return { - kind: "prefix", - segments, - }; -} - -export const SidecarCapabilitySelector = type("string > 0").narrow( - (value, ctx) => - parseSidecarCapabilitySelector(value) !== null || - ctx.mustBe( - "an exact capability, a trailing namespace selector such as runtime:*, or *", - ), -); -export type SidecarCapabilitySelector = typeof SidecarCapabilitySelector.infer; - -export const SidecarCapabilityRule = type({ - capability: SidecarCapabilitySelector, - effect: "'require' | 'block'", -}); -export type SidecarCapabilityRule = typeof SidecarCapabilityRule.infer; - -export const SidecarCapabilityDeclaration = type({ - capability: SidecarCapabilitySelector, - state: "'available' | 'blocked'", -}); -export type SidecarCapabilityDeclaration = - typeof SidecarCapabilityDeclaration.infer; - -export const SidecarCapabilityPolicy = type({ - "capabilities?": SidecarCapabilityRule.array(), -}).onUndeclaredKey("reject"); -export type SidecarCapabilityPolicy = typeof SidecarCapabilityPolicy.infer; - -export type TenantSidecarCapabilityPolicy = { - readonly tenantId: string; - readonly rules: readonly SidecarCapabilityRule[]; -}; diff --git a/vendor/intx-types/src/sidecar-oauth-login.test.ts b/vendor/intx-types/src/sidecar-oauth-login.test.ts deleted file mode 100644 index 1027363..0000000 --- a/vendor/intx-types/src/sidecar-oauth-login.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -// CL-7508 local delta: parse/reject tests for the oauth.login.start / -// oauth.login.result frame pair the sidecar ws channel threads (see the -// `vendor/intx/types` ledger row). -import { describe, expect, test } from "bun:test"; -import { type } from "arktype"; -import { - HubFrame, - OAuthLoginCancelFrame, - OAuthLoginResultFrame, - OAuthLoginStartFrame, - SidecarFrame, -} from "./sidecar"; - -describe("oauth.login.start frame", () => { - test("parses a well-formed hub request", () => { - const frame = { - type: "oauth.login.start", - requestId: "req_1", - connectorId: "codex", - }; - expect(OAuthLoginStartFrame(frame)).not.toBeInstanceOf(type.errors); - expect(HubFrame(frame)).not.toBeInstanceOf(type.errors); - }); - - test("accepts every loopback connector id and nothing else", () => { - expect( - OAuthLoginStartFrame({ - type: "oauth.login.start", - requestId: "r", - connectorId: "xai-oauth", - }), - ).not.toBeInstanceOf(type.errors); - expect( - OAuthLoginStartFrame({ - type: "oauth.login.start", - requestId: "r", - connectorId: "github", - }), - ).toBeInstanceOf(type.errors); - }); - - test("rejects a missing requestId", () => { - expect( - OAuthLoginStartFrame({ type: "oauth.login.start", connectorId: "codex" }), - ).toBeInstanceOf(type.errors); - }); - - test("parses the cancel frame the hub sends on timeout", () => { - const frame = { type: "oauth.login.cancel", requestId: "req_1" }; - expect(OAuthLoginCancelFrame(frame)).not.toBeInstanceOf(type.errors); - expect(HubFrame(frame)).not.toBeInstanceOf(type.errors); - expect( - OAuthLoginCancelFrame({ type: "oauth.login.cancel" }), - ).toBeInstanceOf(type.errors); - }); -}); - -describe("oauth.login.result frame", () => { - test("parses the started arm with its authorize URL", () => { - const frame = { - type: "oauth.login.result", - requestId: "req_1", - outcome: { status: "started", authorizeUrl: "https://auth.example/authorize?x=1" }, - }; - expect(OAuthLoginResultFrame(frame)).not.toBeInstanceOf(type.errors); - expect(SidecarFrame(frame)).not.toBeInstanceOf(type.errors); - }); - - test("parses the completed arm with tokens", () => { - const frame = { - type: "oauth.login.result", - requestId: "req_1", - outcome: { - status: "completed", - tokens: { - access: "at", - refresh: "rt", - expiresAt: 123, - idToken: "idt", - accountId: "acc", - }, - }, - }; - expect(OAuthLoginResultFrame(frame)).not.toBeInstanceOf(type.errors); - }); - - test("parses the error arm", () => { - expect( - OAuthLoginResultFrame({ - type: "oauth.login.result", - requestId: "req_1", - outcome: { status: "error", message: "port in use" }, - }), - ).not.toBeInstanceOf(type.errors); - }); - - test("rejects a completed arm without tokens", () => { - expect( - OAuthLoginResultFrame({ - type: "oauth.login.result", - requestId: "req_1", - outcome: { status: "completed" }, - }), - ).toBeInstanceOf(type.errors); - }); - - test("rejects an unknown outcome status", () => { - expect( - OAuthLoginResultFrame({ - type: "oauth.login.result", - requestId: "req_1", - outcome: { status: "pending" }, - }), - ).toBeInstanceOf(type.errors); - }); -}); diff --git a/vendor/intx-types/src/sidecar.ts b/vendor/intx-types/src/sidecar.ts deleted file mode 100644 index d961a5d..0000000 --- a/vendor/intx-types/src/sidecar.ts +++ /dev/null @@ -1,1056 +0,0 @@ -// Websocket wire protocol for hub↔sidecar communication. -// -// One websocket connection per sidecar↔hub pair. All traffic is multiplexed -// as JSON frames with a `type` discriminator. The sidecar initiates the -// connection; the hub is the server. -// -// Mail bytes are base64-encoded in JSON frames. Binary frames would be more -// efficient but JSON is simpler to debug and inspect. - -import { type } from "arktype"; -import { GrantWalkSnapshot } from "./grant-snapshot"; -import { WireGrantRule } from "./grant-wire"; -import { - BoundedApprovalSnapshot, - ConnectorThreadState, - HarnessConfig, - InferenceEvent, - InferenceSource, -} from "./runtime"; -import { SignalKind } from "./signals"; -import { ToolPackageManifest } from "./tool-packages"; -import { WorkflowDefinitionSource } from "./workflow-sources"; - -// --------------------------------------------------------------------------- -// Sidecar → Hub -// --------------------------------------------------------------------------- - -/** - * Sent on first connect when the sidecar has no existing agents in its data - * directory. Identifies the sidecar and declares it ready to receive - * agent.deploy frames. - */ -export const RegisterFrame = type({ - type: "'register'", - sidecarId: "string", - token: "string", - agentAddresses: "string[]", -}); -export type RegisterFrame = typeof RegisterFrame.infer; - -/** - * Sent on connect after a provisioned sidecar restores its deployment. - * The bearer token binds the connection to one allocation generation, so the - * Hub accepts only that allocation's workflow address. - */ -export const ReconnectFrame = type({ - type: "'reconnect'", - sidecarId: "string", - token: "string", - agentAddresses: "string[]", -}); -export type ReconnectFrame = typeof ReconnectFrame.infer; - -/** - * Acknowledges a successful agent deployment. Includes the agent's Ed25519 - * public key (hex-encoded) for published identity and content provenance. - * Reconnect authority comes from the allocation credential. - */ -export const AgentDeployAckFrame = type({ - type: "'agent.deploy.ack'", - agentAddress: "string", - publicKey: "string", -}); -export type AgentDeployAckFrame = typeof AgentDeployAckFrame.infer; - -/** - * Reports a failed agent deployment. - */ -export const AgentErrorFrame = type({ - type: "'agent.error'", - agentAddress: "string", - error: "string", -}); -export type AgentErrorFrame = typeof AgentErrorFrame.infer; - -/** - * A message from a local agent. When `delivered` is absent or false the hub - * should route the message to its recipients. When `delivered` is true the - * message was already delivered locally and is forwarded for audit/projection - * only — the hub must not re-route it. - * - * Structured metadata (senderAddress, messageId, to, cc) is available for - * audit and projection purposes without parsing the raw MIME bytes. - */ -export const MailOutboundFrame = type({ - type: "'mail.outbound'", - rawMessage: "string", - recipients: "string[]", - senderAddress: "string", - "sessionId?": "string", - "messageId?": "string", - "to?": "string[]", - "cc?": "string[]", - "delivered?": "boolean", -}); -export type MailOutboundFrame = typeof MailOutboundFrame.infer; - -/** - * An InferenceEvent from the reactor, forwarded for UI consumption. Tagged - * with the run address so the hub can route to the correct UI client. - */ -export const AgentEventFrame = type({ - type: "'agent.event'", - agentAddress: "string", - sessionId: "string", - event: InferenceEvent, - "childRunId?": "string", -}); -export type AgentEventFrame = typeof AgentEventFrame.infer; - -/** - * Notifies the hub that the agent's connector-thread state has changed. - * The sidecar emits this when the harness's connector router commits a - * start/continue decision, when an outbound reply advances the - * lastMessageId, and when load-time restore brings persisted state into - * memory. The hub uses the cached state to set threading headers on - * user-originated mail so the harness routes it as `continue` rather - * than `passthrough`. - * - * `connectorState` is `null` when no active thread exists. - */ -export const ConnectorStateChangedFrame = type({ - type: "'connector.state.changed'", - agentAddress: "string", - connectorState: ConnectorThreadState.or("null"), -}); -export type ConnectorStateChangedFrame = - typeof ConnectorStateChangedFrame.infer; - -/** - * Keepalive ping sent by the sidecar. The hub responds with a pong frame. - * If the hub stops receiving pings, it considers the sidecar dead. - */ -export const PingFrame = type({ type: "'ping'" }); -export type PingFrame = typeof PingFrame.infer; - -/** - * Acknowledges a request from the hub (sources.update). - */ -export const SessionAckFrame = type({ - type: "'session.ack'", - requestId: "string", -}); -export type SessionAckFrame = typeof SessionAckFrame.infer; - -/** - * Reports an error processing a hub request. - */ -export const SessionErrorFrame = type({ - type: "'session.error'", - requestId: "string", - error: "string", -}); -export type SessionErrorFrame = typeof SessionErrorFrame.infer; - -/** - * Acknowledges that an agent has been fully undeployed: the deployment's - * workflow child stopped, state pushed (best-effort), and directory deleted. - */ -export const AgentUndeployAckFrame = type({ - type: "'agent.undeploy.ack'", - agentAddress: "string", - statePushed: "boolean", -}); -export type AgentUndeployAckFrame = typeof AgentUndeployAckFrame.infer; - -/** - * Registers a control-signal correlation as a workflow agent step suspends. - * The fields on this frame all converge at the sidecar's suspend emit point; - * the hub uses them to co-write the `signal_correlation` routing row and the - * `approval` row in one transaction, so the eventual resolver can route a - * delivered decision back to the parked run and flip its approval. - * - * `signalName` is deliberately NOT on the wire: it is a pure function of - * `correlationId` (`signalName(correlationId)` in `./signals`), so the hub - * computes it rather than trusting a value the sidecar could disagree on. - * `anchorRunId` is the anchor run the parked run belongs to; `agentAddress` - * is the anchor run's routable address the hub resolves tenancy from. - */ -export const SignalCorrelationRegisterFrame = type({ - type: "'signal.correlation.register'", - correlationId: "string", - runId: "string", - anchorRunId: "string", - agentAddress: "string", - kind: SignalKind, - // Approver-facing snapshot of the suspended tool call, size-capped at this - // trust boundary. Required: the ask rail is the only producer of this frame - // and always carries a snapshot, so a snapshot-absent frame fails this parse - // at the receiver (logged and dropped, never co-written as a null row). - snapshot: BoundedApprovalSnapshot, -}); -export type SignalCorrelationRegisterFrame = - typeof SignalCorrelationRegisterFrame.infer; - -// --------------------------------------------------------------------------- -// Hub → Sidecar -// --------------------------------------------------------------------------- - -/** - * Hub acknowledges a `signal.correlation.register`: the routing + approval - * co-write for this correlationId is durable (whether this frame inserted the - * rows or found them already present). It lets the sidecar's link stop - * retrying a register whose frame may have been lost on an open socket or - * evicted from the bounded send queue. Keyed on correlationId alone -- every - * producer of the register (the initial park, the respawn/reconnect re-emit, a - * link retry) carries the same correlationId and drives the same idempotent - * co-write, so the ack asserts the one fact that matters: a row exists for this - * correlation. - */ -export const SignalCorrelationRegisterAckFrame = type({ - type: "'signal.correlation.register.ack'", - agentAddress: "string", - correlationId: "string", -}); -export type SignalCorrelationRegisterAckFrame = - typeof SignalCorrelationRegisterAckFrame.infer; - -/** - * A message to deliver to a local agent's INBOX. The hub routes inbound - * mail (from UI users, from agents on other sidecars) to the correct - * sidecar connection. - * - * `messageId` is the hub-minted id of this delivery, carried so the sidecar - * can acknowledge durable receipt (`mail.inbound.ack`) keyed on the SAME id - * the hub tracks -- no per-side re-derivation. It is the id the hub minted at - * ingress (also the message's `Message-ID` header), so a redelivery replays - * identical bytes and the downstream `RunStarted` dedup (consumedMessageIds) - * makes at-least-once effectively-once. Present only on hub-originated mail - * that participates in the ack/retry handshake (workflow trigger mail, session - * conversation mail); agent-to-agent relayed mail omits it. - */ -export const MailInboundFrame = type({ - type: "'mail.inbound'", - agentAddress: "string", - rawMessage: "string", - "messageId?": "string", -}); -export type MailInboundFrame = typeof MailInboundFrame.infer; - -/** - * Sidecar acknowledges durable receipt of a `mail.inbound`: the message is in - * the agent's on-disk inbox. The hub holds each delivered mail in a pending - * map and retries until this ack lands (or reconnect-redelivers it), so a - * message dropped in the connected/reconnecting window is not silently lost. - * Keyed on the hub-minted `messageId` the `mail.inbound` carried, so the ack - * clears exactly the pending entry it resolves; the ack is only sent AFTER the - * durable inbox write resolves (a non-ack IS the retry signal). At-least-once - * delivery is made effectively-once by the `RunStarted`/signal dedup guards. - */ -export const MailInboundAckFrame = type({ - type: "'mail.inbound.ack'", - agentAddress: "string", - messageId: "string", -}); -export type MailInboundAckFrame = typeof MailInboundAckFrame.infer; - -/** - * Deliver a workflow-run signal to a multi-step deployment's - * supervisor. The hub forwards the frame to the sidecar that hosts the - * deployment named by `agentAddress` (the deployment-level mail - * address). The sidecar's hub-link routes the frame into the matching - * supervisor's `deliverSignal`, which sends a `signal.deliver` control - * IPC frame to the workflow-process child. The child commits the - * `SignalReceived` event through its own substrate -- the single - * writer of the workflow-run repo on the sidecar side -- so the - * pack-push pipeline that propagates the commit to the hub never sees - * a concurrent writer at the same ref. - * - * `signalId` is supplied by the producer so the workflow-run state - * machine's dedup index (`observedSignalIds`) rejects a duplicate - * delivery cleanly; a fresh value per call is the producer's - * responsibility. - */ -export const SignalDeliverFrame = type({ - type: "'signal.deliver'", - agentAddress: "string", - runId: "string", - signalName: "string", - signalId: "string", - payload: "unknown", -}); -export type SignalDeliverFrame = typeof SignalDeliverFrame.infer; - -/** - * Deliver a run's authorization grants to a multi-step deployment's - * supervisor. The hub forwards the frame to the sidecar that hosts the - * deployment named by `agentAddress` (the deployment-level mail - * address). The sidecar's hub-link routes the frame into the matching - * deployment's wiring, which writes the grants to `runs//grants.json` - * inside the deployment's `workflow-run` repo -- sibling to the run's - * `runs//events/` subtree. - * - * `stepGrants` carries the same `WireGrantRule` shape the `agent.deploy` - * frame's `config.grants` ships, so the run's grants ride the same - * validated grant encoding as the deploy-time step grants rather than a - * new one. - */ -export const RunGrantsFrame = type({ - type: "'run.grants'", - agentAddress: "string", - runId: "string", - stepGrants: WireGrantRule.array(), -}); -export type RunGrantsFrame = typeof RunGrantsFrame.infer; - -/** - * Deliver a workflow-host drain control payload to a multi-step - * deployment's supervisor. The hub forwards the frame to the sidecar - * that hosts the deployment named by `agentAddress` (the - * deployment-level mail address). The sidecar's hub-link routes the - * frame into the matching supervisor's `drain`, which sends a `drain` - * control IPC frame to the workflow-process child and arms one - * `drainTimeout` accumulator per in-flight run. Cancel-mode in-flight - * steps abort on the child side as the controller's signal flips; - * wait-mode steps continue. Each accumulator commits a signed - * `CancelRequested{origin: "supervisor-drain"}` against the - * workflow-run repo through the supervisor's substrate when the - * deadline expires. - * - * `deadlineMs` is the wire-level policy hint the child echoes in its - * logs. The supervisor's accumulator is driven by its own bindings' - * `drainTimeoutMs` -- a per-deployment operator setting -- not by this - * value; the wire field exists so the child's log reflects the - * caller's intent. - */ -export const DrainDeliverFrame = type({ - type: "'drain.deliver'", - agentAddress: "string", - deadlineMs: "number", -}); -export type DrainDeliverFrame = typeof DrainDeliverFrame.infer; - -import { - WorkflowProjectionDefinition, - WorkflowProjectionWithSources, -} from "./wire-workflow"; -// Re-export the wire-step/projection contracts that moved to `./wire-workflow` -// so existing `@intx/types/sidecar` consumers keep resolving them here. Each -// name is an arktype schema, so the single re-export carries both its value and -// its inferred type. -export { WorkflowStep } from "./wire-workflow"; -export { WorkflowProjectionDefinition, WorkflowProjectionWithSources }; - -/** - * The decrypted credential material and per-handle binding descriptors - * delivered to a running agent so its tools can use provider-backed - * credentials. Secrets are decrypted hub-side and ride this payload on the - * live channel ONLY -- the deploy frame at launch, a `credentials.update` - * frame on rotation, and the child's in-memory cell. They are NEVER written to - * disk (they do not ride the git-committed grants file) and NEVER copied into - * any snapshot, event, or state -- redaction is by construction, mirroring how - * an `InferenceSource`'s `apiKey` stays off every egress type. - * - * `materials` is keyed by `credentialId` (a credential can back several handles, - * so its secret is stored once); `bindings` maps each declared tool handle to - * the credential that backs it and the consumer identity allowed to use it. - */ -export const CredentialMaterialEntry = type({ - credentialId: "string", - providerKey: "string", - origin: "string", - secret: "string", -}); -export type CredentialMaterialEntry = typeof CredentialMaterialEntry.infer; - -export const CredentialBindingDescriptor = type({ - handle: "string", - credentialId: "string", - consumer: "string", -}); -export type CredentialBindingDescriptor = - typeof CredentialBindingDescriptor.infer; - -export const CredentialDelivery = type({ - bindings: CredentialBindingDescriptor.array(), - materials: CredentialMaterialEntry.array(), -}); -export type CredentialDelivery = typeof CredentialDelivery.infer; - -/** - * The source-ref pin: where a code-sourced (npm) workflow definition's bytes - * come from (`source`) plus the frozen dependency closure the hub resolved for - * that pin (`closure`, concrete versions + integrity SRIs). The two ALWAYS - * travel together -- the sidecar re-materializes the exact `closure` from - * `source` and re-evaluates the pinned code -- so they are one co-required - * object rather than two independently-optional fields (a "source without - * closure" state could not be re-materialized and re-evaluated, and evaluating - * the pinned code from the closure is the only channel the sidecar has to the - * runnable definition). This is the same shape `WorkflowProbeRequestFrame` - * co-requires. - */ -export const SourceRefPin = type({ - source: WorkflowDefinitionSource, - closure: ToolPackageManifest, -}); -export type SourceRefPin = typeof SourceRefPin.infer; - -/** - * The frozen, fully-serializable record of a code-sourced workflow approval, - * persisted at prepare time and rehydrated to deploy the exact same definition - * later. It is the recovery input for a provisioned workflow: the probe runs - * once on probe-scoped capacity, its result is frozen here, and a ready - * allocation deploys THIS bundle verbatim with no re-probe. - * - * Every field is inert, secret-free data. `source`/`entry` name where the - * definition's bytes come from and the entry module the probe evaluated; - * `projection` is the inert wire projection the freeze hashed; `closure` is the - * frozen dependency closure the pin resolved to; `approvedWireHash` is the freeze - * anchor; `approvedGrants` is the approved grant set (rehydrated to a `Set` on - * the deploy hand-off). Per-step inference sources are deliberately NOT frozen - * here -- they carry credential secrets and are re-resolved from the launch - * spec's offering ids at deploy time. - */ -export const FrozenApprovalBundle = type({ - source: WorkflowDefinitionSource, - entry: "string > 0", - projection: WorkflowProjectionDefinition, - closure: ToolPackageManifest, - approvedWireHash: "string > 0", - approvedGrants: "string[]", -}); -export type FrozenApprovalBundle = typeof FrozenApprovalBundle.infer; - -/** - * A hub asset delivered inline in a source-ref frame so the sidecar can - * materialize a closure entry whose bytes live in that asset. `pack` is the - * base64-encoded git packfile the hub produced for the asset (`createPack` - * output); the sidecar checks out `commitSha` from it as plain files under - * `mountPath`, then the loader resolves each `kind:"asset"` closure entry - * against that mount. `assetId` matches the `source.assetId` the closure - * entries name. - */ -export const WorkflowSourceAssetMount = type({ - assetId: "string", - mountPath: "string", - pack: "string", - ref: "string", - commitSha: "string", -}); -export type WorkflowSourceAssetMount = typeof WorkflowSourceAssetMount.infer; - -/** - * A full workflow deploy frame. The deploy lineage is source-ref only: the - * runnable definition is the pinned code closure the sidecar re-materializes and - * evaluates from `sourceRef`, so the frame carries NO inline `definition`. It - * pins each step's inference sources and the hub-approved wire hash the child - * re-verifies its closure evaluation against, plus the source-ref-specific - * extras. The sources-cover-stepOrder coverage narrow that a projection carries - * runs on the sidecar against the closure-derived definition - * (`validateWorkflowProjection`), since the frame holds no definition to cover. - * - * This is deliberately NOT built on `WorkflowProjectionWithSources`: that shape - * (definition + sources + approved hash) is the approval/probe projection and - * stays intact for the probe surface and for each `referencedDefinitions` body, - * which still carry their own inert definition. - */ -export const AgentDeployWorkflow = type({ - // Per-step inference-source failover chains, one per step in the closure's - // `stepOrder`. Threaded to the workflow-process child so it resolves inference - // at step invocation without a hub round-trip. - sources: { "[string]": InferenceSource.array().atLeastLength(1) }, - // The hub-approved wire hash of the frozen projection -- the freeze anchor the - // hub gate wrote. The sidecar feeds it to the child as `DEFINITION_HASH`, which - // the child re-verifies its closure evaluation against. Optional on the wire - // because the frame schema does not force it; enforcement lives at runtime - // instead -- the production hub builder always stamps it and the sidecar fails - // closed if it is absent. - "approvedWireHash?": "string > 0", - // Extracted trigger bodies -- onTrigger sections and childWorkflow children, - // lifted transitively. Each entry carries the body's inert definition, its own - // per-step inference-source pins, and its approved wire hash. The sidecar seals - // each body's sources into the per-run record and delivers the plaintext to the - // run child through the spawn env, so a body child -- in-process, its env lost - // across a restart -- resolves inference durably without holding the cipher - // key; the body definition itself is resolved in-memory from the parent's - // re-verified closure. Optional: only a deploy that carries an inline onTrigger - // section or childWorkflow child populates it. - "referencedDefinitions?": WorkflowProjectionWithSources.array(), - // Initial credential material for the deployment's tools, decrypted hub-side - // and delivered on the deploy frame so it is resident before any step runs - // (closing the race where a tool resolves a credential before a push lands). - // Run-global: a credential's secret is stored once, keyed by credentialId. - // Optional -- a deploy whose definition binds no credentials omits it. - "credentials?": CredentialDelivery, - // The source-ref pin (`source` + frozen `closure`) the sidecar re-materializes - // and evaluates the pinned code from. Required: source-ref is the only deploy - // lineage, and without the pin the sidecar has no definition to run. - sourceRef: SourceRefPin, - // Source assets a `kind:"asset"` closure entry reads from, delivered inline - // (as on the probe) so the sidecar checks them out into its durable - // per-deployment source store before materializing the pin. Optional: only - // an asset-sourced deploy carries it; a registry-sourced pin fetches its - // tarballs over HTTP and delivers none. - "assets?": WorkflowSourceAssetMount.array(), -}); -export type AgentDeployWorkflow = typeof AgentDeployWorkflow.infer; - -/** - * Deploy an agent to this sidecar. The sidecar spawns a supervised - * workflow-process child to host the deployment. - * - * The deploy router discriminates two shapes by field presence without - * consulting `config`: - * - `workflow` set: a workflow deployment (single-step head or multi-step) - * that spawns the supervised workflow-process child. - * - `provisionStep` true: a no-spawn per-step provision of a multi-step - * deploy -- the sidecar initializes the step's agent-state repo and - * records the hub key so the follow-up deploy pack applies and verifies, - * but spawns nothing. The deployment-level `workflow` frame (sent once - * after every step is provisioned) spawns the child. - * A frame carrying neither is rejected -- there is no in-process - * fall-through. `workflow` and `provisionStep` are mutually exclusive. - */ -export const AgentDeployFrame = type({ - type: "'agent.deploy'", - agentAddress: "string", - agentId: "string", - config: HarnessConfig, - hubPublicKey: "string", - "workflow?": AgentDeployWorkflow, - "provisionStep?": "boolean", -}); -export type AgentDeployFrame = typeof AgentDeployFrame.infer; - -/** - * Remove an agent from this sidecar. The sidecar shuts the deployment's - * supervisor down, pushes state to the hub (best-effort), deletes the agent - * directory, and responds with agent.undeploy.ack. - */ -export const AgentUndeployFrame = type({ - type: "'agent.undeploy'", - agentAddress: "string", - reason: "string", -}); -export type AgentUndeployFrame = typeof AgentUndeployFrame.infer; - -/** - * Keepalive pong sent by the hub in response to a ping frame. - * If the sidecar stops receiving pongs, it considers the hub dead. - */ -export const PongFrame = type({ type: "'pong'" }); -export type PongFrame = typeof PongFrame.infer; - -/** - * Push an updated inference-source list to a running single-step - * deployment. The sidecar routes it to the deployment's supervisor, which - * delivers it to the warm agent and swaps its sources in place. `sources` - * is non-empty (validated at this boundary, mirroring the deploy frame's - * per-step source arrays). Element 0 is the active source; the producer - * sets `defaultSource` to its id -- that equality is producer-enforced, - * not checked here. Responds with session.ack or session.error. - */ -export const SourcesUpdateFrame = type({ - type: "'sources.update'", - requestId: "string", - agentAddress: "string", - sources: InferenceSource.array().atLeastLength(1), - defaultSource: "string", -}); -export type SourcesUpdateFrame = typeof SourcesUpdateFrame.infer; - -/** - * Push refreshed credential material to a running deployment. Mirrors - * `SourcesUpdateFrame`: the sidecar routes it to the deployment's supervisor, - * which forwards it to the child's in-memory cell. The child MERGES `delivery` - * (materials upsert by credentialId, bindings by consumer-and-handle) and drops - * each credentialId in `revoke` plus any binding referencing it. Removal is - * explicit through `revoke` -- omitting a material does not evict it, because - * the cell has several independently-scoped producers and a wholesale swap - * would let one evict another's credentials. A pure revocation carries an empty - * `delivery` and the revoked ids in `revoke`. - */ -export const CredentialsUpdateFrame = type({ - type: "'credentials.update'", - requestId: "string", - agentAddress: "string", - delivery: CredentialDelivery, - "revoke?": "string[]", -}); -export type CredentialsUpdateFrame = typeof CredentialsUpdateFrame.infer; - -// --------------------------------------------------------------------------- -// Pack transport (bidirectional) -// --------------------------------------------------------------------------- -// -// Git pack data is streamed between hub and sidecar over the existing JSON -// WebSocket. Chunks are base64-encoded (matching the mail convention above). -// A transfer is a sequence of repo.pack.push frames followed by a -// repo.pack.done, correlated by transferId. The receiver responds with -// repo.pack.ack or repo.pack.reject. -// -// Each pack frame carries two complementary addressing fields: -// -// - `agentAddress` identifies the destination agent on the receiving -// sidecar. The sidecar manages per-agent state and uses this field to -// route the pack to the correct workspace. For agent-state packs the -// sidecar applies the pack onto the agent's deploy/state tree. -// -// - `repoId` identifies the source repo at the hub. The hub maps `repoId` -// to the originating entry in its kind-keyed RepoStore. For -// `repoId.kind === "agent-state"`, `repoId.id` is the run address -// (the deploy/state repo and the destination agent are the same), so -// the two fields carry the same value. Future kinds (e.g. assets) use -// `repoId` to name a non-agent source while `agentAddress` continues -// to address the destination agent. -// -// Flow control: deferred. Agent deploy trees are small enough that the sender -// can push all chunks without windowing. If this becomes a problem, a credit- -// based mechanism can be added later. - -/** - * Tag identifying a kind of repository in the hub's kind-keyed RepoStore. - * Lives in `@intx/types` because the wire-level pack frames reference it; - * the substrate package re-exports it for handler authors. - */ -export const RepoKind = type.enumerated( - "agent-state", - "skill", - "package-registry", - "workflow", - "workflow-run", -); -export type RepoKind = typeof RepoKind.infer; - -/** - * Operations a principal may invoke against a repo in the RepoStore. - * Lives in `@intx/types` so storage layers (e.g. `@intx/db`) can validate - * persisted action vocabularies without depending on the substrate - * package. The substrate re-exports it for handler authors. - */ -export const RepoAction = type.enumerated( - "init", - "writeTree", - "receivePack", - "createPack", - "resolveRef", -); -export type RepoAction = typeof RepoAction.infer; - -/** - * Hub-side identity of a repository in the RepoStore. Pack frames carry - * this alongside `agentAddress` so the hub can map a pack back to the - * originating repo independently of which sidecar/agent it is destined for. - */ -export const RepoId = type({ - kind: RepoKind, - id: "string", -}); -export type RepoId = typeof RepoId.infer; - -/** - * A chunk of git pack data. The sender splits the packfile into chunks of at - * most 64 KiB (before base64 encoding) and sends them in order. - * - * `seq` is monotonically increasing per transferId, starting at 0. The - * receiver must reject the transfer if a gap is detected. - */ -export const PackPushFrame = type({ - type: "'repo.pack.push'", - agentAddress: "string", - repoId: RepoId, - transferId: "string", - seq: "number", - data: "string", -}); -export type PackPushFrame = typeof PackPushFrame.infer; - -/** - * Signals the end of a pack transfer. The receiver applies the pack and - * updates `ref` to point at `commitSha`. If the post-apply HEAD does not - * match `commitSha`, the receiver must reject with reason "sha_mismatch". - * - * When `mountPath` is set, the receiver materializes the pack at - * `workspace//` instead of the hardcoded agent deploy tree. - * Absent for agent-state deploy/state flows and workflow-run restoration. - * The receiver distinguishes those paths by `repoId.kind`. - */ -export const PackDoneFrame = type({ - type: "'repo.pack.done'", - agentAddress: "string", - repoId: RepoId, - transferId: "string", - ref: "string", - commitSha: "string", - "mountPath?": "string", -}); -export type PackDoneFrame = typeof PackDoneFrame.infer; - -/** - * Receiver acknowledges successful application of a pack transfer. - */ -export const PackAckFrame = type({ - type: "'repo.pack.ack'", - agentAddress: "string", - repoId: RepoId, - transferId: "string", -}); -export type PackAckFrame = typeof PackAckFrame.infer; - -export const PackRejectReason = type.enumerated( - "signature_invalid", - "path_violation", - "conflict", - "corrupt", - "sha_mismatch", - "timeout", -); -export type PackRejectReason = typeof PackRejectReason.infer; - -/** - * Receiver rejects a pack transfer. - */ -export const PackRejectFrame = type({ - type: "'repo.pack.reject'", - agentAddress: "string", - repoId: RepoId, - transferId: "string", - // Validated as a plain string, NOT the closed `PackRejectReason` enum, on - // purpose. A reject carrying a reason value a newer peer added must still pass - // `HubFrame` validation and reach the reject handler (which latches the - // transfer) rather than failing validation and being dropped -- a dropped - // reject leaves the transfer neither acked nor rejected, stalling it until the - // next disconnect. Producers still classify and construct through - // `PackRejectReason`, so a known reason is what actually gets sent today; the - // reader treats any reason as a terminal reject (surfaces it, latches). - reason: "string", - // Optional human-readable cause carried alongside the machine reason, so the - // sender's operator sees WHY (e.g. "symlink at X is not supported") instead of - // only the coarse reason. Absent on rejects that have no extra detail. - "detail?": "string", -}); -export type PackRejectFrame = typeof PackRejectFrame.infer; - -/** - * Categories of deploy-apply failure surfaced by the sidecar's - * tool-package loader. Each value maps one-to-one to a distinct point in - * the apply pipeline; a single category fires per failed attempt. - * - * tarball.missing — a manifest entry's asset-sourced tarball - * is not present at the recorded path. - * asset.mount.missing — a `kind: "asset"` manifest entry names - * an `assetId` that the deploy pack's - * `deploy/asset-mounts.json` does not - * cover. Indicates a mismatch between the - * resolver's view of attached assets and - * the materialization fan-out, not a - * missing file on disk. - * integrity.mismatch — fetched tarball bytes do not match the - * manifest's pinned SRI integrity. - * registry.fetch.failed — the configured registry refused or - * dropped the request for a tarball. - * registry.unknown — the manifest entry references a registry - * name not present in the sidecar's - * registry config. - * registry.auth.failed — the registry rejected the sidecar's - * credentials. - * tarball.extract.failed — tar extraction failed or the extracted - * tree was malformed. - * git.materialization.failed - * — a git-sourced entry could not be - * materialized from its checked-out - * subtree, or reached a loader that does - * not materialize git sources. - * manifest.invalid — the manifest itself did not validate - * at the loader boundary (JSON.parse - * failure or arktype schema failure). - * Peer-dependency violations are caught - * earlier by the hub's resolver and - * surface as a launch failure rather - * than this frame. - * package.entry.missing — a top-level package's package.json had - * no `interchange.tools` field. - * package.entry.invalid — the resolved `interchange.tools` module - * exported nothing that looked like an - * AnnotatedToolFactory. - * factory.construct.failed — a factory invocation threw, or required - * a capability key the env did not provide. - * tool.name.duplicate — a tool name is registered more than - * once in the apply's loaded set. The - * cross-bundle case (two pinned packages - * share a bundle id, producing colliding - * prefixed tool names) is rejected at - * apply time, before the caller commits. - * The intra-bundle case (one package - * exports two definitions sharing a raw - * name) surfaces at first agent - * construction with the same category - * instead of apply rejection: the loader - * cannot see `bundle.definitions` without - * invoking the factory, and the `BaseEnv` - * the factory needs is constructed by the - * workflow child's step build env AFTER - * the commit. Both paths carry the same - * category so the operator-facing failure - * shape is uniform regardless of which - * check fired; only the channel - * (apply.error frame vs runtime construct - * failure) differs. - * apply.swap.failed — DEPRECATED, no longer emitted. The apply - * protocol stages each deploy into a stable - * per-deploy-id directory and commits via a - * single `active-deploy-id` file write, so - * there is no filesystem rename that can - * fail. The value is retained in the enum - * for wire compatibility: during a rolling - * upgrade an older sidecar can still emit - * it, and dropping the member would make a - * newer hub's frame validator reject that - * frame. - * apply.previous-rotation.failed - * — every loaded factory validated and the - * new deploy was staged, but persisting the - * instance's `active-deploy-id` file (the - * commit) degraded: the id was written - * through the no-fsync / dirty-marker - * fallback ladder rather than durably - * flushed. The new deploy is logically - * live, so `previousDeployId` on this - * failure carries the NEW deploy id rather - * than the pre-apply one. The next boot - * reconciles the recorded id from the dirty - * marker. - */ -export const DeployApplyErrorCategory = type.enumerated( - "tarball.missing", - "asset.mount.missing", - "integrity.mismatch", - "registry.fetch.failed", - "registry.unknown", - "registry.auth.failed", - "tarball.extract.failed", - "git.materialization.failed", - "manifest.invalid", - "package.entry.missing", - "package.entry.invalid", - "factory.construct.failed", - "tool.name.duplicate", - "apply.swap.failed", - "apply.previous-rotation.failed", -); -export type DeployApplyErrorCategory = typeof DeployApplyErrorCategory.infer; - -/** - * Hub requests the sidecar to push its current agent state. The sidecar - * responds by sending pack.push frames followed by pack.done using the - * same transferId. - */ -export const SyncRequestFrame = type({ - type: "'sync.request'", - agentAddress: "string", - transferId: "string", -}); -export type SyncRequestFrame = typeof SyncRequestFrame.infer; - -// --------------------------------------------------------------------------- -// Workflow probe (bidirectional) -// --------------------------------------------------------------------------- -// -// A probe asks a connected sidecar to inspect a code-sourced workflow WITHOUT -// deploying it: materialize the frozen dependency closure, evaluate the entry -// module to a live `WorkflowDefinition`, project it to its inert needs -// surface, and return that projection plus the derived grant set and content -// hash. The request/result/error trio is correlated by `requestId`, entirely -// independent of the address maps -- a token-authed sidecar can serve a probe -// in its pre-deploy state, with no agent deployed and no routable address. - -/** - * Hub asks a connected sidecar to probe a code-sourced workflow. Correlated by - * `requestId`; the sidecar answers with `workflow.probe.result` on success or - * `workflow.probe.error` on failure, both carrying the same `requestId`. - * - * The frame carries everything the sidecar's probe child needs to run the - * probe with no further hub round-trip: - * - `source` names where the definition's bytes come from (a registry, a - * package-registry asset, or a git asset). - * - `closure` is the frozen dependency closure the hub already resolved -- - * concrete versions and integrity SRIs -- so the child materializes the - * exact tree the hub pinned. - * - `entry` is the `interchange.workflow` module path within the package - * whose evaluation produces the `WorkflowDefinition`. - * - `assets` (optional) delivers the hub assets a `kind:"asset"` closure - * entry reads from, inline. Delivery is inline rather than a separate - * streamed transfer (as the deploy path uses) because the probe is a - * single-shot request that already buffers the whole frame -- streaming - * would only add a transfer-vs-probe correlation state a one-shot has no - * use for. The sidecar caps the total inline payload and fails loud past - * it; a git-sourced asset that grows past that cap is the trigger to - * revisit streaming. - */ -export const WorkflowProbeRequestFrame = type({ - type: "'workflow.probe.request'", - requestId: "string", - source: WorkflowDefinitionSource, - closure: ToolPackageManifest, - entry: "string", - "assets?": WorkflowSourceAssetMount.array(), -}); -export type WorkflowProbeRequestFrame = typeof WorkflowProbeRequestFrame.infer; - -/** - * A connected sidecar's answer to a `workflow.probe.request`: the inert - * needs-surface projection of the probed workflow, the inert grant set derived - * from it, and the content hash of the projection. Correlated to the request - * by `requestId`. - * - * `projection` is the same closed `WorkflowProjectionDefinition` a deploy frame - * carries. `grants` is the deployment-wide inert grant surface -- the deduped, - * sorted union of every step's grant strings -- for pre-deploy operator - * inspection. `wireHash` is the hex SHA-256 of the projection's canonical JSON - * (`computeWireDefinitionHash` in `@intx/types/wire-definition-hash`), the - * deployment's content-addressed handle. - * - * `grantWalkSnapshot` is the UN-flattened capability walk the flattened - * `grants` is derived from: the per-step grant declarations (each step's grant - * strings plus its tool-grant `grantEffects` map) and the definition's full, - * unfiltered `grantRequirements`. It carries the per-step grouping and the - * effect data that `grants` discards, so a later persist step can record the - * complete grant walk rather than only its flattened union. The flattened - * `grants` stays alongside it because the operator-approval gate consumes it. - */ -export const WorkflowProbeResultFrame = type({ - type: "'workflow.probe.result'", - requestId: "string", - projection: WorkflowProjectionDefinition, - grants: "string[]", - grantWalkSnapshot: GrantWalkSnapshot, - wireHash: "string", -}); -export type WorkflowProbeResultFrame = typeof WorkflowProbeResultFrame.infer; - -/** - * A connected sidecar reports that a `workflow.probe.request` failed -- - * materialization, evaluation, projection, or hashing threw. Correlated to the - * request by `requestId`; `error` describes the failure. - */ -export const WorkflowProbeErrorFrame = type({ - type: "'workflow.probe.error'", - requestId: "string", - error: "string", -}); -export type WorkflowProbeErrorFrame = typeof WorkflowProbeErrorFrame.infer; - -// --------------------------------------------------------------------------- -// Sidecar-hosted OAuth loopback login (CL-7508) -// --------------------------------------------------------------------------- - -/** Tokens a sidecar-hosted loopback login staged. The PKCE verifier never - * crosses the wire — it lives only in the sidecar's login service and dies - * with the callback server. */ -export const OAuthLoginTokens = type({ - access: "string", - "refresh?": "string", - /** Epoch ms the access token expires; absent when the issuer stated no - * lifetime (stored non-due, never a short artificial timer). */ - "expiresAt?": "number", - /** The issuer's id_token when it issues one (xai-oauth retains it). */ - "idToken?": "string", - /** id_token-derived account label (codex: `chatgpt_account_id`), the - * value `accountIdFromIdToken` decodes; threaded into credential - * metadata hub-side. */ - "accountId?": "string", -}); -export type OAuthLoginTokens = typeof OAuthLoginTokens.infer; - -/** Outcome arms a `oauth.login.result` frame may carry. A login sends - * `started` once its callback server is bound and the authorize URL is - * ready for the web UI to navigate; exactly one terminal arm (`completed` - * or `error`) follows. */ -export const OAuthLoginOutcome = type({ - status: "'started'", - authorizeUrl: "string", -}) - .or({ status: "'completed'", tokens: OAuthLoginTokens }) - .or({ status: "'error'", message: "string" }); -export type OAuthLoginOutcome = typeof OAuthLoginOutcome.infer; - -/** Hub → sidecar: run the named connector's loopback PKCE login on the - * machine this sidecar runs on. The connector must pin a fixed loopback - * redirect (`codex` → localhost:1455, `xai-oauth` → 127.0.0.1:1456); the - * hub never hosts a listener for these flows. */ -export const OAuthLoginStartFrame = type({ - type: "'oauth.login.start'", - requestId: "string", - connectorId: "'codex' | 'xai-oauth'", -}); -export type OAuthLoginStartFrame = typeof OAuthLoginStartFrame.infer; - -/** Sidecar → hub: the staged progress / terminal outcome of the login the - * hub requested with the same `requestId`. */ -export const OAuthLoginResultFrame = type({ - type: "'oauth.login.result'", - requestId: "string", - outcome: OAuthLoginOutcome, -}); -export type OAuthLoginResultFrame = typeof OAuthLoginResultFrame.infer; - -/** Hub → sidecar: tear the staged login with this `requestId` down and - * close its pinned-port callback listener. The hub sends it when it gives - * up on a login (whole-login timeout) so an abandoned bind does not wedge - * every retry of the connector until the sidecar restarts. */ -export const OAuthLoginCancelFrame = type({ - type: "'oauth.login.cancel'", - requestId: "string", -}); -export type OAuthLoginCancelFrame = typeof OAuthLoginCancelFrame.infer; - -// --------------------------------------------------------------------------- -// Discriminated frame unions -// --------------------------------------------------------------------------- - -/** All frame types the sidecar sends to the hub. */ -export const SidecarFrame = RegisterFrame.or(ReconnectFrame) - .or(AgentDeployAckFrame) - .or(AgentErrorFrame) - .or(MailOutboundFrame) - .or(AgentEventFrame) - .or(ConnectorStateChangedFrame) - .or(PingFrame) - .or(SessionAckFrame) - .or(SessionErrorFrame) - .or(AgentUndeployAckFrame) - .or(SignalCorrelationRegisterFrame) - .or(PackPushFrame) - .or(PackDoneFrame) - .or(PackAckFrame) - .or(PackRejectFrame) - .or(MailInboundAckFrame) - .or(WorkflowProbeResultFrame) - .or(WorkflowProbeErrorFrame) - .or(OAuthLoginResultFrame); -export type SidecarFrame = typeof SidecarFrame.infer; - -/** All frame types the hub sends to the sidecar. */ -export const HubFrame = MailInboundFrame.or(AgentDeployFrame) - .or(AgentUndeployFrame) - .or(PongFrame) - .or(SourcesUpdateFrame) - .or(CredentialsUpdateFrame) - .or(PackPushFrame) - .or(PackDoneFrame) - .or(PackAckFrame) - .or(PackRejectFrame) - .or(SyncRequestFrame) - .or(SignalDeliverFrame) - .or(RunGrantsFrame) - .or(SignalCorrelationRegisterAckFrame) - .or(DrainDeliverFrame) - .or(WorkflowProbeRequestFrame) - .or(OAuthLoginStartFrame) - .or(OAuthLoginCancelFrame); -export type HubFrame = typeof HubFrame.infer; - -/** Any frame on the wire, regardless of direction. */ -export const WireFrame = SidecarFrame.or(HubFrame); -export type WireFrame = typeof WireFrame.infer; diff --git a/vendor/intx-types/src/signals.ts b/vendor/intx-types/src/signals.ts deleted file mode 100644 index 53855c2..0000000 --- a/vendor/intx-types/src/signals.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { type } from "arktype"; - -import type { GateType } from "./runtime"; - -/** - * The kinds of external control signal an agent can suspend on and later - * resume from. Exposed as both an arktype validator (so members are - * iterable and can be composed into wire validators) and a derived - * TypeScript union. - */ -export const signalKinds = ["approval"] as const; -export const SignalKind = type.enumerated(...signalKinds); -export type SignalKind = typeof SignalKind.infer; - -/** - * The internal resumption taxonomy: how a parked run resumes, keyed by - * (`kind`, `outcome`). This is NOT the approver's wire decision -- that is - * `ApprovalDecision`, which the delivery path parses. `ControlSignal` is the - * `kind`-discriminated union the resumption dispatch is designed around; - * `correlationId` ties an entry back to the suspension it resolves and - * `payload` carries kind-specific data opaquely. It is intentionally ahead of - * its consumers: the `approval` arm is the only one wired today, and its - * `timeout` outcome arrives via the gate-timeout path, not as a delivered - * decision. Each remaining signal flow activates its own arm as it lands. - */ -export const ControlSignal = type({ - correlationId: "string", - kind: "'approval'", - outcome: "'approved' | 'rejected' | 'timeout'", - payload: "unknown", -}); -export type ControlSignal = typeof ControlSignal.infer; - -/** - * The decision an approver hands back when they resolve an approval. This is - * the payload delivered to the parked run through `sendSignalDeliver`; the - * run's `parkOnSignal` awaitNext returns it verbatim as the correlated inbound. - * `scope` is deliberately absent: it is a storage-and-grant concern the - * resolver records on the approval row, not something the resumed run consumes. - */ -export const ApprovalDecision = type({ - outcome: "'approved' | 'rejected'", - "message?": "string", -}); -export type ApprovalDecision = typeof ApprovalDecision.infer; - -/** - * Map a signal kind to the reactor gate type it clears. The default arm - * calls `assertNever` so a newly added SignalKind that is not classified - * here fails to type-check — a bare switch without a default does not. - */ -export function signalKindToGateType(kind: SignalKind): GateType { - switch (kind) { - case "approval": - return "approval"; - default: - return assertNever(kind); - } -} - -function assertNever(x: never): never { - throw new Error(`Unclassified signal kind: ${JSON.stringify(x)}`); -} - -/** - * The reserved prefix that marks a signal name as an internal - * control-plane channel rather than a free-form `awaitSignal` gate name. - * The writer (`signalName`) and the reader (`correlationIdFromSignalName`) - * share this one constant so the two cannot drift. - */ -const SIGNAL_NAME_PREFIX = "__signal__:"; - -/** - * Construct the reserved, `__signal__:`-prefixed name under which a control - * signal for `correlationId` is delivered. This reserves a name namespace - * distinct from the user-authored workflow-signal names that flow through - * `SignalDeliverFrame.signalName` in `./sidecar`: those are free-form - * `awaitSignal` gate names chosen by workflow authors, whereas this helper - * mints an internal name the control plane owns, so the two cannot collide. - */ -export function signalName(correlationId: string): string { - return `${SIGNAL_NAME_PREFIX}${correlationId}`; -} - -/** - * Recover the `correlationId` from a reserved control-plane signal name - * minted by `signalName`. Returns `undefined` for a name that does not - * carry the reserved prefix (a free-form `awaitSignal` gate name), so a - * caller can tell a control-plane channel apart from an author-chosen one. - * Symmetric with `signalName`: `correlationIdFromSignalName(signalName(id)) - * === id`. - */ -export function correlationIdFromSignalName(name: string): string | undefined { - if (!name.startsWith(SIGNAL_NAME_PREFIX)) return undefined; - return name.slice(SIGNAL_NAME_PREFIX.length); -} diff --git a/vendor/intx-types/src/signer-identity.ts b/vendor/intx-types/src/signer-identity.ts deleted file mode 100644 index 3737d06..0000000 --- a/vendor/intx-types/src/signer-identity.ts +++ /dev/null @@ -1,33 +0,0 @@ -// How the signer behind a signature is identified. -// -// The only signer today is a principal whose Ed25519 private key the hub -// custodies (`local-principal`). The union is keyed on `kind` so a future -// signer flavour (say a hub-held key, or an externally-held key) is added with -// `.or()` and every by-value consumer that switches on `kind` gains a compile -// error for the unhandled variant. - -import { type } from "arktype"; - -/** - * A signer whose private key the hub custodies on a principal's behalf. - * - * `publicKey` is the RESOLVED, hex-encoded Ed25519 public key read from the - * hub's own principal-key store -- it is the trusted key for `principalId`. It - * MUST NOT be populated from untrusted input (e.g. a public key claimed on an - * inbound message): a verifier resolves the key from the store by `principalId` - * and checks the signature against that, never against a key from the wire. - */ -export const LocalPrincipalSigner = type({ - kind: "'local-principal'", - principalId: "string", - publicKey: "string", -}); -export type LocalPrincipalSigner = typeof LocalPrincipalSigner.infer; - -/** - * Discriminated union over how a signature's signer is identified, keyed on - * `kind`. Only the hub-custodied `local-principal` signer exists today; widen - * it here with `.or()` and every by-value consumer follows. - */ -export const SignerIdentity = LocalPrincipalSigner; -export type SignerIdentity = typeof SignerIdentity.infer; diff --git a/vendor/intx-types/src/tenants.ts b/vendor/intx-types/src/tenants.ts deleted file mode 100644 index bf1ffa7..0000000 --- a/vendor/intx-types/src/tenants.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { type } from "arktype"; - -import { SidecarCapabilityPolicy } from "./sidecar-capabilities"; - -export const TenantConfig = type({ - "sidecarPlacement?": SidecarCapabilityPolicy, - "[string]": "unknown", -}); -export type TenantConfig = typeof TenantConfig.infer; - -export const CreateTenant = type({ - name: "string", - slug: "string", - "parentId?": "string | null", -}); - -export const UpdateTenant = type({ - "name?": "string", - "config?": TenantConfig, -}); - -export const TenantResponse = type({ - id: "string", - name: "string", - slug: "string", - domain: "string", - "parentId?": "string | null", - "config?": TenantConfig, - createdAt: "string", - updatedAt: "string", -}); - -export const FederationTrust = type({ - tenantId: "string", - tenantName: "string", - tenantDomain: "string", - direction: "'inbound' | 'outbound' | 'bilateral'", - createdAt: "string", -}); - -export const CreateFederationTrust = type({ - targetTenantId: "string", - direction: "'inbound' | 'outbound' | 'bilateral'", -}); diff --git a/vendor/intx-types/src/tool-packages.ts b/vendor/intx-types/src/tool-packages.ts deleted file mode 100644 index fad721d..0000000 --- a/vendor/intx-types/src/tool-packages.ts +++ /dev/null @@ -1,312 +0,0 @@ -// Schemas for the tool-package distribution path. -// -// An agent pins one or more tool packages via `ToolPackagePin[]`. At -// deploy-assembly time, the hub walks the pinned set, resolves the full -// dependency closure, and writes a `ToolPackageManifest` into the deploy -// pack. The sidecar reads the manifest at apply time and materializes -// every entry. -// -// Only entries listed in `topLevel` contribute tools to the agent; -// transitive entries exist to satisfy `require()` / `import` resolution -// inside the top-level packages. - -import { type } from "arktype"; -import semver from "semver"; - -import { - ToolCredentialDeclarationArray, - isContainedEntryPath, -} from "./package-json"; - -/** - * npm's documented package-name rules expressed as an arktype regex - * literal: lowercase, may begin with a scope (`@scope/`), the rest of - * each segment is URL-safe (letters, digits, `_`, `-`, `.`), no - * leading dot or underscore, scoped names require a `/`. The npm - * registry rejects anything else; mirroring the rule at the REST - * boundary keeps mixed-case or malformed pins from threading past - * the API into the resolver, which would otherwise self-resolve - * them and then fail at the sidecar loader. - * - * Using a regex literal (rather than a `narrow` predicate) lets the - * JSON-Schema generator surface the rule as a `pattern` field in the - * OpenAPI spec without a fallback hook. - */ -export const ToolPackagePinName = type( - /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/, -); - -/** - * A pin in an agent definition: name + version range. The hub resolves - * this against configured registries at deploy-assembly time. - * - * `version` is an npm-style spec ("^1.2.3", "~1.2", "1.2.3", "*"). - * Resolution is performed by `npm-pick-manifest` against the registry - * packument. Semver-range validation lives on `ToolPackagePinArray` - * (below) so the JSON-Schema generator sees a plain string here; the - * array narrow is the actual REST boundary for pins and runs before - * any value reaches the resolver. - * - * `name` must match npm's documented package-name rules — lowercase, - * optional scope prefix, URL-safe characters only. npm itself rejects - * uppercase names; packuments arrive lowercased, so a mixed-case pin - * would self-resolve and then silently fail the sidecar loader's - * `${name}@${version}` lookup against the lowercase entry the - * packument produced. - * - * A `ToolPackagePin[]` must contain at most one entry per `name`. Use - * `ToolPackagePinArray` (below) at REST boundaries to enforce dedup - * before the resolver runs; the resolver still rejects duplicates at - * its own boundary as belt-and-suspenders. - */ -export const ToolPackagePin = type({ - name: ToolPackagePinName, - version: "string", -}); -export type ToolPackagePin = typeof ToolPackagePin.infer; - -/** - * Array of pins with the no-duplicate-name and parseable-version - * invariants enforced at parse time. The downstream resolver keys - * its top-level resolution map by name; two pins of the same name - * would silently collapse to the first arrival's resolved version, - * and an unparseable semver range would fail mid-walk. Rejecting - * both at the REST boundary surfaces the bug to the caller instead - * of leaving it to misbehave at launch time. - * - * `*` is accepted as the documented any-version range; anything - * else must satisfy `semver.validRange`. - * - * NOTE: the same `*` special-case lives in `parsePin` inside the - * tool-packaging resolver. Any new magic-range additions need to be - * carved at both sites — the packages are separated by the wire-type - * vs. resolver boundary and cannot import each other. - */ -export const ToolPackagePinArray = ToolPackagePin.array().narrow( - (pins, ctx) => { - const seen = new Set(); - for (const pin of pins) { - if (seen.has(pin.name)) { - return ctx.mustBe( - `an array with no duplicate package names; "${pin.name}" appears more than once`, - ); - } - seen.add(pin.name); - if (pin.version !== "*" && semver.validRange(pin.version) === null) { - return ctx.mustBe( - `every pin to carry a parseable semver range; "${pin.name}" has version ${JSON.stringify(pin.version)}`, - ); - } - } - return true; - }, -); -export type ToolPackagePinArray = typeof ToolPackagePinArray.infer; - -/** - * A top-level manifest entry: a pinned package at its concrete resolved - * version, carrying the credential declarations harvested from the package's - * `interchange.credentials` (absent when it declares none). Only top-level - * pins contribute declarations; transitive dependencies never do, which is why - * this shape hangs off `topLevel` rather than `entries`. - */ -export const ToolPackageTopLevelEntry = type({ - name: ToolPackagePinName, - version: "string", - "credentials?": ToolCredentialDeclarationArray, -}); -export type ToolPackageTopLevelEntry = typeof ToolPackageTopLevelEntry.infer; - -/** - * The manifest's top-level entries with the no-duplicate-name invariant - * preserved -- the same guarantee `ToolPackagePinArray` gives agent-side pins. - * Versions here are concrete (already picked by the resolver), so the - * semver-range check that guards agent-side pins is unnecessary. - */ -export const ToolPackageTopLevelArray = ToolPackageTopLevelEntry.array().narrow( - (entries, ctx) => { - const seen = new Set(); - for (const entry of entries) { - if (seen.has(entry.name)) { - return ctx.mustBe( - `an array with no duplicate package names; "${entry.name}" appears more than once`, - ); - } - seen.add(entry.name); - } - return true; - }, -); -export type ToolPackageTopLevelArray = typeof ToolPackageTopLevelArray.infer; - -/** - * A pinned entry's bytes are fetched from an EXTERNAL npm registry at - * apply time. The sidecar's registry config maps `registry` to a URL and - * credentials. - * - * `integrity` is the SRI string ("sha512-...") the registry served for - * the picked version. The loader verifies the fetched bytes against it - * before unpacking and uses it as the content-addressed cache key. - */ -export const ToolPackageRegistrySource = type({ - kind: "'registry'", - registry: "string", - integrity: "string", -}); -export type ToolPackageRegistrySource = typeof ToolPackageRegistrySource.infer; - -/** - * The entry's bytes are a prepackaged npm tarball living at `path` inside - * the asset's checkout (the package-registry kind stores them under - * `tarballs/.tgz`). The loader reads the blob and extracts it. - * - * `integrity` is the SRI string ("sha512-...") of the tarball bytes. A - * reclassified npm tarball keeps its SRI: it is the same artifact an - * external registry would serve, so the loader verifies the read bytes - * against it and uses it as the content-addressed cache key, and a - * byte-identical tarball has one identity regardless of transport. - */ -export const ToolPackageAssetTarball = type({ - format: "'tarball'", - path: "string", - integrity: "string", -}); -export type ToolPackageAssetTarball = typeof ToolPackageAssetTarball.infer; - -/** - * The entry's bytes are a source package: the subtree at `packageDir` - * of the asset's checkout at `commitSha`, used in place (not packed). - * The loader checks the tree out and copies the subtree into the store. - * - * `packageDir` is the resolved POSIX subtree path of this package within - * the repo ("." for a single-package repo root, "packages/foo" for a - * monorepo member). It is a resolved directory, not a package name: a - * frozen materialization coordinate must not require re-resolving a - * `package.json` name against the tree at apply time. The narrow rejects - * absolute paths and `..` traversal at the boundary. - * - * `treeOid` is the git tree object id of the subtree at `commitSha` -- - * the content identity the loader verifies the checked-out subtree - * against. Unlike a tarball's `integrity`, it is a git tree oid, not an - * SRI, because a source subtree has no tarball bytes to hash. - */ -export const ToolPackageAssetSourceTree = type({ - format: "'source'", - commitSha: "string", - packageDir: type("string").narrow((dir, ctx) => - isContainedEntryPath(dir) - ? true - : ctx.mustBe("a repo-relative path with no '..' traversal"), - ), - treeOid: "string", -}); -export type ToolPackageAssetSourceTree = - typeof ToolPackageAssetSourceTree.infer; - -/** - * A pinned entry's bytes come from a hub `asset` -- a checked-out git - * repo attached to the agent at session time. `assetId` is the hub-side - * asset row id; the sidecar resolves it against the deploy pack's mount - * map to reach the asset's checkout. The package lives at a location - * within the checkout, either a prepackaged `tarball` or a `source` - * subtree, discriminated by `package.format`. - */ -export const ToolPackageAssetSource = type({ - kind: "'asset'", - assetId: "string", - package: ToolPackageAssetTarball.or(ToolPackageAssetSourceTree), -}); -export type ToolPackageAssetSource = typeof ToolPackageAssetSource.infer; - -/** - * Discriminated union over where a manifest entry's bytes come from: an - * external npm `registry`, or a hub `asset` (a git checkout holding a - * tarball or a source package). - */ -export const ToolPackageSource = ToolPackageRegistrySource.or( - ToolPackageAssetSource, -); -export type ToolPackageSource = typeof ToolPackageSource.infer; - -/** - * A closure entry's content identity, whatever its source: the tarball - * SRI for a `registry` entry or an `asset` tarball, the subtree git tree - * oid for an `asset` source package. Cache-bust keys read this rather - * than reaching into a shape-specific field. - */ -export function getToolPackageSourceContentIdentity( - source: ToolPackageSource, -): string { - if (source.kind === "registry") { - return source.integrity; - } - return source.package.format === "tarball" - ? source.package.integrity - : source.package.treeOid; -} - -/** - * A single pinned package in the closure. - * - * The entry's content identity lives on its `source` arm, because how it - * is derived and verified depends on where the bytes come from: an SRI - * over tarball bytes for the `asset` and `registry` arms. - * - * `os` / `cpu` are present when the entry comes from an - * `optionalDependencies` declaration with platform constraints. The - * sidecar filters entries by its own host before fetching; entries - * whose `os` or `cpu` does not include the host's value are skipped - * with a `platform.mismatch.skipped` debug log. - * - * `tarballUrl` is preserved for registry-sourced entries so the sidecar - * can fetch without re-resolving against the registry's packument; the - * hub recorded the exact URL the registry served at resolution time. - */ -export const ToolPackageManifestEntry = type({ - name: "string", - version: "string", - source: ToolPackageSource, - "os?": "string[]", - "cpu?": "string[]", - "tarballUrl?": "string", -}); -export type ToolPackageManifestEntry = typeof ToolPackageManifestEntry.infer; - -/** - * The manifest written into the deploy pack at - * `deploy/tool-packages-manifest.json`. - * - * `schemaVersion` is a literal "1" for now. Future schema changes bump - * this and the loader refuses unknown versions with `manifest.invalid`. - * - * `topLevel` enumerates the packages the agent definition explicitly - * pinned. The loader only scans these for `interchange.tools`; entries - * present in `entries` but absent from `topLevel` are transitive - * dependencies materialized for runtime `require()` / `import` - * resolution. - * - * `topLevel` extends the agent-side `ToolPackagePin` shape with the - * package's harvested `credentials` declarations. The `version` field - * here is always a concrete version (e.g. `"1.2.3"`), not a range. The resolver walks each - * agent-side pin's range through `npm-pick-manifest` and writes the - * picked version. The sidecar loader pairs `topLevel[i]` against - * `entries[j]` by `${name}@${version}` equality, so a range-form - * `version` here would never match any entry and the package would - * silently contribute no tool factories at apply time. - * - * `entries` carries the full pinned closure: every top-level pin plus - * every transitive dependency, deduped by `(name, version)`. The - * sidecar materializes every entry whose `os`/`cpu` matches its host. - */ -export const ToolPackageManifest = type({ - schemaVersion: "'1'", - // Use the array-level narrow so the wire validator catches duplicate - // top-level names directly, even when the manifest is produced by a - // hub the resolver did not author. The resolver enforces uniqueness - // when building the manifest; the validator is the second line of - // defense for any third-party hub or hand-edited file that slips a - // duplicate through. - topLevel: ToolPackageTopLevelArray, - entries: ToolPackageManifestEntry.array(), -}); -export type ToolPackageManifest = typeof ToolPackageManifest.infer; diff --git a/vendor/intx-types/src/wallets.ts b/vendor/intx-types/src/wallets.ts deleted file mode 100644 index 0c66ac4..0000000 --- a/vendor/intx-types/src/wallets.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { type } from "arktype"; - -export const walletBackendTypes = ["crypto", "fiat", "credits"] as const; -export type WalletBackendType = (typeof walletBackendTypes)[number]; - -const BackendType = type.enumerated(...walletBackendTypes); - -const backendTypeDescription = - "Settlement backend the wallet is denominated in: `crypto` (on-chain assets), `fiat` (national currency), or `credits` (internal accounting units). Determines how balances and transactions are settled."; - -const walletConfigDescription = - "Backend-specific configuration for the wallet (for example chain or account details for a `crypto` backend). Shape depends on `backendType`; not interpreted by the hub."; - -const balanceDescription = - "Current balance as a decimal string in the wallet's `currency`. Stored as a string to preserve precision for both crypto and fiat amounts."; - -export const CreateWallet = type({ - name: "string", - backendType: BackendType.describe(backendTypeDescription), - currency: "string", - "config?": type("Record").describe(walletConfigDescription), -}); - -export const UpdateWallet = type({ - "name?": "string", - "config?": type("Record").describe(walletConfigDescription), -}); - -export const WalletResponse = type({ - id: "string", - tenantId: "string", - name: "string", - backendType: BackendType.describe(backendTypeDescription), - currency: "string", - balance: type("string").describe(balanceDescription), - "config?": type("Record").describe(walletConfigDescription), - createdAt: "string", - updatedAt: "string", -}); - -export const TransactionResponse = type({ - id: "string", - walletId: "string", - "runId?": "string | null", - direction: type("'inbound' | 'outbound'").describe( - "Whether funds moved into the wallet (`inbound`) or out of it (`outbound`).", - ), - amount: type("string").describe( - "Transaction amount as a decimal string in `currency`, stored as a string to preserve precision.", - ), - currency: "string", - "recipientId?": "string | null", - "senderId?": "string | null", - "requestId?": "string | null", - status: type("'pending' | 'completed' | 'failed'").describe( - "Settlement state of the transaction: `pending` (initiated, not yet settled), `completed`, or `failed`.", - ), - createdAt: "string", -}); diff --git a/vendor/intx-types/src/wire-definition-hash.ts b/vendor/intx-types/src/wire-definition-hash.ts deleted file mode 100644 index dfe9fe7..0000000 --- a/vendor/intx-types/src/wire-definition-hash.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Content-addressed hash of a wire-projected workflow definition. -// -// The deploy gate, the install-time probe, and re-verify must all agree -// on the deployment's content handle, so they hash the exact same -// canonical form. This module is the single source of truth those call -// sites import; the hash is a hex SHA-256 of the definition's canonical -// JSON. - -import { hexEncode } from "./hex"; - -/** - * Project a value into a canonical JSON string with deterministically - * sorted object keys. Object key order and surrounding whitespace do not - * affect the output, so two structurally equal values serialize to - * byte-identical strings and therefore hash equal. - * - * Values follow `JSON.stringify`'s semantics for what JSON can represent: - * an object key whose value is `undefined`, a function, or a symbol is - * dropped, and such an array element renders as `null`. The only intended - * difference from `JSON.stringify` is the deterministic key order. So for - * JSON-representable values the output is invariant across a JSON - * round-trip; a value with a custom `toJSON` (e.g. a `Date`) is NOT - * round-trip invariant here, because this canonicalizer does not invoke - * `toJSON` -- a caller needing round-trip invariance must pass it - * already-JSON values. - */ -export function canonicalJsonStringify(value: unknown): string { - if (value === null) return "null"; - if (typeof value !== "object") { - // Primitives serialize as JSON does. A value JSON cannot represent - // (`undefined`, a function, a symbol) has no JSON text, so - // `JSON.stringify` returns `undefined`; canonicalize it to `null`, matching - // how JSON renders such a value as an array element (see below). A - // top-level such value never reaches a definition hash. - return JSON.stringify(value) ?? "null"; - } - if (Array.isArray(value)) { - // JSON renders an `undefined`/function/symbol array element as `null`; the - // scalar branch above produces exactly that, so a plain recursive map keeps - // parity. - return `[${value.map((v) => canonicalJsonStringify(v)).join(",")}]`; - } - // Drop keys whose value JSON.stringify would omit (`undefined`, functions, - // symbols), mirroring how JSON serializes an object. - const entries = Object.entries(value) - .filter( - ([, v]) => - v !== undefined && typeof v !== "function" && typeof v !== "symbol", - ) - .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); - return `{${entries - .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`) - .join(",")}}`; -} - -/** - * Compute the content hash for a wire-projected workflow definition: - * SHA-256 of the canonical JSON of the `WorkflowDefinition` projection, - * hex-encoded. This is the deployment's content-addressed handle; every - * party that binds identity, approval, or re-verify to a deployment - * derives it from the same canonical form so their values compare by - * byte equality. - * - * Invariance across the boundary is load-bearing: the hub hashes a - * projection parsed off the JSON wire (where `undefined`-valued keys are - * already gone) while a child hashes an in-memory projection, and the two - * must produce byte-identical strings or re-verify would fail on a - * legitimately-approved definition. The canonicalizer's Date/`toJSON` - * caveat is moot here: both sides hash a projection parsed from the JSON - * wire, so no custom-`toJSON` value ever reaches the canonicalizer and the - * two sides agree. - */ -export async function computeWireDefinitionHash( - definition: unknown, -): Promise { - const canonical = canonicalJsonStringify(definition); - const digest = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(canonical), - ); - return hexEncode(new Uint8Array(digest)); -} diff --git a/vendor/intx-types/src/wire-workflow.ts b/vendor/intx-types/src/wire-workflow.ts deleted file mode 100644 index 358d7d3..0000000 --- a/vendor/intx-types/src/wire-workflow.ts +++ /dev/null @@ -1,170 +0,0 @@ -// Wire contracts for a workflow definition projected onto an `agent.deploy` -// frame: the per-step schema and the projection shapes the sidecar deploy -// router and the workflow-process child validate. Extracted from `sidecar.ts` -// so both files stay focused; `sidecar.ts` re-exports the public names, so -// `@intx/types/sidecar` consumers are unaffected. - -import { type } from "arktype"; - -import { CredentialBinding } from "./credentials"; -import { InferenceSource } from "./runtime"; -import { SidecarCapabilityPolicy } from "./sidecar-capabilities"; - -/** - * Fields every wire step carries regardless of `kind`. All other keys pass - * through unmodified (arktype's default), including the nested `agent`, inner - * `step`, `body`, `on`, and selector fields -- typed nowhere here on purpose, - * because two producers feed this schema: the live-deploy passthrough ships a - * step whose `agent.toolFactories` are functions that JSON-encode to `null`, - * while the live->inert projector in `@intx/workflow-deploy` ships a reified - * plain-data agent. Both must validate; reifying the grant surface into plain - * data is the projector's job, not this envelope's. - */ -const commonStepFields = { - "id?": "string", - "after?": "string[]", -} as const; - -/** - * A wire step: its `kind` must be one of the ten known primitives, plus the - * common `id`/`after` fields; all other keys pass through unmodified. Exported - * so the live->inert projector's producer and its mutation-test suite validate - * a single step against the same schema the deploy frame applies to every step. - * - * The load-bearing check here is the KIND discriminant -- a step with no `kind` - * or a `kind` outside the set is rejected at this boundary rather than carried - * through opaque, and the membership is what makes a step's canonical JSON - * deterministic across the child->hub boundary. Per-variant field validation is - * deliberately NOT done here (deeper authoring-time validation -- required - * fields, selector resolvability, DAG shape -- lives on `@intx/workflow`), so - * the ten variants collapse to one schema over the kind enum rather than ten - * near-identical arms whose fields were all optional passthrough anyway. - */ -export const WorkflowStep = type({ - kind: "'step' | 'map' | 'gate' | 'awaitSignal' | 'sleep' | 'childWorkflow' | 'escalation' | 'action' | 'loop' | 'onTrigger'", - ...commonStepFields, -}); -export type WorkflowStep = typeof WorkflowStep.infer; - -/** - * The `steps` record on a wire projection: every value must validate - * against the closed `WorkflowStep` union. The runtime constraint runs - * through a `.narrow` over a `Record` rather than a typed - * `{ "[string]": WorkflowStep }` on purpose: the inferred type stays - * `Record` so the existing live-deploy producer - * (`toWireWorkflowDefinition`, which hands a `Record` - * steps map to `sendAgentDeploy`) still typechecks, while the runtime - * validation is fully closed over the primitive-kind set. - */ -const WorkflowSteps = type({ "[string]": "unknown" }).narrow((steps, ctx) => { - for (const [stepId, step] of Object.entries(steps)) { - const parsed = WorkflowStep(step); - if (parsed instanceof type.errors) { - return ctx.mustBe( - `a record whose every step matches a known workflow primitive ` + - `variant; step ${JSON.stringify(stepId)} did not (${parsed.summary})`, - ); - } - } - return true; -}); - -/** - * Workflow projection carried on an `agent.deploy` frame. Its presence - * at the deploy router routes the frame to the workflow deploy path -- - * single- or multi-step, both of which spawn the workflow-process child - * -- as opposed to a per-step provision frame. - * - * `definition` is the wire projection of `WorkflowDefinition` from - * `@intx/workflow`. The arktype validator enforces the structural - * envelope the workflow-process child re-parses on the sidecar after - * materialization (`packages/hub-sessions/src/workflow-kind.ts`'s - * `workflowDefinitionEnvelopeSchema`): `id`, `triggers`, `steps`, - * `stepOrder`, optional `state`. The wire validator MUST require every - * field the envelope requires — this projection is the approved surface - * the source-ref child re-verifies its closure-evaluated definition - * against, and the child rejects a tree missing any envelope-required - * field. Deeper validation of authoring-time primitive shape lives on the - * workflow definition surface in `@intx/workflow`, not on the wire. - * - * `sources` pins an ordered, non-empty inference-source list per step in - * `definition.stepOrder` so the workflow-process child can resolve inference - * at step invocation without a round trip to the hub. The list is the step's - * failover chain: element 0 is the active source (its id is the step's - * `defaultSource`), and the reactor fails over forward through the tail on a - * transient inference error. A workflow step pins a single-element list (no - * per-step failover); a single-agent instance pins the instance's full - * ordered source chain. Every `stepOrder` entry must have a matching - * `sources` entry; the validator rejects frames that violate this at the - * boundary. - */ -export const WorkflowProjectionDefinition = type({ - id: "string > 0", - triggers: "unknown[]", - stepOrder: "string[]", - steps: WorkflowSteps, - "state?": "Record", - // The definition's credential bindings, projected verbatim by the - // live->inert projector (`projectDefinition`). This MUST stay in sync with - // that projector: because of the `"+": "delete"` below, a binding the - // projector emits but this schema omits would be silently stripped at the - // wire boundary, desyncing the hub-resolved bindings from the projection - // the sidecar validates and re-verifies. Bindings are the operator-approved - // credential request surface (no secret material), so they belong in the - // hashed projection. - "credentialBindings?": CredentialBinding.array(), - "sidecarPlacement?": SidecarCapabilityPolicy, - "+": "delete", -}).narrow((value, ctx) => { - // Every `stepOrder` entry must name a defined step. A legitimately projected - // definition always satisfies this (the authoring validator enforces it), so - // this rejects only a projector-bypassing or tampered wire frame -- closing a - // phantom-stepOrder entry at the trust boundary for every consumer, rather - // than letting a downstream reader index `steps[missing]` as `undefined` and - // silently take a default path. - for (const stepId of value.stepOrder) { - if (!Object.prototype.hasOwnProperty.call(value.steps, stepId)) { - return ctx.mustBe( - `a workflow projection whose stepOrder names only defined steps; ${JSON.stringify(stepId)} has no matching entry in steps`, - ); - } - } - return true; -}); -export type WorkflowProjectionDefinition = - typeof WorkflowProjectionDefinition.infer; - -/** - * A workflow projection paired with its per-step inference-source pins and the - * hub-approved wire hash, with the invariant that every `stepOrder` entry has a - * `sources` failover chain. This is the shared base for BOTH the top-level - * deploy frame (`AgentDeployWorkflow`, which intersects its extras onto this) - * AND each extracted trigger body (onTrigger section or childWorkflow child) - * under `referencedDefinitions` -- so the field set and the coverage narrow are - * defined once and a body's sources cover the body's stepOrder just as the - * top-level's cover the top-level's. - */ -export const WorkflowProjectionWithSources = type({ - definition: WorkflowProjectionDefinition, - sources: { "[string]": InferenceSource.array().atLeastLength(1) }, - // The hub-approved wire hash of `definition`'s projection -- the freeze anchor - // the hub gate wrote (`computeWireDefinitionHash`). The sidecar feeds it to - // the child as the `DEFINITION_HASH` it re-verifies its own recompute - // against, rather than trusting a sidecar-computed hash. At the top level it - // pins the deployment's content handle; per body it pins the body's - // projection, which is re-verified in-memory as part of the parent's - // already-re-verified closure. Optional on the wire because the frame schema - // does not force it; the production hub builder always stamps it. - "approvedWireHash?": "string > 0", -}).narrow((value, ctx) => { - for (const stepId of value.definition.stepOrder) { - if (!Object.prototype.hasOwnProperty.call(value.sources, stepId)) { - return ctx.mustBe( - `a workflow projection whose sources cover every step in stepOrder; ${JSON.stringify(stepId)} is missing`, - ); - } - } - return true; -}); -export type WorkflowProjectionWithSources = - typeof WorkflowProjectionWithSources.infer; diff --git a/vendor/intx-types/src/workflow-run-id.ts b/vendor/intx-types/src/workflow-run-id.ts deleted file mode 100644 index 7eee721..0000000 --- a/vendor/intx-types/src/workflow-run-id.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Canonical runId derivation for a workflow deployment's top-level run. -// -// A workflow deployment has ONE addressable top-level run, whose stable runId -// is the local part of the deployment's mail address -- the `` in -// `@`. The supervisor's dispatch loop keys its per-run state, -// its grants barrier, and its terminal wait on this id. Every producer of a -// run's grants -- the hub-api trigger route and the sidecar's mail-deliver -// path -- must stage those grants under the SAME id, or they land under a run -// id the supervisor never looks up and the run fails closed on its -// `onRunStart` barrier. -// -// This module is the single source of truth those producers import, so -// their derivations cannot diverge. It exists to end the divergence that -// let the mail's Message-ID (a per-message identifier) masquerade as the -// runId: the runId is a property of the deployment, not of the individual -// trigger occurrence. Internal section/body runs still receive their own -// synthetic run ids and are not externally addressable. - -import { parseRunAddress } from "./agent-address"; - -/** - * The stable runId for a workflow deployment's one addressable top-level run: - * the local part of its mail address, before the `@`. Callers hold the - * deployment mail address in different forms -- a routing recipient, a - * supervisor binding, a route-derived address -- and route it through this one - * function so the runId contract is stated in exactly one place. - * - * Delegates to `parseRunAddress` so a single function owns the `@`-split: the - * runId is the parsed local part. A malformed address (no `run_` marker, no - * `@`, or an empty domain) is a caller bug, not a value to key state under, so - * this throws rather than returning a fabricated id that would land run state - * under an id the supervisor never looks up. - */ -export function deriveWorkflowRunId(address: string): string { - const parsed = parseRunAddress(address); - if (parsed === null) { - throw new Error(`Invalid run address: ${JSON.stringify(address)}`); - } - return parsed.runId; -} diff --git a/vendor/intx-types/src/workflow-sources.ts b/vendor/intx-types/src/workflow-sources.ts deleted file mode 100644 index 4a93137..0000000 --- a/vendor/intx-types/src/workflow-sources.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Schemas for where a code-sourced workflow definition's bytes come from. -// -// A workflow install carries a `WorkflowDefinitionSource` to say where the -// definition should be fetched from at apply time. Two origins exist: the -// `registry` variant names an EXTERNAL npm registry that publishes the -// definition package; the `asset` variant names a hub asset -- a checked-out -// git repo -- that holds the definition either as a published `tarball` -// (selected by the install pin) or as a `source` codebase at a pinned commit. - -import { type } from "arktype"; - -/** - * A workflow definition published to an external npm registry, fetched at - * apply time. The sidecar's registry config maps `registry` to a URL and - * credentials. The install call's version pin selects the definition. - */ -export const WorkflowDefinitionRegistrySource = type({ - kind: "'registry'", - registry: "string", -}); -export type WorkflowDefinitionRegistrySource = - typeof WorkflowDefinitionRegistrySource.infer; - -/** - * The definition is a published tarball inside the hub asset. This names - * only the format: the install call's version pin selects which package - * inside the asset is the definition, exactly as the `registry` variant - * leaves the pin to travel separately. - */ -export const WorkflowDefinitionAssetTarball = type({ - format: "'tarball'", -}); -export type WorkflowDefinitionAssetTarball = - typeof WorkflowDefinitionAssetTarball.infer; - -/** - * The definition is the codebase in the hub asset's git checkout at a pinned - * commit. `commitSha` IS the pin, and the content hash of the tree at that - * commit is the definition's identity (the member `package.json` version is - * only an advisory label). `packageName` selects which workspace member of a - * monorepo codebase is the definition, by its `package.json` name; absent - * means the codebase is a single package rooted at the tree. - */ -export const WorkflowDefinitionAssetSourceTree = type({ - format: "'source'", - commitSha: "string", - "packageName?": "string", -}); -export type WorkflowDefinitionAssetSourceTree = - typeof WorkflowDefinitionAssetSourceTree.infer; - -/** - * A workflow definition sourced from a hub `asset` -- a checked-out git repo. - * The definition lives inside it either as a published `tarball` or as a - * `source` codebase, discriminated by `package.format`. - */ -export const WorkflowDefinitionAssetSource = type({ - kind: "'asset'", - assetId: "string", - package: WorkflowDefinitionAssetTarball.or(WorkflowDefinitionAssetSourceTree), -}); -export type WorkflowDefinitionAssetSource = - typeof WorkflowDefinitionAssetSource.infer; - -/** - * Discriminated union over where a workflow definition's bytes come from, - * keyed on `kind`. Widen it here and every by-value consumer - * (`SourceRefPin`, the probe/deploy wire frames) follows; a consumer that - * switches on `kind` gains a compile error for any unhandled variant. - */ -export const WorkflowDefinitionSource = WorkflowDefinitionRegistrySource.or( - WorkflowDefinitionAssetSource, -); -export type WorkflowDefinitionSource = typeof WorkflowDefinitionSource.infer; diff --git a/vendor/intx-types/src/workflows.ts b/vendor/intx-types/src/workflows.ts deleted file mode 100644 index 7677f9c..0000000 --- a/vendor/intx-types/src/workflows.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Status vocabulary for the first-class workflow definition model, kept in its -// own workflow-scoped module. -export const workflowDefinitionStatuses = ["deployed", "stopped"] as const; -export type WorkflowDefinitionStatus = - (typeof workflowDefinitionStatuses)[number]; - -export const workflowDefinitionVersionStatuses = [ - "active", - "inactive", - "failed", -] as const; -export type WorkflowDefinitionVersionStatus = - (typeof workflowDefinitionVersionStatuses)[number]; - -import { type } from "arktype"; - -const WorkflowDefinitionStatusType = type.enumerated( - ...workflowDefinitionStatuses, -); -const WorkflowDefinitionVersionStatusType = type.enumerated( - ...workflowDefinitionVersionStatuses, -); - -// One entry in a definition's version history. -export const WorkflowDefinitionVersion = type({ - version: "string", - status: WorkflowDefinitionVersionStatusType, - createdAt: "string", -}); - -// The first-class workflow definition, as returned by the definition routes. -export const WorkflowDefinitionResponse = type({ - id: "string", - tenantId: "string", - name: "string", - "description?": "string | null", - currentVersion: "string", - status: WorkflowDefinitionStatusType.describe( - "Lifecycle state of the definition: `deployed` (a launchable version is active) or `stopped` (deactivated).", - ), - createdAt: "string", - updatedAt: "string", -}); - -// Rollback a definition to a prior version. -export const WorkflowRollbackRequest = type({ - version: "string", -}); From cfcf03e56e4134fbe456ef5c3eb45df4ca7509d6 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Mon, 21 Sep 2026 23:46:30 -0700 Subject: [PATCH 3/3] build(deps): write bun.lock with the CI-pinned bun 1.3.14 (CL-8797) --- bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 517e221..b353f20 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,5 @@ { - "lockfileVersion": 2, + "lockfileVersion": 1, "configVersion": 1, "workspaces": { "": {