failproofai 1.0.1 → 1.0.2-beta.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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- 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 +2 -2
- 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 +2 -2
- 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 +3 -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 +5 -4
- 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 +2 -2
- 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 +4 -3
- 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 +4 -4
- 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 +4 -4
- 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 +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 +2 -2
- 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 +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- 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 +2 -2
- 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 +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 +2 -2
- 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 +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- 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/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.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_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware.js +1 -1
- 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 +71 -27
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
- package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
- package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
- package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +153 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -14
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +29 -22
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +145 -11
- package/dist/cli.mjs +4482 -2205
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2399 -1978
- package/package.json +29 -22
- package/policy-pack/SHA256SUMS +2 -0
- package/policy-pack/failproofai-pack.json +732 -0
- package/policy-pack/failproofai-pack.mjs +2017 -0
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +1 -1
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +26 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +418 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +505 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +344 -0
- package/src/hooks/pack-store.ts +930 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-presets.ts +4 -2
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +570 -0
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""`docs/` is the integration surface, so it is code under test.
|
|
2
|
+
|
|
3
|
+
The guide and the runnable code live together — one directory per framework,
|
|
4
|
+
each holding the README somebody reads and the `examples/` they run:
|
|
5
|
+
|
|
6
|
+
docs/
|
|
7
|
+
README.md the index
|
|
8
|
+
_shared/ cosmetics for the examples; never part of the SDK
|
|
9
|
+
manual/ no framework, raw scopes + event.*
|
|
10
|
+
langgraph/ crewai/ llama_index/ pydantic_ai/
|
|
11
|
+
README.md the guide
|
|
12
|
+
examples/*.py runnable, and run before shipping
|
|
13
|
+
|
|
14
|
+
Nothing here checks prose. It checks the three ways a docs tree rots without
|
|
15
|
+
anybody noticing: a framework gains an adapter and never gains a page, a page or
|
|
16
|
+
an example names an API that no longer exists, and an example drifts into
|
|
17
|
+
teaching the manual identity path the scopes replaced.
|
|
18
|
+
|
|
19
|
+
The examples themselves need a framework and an API key, so they cannot run in
|
|
20
|
+
unit CI — which is exactly why they need a guard that does not.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import ast
|
|
25
|
+
import re
|
|
26
|
+
import textwrap
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
import pytest
|
|
30
|
+
|
|
31
|
+
import failproofai_sdk
|
|
32
|
+
|
|
33
|
+
DOCS = Path(failproofai_sdk.__file__).resolve().parent.parent / "docs"
|
|
34
|
+
|
|
35
|
+
#: adapter registry name -> the directory that documents it.
|
|
36
|
+
#: `langgraph` is the directory for the `langchain` adapter: it is the spelling
|
|
37
|
+
#: people search for, and the adapter serves both.
|
|
38
|
+
ADAPTER_DIRS = {
|
|
39
|
+
"langchain": "langgraph",
|
|
40
|
+
"crewai": "crewai",
|
|
41
|
+
"llama_index": "llama_index",
|
|
42
|
+
"pydantic_ai": "pydantic_ai",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#: directory -> the failproofai-sdk extra its guide must name.
|
|
46
|
+
DIR_EXTRA = {
|
|
47
|
+
"langgraph": "langgraph",
|
|
48
|
+
"crewai": "crewai",
|
|
49
|
+
"llama_index": "llamaindex",
|
|
50
|
+
"pydantic_ai": "pydantic-ai",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#: directory -> third-party modules its examples may import. `manual/` maps to
|
|
54
|
+
#: the openai client and nothing else: it is the "no framework" path, so
|
|
55
|
+
#: borrowing one would defeat the point of it.
|
|
56
|
+
DIR_IMPORTS = {
|
|
57
|
+
"langgraph": {"langchain_core", "langgraph", "langchain_openai"},
|
|
58
|
+
"crewai": {"crewai"},
|
|
59
|
+
"llama_index": {"llama_index"},
|
|
60
|
+
"pydantic_ai": {"pydantic_ai", "pydantic"},
|
|
61
|
+
"manual": {"openai"},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#: stdlib and local helpers every example may import. `_shared` is the trace
|
|
65
|
+
#: printer and is explicitly not part of the SDK surface.
|
|
66
|
+
ALWAYS_ALLOWED = {"failproofai_sdk", "_shared", "os", "sys", "json", "asyncio", "pathlib"}
|
|
67
|
+
|
|
68
|
+
ALL_DIRS = sorted(DIR_IMPORTS)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _guides() -> list[Path]:
|
|
72
|
+
"""The framework guides. `_shared/README.md` documents the trace printer,
|
|
73
|
+
which is cosmetics rather than an integration, so it is not one of these."""
|
|
74
|
+
return sorted((DOCS / d / "README.md") for d in ALL_DIRS)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _examples() -> list[Path]:
|
|
78
|
+
return sorted(p for d in ALL_DIRS for p in (DOCS / d / "examples").glob("*.py"))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
EXAMPLES = _examples()
|
|
82
|
+
EXAMPLE_IDS = [f"{p.parents[1].name}/{p.name}" for p in EXAMPLES]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _imports(tree: ast.AST) -> set[str]:
|
|
86
|
+
out: set[str] = set()
|
|
87
|
+
for node in ast.walk(tree):
|
|
88
|
+
if isinstance(node, ast.Import):
|
|
89
|
+
out |= {a.name.split(".")[0] for a in node.names}
|
|
90
|
+
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
|
|
91
|
+
out.add(node.module.split(".")[0])
|
|
92
|
+
return out
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
# Shape
|
|
97
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_the_docs_tree_exists():
|
|
101
|
+
assert DOCS.is_dir(), f"{DOCS} is missing"
|
|
102
|
+
assert (DOCS / "README.md").is_file(), "docs/README.md — the index — is missing"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_there_is_a_directory_for_every_adapter():
|
|
106
|
+
"""An adapter with no guide is one nobody will find."""
|
|
107
|
+
from failproofai_sdk.integrations import _REGISTRY
|
|
108
|
+
|
|
109
|
+
assert set(ADAPTER_DIRS) == set(_REGISTRY), (
|
|
110
|
+
f"adapters {sorted(_REGISTRY)} but guides for {sorted(ADAPTER_DIRS)}"
|
|
111
|
+
)
|
|
112
|
+
for adapter, directory in ADAPTER_DIRS.items():
|
|
113
|
+
assert (DOCS / directory).is_dir(), f"{adapter} has no docs/{directory}/"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_every_documented_directory_is_accounted_for():
|
|
117
|
+
"""A directory nobody links to is a directory nobody maintains."""
|
|
118
|
+
found = {p.name for p in DOCS.iterdir() if p.is_dir() and p.name != "_shared"}
|
|
119
|
+
assert found == set(ALL_DIRS)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.parametrize("directory", ALL_DIRS)
|
|
123
|
+
def test_a_directory_has_a_guide_and_examples(directory):
|
|
124
|
+
assert (DOCS / directory / "README.md").is_file(), f"{directory}/README.md missing"
|
|
125
|
+
examples = list((DOCS / directory / "examples").glob("*.py"))
|
|
126
|
+
assert examples, f"{directory}/examples/ has no runnable code"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.parametrize("directory", ALL_DIRS)
|
|
130
|
+
def test_a_directory_has_a_quickstart(directory):
|
|
131
|
+
"""`quickstart.py` is the fixed entry point every guide links to."""
|
|
132
|
+
assert (DOCS / directory / "examples" / "quickstart.py").is_file()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.mark.parametrize("directory", ALL_DIRS)
|
|
136
|
+
def test_a_directory_has_more_than_a_quickstart(directory):
|
|
137
|
+
"""A quickstart alone never shows a multi-event flow, which is the point."""
|
|
138
|
+
assert len(list((DOCS / directory / "examples").glob("*.py"))) >= 2
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_the_index_links_to_every_directory():
|
|
142
|
+
index = (DOCS / "README.md").read_text(encoding="utf-8")
|
|
143
|
+
for directory in ALL_DIRS:
|
|
144
|
+
assert f"{directory}/" in index, f"docs/README.md never links to {directory}/"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
# The guides
|
|
149
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.mark.parametrize("guide", _guides(), ids=lambda p: p.parent.name)
|
|
153
|
+
def test_a_guide_only_names_api_this_package_exports(guide):
|
|
154
|
+
"""A rename in the SDK must break this test, not somebody's copy-paste."""
|
|
155
|
+
text = guide.read_text(encoding="utf-8")
|
|
156
|
+
referenced = set(re.findall(r"failproofai_sdk\.([a-z_]+)\s*\(", text)) - {"event"}
|
|
157
|
+
missing = {name for name in referenced if not hasattr(failproofai_sdk, name)}
|
|
158
|
+
assert not missing, f"{guide.parent.name} names failproofai_sdk.{missing}, which does not exist"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@pytest.mark.parametrize("guide", _guides(), ids=lambda p: p.parent.name)
|
|
162
|
+
def test_a_guide_only_names_event_methods_that_exist(guide):
|
|
163
|
+
text = guide.read_text(encoding="utf-8")
|
|
164
|
+
referenced = set(re.findall(r"failproofai_sdk\.event\.([a-z_]+)\s*\(", text))
|
|
165
|
+
missing = {n for n in referenced if not hasattr(failproofai_sdk.event, n)}
|
|
166
|
+
assert not missing, f"{guide.parent.name} names event.{missing}, which does not exist"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.mark.parametrize("guide", _guides() + [DOCS / "README.md"], ids=lambda p: str(p.parent.name or "index"))
|
|
170
|
+
def test_a_guide_only_names_extras_that_exist(guide):
|
|
171
|
+
"""A wrong extra in an install line is a five-minute dead end."""
|
|
172
|
+
try: # Python 3.11+
|
|
173
|
+
import tomllib
|
|
174
|
+
except ModuleNotFoundError:
|
|
175
|
+
import tomli as tomllib
|
|
176
|
+
|
|
177
|
+
manifest = tomllib.loads((DOCS.parent / "pyproject.toml").read_text(encoding="utf-8"))
|
|
178
|
+
extras = set(manifest["project"]["optional-dependencies"])
|
|
179
|
+
named = set(re.findall(r"failproofai-sdk\[([a-z-]+)\]", guide.read_text(encoding="utf-8")))
|
|
180
|
+
assert named <= extras, f"{guide} names non-existent extras: {sorted(named - extras)}"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@pytest.mark.parametrize("directory,extra", sorted(DIR_EXTRA.items()))
|
|
184
|
+
def test_a_framework_guide_names_its_own_extra(directory, extra):
|
|
185
|
+
text = (DOCS / directory / "README.md").read_text(encoding="utf-8")
|
|
186
|
+
assert "pip install" in text, f"{directory} never says how to install it"
|
|
187
|
+
assert f"failproofai-sdk[{extra}]" in text, f"{directory} does not name the {extra!r} extra"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.parametrize("directory", sorted(DIR_EXTRA))
|
|
191
|
+
def test_a_framework_guide_shows_instrument_and_a_session(directory):
|
|
192
|
+
text = (DOCS / directory / "README.md").read_text(encoding="utf-8")
|
|
193
|
+
assert "failproofai_sdk.instrument(" in text, f"{directory} never shows instrument()"
|
|
194
|
+
assert "failproofai_sdk.session()" in text, f"{directory} never shows a session"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.parametrize("guide", _guides(), ids=lambda p: p.parent.name)
|
|
198
|
+
def test_a_guide_links_to_the_examples_it_documents(guide):
|
|
199
|
+
"""A guide describing a file that does not exist is worse than no guide."""
|
|
200
|
+
text = guide.read_text(encoding="utf-8")
|
|
201
|
+
linked = set(re.findall(r"\(examples/([\w.]+\.py)\)", text))
|
|
202
|
+
on_disk = {p.name for p in (guide.parent / "examples").glob("*.py")}
|
|
203
|
+
assert linked, f"{guide.parent.name} links to none of its examples"
|
|
204
|
+
assert linked <= on_disk, f"{guide.parent.name} links to missing {sorted(linked - on_disk)}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_the_manual_guide_does_not_teach_instrument():
|
|
208
|
+
"""`manual/` is the no-adapter path; showing instrument() there is a slip.
|
|
209
|
+
|
|
210
|
+
Scoped to the part of the page that teaches the manual API — the section on
|
|
211
|
+
unsupported frameworks legitimately contrasts the two.
|
|
212
|
+
"""
|
|
213
|
+
text = (DOCS / "manual" / "README.md").read_text(encoding="utf-8")
|
|
214
|
+
body = text.split("## Instrumenting an unsupported framework")[0]
|
|
215
|
+
assert "failproofai_sdk.instrument()" not in body
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_the_index_lists_every_event_type():
|
|
219
|
+
"""The event matrix is the index's whole value; a missing row is a silent gap."""
|
|
220
|
+
from failproofai_sdk._events import EventNamespace
|
|
221
|
+
|
|
222
|
+
text = (DOCS / "README.md").read_text(encoding="utf-8")
|
|
223
|
+
methods = [
|
|
224
|
+
n for n in vars(EventNamespace)
|
|
225
|
+
if not n.startswith("_") and callable(getattr(EventNamespace, n))
|
|
226
|
+
]
|
|
227
|
+
missing = [m for m in methods if m not in text]
|
|
228
|
+
assert not missing, f"docs/README.md does not mention {missing}"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
232
|
+
# The examples
|
|
233
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
237
|
+
def test_an_example_parses(path):
|
|
238
|
+
ast.parse(path.read_text(encoding="utf-8"))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
242
|
+
def test_an_example_imports_only_its_own_framework(path):
|
|
243
|
+
"""Borrowing a second framework turns an example into an install problem."""
|
|
244
|
+
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
245
|
+
allowed = ALWAYS_ALLOWED | DIR_IMPORTS[path.parents[1].name]
|
|
246
|
+
unexpected = _imports(tree) - allowed
|
|
247
|
+
assert not unexpected, f"{path.parents[1].name}/{path.name} imports {sorted(unexpected)}"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
251
|
+
def test_an_example_calls_api_this_package_exports(path):
|
|
252
|
+
called = {
|
|
253
|
+
node.func.attr
|
|
254
|
+
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8")))
|
|
255
|
+
if isinstance(node, ast.Call)
|
|
256
|
+
and isinstance(node.func, ast.Attribute)
|
|
257
|
+
and isinstance(node.func.value, ast.Name)
|
|
258
|
+
and node.func.value.id == "failproofai_sdk"
|
|
259
|
+
}
|
|
260
|
+
assert called, f"{path.name} never calls failproofai_sdk.*"
|
|
261
|
+
missing = {c for c in called if not hasattr(failproofai_sdk, c)}
|
|
262
|
+
assert not missing, f"{path.name} calls failproofai_sdk.{missing} which does not exist"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
266
|
+
def test_an_example_does_not_thread_identity_by_hand(path):
|
|
267
|
+
"""`session_id=` everywhere teaches the path the scopes were built to remove.
|
|
268
|
+
|
|
269
|
+
Checked over the AST rather than the raw text, so an example is free to
|
|
270
|
+
*explain* `session_id=` in its docstring — which the manual quickstart has
|
|
271
|
+
to, since explaining what the scopes replace is its whole job.
|
|
272
|
+
"""
|
|
273
|
+
threaded = [
|
|
274
|
+
node
|
|
275
|
+
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8")))
|
|
276
|
+
if isinstance(node, ast.Call)
|
|
277
|
+
for kw in node.keywords
|
|
278
|
+
if kw.arg in ("session_id", "agent_id")
|
|
279
|
+
]
|
|
280
|
+
assert not threaded, (
|
|
281
|
+
f"{path.parents[1].name}/{path.name} passes session_id/agent_id by hand"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
286
|
+
def test_an_example_opens_a_session(path):
|
|
287
|
+
source = path.read_text(encoding="utf-8")
|
|
288
|
+
assert "failproofai_sdk.session()" in source, f"{path.name} never opens a session"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@pytest.mark.parametrize(
|
|
292
|
+
"path",
|
|
293
|
+
[p for p in EXAMPLES if p.parents[1].name != "manual"],
|
|
294
|
+
ids=[i for i in EXAMPLE_IDS if not i.startswith("manual/")],
|
|
295
|
+
)
|
|
296
|
+
def test_a_framework_example_instruments(path):
|
|
297
|
+
source = path.read_text(encoding="utf-8")
|
|
298
|
+
assert "failproofai_sdk.instrument()" in source, f"{path.name} never instruments"
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@pytest.mark.parametrize(
|
|
302
|
+
"path",
|
|
303
|
+
[p for p in EXAMPLES if p.parents[1].name == "manual"],
|
|
304
|
+
ids=[i for i in EXAMPLE_IDS if i.startswith("manual/")],
|
|
305
|
+
)
|
|
306
|
+
def test_a_manual_example_does_not_instrument(path):
|
|
307
|
+
"""`manual/` exists to show the path with no adapter."""
|
|
308
|
+
source = path.read_text(encoding="utf-8")
|
|
309
|
+
assert "failproofai_sdk.instrument()" not in source
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@pytest.mark.parametrize("path", EXAMPLES, ids=EXAMPLE_IDS)
|
|
313
|
+
def test_an_example_documents_how_to_run_itself(path):
|
|
314
|
+
"""The docstring is what somebody reads before running it."""
|
|
315
|
+
tree = ast.parse(path.read_text(encoding="utf-8"))
|
|
316
|
+
doc = ast.get_docstring(tree) or ""
|
|
317
|
+
assert "pip install" in doc, f"{path.name} does not say how to install it"
|
|
318
|
+
expected = f"python docs/{path.parents[1].name}/examples/{path.name}"
|
|
319
|
+
assert expected in doc, f"{path.name} does not show `{expected}`"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_the_shared_helper_is_not_importable_from_the_sdk():
|
|
323
|
+
"""`_shared` is cosmetics. If it ever became a dependency of the package,
|
|
324
|
+
the zero-dependency promise would be quietly routed around."""
|
|
325
|
+
import failproofai_sdk.integrations as integrations
|
|
326
|
+
|
|
327
|
+
for module in (failproofai_sdk, integrations):
|
|
328
|
+
source = Path(module.__file__).read_text(encoding="utf-8")
|
|
329
|
+
assert "_shared" not in source
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
333
|
+
# Documented options must exist
|
|
334
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
335
|
+
#
|
|
336
|
+
# The guides shipped naming `capture_content` for CrewAI and `session_id` for
|
|
337
|
+
# LlamaIndex. Neither adapter reads either one, so both were silently ignored:
|
|
338
|
+
# `instrument()` passes the same dict to every adapter and unknown keys are
|
|
339
|
+
# dropped by design, which means a reader following the docs got no error and no
|
|
340
|
+
# effect. Nothing checked, because the option lists were prose.
|
|
341
|
+
|
|
342
|
+
#: docs directory -> the adapter module that backs it.
|
|
343
|
+
DIR_ADAPTER = {
|
|
344
|
+
"langgraph": "langchain",
|
|
345
|
+
"crewai": "crewai",
|
|
346
|
+
"llama_index": "llama_index",
|
|
347
|
+
"pydantic_ai": "pydantic_ai",
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _adapter_options(module_name: str) -> set[str]:
|
|
352
|
+
"""Every option key an adapter actually reads, from its source.
|
|
353
|
+
|
|
354
|
+
Two spellings, because the adapters use two: `options.get("x")` for the ones
|
|
355
|
+
that read the dict directly, and a dataclass of defaults for LangChain,
|
|
356
|
+
which parses the dict into `_Options` first.
|
|
357
|
+
"""
|
|
358
|
+
import failproofai_sdk
|
|
359
|
+
|
|
360
|
+
source = (
|
|
361
|
+
Path(failproofai_sdk.__file__).resolve().parent
|
|
362
|
+
/ "integrations"
|
|
363
|
+
/ f"{module_name}.py"
|
|
364
|
+
).read_text(encoding="utf-8")
|
|
365
|
+
|
|
366
|
+
names = set(re.findall(r'options\.get\(\s*["\'](\w+)["\']', source))
|
|
367
|
+
|
|
368
|
+
tree = ast.parse(source)
|
|
369
|
+
for node in ast.walk(tree):
|
|
370
|
+
if isinstance(node, ast.ClassDef) and node.name == "_Options":
|
|
371
|
+
names |= {
|
|
372
|
+
stmt.target.id
|
|
373
|
+
for stmt in node.body
|
|
374
|
+
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name)
|
|
375
|
+
}
|
|
376
|
+
return names
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _documented_options(text: str, adapter_name: str) -> set[str]:
|
|
380
|
+
"""Keyword names inside a documented `instrument("<name>", ...)` call."""
|
|
381
|
+
documented: set[str] = set()
|
|
382
|
+
for block in re.findall(r"```python[^\n]*\n(.*?)```", text, re.S):
|
|
383
|
+
try:
|
|
384
|
+
tree = ast.parse(textwrap.dedent(block))
|
|
385
|
+
except SyntaxError: # a fragment, not a whole program
|
|
386
|
+
continue
|
|
387
|
+
for node in ast.walk(tree):
|
|
388
|
+
if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
|
|
389
|
+
continue
|
|
390
|
+
if node.func.attr != "instrument":
|
|
391
|
+
continue
|
|
392
|
+
first = node.args[0] if node.args else None
|
|
393
|
+
if isinstance(first, ast.Constant) and first.value == adapter_name:
|
|
394
|
+
documented |= {kw.arg for kw in node.keywords if kw.arg}
|
|
395
|
+
return documented
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@pytest.mark.parametrize("directory,module_name", sorted(DIR_ADAPTER.items()))
|
|
399
|
+
def test_a_guide_only_documents_options_the_adapter_reads(directory, module_name):
|
|
400
|
+
text = (DOCS / directory / "README.md").read_text(encoding="utf-8")
|
|
401
|
+
documented = _documented_options(text, module_name)
|
|
402
|
+
if not documented:
|
|
403
|
+
pytest.skip(f"{directory} documents no instrument() options")
|
|
404
|
+
real = _adapter_options(module_name)
|
|
405
|
+
invented = documented - real
|
|
406
|
+
assert not invented, (
|
|
407
|
+
f"{directory}/README.md documents instrument({module_name!r}, ...) options "
|
|
408
|
+
f"{sorted(invented)} that the adapter never reads. Unknown keys are "
|
|
409
|
+
f"dropped silently, so a reader following this gets no error and no effect. "
|
|
410
|
+
f"Real options: {sorted(real)}"
|
|
411
|
+
)
|