failproofai 1.0.7-beta.1 → 1.0.7-beta.2
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +7 -7
- package/.next/standalone/.next/server/app/settings/page.js +2 -2
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0yxwl6j._.js → [root-of-the-server]__0l44ual._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_214wgrp._.js → _1mel6y1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0bn2oo8._.js → _1v-jvrv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +22 -22
- package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +2 -0
- package/.next/standalone/.next/static/chunks/0fqd7m_u81mi5.js +1 -0
- package/.next/standalone/.next/static/chunks/129ag2bw93bdh.js +1 -0
- package/.next/standalone/.next/static/chunks/{29fql3nbnfc9q.js → 1qd741hzlmjbo.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0vmd180qfntfb.js → 2_pltstd8-xgs.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1u5zsejmgrir_.js → 2c8j9l6j_b1ci.js} +1 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +1 -0
- package/.next/standalone/.next/static/chunks/{258668t68du6b.js → 3brze37td_wnc.js} +1 -1
- package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +6 -0
- package/.next/standalone/.next/static/chunks/{12tvm75t5ffui.js → 3ugmd_7dyn0id.js} +1 -1
- package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +69 -0
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +38 -23
- package/.next/standalone/app/actions/update-jev-config.ts +1 -1
- package/.next/standalone/app/settings/jev-panel.tsx +33 -29
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/python/skill/SKILL.md +60 -14
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +2 -1
- package/.next/standalone/sdk/python/skill/references/evaluator.md +255 -0
- package/.next/standalone/sdk/python/skill/references/events.md +17 -8
- package/.next/standalone/sdk/python/skill/references/frameworks.md +3 -0
- package/.next/standalone/sdk/python/skill/references/install.md +3 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +6 -2
- package/.next/standalone/sdk/python/skill/references/typescript.md +568 -0
- package/.next/standalone/sdk/typescript/CHANGELOG.md +119 -0
- package/.next/standalone/sdk/typescript/LICENSE +42 -0
- package/.next/standalone/sdk/typescript/README.md +552 -0
- package/.next/standalone/sdk/typescript/eslint.config.mjs +59 -0
- package/.next/standalone/sdk/typescript/examples/research-agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +920 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/agent.ts +337 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +281 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/surfaces.ts +605 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/agent.ts +342 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +168 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/surfaces.ts +628 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/agent.ts +346 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +156 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/agent.ts +350 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +153 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +13 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/surfaces.ts +651 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/tsconfig.surfaces.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +463 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent-v1.ts +99 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/agent.ts +623 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +336 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/agent.ts +96 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +579 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.cjs +53 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/index.mjs +55 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/vendor/lc-weather-provider/package.json +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +635 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/agent.ts +659 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +553 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/agent.ts +877 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +6417 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/agent.ts +872 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/mcp-server.mjs +66 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +2540 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/actions.ts +18 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/ai/route.ts +42 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/edge/route.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/langgraph/route.ts +21 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/llamaindex/route.ts +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/mastra/route.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/api/status/route.ts +7 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/layout.tsx +9 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/app/page.tsx +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/instrumentation.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/ai.ts +61 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/langgraph.ts +68 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/llamaindex.ts +98 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/lib/mastra.ts +90 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/next.config.ts +52 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4343 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +28 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/agent.ts +51 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/deno-npm.ts +76 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +484 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +15 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/agent.ts +79 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package-lock.json +740 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/types/package.json +11 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/agent.ts +197 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package-lock.json +70 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/package.json +12 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/vanilla/tsconfig.json +12 -0
- package/.next/standalone/sdk/typescript/integration/global-setup.ts +29 -0
- package/.next/standalone/sdk/typescript/integration/harness.ts +451 -0
- package/.next/standalone/sdk/typescript/integration/langchain.test.ts +682 -0
- package/.next/standalone/sdk/typescript/integration/llamaindex.test.ts +709 -0
- package/.next/standalone/sdk/typescript/integration/mastra-coverage.test.ts +386 -0
- package/.next/standalone/sdk/typescript/integration/mastra.test.ts +311 -0
- package/.next/standalone/sdk/typescript/integration/nextjs.test.ts +341 -0
- package/.next/standalone/sdk/typescript/integration/runtime-parity.ts +180 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.bun.test.ts +15 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.core.test.ts +113 -0
- package/.next/standalone/sdk/typescript/integration/runtimes.deno.test.ts +19 -0
- package/.next/standalone/sdk/typescript/integration/types.test.ts +141 -0
- package/.next/standalone/sdk/typescript/integration/vanilla.test.ts +255 -0
- package/.next/standalone/sdk/typescript/package-lock.json +2640 -0
- package/.next/standalone/sdk/typescript/package.json +401 -0
- package/.next/standalone/sdk/typescript/scripts/finalize-build.mjs +123 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +177 -0
- package/.next/standalone/sdk/typescript/src/clock.ts +58 -0
- package/.next/standalone/sdk/typescript/src/context.ts +214 -0
- package/.next/standalone/sdk/typescript/src/edge/adapter.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/ai.ts +116 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +238 -0
- package/.next/standalone/sdk/typescript/src/edge/langchain.ts +18 -0
- package/.next/standalone/sdk/typescript/src/edge/llamaindex.ts +12 -0
- package/.next/standalone/sdk/typescript/src/edge/mastra.ts +17 -0
- package/.next/standalone/sdk/typescript/src/edge/notice.ts +33 -0
- package/.next/standalone/sdk/typescript/src/environment.ts +75 -0
- package/.next/standalone/sdk/typescript/src/evaluator/authoring.ts +480 -0
- package/.next/standalone/sdk/typescript/src/evaluator/cli.ts +96 -0
- package/.next/standalone/sdk/typescript/src/evaluator/client.ts +421 -0
- package/.next/standalone/sdk/typescript/src/evaluator/expression.ts +1292 -0
- package/.next/standalone/sdk/typescript/src/evaluator/index.ts +144 -0
- package/.next/standalone/sdk/typescript/src/evaluator/protocol.ts +747 -0
- package/.next/standalone/sdk/typescript/src/evaluator/runtime.ts +930 -0
- package/.next/standalone/sdk/typescript/src/evaluator/sandbox-worker.ts +171 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source-limits.ts +27 -0
- package/.next/standalone/sdk/typescript/src/evaluator/source.ts +509 -0
- package/.next/standalone/sdk/typescript/src/events.ts +879 -0
- package/.next/standalone/sdk/typescript/src/exit.ts +117 -0
- package/.next/standalone/sdk/typescript/src/index.ts +186 -0
- package/.next/standalone/sdk/typescript/src/integrations/ai.ts +1566 -0
- package/.next/standalone/sdk/typescript/src/integrations/compat.ts +322 -0
- package/.next/standalone/sdk/typescript/src/integrations/core.ts +1321 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +355 -0
- package/.next/standalone/sdk/typescript/src/integrations/langchain.ts +2340 -0
- package/.next/standalone/sdk/typescript/src/integrations/llamaindex.ts +2111 -0
- package/.next/standalone/sdk/typescript/src/integrations/mastra.ts +1802 -0
- package/.next/standalone/sdk/typescript/src/logger.ts +98 -0
- package/.next/standalone/sdk/typescript/src/next.ts +115 -0
- package/.next/standalone/sdk/typescript/src/node-require.ts +446 -0
- package/.next/standalone/sdk/typescript/src/redact.ts +305 -0
- package/.next/standalone/sdk/typescript/src/resolver.ts +120 -0
- package/.next/standalone/sdk/typescript/src/runtime.ts +29 -0
- package/.next/standalone/sdk/typescript/src/schema.ts +410 -0
- package/.next/standalone/sdk/typescript/src/scopes.ts +701 -0
- package/.next/standalone/sdk/typescript/src/shared.ts +33 -0
- package/.next/standalone/sdk/typescript/src/version.ts +5 -0
- package/.next/standalone/sdk/typescript/src/writer.ts +934 -0
- package/.next/standalone/sdk/typescript/test/adapters.test.ts +397 -0
- package/.next/standalone/sdk/typescript/test/ai.test.ts +1076 -0
- package/.next/standalone/sdk/typescript/test/copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/edge.test.ts +183 -0
- package/.next/standalone/sdk/typescript/test/evaluator-client.test.ts +234 -0
- package/.next/standalone/sdk/typescript/test/evaluator-protocol.test.ts +225 -0
- package/.next/standalone/sdk/typescript/test/events.test.ts +193 -0
- package/.next/standalone/sdk/typescript/test/expression.test.ts +181 -0
- package/.next/standalone/sdk/typescript/test/global-setup.ts +26 -0
- package/.next/standalone/sdk/typescript/test/helpers.ts +130 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +369 -0
- package/.next/standalone/sdk/typescript/test/langchain-copies.test.ts +204 -0
- package/.next/standalone/sdk/typescript/test/langchain.test.ts +999 -0
- package/.next/standalone/sdk/typescript/test/llamaindex.test.ts +1760 -0
- package/.next/standalone/sdk/typescript/test/mastra-coverage.test.ts +501 -0
- package/.next/standalone/sdk/typescript/test/mastra-lifecycle.test.ts +479 -0
- package/.next/standalone/sdk/typescript/test/mastra.test.ts +285 -0
- package/.next/standalone/sdk/typescript/test/next.test.ts +109 -0
- package/.next/standalone/sdk/typescript/test/packaging.test.ts +312 -0
- package/.next/standalone/sdk/typescript/test/redaction.test.ts +171 -0
- package/.next/standalone/sdk/typescript/test/runtimes.test.ts +101 -0
- package/.next/standalone/sdk/typescript/test/sandbox.test.ts +189 -0
- package/.next/standalone/sdk/typescript/test/scopes.test.ts +271 -0
- package/.next/standalone/sdk/typescript/test/setup.ts +19 -0
- package/.next/standalone/sdk/typescript/test/skill-snippets.test.ts +73 -0
- package/.next/standalone/sdk/typescript/test/spool-contract.test.ts +124 -0
- package/.next/standalone/sdk/typescript/test/tracker-bounds.test.ts +191 -0
- package/.next/standalone/sdk/typescript/test/wire-format.test.ts +214 -0
- package/.next/standalone/sdk/typescript/test/writer.test.ts +407 -0
- package/.next/standalone/sdk/typescript/tsconfig.build.json +15 -0
- package/.next/standalone/sdk/typescript/tsconfig.cjs.json +19 -0
- package/.next/standalone/sdk/typescript/tsconfig.json +28 -0
- package/.next/standalone/sdk/typescript/vitest.config.ts +33 -0
- package/.next/standalone/sdk/typescript/vitest.integration.config.ts +23 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +2 -2
- package/dist/cli.mjs +208 -27
- package/dist/worker.mjs +120 -14
- package/package.json +10 -10
- package/scripts/build-policy-pack.mjs +14 -0
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +156 -5
- package/src/hooks/jev-cli.ts +57 -1
- package/src/hooks/manager.ts +1 -1
- package/src/hooks/pack-cli.ts +150 -18
- package/src/hooks/pack-store.ts +1 -1
- package/src/hooks/policy-catalog.ts +148 -9
- package/src/hooks/types.ts +1 -1
- package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +0 -6
- package/.next/standalone/.next/static/chunks/0qrbdkv9qmvli.js +0 -69
- package/.next/standalone/.next/static/chunks/0uldbut9y2-e8.js +0 -1
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +0 -2
- package/.next/standalone/.next/static/chunks/2vkvu9-opa_1z.js +0 -1
- package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +0 -1
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{T8YAYM9h_64fKv705vug7 → gbEOjBgZAxF2UIUwZVHNu}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A restricted deterministic expression language for server-authored
|
|
3
|
+
* evaluations, parsed and **interpreted** here — never `eval`'d, never handed
|
|
4
|
+
* to `node:vm`.
|
|
5
|
+
*
|
|
6
|
+
* ## Why an interpreter and not `vm`
|
|
7
|
+
*
|
|
8
|
+
* The Python SDK validates an AST against an allowlist and then calls `eval`
|
|
9
|
+
* with empty builtins. The same shape in JavaScript is not safe, because
|
|
10
|
+
* JavaScript has a reachable path from any value to arbitrary code:
|
|
11
|
+
*
|
|
12
|
+
* (() => {}).constructor("return process")()
|
|
13
|
+
* x["constructor"]["constructor"]("…")()
|
|
14
|
+
*
|
|
15
|
+
* A STATIC allowlist cannot close the second one: `x[k]` is a property read
|
|
16
|
+
* whose key is only known at runtime, so `x["constructor"]` passes any check
|
|
17
|
+
* made over the source text. `node:vm` does not close it either — a vm context
|
|
18
|
+
* has its own `Function`, and reaching it from inside is a well-known escape.
|
|
19
|
+
*
|
|
20
|
+
* Interpreting removes the question. Every property read goes through
|
|
21
|
+
* `readProperty` below, which is an allowlist evaluated against the ACTUAL key
|
|
22
|
+
* at the moment of the read, and there is no path from a value to a function
|
|
23
|
+
* constructor because the interpreter never constructs one. The
|
|
24
|
+
* `worker_threads` sandbox around this (see `source.ts`) is then a RESOURCE
|
|
25
|
+
* bound — CPU, heap, wall-clock — rather than the only thing standing between
|
|
26
|
+
* tenant source and the worker's credentials.
|
|
27
|
+
*
|
|
28
|
+
* ## The grammar
|
|
29
|
+
*
|
|
30
|
+
* One expression. Literals, arrays, objects, template strings, member access,
|
|
31
|
+
* calls, arrow functions (the analogue of Python's comprehensions — `.map` /
|
|
32
|
+
* `.filter` need them), the arithmetic, comparison and logical operators, and
|
|
33
|
+
* the conditional operator. No statements, no assignment, no `new`, no
|
|
34
|
+
* `function`, no `async`, no `await`, no regular-expression literals, no
|
|
35
|
+
* optional chaining into calls.
|
|
36
|
+
*
|
|
37
|
+
* DEFAULT-DENY throughout: anything the parser does not explicitly understand
|
|
38
|
+
* is a rejection, not a pass-through. A grammar gap is therefore a definition
|
|
39
|
+
* that will not run, never a definition that runs unchecked.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export class UnsafeEvaluatorSource extends Error {
|
|
43
|
+
constructor(message: string) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "UnsafeEvaluatorSource";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A sandboxed evaluation exceeded its step, depth or value budget. */
|
|
50
|
+
export class EvaluationBudgetExceeded extends Error {
|
|
51
|
+
constructor(message: string) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = "EvaluationBudgetExceeded";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const MAX_AST_NODES = 5_000;
|
|
58
|
+
export const MAX_POW_EXPONENT = 64;
|
|
59
|
+
/**
|
|
60
|
+
* Interpreter step ceiling. There are no loop statements in this grammar, but
|
|
61
|
+
* `.map` over a large array and self-application (`(f => f(f))(f => f(f))`, the
|
|
62
|
+
* Y-combinator — reachable with nothing but arrow functions) both run forever.
|
|
63
|
+
* The worker's wall-clock kill is the outer bound; this one fails the
|
|
64
|
+
* evaluation with a message that says what happened instead of a silent
|
|
65
|
+
* timeout.
|
|
66
|
+
*/
|
|
67
|
+
export const MAX_STEPS = 2_000_000;
|
|
68
|
+
export const MAX_CALL_DEPTH = 64;
|
|
69
|
+
/** Cap on a single constructed string, so concatenation cannot build a bomb. */
|
|
70
|
+
export const MAX_STRING_LENGTH = 1_000_000;
|
|
71
|
+
/** Cap on a single constructed array, for the same reason. */
|
|
72
|
+
export const MAX_ARRAY_LENGTH = 100_000;
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Allowlists
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Pure data methods. Allowed only at a CALL SITE — `_parse` requires each of
|
|
80
|
+
* these names to be the callee of a call, so a bare `payload.get` reference
|
|
81
|
+
* cannot be smuggled into a result field.
|
|
82
|
+
*
|
|
83
|
+
* Conspicuously absent: `constructor`, `apply`, `call`, `bind`, `toString`,
|
|
84
|
+
* `valueOf`, `__proto__`, `prototype`. Not as a denylist — they are absent
|
|
85
|
+
* because this is an allowlist, which is the whole point: a method added to
|
|
86
|
+
* `Array.prototype` by a future runtime is denied by default rather than
|
|
87
|
+
* needing to be discovered and listed.
|
|
88
|
+
*/
|
|
89
|
+
const METHOD_ATTRS = new Set([
|
|
90
|
+
// Transcript methods.
|
|
91
|
+
"eventsOfType",
|
|
92
|
+
"count",
|
|
93
|
+
// Array data methods.
|
|
94
|
+
"map",
|
|
95
|
+
"filter",
|
|
96
|
+
"some",
|
|
97
|
+
"every",
|
|
98
|
+
"find",
|
|
99
|
+
"findIndex",
|
|
100
|
+
"includes",
|
|
101
|
+
"indexOf",
|
|
102
|
+
"lastIndexOf",
|
|
103
|
+
"slice",
|
|
104
|
+
"concat",
|
|
105
|
+
"join",
|
|
106
|
+
"flat",
|
|
107
|
+
"flatMap",
|
|
108
|
+
"reduce",
|
|
109
|
+
"at",
|
|
110
|
+
"reverse",
|
|
111
|
+
// String data methods.
|
|
112
|
+
"toLowerCase",
|
|
113
|
+
"toUpperCase",
|
|
114
|
+
"trim",
|
|
115
|
+
"trimStart",
|
|
116
|
+
"trimEnd",
|
|
117
|
+
"split",
|
|
118
|
+
"startsWith",
|
|
119
|
+
"endsWith",
|
|
120
|
+
"replace",
|
|
121
|
+
"replaceAll",
|
|
122
|
+
"padStart",
|
|
123
|
+
"padEnd",
|
|
124
|
+
"repeat",
|
|
125
|
+
"charAt",
|
|
126
|
+
"codePointAt",
|
|
127
|
+
"normalize",
|
|
128
|
+
"localeCompare",
|
|
129
|
+
// Number data methods.
|
|
130
|
+
"toFixed",
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Property names that must never be readable, whatever the allowlist says and
|
|
135
|
+
* however the key is computed. `readProperty` checks this on the RUNTIME key,
|
|
136
|
+
* which is what closes `x[someExpressionThatYields("constructor")]`.
|
|
137
|
+
*/
|
|
138
|
+
const FORBIDDEN_KEYS = new Set([
|
|
139
|
+
"constructor",
|
|
140
|
+
"prototype",
|
|
141
|
+
"__proto__",
|
|
142
|
+
"__defineGetter__",
|
|
143
|
+
"__defineSetter__",
|
|
144
|
+
"__lookupGetter__",
|
|
145
|
+
"__lookupSetter__",
|
|
146
|
+
"apply",
|
|
147
|
+
"call",
|
|
148
|
+
"bind",
|
|
149
|
+
"arguments",
|
|
150
|
+
"caller",
|
|
151
|
+
"toString",
|
|
152
|
+
"valueOf",
|
|
153
|
+
"toJSON",
|
|
154
|
+
"then",
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
/** Namespace globals, each with its own fixed member set. */
|
|
158
|
+
const NAMESPACE_MEMBERS: Record<string, ReadonlySet<string>> = {
|
|
159
|
+
Math: new Set([
|
|
160
|
+
"abs",
|
|
161
|
+
"min",
|
|
162
|
+
"max",
|
|
163
|
+
"round",
|
|
164
|
+
"floor",
|
|
165
|
+
"ceil",
|
|
166
|
+
"trunc",
|
|
167
|
+
"sign",
|
|
168
|
+
"sqrt",
|
|
169
|
+
"log",
|
|
170
|
+
"log2",
|
|
171
|
+
"log10",
|
|
172
|
+
"exp",
|
|
173
|
+
"hypot",
|
|
174
|
+
"PI",
|
|
175
|
+
"E",
|
|
176
|
+
]),
|
|
177
|
+
Object: new Set(["keys", "values", "entries", "fromEntries"]),
|
|
178
|
+
Array: new Set(["isArray", "from", "of"]),
|
|
179
|
+
Number: new Set(["isFinite", "isInteger", "isNaN", "parseFloat", "parseInt", "MAX_SAFE_INTEGER"]),
|
|
180
|
+
JSON: new Set(["stringify"]),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Tokenizer
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
type TokenType = "num" | "str" | "template" | "name" | "punct" | "eof";
|
|
188
|
+
|
|
189
|
+
interface TemplatePart {
|
|
190
|
+
readonly cooked: string;
|
|
191
|
+
readonly expression: string | null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface Token {
|
|
195
|
+
readonly type: TokenType;
|
|
196
|
+
readonly value: string;
|
|
197
|
+
readonly start: number;
|
|
198
|
+
readonly numeric?: number;
|
|
199
|
+
readonly parts?: readonly TemplatePart[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const PUNCTUATORS = [
|
|
203
|
+
"===",
|
|
204
|
+
"!==",
|
|
205
|
+
"**",
|
|
206
|
+
"&&",
|
|
207
|
+
"||",
|
|
208
|
+
"??",
|
|
209
|
+
"==",
|
|
210
|
+
"!=",
|
|
211
|
+
"<=",
|
|
212
|
+
">=",
|
|
213
|
+
"=>",
|
|
214
|
+
"(",
|
|
215
|
+
")",
|
|
216
|
+
"[",
|
|
217
|
+
"]",
|
|
218
|
+
"{",
|
|
219
|
+
"}",
|
|
220
|
+
",",
|
|
221
|
+
".",
|
|
222
|
+
":",
|
|
223
|
+
"?",
|
|
224
|
+
"+",
|
|
225
|
+
"-",
|
|
226
|
+
"*",
|
|
227
|
+
"/",
|
|
228
|
+
"%",
|
|
229
|
+
"<",
|
|
230
|
+
">",
|
|
231
|
+
"!",
|
|
232
|
+
].sort((a, b) => b.length - a.length);
|
|
233
|
+
|
|
234
|
+
const IDENT_START = /[A-Za-z_$]/;
|
|
235
|
+
const IDENT_PART = /[A-Za-z0-9_$]/;
|
|
236
|
+
|
|
237
|
+
function fail(fieldName: string, detail: string): never {
|
|
238
|
+
throw new UnsafeEvaluatorSource(`${fieldName} ${detail}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class Tokenizer {
|
|
242
|
+
private index = 0;
|
|
243
|
+
constructor(
|
|
244
|
+
private readonly text: string,
|
|
245
|
+
private readonly fieldName: string,
|
|
246
|
+
) {}
|
|
247
|
+
|
|
248
|
+
tokenize(): Token[] {
|
|
249
|
+
const tokens: Token[] = [];
|
|
250
|
+
for (;;) {
|
|
251
|
+
this.skipTrivia();
|
|
252
|
+
if (this.index >= this.text.length) {
|
|
253
|
+
tokens.push({ type: "eof", value: "", start: this.index });
|
|
254
|
+
return tokens;
|
|
255
|
+
}
|
|
256
|
+
tokens.push(this.next());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Whitespace only. Comments are NOT skipped — they are rejected by the
|
|
262
|
+
* punctuator table, because a language that accepts `//` and `/*` has to get
|
|
263
|
+
* their interaction with division exactly right to stay unambiguous, and an
|
|
264
|
+
* expression that needs a comment is one that should be simpler.
|
|
265
|
+
*/
|
|
266
|
+
private skipTrivia(): void {
|
|
267
|
+
while (this.index < this.text.length && /\s/.test(this.text[this.index]!)) this.index += 1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private next(): Token {
|
|
271
|
+
const start = this.index;
|
|
272
|
+
const char = this.text[start]!;
|
|
273
|
+
|
|
274
|
+
if (char >= "0" && char <= "9") return this.number(start);
|
|
275
|
+
if (char === "." && /[0-9]/.test(this.text[start + 1] ?? "")) return this.number(start);
|
|
276
|
+
if (char === '"' || char === "'") return this.string(start, char);
|
|
277
|
+
if (char === "`") return this.template(start);
|
|
278
|
+
if (IDENT_START.test(char)) return this.name(start);
|
|
279
|
+
|
|
280
|
+
for (const punctuator of PUNCTUATORS) {
|
|
281
|
+
if (this.text.startsWith(punctuator, start)) {
|
|
282
|
+
this.index = start + punctuator.length;
|
|
283
|
+
return { type: "punct", value: punctuator, start };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
fail(this.fieldName, `contains an unexpected character ${JSON.stringify(char)}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private number(start: number): Token {
|
|
290
|
+
let end = start;
|
|
291
|
+
while (end < this.text.length && /[0-9]/.test(this.text[end]!)) end += 1;
|
|
292
|
+
if (this.text[end] === ".") {
|
|
293
|
+
end += 1;
|
|
294
|
+
while (end < this.text.length && /[0-9]/.test(this.text[end]!)) end += 1;
|
|
295
|
+
}
|
|
296
|
+
if (this.text[end] === "e" || this.text[end] === "E") {
|
|
297
|
+
let cursor = end + 1;
|
|
298
|
+
if (this.text[cursor] === "+" || this.text[cursor] === "-") cursor += 1;
|
|
299
|
+
if (/[0-9]/.test(this.text[cursor] ?? "")) {
|
|
300
|
+
cursor += 1;
|
|
301
|
+
while (cursor < this.text.length && /[0-9]/.test(this.text[cursor]!)) cursor += 1;
|
|
302
|
+
end = cursor;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// A trailing identifier character means `1n` (BigInt) or `0x…`; both are
|
|
306
|
+
// outside this grammar and must be rejected rather than silently truncated
|
|
307
|
+
// to the numeric prefix.
|
|
308
|
+
if (IDENT_PART.test(this.text[end] ?? "")) {
|
|
309
|
+
fail(this.fieldName, "contains an unsupported numeric literal");
|
|
310
|
+
}
|
|
311
|
+
const raw = this.text.slice(start, end);
|
|
312
|
+
this.index = end;
|
|
313
|
+
return { type: "num", value: raw, start, numeric: Number(raw) };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private readEscape(): string {
|
|
317
|
+
// `this.index` points at the backslash.
|
|
318
|
+
this.index += 1;
|
|
319
|
+
const char = this.text[this.index];
|
|
320
|
+
if (char === undefined) fail(this.fieldName, "ends inside a string escape");
|
|
321
|
+
this.index += 1;
|
|
322
|
+
switch (char) {
|
|
323
|
+
case "n":
|
|
324
|
+
return "\n";
|
|
325
|
+
case "t":
|
|
326
|
+
return "\t";
|
|
327
|
+
case "r":
|
|
328
|
+
return "\r";
|
|
329
|
+
case "b":
|
|
330
|
+
return "\b";
|
|
331
|
+
case "f":
|
|
332
|
+
return "\f";
|
|
333
|
+
case "v":
|
|
334
|
+
return "\v";
|
|
335
|
+
case "0":
|
|
336
|
+
return "\0";
|
|
337
|
+
case "u": {
|
|
338
|
+
if (this.text[this.index] === "{") {
|
|
339
|
+
const close = this.text.indexOf("}", this.index);
|
|
340
|
+
if (close === -1) fail(this.fieldName, "has an unterminated unicode escape");
|
|
341
|
+
const hex = this.text.slice(this.index + 1, close);
|
|
342
|
+
if (!/^[0-9a-fA-F]{1,6}$/.test(hex)) fail(this.fieldName, "has a malformed unicode escape");
|
|
343
|
+
this.index = close + 1;
|
|
344
|
+
return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
345
|
+
}
|
|
346
|
+
const hex = this.text.slice(this.index, this.index + 4);
|
|
347
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail(this.fieldName, "has a malformed unicode escape");
|
|
348
|
+
this.index += 4;
|
|
349
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
350
|
+
}
|
|
351
|
+
case "x": {
|
|
352
|
+
const hex = this.text.slice(this.index, this.index + 2);
|
|
353
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex)) fail(this.fieldName, "has a malformed hex escape");
|
|
354
|
+
this.index += 2;
|
|
355
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
356
|
+
}
|
|
357
|
+
default:
|
|
358
|
+
return char;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private string(start: number, quote: string): Token {
|
|
363
|
+
this.index = start + 1;
|
|
364
|
+
let out = "";
|
|
365
|
+
while (this.index < this.text.length) {
|
|
366
|
+
const char = this.text[this.index]!;
|
|
367
|
+
if (char === quote) {
|
|
368
|
+
this.index += 1;
|
|
369
|
+
return { type: "str", value: out, start };
|
|
370
|
+
}
|
|
371
|
+
if (char === "\\") {
|
|
372
|
+
out += this.readEscape();
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (char === "\n") fail(this.fieldName, "has an unterminated string literal");
|
|
376
|
+
out += char;
|
|
377
|
+
this.index += 1;
|
|
378
|
+
}
|
|
379
|
+
fail(this.fieldName, "has an unterminated string literal");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private template(start: number): Token {
|
|
383
|
+
this.index = start + 1;
|
|
384
|
+
const parts: TemplatePart[] = [];
|
|
385
|
+
let cooked = "";
|
|
386
|
+
while (this.index < this.text.length) {
|
|
387
|
+
const char = this.text[this.index]!;
|
|
388
|
+
if (char === "`") {
|
|
389
|
+
this.index += 1;
|
|
390
|
+
parts.push({ cooked, expression: null });
|
|
391
|
+
return { type: "template", value: "", start, parts };
|
|
392
|
+
}
|
|
393
|
+
if (char === "\\") {
|
|
394
|
+
cooked += this.readEscape();
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (char === "$" && this.text[this.index + 1] === "{") {
|
|
398
|
+
const expression = this.readInterpolation();
|
|
399
|
+
parts.push({ cooked, expression });
|
|
400
|
+
cooked = "";
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
cooked += char;
|
|
404
|
+
this.index += 1;
|
|
405
|
+
}
|
|
406
|
+
fail(this.fieldName, "has an unterminated template literal");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** The source between `${` and its matching `}`, tracking nesting and strings. */
|
|
410
|
+
private readInterpolation(): string {
|
|
411
|
+
this.index += 2;
|
|
412
|
+
const begin = this.index;
|
|
413
|
+
let depth = 1;
|
|
414
|
+
while (this.index < this.text.length) {
|
|
415
|
+
const char = this.text[this.index]!;
|
|
416
|
+
if (char === "{") depth += 1;
|
|
417
|
+
else if (char === "}") {
|
|
418
|
+
depth -= 1;
|
|
419
|
+
if (depth === 0) {
|
|
420
|
+
const source = this.text.slice(begin, this.index);
|
|
421
|
+
this.index += 1;
|
|
422
|
+
return source;
|
|
423
|
+
}
|
|
424
|
+
} else if (char === '"' || char === "'" || char === "`") {
|
|
425
|
+
this.string(this.index, char);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
this.index += 1;
|
|
429
|
+
}
|
|
430
|
+
fail(this.fieldName, "has an unterminated template interpolation");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private name(start: number): Token {
|
|
434
|
+
let end = start;
|
|
435
|
+
while (end < this.text.length && IDENT_PART.test(this.text[end]!)) end += 1;
|
|
436
|
+
this.index = end;
|
|
437
|
+
return { type: "name", value: this.text.slice(start, end), start };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// AST
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
type Node =
|
|
446
|
+
| { kind: "literal"; value: unknown }
|
|
447
|
+
| { kind: "template"; parts: ReadonlyArray<{ cooked: string; expression: Node | null }> }
|
|
448
|
+
| { kind: "name"; name: string }
|
|
449
|
+
| { kind: "array"; elements: readonly Node[] }
|
|
450
|
+
| { kind: "object"; properties: ReadonlyArray<{ key: Node; value: Node }> }
|
|
451
|
+
| { kind: "member"; object: Node; property: Node; computed: boolean; optional: boolean }
|
|
452
|
+
| { kind: "call"; callee: Node; args: readonly Node[]; optional: boolean }
|
|
453
|
+
| { kind: "unary"; operator: string; argument: Node }
|
|
454
|
+
| { kind: "binary"; operator: string; left: Node; right: Node }
|
|
455
|
+
| { kind: "logical"; operator: string; left: Node; right: Node }
|
|
456
|
+
| { kind: "conditional"; test: Node; consequent: Node; alternate: Node }
|
|
457
|
+
| { kind: "arrow"; params: readonly string[]; body: Node };
|
|
458
|
+
|
|
459
|
+
const RESERVED_NAMES = new Set([
|
|
460
|
+
"new",
|
|
461
|
+
"function",
|
|
462
|
+
"class",
|
|
463
|
+
"await",
|
|
464
|
+
"yield",
|
|
465
|
+
"delete",
|
|
466
|
+
"typeof",
|
|
467
|
+
"instanceof",
|
|
468
|
+
"void",
|
|
469
|
+
"this",
|
|
470
|
+
"super",
|
|
471
|
+
"import",
|
|
472
|
+
"export",
|
|
473
|
+
"var",
|
|
474
|
+
"let",
|
|
475
|
+
"const",
|
|
476
|
+
"return",
|
|
477
|
+
"if",
|
|
478
|
+
"else",
|
|
479
|
+
"for",
|
|
480
|
+
"while",
|
|
481
|
+
"do",
|
|
482
|
+
"switch",
|
|
483
|
+
"case",
|
|
484
|
+
"try",
|
|
485
|
+
"catch",
|
|
486
|
+
"finally",
|
|
487
|
+
"throw",
|
|
488
|
+
"in",
|
|
489
|
+
"of",
|
|
490
|
+
"with",
|
|
491
|
+
"debugger",
|
|
492
|
+
]);
|
|
493
|
+
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Parser
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
class Parser {
|
|
499
|
+
private position = 0;
|
|
500
|
+
private nodes = 0;
|
|
501
|
+
|
|
502
|
+
constructor(
|
|
503
|
+
private readonly tokens: readonly Token[],
|
|
504
|
+
private readonly fieldName: string,
|
|
505
|
+
private readonly globals: ReadonlySet<string>,
|
|
506
|
+
private scopes: readonly (readonly string[])[] = [],
|
|
507
|
+
) {}
|
|
508
|
+
|
|
509
|
+
static parse(
|
|
510
|
+
source: string,
|
|
511
|
+
fieldName: string,
|
|
512
|
+
globals: ReadonlySet<string>,
|
|
513
|
+
scopes: readonly (readonly string[])[] = [],
|
|
514
|
+
): Node {
|
|
515
|
+
const parser = new Parser(new Tokenizer(source, fieldName).tokenize(), fieldName, globals, scopes);
|
|
516
|
+
const node = parser.expression();
|
|
517
|
+
parser.expect("eof");
|
|
518
|
+
return node;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private count(): void {
|
|
522
|
+
this.nodes += 1;
|
|
523
|
+
if (this.nodes > MAX_AST_NODES) {
|
|
524
|
+
fail(this.fieldName, `is too large (${MAX_AST_NODES}-node ceiling)`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private peek(offset = 0): Token {
|
|
529
|
+
return this.tokens[Math.min(this.position + offset, this.tokens.length - 1)]!;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private at(value: string): boolean {
|
|
533
|
+
const token = this.peek();
|
|
534
|
+
return token.type === "punct" && token.value === value;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private eat(value: string): boolean {
|
|
538
|
+
if (!this.at(value)) return false;
|
|
539
|
+
this.position += 1;
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private expect(type: TokenType, value?: string): Token {
|
|
544
|
+
const token = this.peek();
|
|
545
|
+
if (token.type !== type || (value !== undefined && token.value !== value)) {
|
|
546
|
+
fail(
|
|
547
|
+
this.fieldName,
|
|
548
|
+
`is not one valid expression (unexpected ${
|
|
549
|
+
token.type === "eof" ? "end of input" : JSON.stringify(token.value)
|
|
550
|
+
})`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
this.position += 1;
|
|
554
|
+
return token;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private bound(name: string): boolean {
|
|
558
|
+
return this.scopes.some((scope) => scope.includes(name));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private withScope<T>(params: readonly string[], body: () => T): T {
|
|
562
|
+
const previous = this.scopes;
|
|
563
|
+
this.scopes = [...previous, params];
|
|
564
|
+
try {
|
|
565
|
+
return body();
|
|
566
|
+
} finally {
|
|
567
|
+
this.scopes = previous;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
expression(): Node {
|
|
572
|
+
return this.conditional();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private conditional(): Node {
|
|
576
|
+
const test = this.nullish();
|
|
577
|
+
if (!this.eat("?")) return test;
|
|
578
|
+
this.count();
|
|
579
|
+
const consequent = this.expression();
|
|
580
|
+
this.expect("punct", ":");
|
|
581
|
+
const alternate = this.expression();
|
|
582
|
+
return { kind: "conditional", test, consequent, alternate };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private nullish(): Node {
|
|
586
|
+
let left = this.logicalOr();
|
|
587
|
+
while (this.at("??")) {
|
|
588
|
+
this.position += 1;
|
|
589
|
+
this.count();
|
|
590
|
+
left = { kind: "logical", operator: "??", left, right: this.logicalOr() };
|
|
591
|
+
}
|
|
592
|
+
return left;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private logicalOr(): Node {
|
|
596
|
+
let left = this.logicalAnd();
|
|
597
|
+
while (this.at("||")) {
|
|
598
|
+
this.position += 1;
|
|
599
|
+
this.count();
|
|
600
|
+
left = { kind: "logical", operator: "||", left, right: this.logicalAnd() };
|
|
601
|
+
}
|
|
602
|
+
return left;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private logicalAnd(): Node {
|
|
606
|
+
let left = this.equality();
|
|
607
|
+
while (this.at("&&")) {
|
|
608
|
+
this.position += 1;
|
|
609
|
+
this.count();
|
|
610
|
+
left = { kind: "logical", operator: "&&", left, right: this.equality() };
|
|
611
|
+
}
|
|
612
|
+
return left;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private equality(): Node {
|
|
616
|
+
let left = this.relational();
|
|
617
|
+
for (;;) {
|
|
618
|
+
const operator = ["===", "!==", "==", "!="].find((op) => this.at(op));
|
|
619
|
+
if (operator === undefined) return left;
|
|
620
|
+
this.position += 1;
|
|
621
|
+
this.count();
|
|
622
|
+
left = { kind: "binary", operator, left, right: this.relational() };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private relational(): Node {
|
|
627
|
+
let left = this.additive();
|
|
628
|
+
for (;;) {
|
|
629
|
+
const operator = ["<=", ">=", "<", ">"].find((op) => this.at(op));
|
|
630
|
+
if (operator === undefined) return left;
|
|
631
|
+
this.position += 1;
|
|
632
|
+
this.count();
|
|
633
|
+
left = { kind: "binary", operator, left, right: this.additive() };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
private additive(): Node {
|
|
638
|
+
let left = this.multiplicative();
|
|
639
|
+
for (;;) {
|
|
640
|
+
const operator = ["+", "-"].find((op) => this.at(op));
|
|
641
|
+
if (operator === undefined) return left;
|
|
642
|
+
this.position += 1;
|
|
643
|
+
this.count();
|
|
644
|
+
left = { kind: "binary", operator, left, right: this.multiplicative() };
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private multiplicative(): Node {
|
|
649
|
+
let left = this.exponent();
|
|
650
|
+
for (;;) {
|
|
651
|
+
const operator = ["*", "/", "%"].find((op) => this.at(op));
|
|
652
|
+
if (operator === undefined) return left;
|
|
653
|
+
this.position += 1;
|
|
654
|
+
this.count();
|
|
655
|
+
left = { kind: "binary", operator, left, right: this.exponent() };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
private exponent(): Node {
|
|
660
|
+
const left = this.unary();
|
|
661
|
+
if (!this.at("**")) return left;
|
|
662
|
+
this.position += 1;
|
|
663
|
+
this.count();
|
|
664
|
+
const right = this.exponent();
|
|
665
|
+
// Defence in depth: a literal `10 ** 20` (or `2 ** (10 ** 8)`) builds a
|
|
666
|
+
// number — or, with strings, a memory bomb — at compile-time-visible size.
|
|
667
|
+
// Require the exponent to be a small non-negative integer literal.
|
|
668
|
+
// Runtime-sized bombs still exist and are caught by the step budget and the
|
|
669
|
+
// worker's limits, not here.
|
|
670
|
+
if (
|
|
671
|
+
right.kind !== "literal" ||
|
|
672
|
+
typeof right.value !== "number" ||
|
|
673
|
+
!Number.isInteger(right.value) ||
|
|
674
|
+
right.value < 0 ||
|
|
675
|
+
right.value > MAX_POW_EXPONENT
|
|
676
|
+
) {
|
|
677
|
+
fail(this.fieldName, `exponent must be an integer constant in 0..${MAX_POW_EXPONENT}`);
|
|
678
|
+
}
|
|
679
|
+
return { kind: "binary", operator: "**", left, right };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private unary(): Node {
|
|
683
|
+
const operator = ["!", "-", "+"].find((op) => this.at(op));
|
|
684
|
+
if (operator === undefined) return this.postfix();
|
|
685
|
+
this.position += 1;
|
|
686
|
+
this.count();
|
|
687
|
+
return { kind: "unary", operator, argument: this.unary() };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private postfix(): Node {
|
|
691
|
+
let node = this.primary();
|
|
692
|
+
for (;;) {
|
|
693
|
+
if (this.eat(".")) {
|
|
694
|
+
this.count();
|
|
695
|
+
const name = this.expect("name");
|
|
696
|
+
this.checkAttribute(name.value);
|
|
697
|
+
node = {
|
|
698
|
+
kind: "member",
|
|
699
|
+
object: node,
|
|
700
|
+
property: { kind: "literal", value: name.value },
|
|
701
|
+
computed: false,
|
|
702
|
+
optional: false,
|
|
703
|
+
};
|
|
704
|
+
// A method name must be the callee of a call. A bare reference is only
|
|
705
|
+
// ever useful for getting a function object somewhere it can be
|
|
706
|
+
// re-entered, and nothing a real evaluation does needs one.
|
|
707
|
+
if (METHOD_ATTRS.has(name.value) && !this.at("(")) {
|
|
708
|
+
fail(
|
|
709
|
+
this.fieldName,
|
|
710
|
+
`may reference method '${name.value}' only to call it`,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (this.eat("[")) {
|
|
716
|
+
this.count();
|
|
717
|
+
const property = this.expression();
|
|
718
|
+
this.expect("punct", "]");
|
|
719
|
+
// A computed key that is a literal can be checked now, so
|
|
720
|
+
// `x["constructor"]` fails when the definition is published rather than
|
|
721
|
+
// on the first session it runs against. A computed key that is not a
|
|
722
|
+
// literal cannot be, which is why `readProperty` checks every key again
|
|
723
|
+
// at the moment of the read.
|
|
724
|
+
if (property.kind === "literal" && typeof property.value === "string") {
|
|
725
|
+
this.checkAttribute(property.value);
|
|
726
|
+
}
|
|
727
|
+
node = { kind: "member", object: node, property, computed: true, optional: false };
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (this.at("(")) {
|
|
731
|
+
this.count();
|
|
732
|
+
node = { kind: "call", callee: node, args: this.arguments(), optional: false };
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
return node;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* What the PARSER can rule out, which is less than it looks.
|
|
741
|
+
*
|
|
742
|
+
* An event payload's keys are whatever the agent put in it — `error`,
|
|
743
|
+
* `tool_name`, `latency_ms`, anything — so a static allowlist of attribute
|
|
744
|
+
* names cannot be the boundary here without making the language unable to
|
|
745
|
+
* read the data it exists to read. And it would not be the boundary anyway:
|
|
746
|
+
* `x[k]` is a property read whose key is only known at runtime, so anything
|
|
747
|
+
* a static check rejects can be spelled computed.
|
|
748
|
+
*
|
|
749
|
+
* So `readProperty` is the boundary, checked against the ACTUAL key at the
|
|
750
|
+
* moment of the read, and this check rejects only the names that can never be
|
|
751
|
+
* legitimate — private/dunder, and the introspection surface — plus the rule
|
|
752
|
+
* that a method may be referenced only to call it.
|
|
753
|
+
*/
|
|
754
|
+
private checkAttribute(name: string): void {
|
|
755
|
+
if (name.startsWith("_")) {
|
|
756
|
+
fail(this.fieldName, "may not access private or dunder attributes");
|
|
757
|
+
}
|
|
758
|
+
if (FORBIDDEN_KEYS.has(name)) {
|
|
759
|
+
fail(this.fieldName, `may not access attribute '${name}'`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private arguments(): Node[] {
|
|
764
|
+
this.expect("punct", "(");
|
|
765
|
+
const args: Node[] = [];
|
|
766
|
+
if (this.eat(")")) return args;
|
|
767
|
+
for (;;) {
|
|
768
|
+
args.push(this.expression());
|
|
769
|
+
if (this.eat(",")) {
|
|
770
|
+
if (this.eat(")")) return args; // trailing comma
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
this.expect("punct", ")");
|
|
774
|
+
return args;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private primary(): Node {
|
|
779
|
+
this.count();
|
|
780
|
+
const token = this.peek();
|
|
781
|
+
|
|
782
|
+
if (token.type === "num") {
|
|
783
|
+
this.position += 1;
|
|
784
|
+
return { kind: "literal", value: token.numeric };
|
|
785
|
+
}
|
|
786
|
+
if (token.type === "str") {
|
|
787
|
+
this.position += 1;
|
|
788
|
+
return { kind: "literal", value: token.value };
|
|
789
|
+
}
|
|
790
|
+
if (token.type === "template") {
|
|
791
|
+
this.position += 1;
|
|
792
|
+
return {
|
|
793
|
+
kind: "template",
|
|
794
|
+
parts: (token.parts ?? []).map((part) => ({
|
|
795
|
+
cooked: part.cooked,
|
|
796
|
+
expression:
|
|
797
|
+
part.expression === null
|
|
798
|
+
? null
|
|
799
|
+
: Parser.parse(part.expression, this.fieldName, this.globals, this.scopes),
|
|
800
|
+
})),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (token.type === "name") {
|
|
804
|
+
if (token.value === "true" || token.value === "false") {
|
|
805
|
+
this.position += 1;
|
|
806
|
+
return { kind: "literal", value: token.value === "true" };
|
|
807
|
+
}
|
|
808
|
+
if (token.value === "null" || token.value === "undefined") {
|
|
809
|
+
this.position += 1;
|
|
810
|
+
return { kind: "literal", value: token.value === "null" ? null : undefined };
|
|
811
|
+
}
|
|
812
|
+
// `x => …`
|
|
813
|
+
if (this.peek(1).type === "punct" && this.peek(1).value === "=>") {
|
|
814
|
+
return this.arrow([this.parameterName(token.value)]);
|
|
815
|
+
}
|
|
816
|
+
this.position += 1;
|
|
817
|
+
if (RESERVED_NAMES.has(token.value)) {
|
|
818
|
+
fail(this.fieldName, `may not use the reserved word '${token.value}'`);
|
|
819
|
+
}
|
|
820
|
+
if (token.value.startsWith("_")) fail(this.fieldName, "may not access private names");
|
|
821
|
+
if (!this.bound(token.value) && !this.globals.has(token.value)) {
|
|
822
|
+
fail(this.fieldName, `may not reference '${token.value}'`);
|
|
823
|
+
}
|
|
824
|
+
return { kind: "name", name: token.value };
|
|
825
|
+
}
|
|
826
|
+
if (this.at("[")) {
|
|
827
|
+
this.position += 1;
|
|
828
|
+
const elements: Node[] = [];
|
|
829
|
+
if (this.eat("]")) return { kind: "array", elements };
|
|
830
|
+
for (;;) {
|
|
831
|
+
elements.push(this.expression());
|
|
832
|
+
if (this.eat(",")) {
|
|
833
|
+
if (this.eat("]")) return { kind: "array", elements };
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
this.expect("punct", "]");
|
|
837
|
+
return { kind: "array", elements };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (this.at("{")) return this.objectLiteral();
|
|
841
|
+
if (this.at("(")) {
|
|
842
|
+
const arrowParams = this.tryParenArrowParams();
|
|
843
|
+
if (arrowParams !== null) return this.arrow(arrowParams);
|
|
844
|
+
this.position += 1;
|
|
845
|
+
const inner = this.expression();
|
|
846
|
+
this.expect("punct", ")");
|
|
847
|
+
return inner;
|
|
848
|
+
}
|
|
849
|
+
fail(
|
|
850
|
+
this.fieldName,
|
|
851
|
+
`is not one valid expression (unexpected ${
|
|
852
|
+
token.type === "eof" ? "end of input" : JSON.stringify(token.value)
|
|
853
|
+
})`,
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private parameterName(name: string): string {
|
|
858
|
+
if (RESERVED_NAMES.has(name)) fail(this.fieldName, `may not use '${name}' as a parameter`);
|
|
859
|
+
if (name.startsWith("_")) fail(this.fieldName, "may not declare private names");
|
|
860
|
+
return name;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* `(a, b) =>` — decided by lookahead rather than by backtracking a parse.
|
|
865
|
+
*
|
|
866
|
+
* Returns the parameter names when the parenthesised group is a bare
|
|
867
|
+
* identifier list followed by `=>`, and null otherwise (in which case it is a
|
|
868
|
+
* parenthesised expression). Destructuring, defaults and rest are not part of
|
|
869
|
+
* the grammar, so anything else in the group means "not an arrow".
|
|
870
|
+
*/
|
|
871
|
+
private tryParenArrowParams(): string[] | null {
|
|
872
|
+
let offset = 1;
|
|
873
|
+
const params: string[] = [];
|
|
874
|
+
if (this.peek(offset).type === "punct" && this.peek(offset).value === ")") {
|
|
875
|
+
offset += 1;
|
|
876
|
+
} else {
|
|
877
|
+
for (;;) {
|
|
878
|
+
const token = this.peek(offset);
|
|
879
|
+
if (token.type !== "name") return null;
|
|
880
|
+
params.push(token.value);
|
|
881
|
+
offset += 1;
|
|
882
|
+
const next = this.peek(offset);
|
|
883
|
+
if (next.type !== "punct") return null;
|
|
884
|
+
if (next.value === ",") {
|
|
885
|
+
offset += 1;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (next.value === ")") {
|
|
889
|
+
offset += 1;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const arrow = this.peek(offset);
|
|
896
|
+
if (arrow.type !== "punct" || arrow.value !== "=>") return null;
|
|
897
|
+
this.position += offset;
|
|
898
|
+
return params.map((name) => this.parameterName(name));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
private arrow(params: readonly string[]): Node {
|
|
902
|
+
// The caller has consumed everything up to but not including `=>` for the
|
|
903
|
+
// single-identifier form, and up to and including `)` for the parenthesised
|
|
904
|
+
// one; normalise by consuming the arrow here.
|
|
905
|
+
if (!this.at("=>")) {
|
|
906
|
+
this.position += 1; // the lone identifier
|
|
907
|
+
}
|
|
908
|
+
this.expect("punct", "=>");
|
|
909
|
+
this.count();
|
|
910
|
+
if (new Set(params).size !== params.length) {
|
|
911
|
+
fail(this.fieldName, "may not declare a parameter twice");
|
|
912
|
+
}
|
|
913
|
+
if (this.at("{")) {
|
|
914
|
+
fail(this.fieldName, "arrow functions must have an expression body, not a block");
|
|
915
|
+
}
|
|
916
|
+
const body = this.withScope(params, () => this.expression());
|
|
917
|
+
return { kind: "arrow", params, body };
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
private objectLiteral(): Node {
|
|
921
|
+
this.expect("punct", "{");
|
|
922
|
+
const properties: Array<{ key: Node; value: Node }> = [];
|
|
923
|
+
if (this.eat("}")) return { kind: "object", properties };
|
|
924
|
+
for (;;) {
|
|
925
|
+
this.count();
|
|
926
|
+
let key: Node;
|
|
927
|
+
const token = this.peek();
|
|
928
|
+
if (token.type === "name") {
|
|
929
|
+
this.position += 1;
|
|
930
|
+
key = { kind: "literal", value: token.value };
|
|
931
|
+
} else if (token.type === "str") {
|
|
932
|
+
this.position += 1;
|
|
933
|
+
key = { kind: "literal", value: token.value };
|
|
934
|
+
} else if (token.type === "num") {
|
|
935
|
+
this.position += 1;
|
|
936
|
+
key = { kind: "literal", value: String(token.numeric) };
|
|
937
|
+
} else if (this.eat("[")) {
|
|
938
|
+
key = this.expression();
|
|
939
|
+
this.expect("punct", "]");
|
|
940
|
+
} else {
|
|
941
|
+
fail(this.fieldName, "has an unsupported object key");
|
|
942
|
+
}
|
|
943
|
+
this.expect("punct", ":");
|
|
944
|
+
properties.push({ key, value: this.expression() });
|
|
945
|
+
if (this.eat(",")) {
|
|
946
|
+
if (this.eat("}")) return { kind: "object", properties };
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
this.expect("punct", "}");
|
|
950
|
+
return { kind: "object", properties };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
// Interpreter
|
|
957
|
+
// ---------------------------------------------------------------------------
|
|
958
|
+
|
|
959
|
+
class Budget {
|
|
960
|
+
steps = 0;
|
|
961
|
+
depth = 0;
|
|
962
|
+
|
|
963
|
+
step(): void {
|
|
964
|
+
this.steps += 1;
|
|
965
|
+
if (this.steps > MAX_STEPS) {
|
|
966
|
+
throw new EvaluationBudgetExceeded(
|
|
967
|
+
`evaluation exceeded ${MAX_STEPS} steps; it is looping or iterating unboundedly`,
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
type Scope = Map<string, unknown>;
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Every property read in the language, checked against the RUNTIME key.
|
|
977
|
+
*
|
|
978
|
+
* This is the security boundary, not the parser: `x[k]` cannot be checked
|
|
979
|
+
* statically, and `x["constructor"]` is the whole escape. Own properties only,
|
|
980
|
+
* so nothing reaches a prototype; allowlisted names only, so nothing reaches an
|
|
981
|
+
* inherited method the allowlist has not vetted.
|
|
982
|
+
*/
|
|
983
|
+
function readProperty(target: unknown, key: unknown): unknown {
|
|
984
|
+
if (target === null || target === undefined) {
|
|
985
|
+
throw new TypeError(`cannot read property ${String(key)} of ${String(target)}`);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const name = typeof key === "number" ? String(key) : String(key);
|
|
989
|
+
// Checked FIRST, on the runtime key, and before anything else looks at the
|
|
990
|
+
// receiver. This one test is what closes `x["constructor"]["constructor"]`
|
|
991
|
+
// and every spelling of it — including keys assembled at runtime, which no
|
|
992
|
+
// amount of source inspection can see.
|
|
993
|
+
if (name.startsWith("_") || FORBIDDEN_KEYS.has(name)) {
|
|
994
|
+
throw new UnsafeEvaluatorSource(`property '${name}' is not readable in an evaluator expression`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// A namespace global gets its own member set: `Math.abs` yes, `Math` beyond
|
|
998
|
+
// that no.
|
|
999
|
+
for (const [namespaceName, members] of Object.entries(NAMESPACE_MEMBERS)) {
|
|
1000
|
+
if (target === NAMESPACES[namespaceName]) {
|
|
1001
|
+
if (!members.has(name)) {
|
|
1002
|
+
throw new UnsafeEvaluatorSource(`${namespaceName}.${name} is not available`);
|
|
1003
|
+
}
|
|
1004
|
+
return (NAMESPACES[namespaceName] as Record<string, unknown>)[name];
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Built-in receivers expose their ALLOWLISTED methods and nothing else: their
|
|
1009
|
+
// prototypes are shared, mutable and full of things that are not data.
|
|
1010
|
+
if (typeof target === "string" || Array.isArray(target)) {
|
|
1011
|
+
if (/^\d+$/.test(name)) return (target as unknown as Record<string, unknown>)[name];
|
|
1012
|
+
if (name === "length") return (target as { length: number }).length;
|
|
1013
|
+
if (!METHOD_ATTRS.has(name)) {
|
|
1014
|
+
throw new UnsafeEvaluatorSource(
|
|
1015
|
+
`'${name}' is not available on ${Array.isArray(target) ? "an array" : "a string"}`,
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
const method = (target as unknown as Record<string, unknown>)[name];
|
|
1019
|
+
if (typeof method !== "function") throw new TypeError(`'${name}' is not a method of this value`);
|
|
1020
|
+
// Bound, so a call site cannot re-target it — `.call` is denied anyway, and
|
|
1021
|
+
// this costs nothing to make structurally impossible as well.
|
|
1022
|
+
return (method as (...args: unknown[]) => unknown).bind(target);
|
|
1023
|
+
}
|
|
1024
|
+
if (typeof target === "number" || typeof target === "boolean") {
|
|
1025
|
+
if (!METHOD_ATTRS.has(name)) {
|
|
1026
|
+
throw new UnsafeEvaluatorSource(`'${name}' is not available on a ${typeof target}`);
|
|
1027
|
+
}
|
|
1028
|
+
const method = (target as unknown as Record<string, unknown>)[name];
|
|
1029
|
+
if (typeof method !== "function") throw new TypeError(`'${name}' is not a method of a number`);
|
|
1030
|
+
return (method as (...args: unknown[]) => unknown).bind(target);
|
|
1031
|
+
}
|
|
1032
|
+
if (typeof target !== "object") {
|
|
1033
|
+
throw new TypeError(`cannot read property '${name}' of a ${typeof target}`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// A plain data object — an event payload, or one the expression built. Its
|
|
1037
|
+
// keys are the agent's own and cannot be enumerated in advance, so ANY key is
|
|
1038
|
+
// readable here except the ones refused above. OWN properties only, so
|
|
1039
|
+
// nothing reaches a prototype and an absent key reads as `undefined` rather
|
|
1040
|
+
// than as whatever `Object.prototype` happens to carry.
|
|
1041
|
+
if (!Object.prototype.hasOwnProperty.call(target, name)) return undefined;
|
|
1042
|
+
const value = (target as Record<string, unknown>)[name];
|
|
1043
|
+
if (typeof value === "function") {
|
|
1044
|
+
// Only the transcript's own data methods are functions on this path — a
|
|
1045
|
+
// JSON payload cannot contain one — but the allowlist still applies, so a
|
|
1046
|
+
// future surface cannot expose something callable by accident.
|
|
1047
|
+
if (!METHOD_ATTRS.has(name)) {
|
|
1048
|
+
throw new UnsafeEvaluatorSource(`'${name}' is not callable in an evaluator expression`);
|
|
1049
|
+
}
|
|
1050
|
+
return (value as (...args: unknown[]) => unknown).bind(target);
|
|
1051
|
+
}
|
|
1052
|
+
return value;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* `==` / `!=` without ever running the operands' own code.
|
|
1057
|
+
*
|
|
1058
|
+
* JavaScript's loose equality calls `valueOf` and `toString` on an object
|
|
1059
|
+
* operand, which is a call into arbitrary code — the one door this interpreter
|
|
1060
|
+
* would otherwise leave open. Mapping `==` to `===` instead was worse in
|
|
1061
|
+
* practice: `payload.error != null` is THE idiom for "this optional field is
|
|
1062
|
+
* present", and under strict equality it silently stops matching `undefined`,
|
|
1063
|
+
* so an evaluation over a payload that simply omits the key reports the
|
|
1064
|
+
* opposite of the truth.
|
|
1065
|
+
*
|
|
1066
|
+
* So: nullish operands compare as JavaScript does, primitives coerce the way
|
|
1067
|
+
* the specification says, and an object compared against a primitive is
|
|
1068
|
+
* `false` rather than an invitation to run `valueOf`.
|
|
1069
|
+
*/
|
|
1070
|
+
function looselyEqual(left: unknown, right: unknown): boolean {
|
|
1071
|
+
const leftNullish = left === null || left === undefined;
|
|
1072
|
+
const rightNullish = right === null || right === undefined;
|
|
1073
|
+
if (leftNullish || rightNullish) return leftNullish && rightNullish;
|
|
1074
|
+
if (typeof left === typeof right) return left === right;
|
|
1075
|
+
const leftPrimitive = typeof left !== "object" && typeof left !== "function";
|
|
1076
|
+
const rightPrimitive = typeof right !== "object" && typeof right !== "function";
|
|
1077
|
+
if (!leftPrimitive || !rightPrimitive) return false;
|
|
1078
|
+
if (typeof left === "boolean") return looselyEqual(Number(left), right);
|
|
1079
|
+
if (typeof right === "boolean") return looselyEqual(left, Number(right));
|
|
1080
|
+
if (typeof left === "number" && typeof right === "string") return left === Number(right);
|
|
1081
|
+
if (typeof left === "string" && typeof right === "number") return Number(left) === right;
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/** The namespace objects the language exposes, frozen and minimal. */
|
|
1086
|
+
const NAMESPACES: Record<string, unknown> = {
|
|
1087
|
+
Math,
|
|
1088
|
+
Object,
|
|
1089
|
+
Array,
|
|
1090
|
+
Number,
|
|
1091
|
+
JSON,
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
function checkString(value: string): string {
|
|
1095
|
+
if (value.length > MAX_STRING_LENGTH) {
|
|
1096
|
+
throw new EvaluationBudgetExceeded(
|
|
1097
|
+
`evaluation built a string longer than ${MAX_STRING_LENGTH} characters`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return value;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function checkArray<T>(value: T[]): T[] {
|
|
1104
|
+
if (value.length > MAX_ARRAY_LENGTH) {
|
|
1105
|
+
throw new EvaluationBudgetExceeded(
|
|
1106
|
+
`evaluation built an array longer than ${MAX_ARRAY_LENGTH} items`,
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
return value;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function evaluate(node: Node, scopes: readonly Scope[], budget: Budget): unknown {
|
|
1113
|
+
budget.step();
|
|
1114
|
+
switch (node.kind) {
|
|
1115
|
+
case "literal":
|
|
1116
|
+
return node.value;
|
|
1117
|
+
|
|
1118
|
+
case "template": {
|
|
1119
|
+
let out = "";
|
|
1120
|
+
for (const part of node.parts) {
|
|
1121
|
+
out = checkString(out + part.cooked);
|
|
1122
|
+
if (part.expression !== null) {
|
|
1123
|
+
out = checkString(out + String(evaluate(part.expression, scopes, budget)));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return out;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
case "name": {
|
|
1130
|
+
for (let i = scopes.length - 1; i >= 0; i -= 1) {
|
|
1131
|
+
const scope = scopes[i]!;
|
|
1132
|
+
if (scope.has(node.name)) return scope.get(node.name);
|
|
1133
|
+
}
|
|
1134
|
+
throw new ReferenceError(`${node.name} is not defined`);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
case "array":
|
|
1138
|
+
return checkArray(node.elements.map((element) => evaluate(element, scopes, budget)));
|
|
1139
|
+
|
|
1140
|
+
case "object": {
|
|
1141
|
+
const out: Record<string, unknown> = {};
|
|
1142
|
+
for (const property of node.properties) {
|
|
1143
|
+
const key = String(evaluate(property.key, scopes, budget));
|
|
1144
|
+
if (key.startsWith("_") || FORBIDDEN_KEYS.has(key)) {
|
|
1145
|
+
throw new UnsafeEvaluatorSource(`'${key}' is not a permitted object key`);
|
|
1146
|
+
}
|
|
1147
|
+
out[key] = evaluate(property.value, scopes, budget);
|
|
1148
|
+
}
|
|
1149
|
+
return out;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
case "member":
|
|
1153
|
+
return readProperty(
|
|
1154
|
+
evaluate(node.object, scopes, budget),
|
|
1155
|
+
evaluate(node.property, scopes, budget),
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
case "call": {
|
|
1159
|
+
// The receiver is resolved through `readProperty`, which already bound
|
|
1160
|
+
// the method — so there is no separate `this` to thread and no way to
|
|
1161
|
+
// re-target one.
|
|
1162
|
+
const callee = evaluate(node.callee, scopes, budget);
|
|
1163
|
+
if (typeof callee !== "function") {
|
|
1164
|
+
throw new TypeError("attempted to call a value that is not a function");
|
|
1165
|
+
}
|
|
1166
|
+
const args = node.args.map((argument) => evaluate(argument, scopes, budget));
|
|
1167
|
+
budget.depth += 1;
|
|
1168
|
+
if (budget.depth > MAX_CALL_DEPTH) {
|
|
1169
|
+
throw new EvaluationBudgetExceeded(
|
|
1170
|
+
`evaluation exceeded a call depth of ${MAX_CALL_DEPTH}`,
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
const result = (callee as (...a: unknown[]) => unknown)(...args);
|
|
1175
|
+
if (typeof result === "string") return checkString(result);
|
|
1176
|
+
if (Array.isArray(result)) return checkArray(result);
|
|
1177
|
+
return result;
|
|
1178
|
+
} finally {
|
|
1179
|
+
budget.depth -= 1;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
case "unary": {
|
|
1184
|
+
const value = evaluate(node.argument, scopes, budget);
|
|
1185
|
+
if (node.operator === "!") return !value;
|
|
1186
|
+
if (node.operator === "-") return -(value as number);
|
|
1187
|
+
return +(value as number);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
case "logical": {
|
|
1191
|
+
const left = evaluate(node.left, scopes, budget);
|
|
1192
|
+
if (node.operator === "&&") return left ? evaluate(node.right, scopes, budget) : left;
|
|
1193
|
+
if (node.operator === "||") return left ? left : evaluate(node.right, scopes, budget);
|
|
1194
|
+
return left === null || left === undefined ? evaluate(node.right, scopes, budget) : left;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
case "conditional":
|
|
1198
|
+
return evaluate(node.test, scopes, budget)
|
|
1199
|
+
? evaluate(node.consequent, scopes, budget)
|
|
1200
|
+
: evaluate(node.alternate, scopes, budget);
|
|
1201
|
+
|
|
1202
|
+
case "binary": {
|
|
1203
|
+
const left = evaluate(node.left, scopes, budget) as never;
|
|
1204
|
+
const right = evaluate(node.right, scopes, budget) as never;
|
|
1205
|
+
switch (node.operator) {
|
|
1206
|
+
case "+": {
|
|
1207
|
+
const sum = (left as unknown as number) + (right as unknown as number);
|
|
1208
|
+
return typeof sum === "string" ? checkString(sum) : sum;
|
|
1209
|
+
}
|
|
1210
|
+
case "-":
|
|
1211
|
+
return (left as number) - (right as number);
|
|
1212
|
+
case "*":
|
|
1213
|
+
return (left as number) * (right as number);
|
|
1214
|
+
case "/":
|
|
1215
|
+
return (left as number) / (right as number);
|
|
1216
|
+
case "%":
|
|
1217
|
+
return (left as number) % (right as number);
|
|
1218
|
+
case "**":
|
|
1219
|
+
return (left as number) ** (right as number);
|
|
1220
|
+
case "<":
|
|
1221
|
+
return left < right;
|
|
1222
|
+
case "<=":
|
|
1223
|
+
return left <= right;
|
|
1224
|
+
case ">":
|
|
1225
|
+
return left > right;
|
|
1226
|
+
case ">=":
|
|
1227
|
+
return left >= right;
|
|
1228
|
+
case "===":
|
|
1229
|
+
return left === right;
|
|
1230
|
+
case "!==":
|
|
1231
|
+
return left !== right;
|
|
1232
|
+
// See `looselyEqual`: the specification's coercions, none of the
|
|
1233
|
+
// operand's own code.
|
|
1234
|
+
case "==":
|
|
1235
|
+
return looselyEqual(left, right);
|
|
1236
|
+
case "!=":
|
|
1237
|
+
return !looselyEqual(left, right);
|
|
1238
|
+
default:
|
|
1239
|
+
throw new UnsafeEvaluatorSource(`unsupported operator '${node.operator}'`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
case "arrow": {
|
|
1244
|
+
const params = node.params;
|
|
1245
|
+
const body = node.body;
|
|
1246
|
+
return (...args: unknown[]): unknown => {
|
|
1247
|
+
budget.step();
|
|
1248
|
+
const scope: Scope = new Map();
|
|
1249
|
+
params.forEach((param, index) => scope.set(param, args[index]));
|
|
1250
|
+
return evaluate(body, [...scopes, scope], budget);
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export interface CompiledExpression {
|
|
1257
|
+
(globals: Record<string, unknown>): unknown;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Parse and validate `source`, returning a function that evaluates it.
|
|
1262
|
+
*
|
|
1263
|
+
* The parse happens once, up front, so unsafe or malformed source is rejected
|
|
1264
|
+
* before anything runs — and the returned function contains no `eval`, no
|
|
1265
|
+
* `Function`, and no reachable path to either.
|
|
1266
|
+
*/
|
|
1267
|
+
export function compileExpression(
|
|
1268
|
+
source: string,
|
|
1269
|
+
options: { fieldName: string; maximumBytes: number; globalNames: readonly string[] },
|
|
1270
|
+
): CompiledExpression {
|
|
1271
|
+
if (typeof source !== "string" || source.trim() === "") {
|
|
1272
|
+
fail(options.fieldName, "must not be empty");
|
|
1273
|
+
}
|
|
1274
|
+
const size = Buffer.byteLength(source, "utf8");
|
|
1275
|
+
if (size > options.maximumBytes) {
|
|
1276
|
+
fail(options.fieldName, `exceeds ${options.maximumBytes} bytes`);
|
|
1277
|
+
}
|
|
1278
|
+
const globals = new Set(options.globalNames);
|
|
1279
|
+
const ast = Parser.parse(source, options.fieldName, globals);
|
|
1280
|
+
|
|
1281
|
+
return (values: Record<string, unknown>): unknown => {
|
|
1282
|
+
const scope: Scope = new Map(Object.entries(values));
|
|
1283
|
+
// Namespace globals are bound per call rather than captured, so a future
|
|
1284
|
+
// change that made one of them mutable could not leak across evaluations.
|
|
1285
|
+
for (const name of Object.keys(NAMESPACES)) {
|
|
1286
|
+
if (globals.has(name)) scope.set(name, NAMESPACES[name]);
|
|
1287
|
+
}
|
|
1288
|
+
return evaluate(ast, [scope], new Budget());
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export { METHOD_ATTRS, FORBIDDEN_KEYS, NAMESPACE_MEMBERS };
|