@fungi.computer/oyster 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1134 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +210 -0
  6. package/dist/app-builds.js +499 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +31 -0
  10. package/dist/app-sources.js +370 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/artifact-writer.d.ts +40 -0
  28. package/dist/build/artifact-writer.js +181 -0
  29. package/dist/build/build-jobs.d.ts +128 -0
  30. package/dist/build/build-jobs.js +203 -0
  31. package/dist/build/build-policy.d.ts +10 -0
  32. package/dist/build/build-policy.js +10 -0
  33. package/dist/build/build-step.d.ts +37 -0
  34. package/dist/build/build-step.js +28 -0
  35. package/dist/build/bundled-html.d.ts +7 -0
  36. package/dist/build/bundled-html.js +64 -0
  37. package/dist/build/candidate-builder.d.ts +68 -0
  38. package/dist/build/candidate-builder.js +91 -0
  39. package/dist/build/candidate-cli.d.ts +1 -0
  40. package/dist/build/candidate-cli.js +119 -0
  41. package/dist/build/candidate-cli.mjs +28170 -0
  42. package/dist/build/content-objects.d.ts +37 -0
  43. package/dist/build/content-objects.js +53 -0
  44. package/dist/build/definition-emission.d.ts +34 -0
  45. package/dist/build/definition-emission.js +201 -0
  46. package/dist/build/effect.d.ts +249 -0
  47. package/dist/build/effect.js +436 -0
  48. package/dist/build/git-environment.d.ts +9 -0
  49. package/dist/build/git-environment.js +60 -0
  50. package/dist/build/html-recipe.d.ts +20 -0
  51. package/dist/build/html-recipe.js +51 -0
  52. package/dist/build/local-build.d.ts +67 -0
  53. package/dist/build/local-build.js +1128 -0
  54. package/dist/build/snapshot-package-store.d.ts +12 -0
  55. package/dist/build/snapshot-package-store.js +36 -0
  56. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  57. package/dist/build/snapshot-program-scripts.js +2 -0
  58. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  59. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  60. package/dist/build/submission-contract.d.ts +828 -0
  61. package/dist/build/submission-contract.js +180 -0
  62. package/dist/build/workspace-build.d.ts +55 -0
  63. package/dist/build/workspace-build.js +200 -0
  64. package/dist/build-contract.d.ts +7 -0
  65. package/dist/build-contract.js +7 -0
  66. package/dist/build-egress.d.ts +62 -0
  67. package/dist/build-egress.js +148 -0
  68. package/dist/build-jobs.d.ts +2 -0
  69. package/dist/build-jobs.js +2 -0
  70. package/dist/build.d.ts +12 -0
  71. package/dist/build.js +14 -0
  72. package/dist/capability-descriptor.d.ts +46 -0
  73. package/dist/capability-descriptor.js +51 -0
  74. package/dist/catalog-frame.d.ts +39 -0
  75. package/dist/catalog-frame.js +72 -0
  76. package/dist/cause-class.d.ts +19 -0
  77. package/dist/cause-class.js +54 -0
  78. package/dist/cloudflare.d.ts +123 -0
  79. package/dist/cloudflare.js +391 -0
  80. package/dist/computer-terminal.d.ts +38 -0
  81. package/dist/computer-terminal.js +410 -0
  82. package/dist/computer.d.ts +60 -0
  83. package/dist/computer.js +33 -0
  84. package/dist/event-recipients.d.ts +57 -0
  85. package/dist/event-recipients.js +144 -0
  86. package/dist/facet-invocation.d.ts +4 -0
  87. package/dist/facet-invocation.js +45 -0
  88. package/dist/files.d.ts +42 -0
  89. package/dist/files.js +123 -0
  90. package/dist/frame-binding-wire-script.d.ts +2 -0
  91. package/dist/frame-binding-wire-script.js +1 -0
  92. package/dist/frame-binding-wire.d.ts +28 -0
  93. package/dist/frame-binding-wire.js +54 -0
  94. package/dist/git-admission.d.ts +52 -0
  95. package/dist/git-admission.js +188 -0
  96. package/dist/git-auth.d.ts +11 -0
  97. package/dist/git-auth.js +27 -0
  98. package/dist/git-door.d.ts +35 -0
  99. package/dist/git-door.js +108 -0
  100. package/dist/git-journal.d.ts +31 -0
  101. package/dist/git-journal.js +188 -0
  102. package/dist/git.d.ts +31 -0
  103. package/dist/git.js +64 -0
  104. package/dist/guest-policy.d.ts +9 -0
  105. package/dist/guest-policy.js +13 -0
  106. package/dist/host.d.ts +244 -0
  107. package/dist/host.js +1043 -0
  108. package/dist/index.d.ts +166 -0
  109. package/dist/index.js +320 -0
  110. package/dist/installation-consent.d.ts +79 -0
  111. package/dist/installation-consent.js +82 -0
  112. package/dist/internal/background-origins.d.ts +3 -0
  113. package/dist/internal/background-origins.js +8 -0
  114. package/dist/internal/delivery.d.ts +12 -0
  115. package/dist/internal/delivery.js +167 -0
  116. package/dist/internal/host-gate.d.ts +14 -0
  117. package/dist/internal/host-gate.js +62 -0
  118. package/dist/internal/registry-installations.d.ts +6 -0
  119. package/dist/internal/registry-installations.js +12 -0
  120. package/dist/invocation.d.ts +251 -0
  121. package/dist/invocation.js +377 -0
  122. package/dist/keys.d.ts +48 -0
  123. package/dist/keys.js +52 -0
  124. package/dist/mcp-owner.d.ts +27 -0
  125. package/dist/mcp-owner.js +155 -0
  126. package/dist/mcp.d.ts +89 -0
  127. package/dist/mcp.js +429 -0
  128. package/dist/media.d.ts +23 -0
  129. package/dist/media.js +12 -0
  130. package/dist/pack.d.ts +79 -0
  131. package/dist/pack.js +151 -0
  132. package/dist/page/capabilities.d.ts +23 -0
  133. package/dist/page/capabilities.js +256 -0
  134. package/dist/page/computer-handles.d.ts +79 -0
  135. package/dist/page/computer-handles.js +44 -0
  136. package/dist/page/gill-observation.d.ts +4 -0
  137. package/dist/page/gill-observation.js +31 -0
  138. package/dist/page/lifetime.d.ts +6 -0
  139. package/dist/page/lifetime.js +19 -0
  140. package/dist/page/mount.d.ts +163 -0
  141. package/dist/page/mount.js +390 -0
  142. package/dist/page/storage.d.ts +3 -0
  143. package/dist/page/storage.js +82 -0
  144. package/dist/page/terminal.d.ts +5 -0
  145. package/dist/page/terminal.js +7 -0
  146. package/dist/page/window-launch.d.ts +18 -0
  147. package/dist/page/window-launch.js +11 -0
  148. package/dist/page-script.d.ts +14 -0
  149. package/dist/page-script.js +5 -0
  150. package/dist/r2-files.d.ts +3 -0
  151. package/dist/r2-files.js +79 -0
  152. package/dist/release.d.ts +83 -0
  153. package/dist/release.js +167 -0
  154. package/dist/root-data.d.ts +86 -0
  155. package/dist/root-data.js +69 -0
  156. package/dist/shell/app-directory.d.ts +542 -0
  157. package/dist/shell/app-directory.js +199 -0
  158. package/dist/shell/browser-actions.d.ts +7 -0
  159. package/dist/shell/browser-actions.js +56 -0
  160. package/dist/shell/command-host.d.ts +112 -0
  161. package/dist/shell/command-host.js +502 -0
  162. package/dist/shell/commands.d.ts +25 -0
  163. package/dist/shell/commands.js +78 -0
  164. package/dist/shell/index.d.ts +8 -0
  165. package/dist/shell/index.js +8 -0
  166. package/dist/shell/keys.d.ts +41 -0
  167. package/dist/shell/keys.js +134 -0
  168. package/dist/shell/shell-module.d.ts +55 -0
  169. package/dist/shell/shell-module.js +86 -0
  170. package/dist/shell/window-manager.d.ts +124 -0
  171. package/dist/shell/window-manager.js +711 -0
  172. package/dist/shell/workbench-contract.d.ts +68 -0
  173. package/dist/shell/workbench-contract.js +1 -0
  174. package/dist/shell/workbench-directory.d.ts +332 -0
  175. package/dist/shell/workbench-directory.js +35 -0
  176. package/dist/shell/workbench-host-types.d.ts +96 -0
  177. package/dist/shell/workbench-host-types.js +1 -0
  178. package/dist/shell/workbench-host.d.ts +34 -0
  179. package/dist/shell/workbench-host.js +347 -0
  180. package/dist/shell/workbench-model.d.ts +192 -0
  181. package/dist/shell/workbench-model.js +53 -0
  182. package/dist/shell/workbench.d.ts +4 -0
  183. package/dist/shell/workbench.js +2 -0
  184. package/dist/source/fork.d.ts +27 -0
  185. package/dist/source/fork.js +46 -0
  186. package/dist/source/index.d.ts +10 -0
  187. package/dist/source/index.js +10 -0
  188. package/dist/source/seed-receipt.d.ts +53 -0
  189. package/dist/source/seed-receipt.js +18 -0
  190. package/dist/source/team-source.d.ts +32 -0
  191. package/dist/source/team-source.js +42 -0
  192. package/dist/source/template-push.d.ts +33 -0
  193. package/dist/source/template-push.js +223 -0
  194. package/dist/source/template-repository.d.ts +4 -0
  195. package/dist/source/template-repository.js +21 -0
  196. package/dist/source/template-seed.d.ts +68 -0
  197. package/dist/source/template-seed.js +80 -0
  198. package/dist/standalone/agent-directory.d.ts +4 -0
  199. package/dist/standalone/agent-directory.js +4 -0
  200. package/dist/standalone/agents.d.ts +220 -0
  201. package/dist/standalone/agents.js +795 -0
  202. package/dist/standalone/app-builds-binding.d.ts +36 -0
  203. package/dist/standalone/app-builds-binding.js +119 -0
  204. package/dist/standalone/app-set.d.ts +91 -0
  205. package/dist/standalone/app-set.js +395 -0
  206. package/dist/standalone/app-source.d.ts +41 -0
  207. package/dist/standalone/app-source.js +59 -0
  208. package/dist/standalone/builds.d.ts +18 -0
  209. package/dist/standalone/builds.js +101 -0
  210. package/dist/standalone/computer-egress.d.ts +37 -0
  211. package/dist/standalone/computer-egress.js +111 -0
  212. package/dist/standalone/computer.d.ts +122 -0
  213. package/dist/standalone/computer.js +714 -0
  214. package/dist/standalone/install-apps-cli.d.ts +2 -0
  215. package/dist/standalone/install-apps-cli.js +126 -0
  216. package/dist/standalone/instance.d.ts +35 -0
  217. package/dist/standalone/instance.js +74 -0
  218. package/dist/standalone/media.d.ts +76 -0
  219. package/dist/standalone/media.js +227 -0
  220. package/dist/standalone/preview.d.ts +51 -0
  221. package/dist/standalone/preview.js +167 -0
  222. package/dist/standalone/registry.d.ts +102 -0
  223. package/dist/standalone/registry.js +193 -0
  224. package/dist/standalone/tenancy.d.ts +42 -0
  225. package/dist/standalone/tenancy.js +241 -0
  226. package/dist/standalone/worker.d.ts +55 -0
  227. package/dist/standalone/worker.js +147 -0
  228. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  229. package/dist/standalone/workflow-authority-readers.js +35 -0
  230. package/dist/standalone/workflow-binding.d.ts +216 -0
  231. package/dist/standalone/workflow-binding.js +217 -0
  232. package/dist/standalone/workflow-events.d.ts +46 -0
  233. package/dist/standalone/workflow-events.js +39 -0
  234. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  235. package/dist/standalone/workflow-git-capture.js +62 -0
  236. package/dist/standalone/workflow-host.d.ts +931 -0
  237. package/dist/standalone/workflow-host.js +751 -0
  238. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  239. package/dist/standalone/workflow-node-capture.js +72 -0
  240. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  241. package/dist/standalone/workflow-root-authority.js +87 -0
  242. package/dist/standalone/workflow-schedules.d.ts +52 -0
  243. package/dist/standalone/workflow-schedules.js +40 -0
  244. package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
  245. package/dist/standalone/workflow-trigger-host.js +52 -0
  246. package/dist/standalone/workflows.d.ts +657 -0
  247. package/dist/standalone/workflows.js +554 -0
  248. package/dist/storage-frame.d.ts +64 -0
  249. package/dist/storage-frame.js +28 -0
  250. package/dist/tenancy/auth0.d.ts +64 -0
  251. package/dist/tenancy/auth0.js +198 -0
  252. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  253. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  254. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  255. package/dist/tenancy/cloudflare/registry.js +301 -0
  256. package/dist/tenancy/cloudflare.d.ts +2 -0
  257. package/dist/tenancy/cloudflare.js +2 -0
  258. package/dist/tenancy/cold-directory.d.ts +13 -0
  259. package/dist/tenancy/cold-directory.js +43 -0
  260. package/dist/tenancy/contracts.d.ts +195 -0
  261. package/dist/tenancy/contracts.js +1 -0
  262. package/dist/tenancy/index.d.ts +19 -0
  263. package/dist/tenancy/index.js +156 -0
  264. package/dist/tenancy/owner-key.d.ts +26 -0
  265. package/dist/tenancy/owner-key.js +63 -0
  266. package/dist/tenancy/oyster.d.ts +4 -0
  267. package/dist/tenancy/oyster.js +25 -0
  268. package/dist/tenancy/registry-record.d.ts +7 -0
  269. package/dist/tenancy/registry-record.js +44 -0
  270. package/dist/tenancy/registry.d.ts +27 -0
  271. package/dist/tenancy/registry.js +138 -0
  272. package/dist/tenancy/resolve.d.ts +7 -0
  273. package/dist/tenancy/resolve.js +64 -0
  274. package/dist/tenant.d.ts +81 -0
  275. package/dist/tenant.js +71 -0
  276. package/dist/testing/capabilities.d.ts +29 -0
  277. package/dist/testing/capabilities.js +130 -0
  278. package/dist/testing/catalog.d.ts +3 -0
  279. package/dist/testing/catalog.js +53 -0
  280. package/dist/testing/index.d.ts +10 -0
  281. package/dist/testing/index.js +10 -0
  282. package/dist/testing/installation.d.ts +11 -0
  283. package/dist/testing/installation.js +25 -0
  284. package/dist/testing/native-worker.d.ts +21 -0
  285. package/dist/testing/native-worker.js +106 -0
  286. package/dist/testing/worker.d.ts +18 -0
  287. package/dist/testing/worker.js +67 -0
  288. package/dist/testing/workers-runtime.d.ts +24 -0
  289. package/dist/testing/workers-runtime.js +33 -0
  290. package/dist/webhook-delivery.d.ts +14 -0
  291. package/dist/webhook-delivery.js +17 -0
  292. package/dist/workers-git.d.ts +285 -0
  293. package/dist/workers-git.js +1174 -0
  294. package/dist/workers.d.ts +352 -0
  295. package/dist/workers.js +868 -0
  296. package/docs/api/README.md +82 -0
  297. package/docs/api/agents.md +332 -0
  298. package/docs/api/app-storage.md +209 -0
  299. package/docs/api/apps.md +1375 -0
  300. package/docs/api/auth.md +63 -0
  301. package/docs/api/build-contract.md +1304 -0
  302. package/docs/api/build.md +1712 -0
  303. package/docs/api/cloudflare.md +421 -0
  304. package/docs/api/computer.md +203 -0
  305. package/docs/api/files.md +117 -0
  306. package/docs/api/git.md +470 -0
  307. package/docs/api/host.md +1328 -0
  308. package/docs/api/index.md +430 -0
  309. package/docs/api/invocation.md +738 -0
  310. package/docs/api/keys.md +175 -0
  311. package/docs/api/mcp.md +301 -0
  312. package/docs/api/media.md +40 -0
  313. package/docs/api/modules.md +50 -0
  314. package/docs/api/pack.md +141 -0
  315. package/docs/api/page/mount.md +298 -0
  316. package/docs/api/root-data.md +111 -0
  317. package/docs/api/shell/app-directory.md +599 -0
  318. package/docs/api/shell/command-host.md +224 -0
  319. package/docs/api/shell/commands.md +197 -0
  320. package/docs/api/shell/keys.md +183 -0
  321. package/docs/api/shell/shell-module.md +204 -0
  322. package/docs/api/shell/window-manager.md +267 -0
  323. package/docs/api/shell/workbench-directory.md +119 -0
  324. package/docs/api/shell/workbench.md +205 -0
  325. package/docs/api/shell.md +1915 -0
  326. package/docs/api/tenancy/auth0.md +110 -0
  327. package/docs/api/tenancy/cloudflare.md +221 -0
  328. package/docs/api/tenancy/cold-directory.md +46 -0
  329. package/docs/api/tenancy/owner-key.md +63 -0
  330. package/docs/api/tenancy/registry.md +77 -0
  331. package/docs/api/tenancy/resolve.md +69 -0
  332. package/docs/api/tenancy.md +430 -0
  333. package/docs/api/tenant.md +194 -0
  334. package/docs/api/workers-git.md +1646 -0
  335. package/docs/api/workers.md +1691 -0
  336. package/docs/site/agent-quickstart.mdx +346 -0
  337. package/examples/standalone/worker.ts +7 -0
  338. package/examples/standalone/wrangler.jsonc +82 -0
  339. package/package.json +300 -3
@@ -0,0 +1,64 @@
1
+ import type { TenantId } from "../tenant.js";
2
+ import type { AuthAdapter, Identity, ReadonlyTenantRegistry, ReadonlyTenancy } from "./contracts.js";
3
+ /** One short-lived, single-use browser authorization transaction. */
4
+ export type Auth0Transaction = Readonly<{
5
+ /** Tenant selected at login by the host resolver. */
6
+ tenantId: TenantId;
7
+ /** Exact canonical callback URI supplied to the provider. */
8
+ redirectUri: string;
9
+ /** State bound to the browser transaction cookie. */
10
+ state: string;
11
+ /** Secret verifier held server-side until the one-use exchange. */
12
+ codeVerifier: string;
13
+ /** Absolute expiration time in milliseconds. */
14
+ expiresAt: number;
15
+ }>;
16
+ /** Server-owned verified identity session; browsers receive only its random handle. */
17
+ export type Auth0Session = Readonly<{
18
+ /** Tenant for which this identity was admitted at callback time. */
19
+ tenantId: TenantId;
20
+ /** Verified issuer/subject; live registry membership still applies per request. */
21
+ identity: Identity;
22
+ /** Absolute expiration, no later than the verified access token's expiry. */
23
+ expiresAt: number;
24
+ }>;
25
+ /** Host ports for optional browser login; stored state must be shared across host instances. */
26
+ export type Auth0BrowserOptions = Readonly<{
27
+ /** Full callback pathname, including the prefix used to mount routes. */
28
+ callbackPath: string;
29
+ /** Use the kit's resolver; never accept a tenant from callback query/body data. */
30
+ resolveTenant: ReadonlyTenancy["resolve"];
31
+ /** Current tenant and membership authority, checked before issuing a session. */
32
+ registry: Pick<ReadonlyTenantRegistry, "get" | "membershipOf">;
33
+ /** Host-owned transaction storage with atomic one-use consumption. */
34
+ transactions: Readonly<{
35
+ /** Store until expiresAt; reject duplicate handles. */
36
+ put(id: string, transaction: Auth0Transaction): Promise<void>;
37
+ /** Atomically remove and return the transaction, or null if missing/consumed. */
38
+ take(id: string): Promise<Auth0Transaction | null>;
39
+ }>;
40
+ /** Host-owned session storage; expire and prune records at expiresAt. */
41
+ sessions: Readonly<{
42
+ /** Store a fresh opaque handle and verified identity until expiry. */
43
+ put(id: string, session: Auth0Session): Promise<void>;
44
+ /** Read a session by its opaque handle, or null after expiry/removal. */
45
+ get(id: string): Promise<Auth0Session | null>;
46
+ }>;
47
+ }>;
48
+ /** Auth0 issuer and audience configuration; browser login is opt-in. */
49
+ export type Auth0Options = Readonly<{
50
+ /** Auth0 issuer hostname, including a configured custom hostname. */
51
+ domain: string;
52
+ /** Registered application's client identifier. */
53
+ clientId: string;
54
+ /** Identifier of the API for which access tokens are issued. */
55
+ audience: string;
56
+ /** Optional confidential-client secret; omit for a registered public PKCE client. */
57
+ clientSecret?: string;
58
+ /** Optional host-owned transport for issuer discovery, keys and token exchange. */
59
+ fetch?: typeof globalThis.fetch;
60
+ /** Browser routes and durable host-owned transaction/session storage. */
61
+ browser?: Auth0BrowserOptions;
62
+ }>;
63
+ /** Verify Auth0 identities; the registry alone supplies canonical membership and role. */
64
+ export declare function auth0(options: Auth0Options): AuthAdapter;
@@ -0,0 +1,198 @@
1
+ import { createRemoteJWKSet, customFetch, errors, jwtVerify } from "jose";
2
+ import * as oauth from "oauth4webapi";
3
+ import * as Option from "effect/Option";
4
+ import * as Schema from "effect/Schema";
5
+ import { Hono } from "hono";
6
+ import { parse, serialize } from "hono/utils/cookie";
7
+ const subject = Schema.decodeUnknownOption(Schema.NonEmptyString);
8
+ const maxTokenLength = 16 * 1024;
9
+ const flowCookie = "__Host-tenancy-auth0-flow";
10
+ const sessionCookie = "__Host-tenancy-auth0";
11
+ function invalidCredential(cause) {
12
+ return (cause instanceof errors.JWTInvalid ||
13
+ cause instanceof errors.JWTExpired ||
14
+ cause instanceof errors.JWTClaimValidationFailed ||
15
+ cause instanceof errors.JWSInvalid ||
16
+ cause instanceof errors.JWSSignatureVerificationFailed ||
17
+ cause instanceof errors.JOSEAlgNotAllowed ||
18
+ cause instanceof errors.JWKSNoMatchingKey);
19
+ }
20
+ function invalidOAuth(cause) {
21
+ return (cause instanceof oauth.AuthorizationResponseError ||
22
+ cause instanceof oauth.ResponseBodyError ||
23
+ cause instanceof oauth.OperationProcessingError ||
24
+ cause instanceof oauth.WWWAuthenticateChallengeError);
25
+ }
26
+ function cookie(name, value, maxAge) {
27
+ return serialize(name, value, {
28
+ secure: true,
29
+ httpOnly: true,
30
+ sameSite: "Lax",
31
+ path: "/",
32
+ maxAge,
33
+ });
34
+ }
35
+ /** Verify Auth0 identities; the registry alone supplies canonical membership and role. */
36
+ export function auth0(options) {
37
+ const issuer = new URL(`https://${options.domain}/`);
38
+ if (issuer.host !== options.domain.toLowerCase() ||
39
+ issuer.pathname !== "/" ||
40
+ issuer.username ||
41
+ issuer.password ||
42
+ issuer.search ||
43
+ issuer.hash ||
44
+ options.clientId.length === 0 ||
45
+ options.audience.length === 0)
46
+ throw new TypeError("Configure an Auth0 hostname, client ID and API audience");
47
+ const transport = options.fetch === undefined ? {} : { [customFetch]: options.fetch };
48
+ const oauthTransport = options.fetch === undefined
49
+ ? {}
50
+ : {
51
+ [oauth.customFetch]: (url, init) => {
52
+ const { body, ...rest } = init;
53
+ return (options.fetch ?? globalThis.fetch)(url, body === undefined ? rest : { ...rest, body });
54
+ },
55
+ };
56
+ const jwks = createRemoteJWKSet(new URL(".well-known/jwks.json", issuer), transport);
57
+ const verify = async (token) => {
58
+ if (token.length === 0 || token.length > maxTokenLength)
59
+ return null;
60
+ try {
61
+ const { payload } = await jwtVerify(token, jwks, {
62
+ issuer: issuer.href,
63
+ audience: options.audience,
64
+ algorithms: ["RS256"],
65
+ requiredClaims: ["sub", "exp", "iat"],
66
+ });
67
+ const decoded = subject(payload.sub);
68
+ if (Option.isNone(decoded) || payload.exp === undefined)
69
+ return null;
70
+ return {
71
+ identity: { provider: issuer.href, subject: decoded.value },
72
+ expiresAt: payload.exp * 1000,
73
+ };
74
+ }
75
+ catch (cause) {
76
+ if (invalidCredential(cause))
77
+ return null;
78
+ throw cause;
79
+ }
80
+ };
81
+ const browser = options.browser;
82
+ const routes = new Hono();
83
+ if (browser !== undefined) {
84
+ const callback = new URL(browser.callbackPath, "https://callback.invalid");
85
+ if (!browser.callbackPath.startsWith("/") ||
86
+ callback.origin !== "https://callback.invalid" ||
87
+ callback.pathname !== browser.callbackPath ||
88
+ callback.search ||
89
+ callback.hash)
90
+ throw new TypeError("Configure a canonical absolute callback pathname");
91
+ const client = { client_id: options.clientId };
92
+ const clientAuth = options.clientSecret === undefined
93
+ ? oauth.None()
94
+ : oauth.ClientSecretPost(options.clientSecret);
95
+ const discover = () => oauth
96
+ .discoveryRequest(issuer, oauthTransport)
97
+ .then((response) => oauth.processDiscoveryResponse(issuer, response));
98
+ routes.get("/login", async (context) => {
99
+ const tenant = await browser.resolveTenant(context.req.raw);
100
+ if (tenant === null)
101
+ return context.json({ error: "not_found" }, 404);
102
+ if (tenant.status !== "active")
103
+ return context.json({ error: "forbidden" }, 403);
104
+ const server = await discover();
105
+ if (server.authorization_endpoint === undefined)
106
+ throw new Error("Auth0 issuer has no authorization endpoint");
107
+ const state = oauth.generateRandomState();
108
+ const codeVerifier = oauth.generateRandomCodeVerifier();
109
+ const redirectUri = new URL(browser.callbackPath, tenant.origin).href;
110
+ const authorization = new URL(server.authorization_endpoint);
111
+ authorization.searchParams.set("client_id", client.client_id);
112
+ authorization.searchParams.set("redirect_uri", redirectUri);
113
+ authorization.searchParams.set("response_type", "code");
114
+ authorization.searchParams.set("scope", "openid");
115
+ authorization.searchParams.set("audience", options.audience);
116
+ authorization.searchParams.set("state", state);
117
+ authorization.searchParams.set("code_challenge_method", "S256");
118
+ authorization.searchParams.set("code_challenge", await oauth.calculatePKCECodeChallenge(codeVerifier));
119
+ await browser.transactions.put(state, {
120
+ tenantId: tenant.id,
121
+ state,
122
+ codeVerifier,
123
+ redirectUri,
124
+ expiresAt: Date.now() + 300_000,
125
+ });
126
+ context.header("Set-Cookie", cookie(flowCookie, state, 300));
127
+ context.header("Cache-Control", "no-store");
128
+ return context.redirect(authorization.href, 302);
129
+ });
130
+ routes.get("/callback", async (context) => {
131
+ context.header("Cache-Control", "no-store");
132
+ context.header("Set-Cookie", cookie(flowCookie, "", 0));
133
+ const flow = parse(context.req.header("Cookie") ?? "")[flowCookie];
134
+ if (flow === undefined || flow.length > 128)
135
+ return context.json({ error: "unauthorized" }, 401);
136
+ const transaction = await browser.transactions.take(flow);
137
+ const tenant = await browser.resolveTenant(context.req.raw);
138
+ const actual = new URL(context.req.url);
139
+ if (transaction === null ||
140
+ transaction.expiresAt <= Date.now() ||
141
+ tenant?.id !== transaction.tenantId ||
142
+ tenant.status !== "active" ||
143
+ `${actual.origin}${actual.pathname}` !== transaction.redirectUri)
144
+ return context.json({ error: "unauthorized" }, 401);
145
+ try {
146
+ const server = await discover();
147
+ const params = oauth.validateAuthResponse(server, client, actual, transaction.state);
148
+ const response = await oauth.authorizationCodeGrantRequest(server, client, clientAuth, params, transaction.redirectUri, transaction.codeVerifier, oauthTransport);
149
+ const result = await oauth.processAuthorizationCodeResponse(server, client, response);
150
+ const verified = await verify(result.access_token);
151
+ if (verified === null)
152
+ return context.json({ error: "unauthorized" }, 401);
153
+ const current = await browser.registry.get(tenant.id);
154
+ if (current?.status !== "active" ||
155
+ (await browser.registry.membershipOf(tenant.id, verified.identity)) === null)
156
+ return context.json({ error: "forbidden" }, 403);
157
+ const id = oauth.generateRandomState();
158
+ const expiresAt = Math.min(verified.expiresAt, Date.now() + 3_600_000);
159
+ await browser.sessions.put(id, {
160
+ tenantId: tenant.id,
161
+ identity: verified.identity,
162
+ expiresAt,
163
+ });
164
+ context.header("Set-Cookie", cookie(sessionCookie, id, Math.floor((expiresAt - Date.now()) / 1000)), { append: true });
165
+ return context.redirect(tenant.origin, 303);
166
+ }
167
+ catch (cause) {
168
+ if (invalidOAuth(cause))
169
+ return context.json({ error: "unauthorized" }, 401);
170
+ throw cause;
171
+ }
172
+ });
173
+ }
174
+ const adapter = {
175
+ id: "auth0",
176
+ async authenticate(request, tenant) {
177
+ const authorization = request.headers.get("Authorization");
178
+ if (authorization?.startsWith("Bearer ") === true) {
179
+ const verified = await verify(authorization.slice(7));
180
+ return verified === null
181
+ ? { kind: "refused", status: 401 }
182
+ : { kind: "identity", identity: verified.identity };
183
+ }
184
+ if (browser !== undefined) {
185
+ const id = parse(request.headers.get("Cookie") ?? "")[sessionCookie];
186
+ if (id !== undefined) {
187
+ const session = id.length > 128 ? null : await browser.sessions.get(id);
188
+ if (session?.tenantId !== tenant.id ||
189
+ session.expiresAt <= Date.now())
190
+ return { kind: "refused", status: 401 };
191
+ return { kind: "identity", identity: session.identity };
192
+ }
193
+ }
194
+ return { kind: "pass" };
195
+ },
196
+ };
197
+ return browser === undefined ? adapter : { ...adapter, routes };
198
+ }
@@ -0,0 +1,8 @@
1
+ import type { TenancyPolicy } from "../contracts.js";
2
+ /** Retry-After is an advisory response delay, not a configured quota or reset time. */
3
+ export type RateLimitOptions = Readonly<{
4
+ /** Positive whole seconds returned on a native refusal. */
5
+ retryAfterSeconds: number;
6
+ }>;
7
+ /** Delegate abuse admission to the configured native binding, using only its key API. */
8
+ export declare function rateLimit(binding: Pick<RateLimit, "limit">, options: RateLimitOptions): TenancyPolicy;
@@ -0,0 +1,19 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** Delegate abuse admission to the configured native binding, using only its key API. */
3
+ export function rateLimit(binding, options) {
4
+ const retryAfter = Schema.decodeUnknownSync(Schema.Int.check(Schema.isGreaterThan(0)))(options.retryAfterSeconds);
5
+ return async ({ tenant, principal }) => {
6
+ const actor = principal.actor;
7
+ const id = actor.kind === "member"
8
+ ? actor.memberId
9
+ : actor.kind === "agent"
10
+ ? actor.agentId
11
+ : actor.keyId;
12
+ const { success } = await binding.limit({
13
+ key: JSON.stringify([tenant.id, actor.kind, id]),
14
+ });
15
+ return success
16
+ ? { kind: "allow" }
17
+ : { kind: "refuse", status: 429, retryAfter };
18
+ };
19
+ }
@@ -0,0 +1,50 @@
1
+ import type { Brand } from "effect/Brand";
2
+ import type { Identity, ManagedTenantRegistry, Membership, ReadonlyTenantRegistry, Tenant } from "../contracts.js";
3
+ /** The registry owner's atomic read of one host and one identity. */
4
+ export type RegistryAdmission = Readonly<{
5
+ /** The tenant registered at the requested host. */
6
+ tenant: Tenant;
7
+ /** The identity's live membership in that tenant, or null. */
8
+ membership: Membership | null;
9
+ }>;
10
+ /** One request's atomic registry read, with the host and identity it named. */
11
+ export type AdmissionRecord = RegistryAdmission & Readonly<{
12
+ /** The canonical URL host the request named. */
13
+ host: string;
14
+ /** The external identity whose membership was read. */
15
+ identity: Identity;
16
+ }>;
17
+ /**
18
+ * One atomic registry read for one request. Only {@link durableObjectRegistry}
19
+ * constructs it, so lower layers cannot mint authority.
20
+ */
21
+ export type TenantAdmission = AdmissionRecord & Brand<"TenantAdmission">;
22
+ /** Native registry instance; its public shape exposes no runtime-private state. */
23
+ export type TenancyRegistryDO = ManagedTenantRegistry & Required<Pick<ReadonlyTenantRegistry, "byDomain">> & Readonly<{
24
+ /** One atomic read of the tenant at a host and one identity's membership. */
25
+ admission(host: string, identity: Identity): Promise<RegistryAdmission | null>;
26
+ }> & Rpc.DurableObjectBranded;
27
+ /** Re-export from a host Worker and bind a SQLite-backed Durable Object namespace. */
28
+ export declare const TenancyRegistryDO: new (ctx: DurableObjectState) => TenancyRegistryDO;
29
+ /** A read-only registry that includes the provisioned-domain index. */
30
+ export type DomainRegistry = ReadonlyTenantRegistry & Required<Pick<ReadonlyTenantRegistry, "byDomain">>;
31
+ /** Read-only native registry, with one-read request admission. */
32
+ export type DurableReadonlyRegistry = DomainRegistry & Readonly<{
33
+ /** Read the tenant at `host` and `identity`'s membership atomically, once. */
34
+ admission(host: string, identity: Identity): Promise<TenantAdmission | null>;
35
+ }>;
36
+ /** Managed native registry includes the provisioned-domain index. */
37
+ export type DurableRegistry = ManagedTenantRegistry & DurableReadonlyRegistry;
38
+ /** Explicitly expose only read methods when the host requests a read-only facade. */
39
+ export declare function durableObjectRegistry(binding: Pick<DurableObjectNamespace<TenancyRegistryDO>, "getByName">, options: {
40
+ readonly: true;
41
+ }): DurableReadonlyRegistry;
42
+ /** Use one namespace's registry object as the tenant and owner mapping authority. */
43
+ export declare function durableObjectRegistry(binding: Pick<DurableObjectNamespace<TenancyRegistryDO>, "getByName">, options?: {
44
+ readonly?: false;
45
+ }): DurableRegistry;
46
+ /**
47
+ * Answer registry reads only from one request's admission. Any other tenant,
48
+ * host, identity or member reads as absent, so every refusal stays closed.
49
+ */
50
+ export declare function admittedRegistry(admission: TenantAdmission): DomainRegistry;
@@ -0,0 +1,301 @@
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import * as Schema from "effect/Schema";
3
+ import { tenantIdSchema } from "../../tenant.js";
4
+ import { checkedOrigin, immutable, createMemberId, membershipVersionSchema, } from "../registry-record.js";
5
+ const statusSchema = Schema.Literals(["active", "suspended", "deleting"]);
6
+ const tenantRow = Schema.Struct({
7
+ id: tenantIdSchema,
8
+ handle: tenantIdSchema,
9
+ origin: Schema.String,
10
+ status: statusSchema,
11
+ });
12
+ const membershipRow = Schema.Struct({
13
+ member_id: Schema.NonEmptyString,
14
+ role: Schema.Literals(["owner", "member"]),
15
+ membership_version: membershipVersionSchema,
16
+ });
17
+ const memberRow = Schema.Struct({ member_id: Schema.NonEmptyString });
18
+ const domainRow = Schema.Struct({ host: Schema.String });
19
+ const identitySchema = Schema.Struct({
20
+ provider: Schema.NonEmptyString,
21
+ subject: Schema.NonEmptyString,
22
+ });
23
+ const tenantAdmissionSchema = Schema.Struct({
24
+ host: Schema.String,
25
+ identity: identitySchema,
26
+ tenant: Schema.Struct({
27
+ id: tenantIdSchema,
28
+ handle: tenantIdSchema,
29
+ origin: Schema.String,
30
+ status: statusSchema,
31
+ owners: Schema.Array(Schema.NonEmptyString),
32
+ domains: Schema.Array(Schema.String),
33
+ }),
34
+ membership: Schema.NullOr(Schema.Struct({
35
+ memberId: Schema.NonEmptyString,
36
+ role: Schema.Literals(["owner", "member"]),
37
+ membershipVersion: membershipVersionSchema,
38
+ })),
39
+ }).pipe(Schema.brand("TenantAdmission"));
40
+ const createSchema = Schema.Struct({
41
+ handle: tenantIdSchema,
42
+ origin: Schema.String,
43
+ domains: Schema.Array(Schema.String),
44
+ owner: identitySchema,
45
+ });
46
+ class RegistryObject extends DurableObject {
47
+ constructor(ctx) {
48
+ super(ctx, {});
49
+ // Pre-launch registries require a fresh store; CREATE IF NOT EXISTS does not
50
+ // upgrade an old memberships table. No incremental migration is supported.
51
+ // DEFAULT 1 is only an insert placeholder: the AFTER INSERT trigger replaces
52
+ // it with the durable clock revision in the same atomic statement. Lookups
53
+ // decode the stored revision and never supply a default.
54
+ ctx.storage.sql.exec(`
55
+ CREATE TABLE IF NOT EXISTS tenants (id TEXT PRIMARY KEY, handle TEXT UNIQUE NOT NULL, origin TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('active','suspended','deleting')));
56
+ CREATE TABLE IF NOT EXISTS domains (host TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, ordinal INTEGER NOT NULL);
57
+ CREATE INDEX IF NOT EXISTS domains_tenant ON domains(tenant_id);
58
+ CREATE TABLE IF NOT EXISTS identities (provider TEXT NOT NULL, subject TEXT NOT NULL, member_id TEXT UNIQUE NOT NULL, PRIMARY KEY(provider,subject));
59
+ CREATE TABLE IF NOT EXISTS memberships (tenant_id TEXT NOT NULL, member_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('owner','member')), membership_version INTEGER NOT NULL DEFAULT 1 CHECK(membership_version BETWEEN 1 AND 9007199254740991), PRIMARY KEY(tenant_id,member_id));
60
+ CREATE TABLE IF NOT EXISTS membership_clock (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL CHECK(revision BETWEEN 0 AND 9007199254740991));
61
+ INSERT OR IGNORE INTO membership_clock VALUES(1,0);
62
+ CREATE TRIGGER IF NOT EXISTS membership_insert_revision AFTER INSERT ON memberships BEGIN
63
+ UPDATE membership_clock SET revision=revision+1 WHERE id=1;
64
+ UPDATE memberships SET membership_version=(SELECT revision FROM membership_clock WHERE id=1) WHERE tenant_id=NEW.tenant_id AND member_id=NEW.member_id;
65
+ END;
66
+ CREATE TRIGGER IF NOT EXISTS membership_role_revision AFTER UPDATE OF role ON memberships WHEN OLD.role!=NEW.role BEGIN
67
+ UPDATE membership_clock SET revision=revision+1 WHERE id=1;
68
+ UPDATE memberships SET membership_version=(SELECT revision FROM membership_clock WHERE id=1) WHERE tenant_id=NEW.tenant_id AND member_id=NEW.member_id;
69
+ END;
70
+ CREATE TRIGGER IF NOT EXISTS membership_delete_revision AFTER DELETE ON memberships BEGIN
71
+ UPDATE membership_clock SET revision=revision+1 WHERE id=1;
72
+ END;
73
+ CREATE TRIGGER IF NOT EXISTS tenant_status_membership_revision AFTER UPDATE OF status ON tenants WHEN OLD.status!=NEW.status BEGIN
74
+ UPDATE membership_clock SET revision=revision+1 WHERE id=1;
75
+ UPDATE memberships SET membership_version=(SELECT revision FROM membership_clock WHERE id=1) WHERE tenant_id=NEW.id;
76
+ END;
77
+ `);
78
+ }
79
+ #get(id) {
80
+ const raw = this.ctx.storage.sql
81
+ .exec("SELECT id,handle,origin,status FROM tenants WHERE id = ?", id)
82
+ .toArray()[0];
83
+ if (raw === undefined)
84
+ return null;
85
+ const row = Schema.decodeUnknownSync(tenantRow)(raw);
86
+ const domains = this.ctx.storage.sql
87
+ .exec("SELECT host FROM domains WHERE tenant_id = ? ORDER BY ordinal", id)
88
+ .toArray()
89
+ .map((value) => Schema.decodeUnknownSync(domainRow)(value).host);
90
+ const owners = this.ctx.storage.sql
91
+ .exec("SELECT member_id FROM memberships WHERE tenant_id = ? AND role = 'owner' ORDER BY member_id", id)
92
+ .toArray()
93
+ .map((value) => Schema.decodeUnknownSync(memberRow)(value).member_id);
94
+ return immutable({
95
+ ...row,
96
+ domains,
97
+ owners,
98
+ origin: checkedOrigin(row.origin, domains),
99
+ });
100
+ }
101
+ get(id) {
102
+ return Promise.resolve(this.#get(Schema.decodeUnknownSync(tenantIdSchema)(id)));
103
+ }
104
+ byHandle(handle) {
105
+ const row = this.ctx.storage.sql
106
+ .exec("SELECT id FROM tenants WHERE handle = ?", handle)
107
+ .toArray()[0];
108
+ return Promise.resolve(row === undefined
109
+ ? null
110
+ : this.#get(Schema.decodeUnknownSync(Schema.Struct({ id: tenantIdSchema }))(row)
111
+ .id));
112
+ }
113
+ byDomain(host) {
114
+ const row = this.ctx.storage.sql
115
+ .exec("SELECT tenant_id FROM domains WHERE host = ?", host)
116
+ .toArray()[0];
117
+ return Promise.resolve(row === undefined
118
+ ? null
119
+ : this.#get(Schema.decodeUnknownSync(Schema.Struct({ tenant_id: tenantIdSchema }))(row).tenant_id));
120
+ }
121
+ status(id) {
122
+ return Promise.resolve(this.#get(id)?.status ?? null);
123
+ }
124
+ memberById(id, memberId) {
125
+ const tenantId = Schema.decodeUnknownSync(tenantIdSchema)(id);
126
+ const canonicalId = Schema.decodeUnknownSync(Schema.NonEmptyString)(memberId);
127
+ const row = this.ctx.storage.sql
128
+ .exec("SELECT member_id,role,membership_version FROM memberships WHERE tenant_id=? AND member_id=?", tenantId, canonicalId)
129
+ .toArray()[0];
130
+ if (row === undefined)
131
+ return Promise.resolve(null);
132
+ const stored = Schema.decodeUnknownSync(membershipRow)(row);
133
+ return Promise.resolve({
134
+ memberId: stored.member_id,
135
+ role: stored.role,
136
+ membershipVersion: stored.membership_version,
137
+ });
138
+ }
139
+ membershipOf(id, identity) {
140
+ return Promise.resolve(this.#membershipOf(id, Schema.decodeUnknownSync(identitySchema)(identity)));
141
+ }
142
+ /** One atomic read of the tenant at `host` and `identity`'s live membership. */
143
+ admission(host, identity) {
144
+ const domain = Schema.decodeUnknownSync(Schema.String)(host);
145
+ const member = Schema.decodeUnknownSync(identitySchema)(identity);
146
+ return Promise.resolve(this.ctx.storage.transactionSync(() => {
147
+ const row = this.ctx.storage.sql
148
+ .exec("SELECT tenant_id FROM domains WHERE host = ?", domain)
149
+ .toArray()[0];
150
+ if (row === undefined)
151
+ return null;
152
+ const tenant = this.#get(Schema.decodeUnknownSync(Schema.Struct({ tenant_id: tenantIdSchema }))(row).tenant_id);
153
+ return tenant === null
154
+ ? null
155
+ : { tenant, membership: this.#membershipOf(tenant.id, member) };
156
+ }));
157
+ }
158
+ #membershipOf(id, owner) {
159
+ const row = this.ctx.storage.sql
160
+ .exec(`SELECT m.member_id,m.role,m.membership_version FROM memberships m JOIN identities i ON m.member_id=i.member_id WHERE m.tenant_id=? AND i.provider=? AND i.subject=?`, id, owner.provider, owner.subject)
161
+ .toArray()[0];
162
+ if (row === undefined)
163
+ return null;
164
+ const stored = Schema.decodeUnknownSync(membershipRow)(row);
165
+ return {
166
+ memberId: stored.member_id,
167
+ role: stored.role,
168
+ membershipVersion: stored.membership_version,
169
+ };
170
+ }
171
+ create(input) {
172
+ return Promise.resolve().then(() => {
173
+ const parsed = Schema.decodeUnknownSync(createSchema)(input);
174
+ const origin = checkedOrigin(parsed.origin, parsed.domains);
175
+ return this.ctx.storage.transactionSync(() => {
176
+ const raw = this.ctx.storage.sql
177
+ .exec("SELECT member_id FROM identities WHERE provider=? AND subject=?", parsed.owner.provider, parsed.owner.subject)
178
+ .toArray()[0];
179
+ const memberId = raw === undefined
180
+ ? createMemberId()
181
+ : Schema.decodeUnknownSync(memberRow)(raw).member_id;
182
+ if (raw === undefined)
183
+ this.ctx.storage.sql.exec("INSERT INTO identities (provider,subject,member_id) VALUES (?,?,?)", parsed.owner.provider, parsed.owner.subject, memberId);
184
+ this.ctx.storage.sql.exec("INSERT INTO tenants (id,handle,origin,status) VALUES (?,?,?,'active')", parsed.handle, parsed.handle, origin);
185
+ for (const [ordinal, host] of parsed.domains.entries())
186
+ this.ctx.storage.sql.exec("INSERT INTO domains (host,tenant_id,ordinal) VALUES (?,?,?)", host, parsed.handle, ordinal);
187
+ this.ctx.storage.sql.exec("INSERT INTO memberships (tenant_id,member_id,role) VALUES (?,?,'owner')", parsed.handle, memberId);
188
+ return immutable({
189
+ id: parsed.handle,
190
+ handle: parsed.handle,
191
+ origin,
192
+ status: "active",
193
+ domains: parsed.domains,
194
+ owners: [memberId],
195
+ });
196
+ });
197
+ });
198
+ }
199
+ setStatus(id, status) {
200
+ const parsed = Schema.decodeUnknownSync(statusSchema)(status);
201
+ if (this.#get(id) === null)
202
+ throw new Error("Tenant does not exist");
203
+ this.ctx.storage.sql.exec("UPDATE tenants SET status=? WHERE id=?", parsed, id);
204
+ return Promise.resolve();
205
+ }
206
+ delete(id) {
207
+ this.ctx.storage.transactionSync(() => {
208
+ this.ctx.storage.sql.exec("DELETE FROM memberships WHERE tenant_id=?", id);
209
+ this.ctx.storage.sql.exec("DELETE FROM domains WHERE tenant_id=?", id);
210
+ this.ctx.storage.sql.exec("DELETE FROM tenants WHERE id=?", id);
211
+ });
212
+ return Promise.resolve();
213
+ }
214
+ }
215
+ /** Re-export from a host Worker and bind a SQLite-backed Durable Object namespace. */
216
+ export const TenancyRegistryDO = RegistryObject;
217
+ export function durableObjectRegistry(binding, options = {}) {
218
+ const stub = () => binding.getByName("registry");
219
+ const read = {
220
+ get: async (id) => {
221
+ const result = await stub().get(id);
222
+ return result === null ? null : immutable(result);
223
+ },
224
+ byHandle: async (handle) => {
225
+ const result = await stub().byHandle(handle);
226
+ return result === null ? null : immutable(result);
227
+ },
228
+ byDomain: async (host) => {
229
+ const result = await stub().byDomain(host);
230
+ return result === null ? null : immutable(result);
231
+ },
232
+ status: async (id) => await stub().status(id),
233
+ memberById: async (id, memberId) => {
234
+ const result = await stub().memberById(id, memberId);
235
+ return result === null
236
+ ? null
237
+ : {
238
+ memberId: result.memberId,
239
+ role: result.role,
240
+ membershipVersion: result.membershipVersion,
241
+ };
242
+ },
243
+ membershipOf: async (id, identity) => {
244
+ const result = await stub().membershipOf(id, identity);
245
+ return result === null
246
+ ? null
247
+ : {
248
+ memberId: result.memberId,
249
+ role: result.role,
250
+ membershipVersion: result.membershipVersion,
251
+ };
252
+ },
253
+ admission: async (host, identity) => {
254
+ const result = await stub().admission(host, identity);
255
+ return result === null
256
+ ? null
257
+ : Schema.decodeUnknownSync(tenantAdmissionSchema)({
258
+ host,
259
+ identity,
260
+ tenant: result.tenant,
261
+ membership: result.membership,
262
+ });
263
+ },
264
+ };
265
+ return options.readonly === true
266
+ ? read
267
+ : {
268
+ ...read,
269
+ create: async (input) => {
270
+ const result = await stub().create(input);
271
+ return immutable(result);
272
+ },
273
+ setStatus: async (id, status) => {
274
+ await stub().setStatus(id, status);
275
+ },
276
+ delete: async (id) => {
277
+ await stub().delete(id);
278
+ },
279
+ };
280
+ }
281
+ /**
282
+ * Answer registry reads only from one request's admission. Any other tenant,
283
+ * host, identity or member reads as absent, so every refusal stays closed.
284
+ */
285
+ export function admittedRegistry(admission) {
286
+ const { host, identity, membership } = admission;
287
+ const tenant = immutable(admission.tenant);
288
+ const selected = (id) => (id === tenant.id ? tenant : null);
289
+ const member = (id) => id === tenant.id && membership !== null ? { ...membership } : null;
290
+ return {
291
+ get: (id) => Promise.resolve(selected(id)),
292
+ byHandle: (handle) => Promise.resolve(handle === tenant.handle ? tenant : null),
293
+ byDomain: (requested) => Promise.resolve(requested === host ? tenant : null),
294
+ status: (id) => Promise.resolve(selected(id)?.status ?? null),
295
+ membershipOf: (id, requested) => Promise.resolve(requested.provider === identity.provider &&
296
+ requested.subject === identity.subject
297
+ ? member(id)
298
+ : null),
299
+ memberById: (id, memberId) => Promise.resolve(member(id)?.memberId === memberId ? member(id) : null),
300
+ };
301
+ }
@@ -0,0 +1,2 @@
1
+ export { rateLimit, type RateLimitOptions } from "./cloudflare/rate-limit.js";
2
+ export { TenancyRegistryDO, admittedRegistry, durableObjectRegistry, type DomainRegistry, type DurableReadonlyRegistry, type AdmissionRecord, type RegistryAdmission, type TenantAdmission, type DurableRegistry, } from "./cloudflare/registry.js";
@@ -0,0 +1,2 @@
1
+ export { rateLimit } from "./cloudflare/rate-limit.js";
2
+ export { TenancyRegistryDO, admittedRegistry, durableObjectRegistry, } from "./cloudflare/registry.js";
@@ -0,0 +1,13 @@
1
+ import type { ResolvedTenant, TenantScope } from "../tenant.js";
2
+ import type { ReadonlyTenantRegistry } from "./contracts.js";
3
+ /** Live directory admission for a host-persisted tenant during background work. */
4
+ export type ColdTenantDirectory = Readonly<{
5
+ /** Select only the current active tenant at its stored canonical origin. */
6
+ resolve(scope: TenantScope): Promise<ResolvedTenant | null>;
7
+ }>;
8
+ /**
9
+ * Reconstruct background admission from the same host-owned tenant registry.
10
+ * Every selection and member lookup reads current authority. This adapter has
11
+ * no credential authority: its authenticate method always refuses requests.
12
+ */
13
+ export declare function coldTenantDirectory(registry: Pick<ReadonlyTenantRegistry, "get" | "status" | "memberById">): ColdTenantDirectory;