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,371 @@
|
|
|
1
|
+
"""The published docs site makes claims about this package, and nothing checked them.
|
|
2
|
+
|
|
3
|
+
`docs/start/integrations/` on the site is a second copy of the integration guide,
|
|
4
|
+
maintained by hand alongside `sdk/python/docs/`. Two copies of the same claims is
|
|
5
|
+
exactly the shape that drifts, and it already had: the site named
|
|
6
|
+
`capture_content` for CrewAI and `session_id` for LlamaIndex, neither of which
|
|
7
|
+
those adapters read, and told readers to verify a Pydantic AI install by printing
|
|
8
|
+
`agent.capabilities`, which raises `AttributeError`.
|
|
9
|
+
|
|
10
|
+
None of that produced an error for a reader — `instrument()` drops unknown option
|
|
11
|
+
keys by design — so only a test can catch it.
|
|
12
|
+
|
|
13
|
+
Same shape as `test_spool_contract.py` and `fp-cloud-cli/tests/test_fp_home_contract.py`:
|
|
14
|
+
read the other side's source, skip when it is genuinely absent (an installed
|
|
15
|
+
sdist has no docs site), and fail when `FAILPROOFAI_SDK_REQUIRE_CONTRACT` says the
|
|
16
|
+
repository should be there.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import ast
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import textwrap
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
|
|
28
|
+
import failproofai_sdk
|
|
29
|
+
from failproofai_sdk import _events as _events_module
|
|
30
|
+
|
|
31
|
+
# sdk/python -> sdk -> repo root -> docs/
|
|
32
|
+
_HERE = Path(failproofai_sdk.__file__).resolve().parent.parent
|
|
33
|
+
SITE = _HERE.parent.parent / "docs" / "start" / "integrations"
|
|
34
|
+
|
|
35
|
+
REQUIRE = os.environ.get("FAILPROOFAI_SDK_REQUIRE_CONTRACT", "").strip().lower() in {
|
|
36
|
+
"1",
|
|
37
|
+
"true",
|
|
38
|
+
"yes",
|
|
39
|
+
"on",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#: site page -> the adapter module it documents.
|
|
43
|
+
PAGE_ADAPTER = {
|
|
44
|
+
"langchain.mdx": "langchain",
|
|
45
|
+
"crewai.mdx": "crewai",
|
|
46
|
+
"llamaindex.mdx": "llama_index",
|
|
47
|
+
"pydantic-ai.mdx": "pydantic_ai",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _page(name: str) -> str:
|
|
52
|
+
path = SITE / name
|
|
53
|
+
if path.is_file():
|
|
54
|
+
return path.read_text(encoding="utf-8")
|
|
55
|
+
message = (
|
|
56
|
+
f"{path} is missing. In an installed sdist that is expected. In the "
|
|
57
|
+
f"repository it means the docs site moved and these claims are now "
|
|
58
|
+
f"unguarded — re-point this test rather than deleting it."
|
|
59
|
+
)
|
|
60
|
+
if REQUIRE:
|
|
61
|
+
pytest.fail(message)
|
|
62
|
+
pytest.skip(message)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _adapter_options(module_name: str) -> set[str]:
|
|
66
|
+
source = (
|
|
67
|
+
Path(failproofai_sdk.__file__).resolve().parent
|
|
68
|
+
/ "integrations"
|
|
69
|
+
/ f"{module_name}.py"
|
|
70
|
+
).read_text(encoding="utf-8")
|
|
71
|
+
names = set(re.findall(r'options\.get\(\s*["\'](\w+)["\']', source))
|
|
72
|
+
for node in ast.walk(ast.parse(source)):
|
|
73
|
+
if isinstance(node, ast.ClassDef) and node.name == "_Options":
|
|
74
|
+
names |= {
|
|
75
|
+
stmt.target.id
|
|
76
|
+
for stmt in node.body
|
|
77
|
+
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name)
|
|
78
|
+
}
|
|
79
|
+
return names
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _documented_options(text: str, adapter: str) -> set[str]:
|
|
83
|
+
out: set[str] = set()
|
|
84
|
+
for block in re.findall(r"```python[^\n]*\n(.*?)```", text, re.S):
|
|
85
|
+
try:
|
|
86
|
+
tree = ast.parse(textwrap.dedent(block))
|
|
87
|
+
except SyntaxError:
|
|
88
|
+
continue
|
|
89
|
+
for node in ast.walk(tree):
|
|
90
|
+
if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
|
|
91
|
+
continue
|
|
92
|
+
if node.func.attr != "instrument" or not node.args:
|
|
93
|
+
continue
|
|
94
|
+
first = node.args[0]
|
|
95
|
+
if isinstance(first, ast.Constant) and first.value == adapter:
|
|
96
|
+
out |= {kw.arg for kw in node.keywords if kw.arg}
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.parametrize("page,adapter", sorted(PAGE_ADAPTER.items()))
|
|
101
|
+
def test_a_site_page_only_documents_options_the_adapter_reads(page, adapter):
|
|
102
|
+
documented = _documented_options(_page(page), adapter)
|
|
103
|
+
if not documented:
|
|
104
|
+
pytest.skip(f"{page} documents no instrument() options")
|
|
105
|
+
invented = documented - _adapter_options(adapter)
|
|
106
|
+
assert not invented, (
|
|
107
|
+
f"docs/start/integrations/{page} documents instrument({adapter!r}, ...) "
|
|
108
|
+
f"options {sorted(invented)} that the adapter never reads. Unknown keys "
|
|
109
|
+
f"are dropped silently, so a reader gets no error and no effect."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
|
|
114
|
+
def test_a_site_page_only_names_event_methods_that_exist(page):
|
|
115
|
+
named = set(re.findall(r"failproofai_sdk\.event\.([a-z_]+)\s*\(", _page(page)))
|
|
116
|
+
missing = {n for n in named if not hasattr(failproofai_sdk.event, n)}
|
|
117
|
+
assert not missing, f"{page} names event.{sorted(missing)}, which does not exist"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
|
|
121
|
+
def test_a_site_page_only_names_api_this_package_exports(page):
|
|
122
|
+
named = set(re.findall(r"failproofai_sdk\.([a-z_]+)\s*\(", _page(page))) - {"event"}
|
|
123
|
+
missing = {n for n in named if not hasattr(failproofai_sdk, n)}
|
|
124
|
+
assert not missing, f"{page} names failproofai_sdk.{sorted(missing)}, which does not exist"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_the_pydantic_page_verifies_the_capability_the_way_that_works():
|
|
128
|
+
"""`agent.capabilities` raises AttributeError.
|
|
129
|
+
|
|
130
|
+
Pydantic AI merges the list you pass into a single `root_capability`, so the
|
|
131
|
+
verification snippet the page tells a reader to run has to go through it.
|
|
132
|
+
"""
|
|
133
|
+
text = _page("pydantic-ai.mdx")
|
|
134
|
+
assert "root_capability.capabilities" in text, (
|
|
135
|
+
"the pydantic page must verify through `agent.root_capability.capabilities`"
|
|
136
|
+
)
|
|
137
|
+
# Scoped to CODE, not prose: the page explains in words that
|
|
138
|
+
# `agent.capabilities` does not exist, and that sentence should stay.
|
|
139
|
+
code = "\n".join(re.findall(r"```python[^\n]*\n(.*?)```", text, re.S))
|
|
140
|
+
assert not re.search(r"(?<!root_capability\.)\bagent\.capabilities\b", code), (
|
|
141
|
+
"a code sample on the pydantic page reads `agent.capabilities`, "
|
|
142
|
+
"which raises AttributeError"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
# Structure
|
|
148
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
#
|
|
150
|
+
# Four framework pages written one at a time drifted into four different shapes:
|
|
151
|
+
# LangGraph explained session control under "Control the session", CrewAI buried
|
|
152
|
+
# the same thing as an H3 inside "Options", LlamaIndex had it in a callout, and
|
|
153
|
+
# Pydantic AI did not mention it at all. A reader who learns one page should be
|
|
154
|
+
# able to skim the next.
|
|
155
|
+
|
|
156
|
+
#: The order every framework page follows. Pages may insert their own sections
|
|
157
|
+
#: between these, but these must appear, in this order.
|
|
158
|
+
SPINE = [
|
|
159
|
+
"Install",
|
|
160
|
+
"Instrument",
|
|
161
|
+
"What gets recorded",
|
|
162
|
+
"Example",
|
|
163
|
+
"Name your spans",
|
|
164
|
+
"Control the session",
|
|
165
|
+
"Options",
|
|
166
|
+
"Common problems",
|
|
167
|
+
"Next",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _headings(page: str) -> list[str]:
|
|
172
|
+
return [h.strip() for h in re.findall(r"^## (.+)$", _page(page), re.M)]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
|
|
176
|
+
def test_a_framework_page_follows_the_shared_spine(page):
|
|
177
|
+
headings = _headings(page)
|
|
178
|
+
missing = [h for h in SPINE if h not in headings]
|
|
179
|
+
assert not missing, f"{page} is missing sections {missing}; has {headings}"
|
|
180
|
+
|
|
181
|
+
positions = [headings.index(h) for h in SPINE]
|
|
182
|
+
assert positions == sorted(positions), (
|
|
183
|
+
f"{page} orders the shared sections differently: "
|
|
184
|
+
f"{[headings[i] for i in positions]}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
|
|
189
|
+
def test_a_framework_page_is_explicit_about_human_in_the_loop(page):
|
|
190
|
+
"""Either it documents the pairs, or it says why there are none.
|
|
191
|
+
|
|
192
|
+
Silence reads as an oversight, and the reader cannot tell whether the
|
|
193
|
+
framework has no HITL surface or we simply did not map it.
|
|
194
|
+
"""
|
|
195
|
+
text = _page(page)
|
|
196
|
+
assert "## Human in the loop" in text or "human-in-the-loop pair" in text, (
|
|
197
|
+
f"{page} neither documents human-in-the-loop nor states that the "
|
|
198
|
+
f"framework has none"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# --- Keyword arguments, on every page in the directory ----------------------
|
|
203
|
+
#
|
|
204
|
+
# The tests above check that a method NAME exists. Nothing checked the keywords,
|
|
205
|
+
# and `event.*` ends in `**fields` — so a wrong one is accepted, stored as a
|
|
206
|
+
# custom field, and never populates the column the reader wanted. The docs told
|
|
207
|
+
# people to call `model_response(response=...)` for a long time; the parameter is
|
|
208
|
+
# `content`, so every reader who copied it got an event whose `content` column
|
|
209
|
+
# was empty and a stray `response` field they never asked for. Nothing raised.
|
|
210
|
+
#
|
|
211
|
+
# These also widen the net: PAGE_ADAPTER covers only the four framework pages, so
|
|
212
|
+
# `custom-agents.mdx` — which carries the most hand-written event calls on the
|
|
213
|
+
# site — was scanned by nothing at all. That is where the bug survived.
|
|
214
|
+
|
|
215
|
+
ALL_PAGES = sorted(p.name for p in SITE.glob("*.mdx")) if SITE.is_dir() else []
|
|
216
|
+
|
|
217
|
+
# Custom fields are legal, and the adapters namespace theirs. A bare unknown
|
|
218
|
+
# keyword on a documented call is the typo case.
|
|
219
|
+
#
|
|
220
|
+
# Two exemptions, both deliberate. `fw_*` is the documented namespace for a
|
|
221
|
+
# framework's own metadata. And `_PROMOTED_NUMERIC` names the keys ingest lifts
|
|
222
|
+
# into real columns — `duration_ms` on `model_response` is the one the docs
|
|
223
|
+
# actively tell you to pass, and it travels through `**fields` by design, so the
|
|
224
|
+
# SDK validates it there rather than declaring it a parameter. Reading the set
|
|
225
|
+
# from the SDK keeps this from drifting the moment a fourth key is promoted.
|
|
226
|
+
_CUSTOM_FIELD_PREFIX = "fw_"
|
|
227
|
+
_PROMOTED = set(_events_module._PROMOTED_NUMERIC)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _event_calls(text: str):
|
|
231
|
+
"""(method, {keywords}) for every failproofai_sdk.event.X(...) in a python block."""
|
|
232
|
+
for block in re.findall(r"```python[^\n]*\n(.*?)```", text, re.S):
|
|
233
|
+
try:
|
|
234
|
+
tree = ast.parse(textwrap.dedent(block))
|
|
235
|
+
except SyntaxError:
|
|
236
|
+
continue
|
|
237
|
+
for node in ast.walk(tree):
|
|
238
|
+
if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
|
|
239
|
+
continue
|
|
240
|
+
value = node.func.value
|
|
241
|
+
if not (isinstance(value, ast.Attribute) and value.attr == "event"):
|
|
242
|
+
continue
|
|
243
|
+
yield node.func.attr, {kw.arg for kw in node.keywords if kw.arg}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@pytest.mark.parametrize("page", ALL_PAGES)
|
|
247
|
+
def test_every_documented_event_call_uses_real_keywords(page):
|
|
248
|
+
import inspect
|
|
249
|
+
|
|
250
|
+
bad = []
|
|
251
|
+
for method, keywords in _event_calls(_page(page)):
|
|
252
|
+
fn = getattr(failproofai_sdk.event, method, None)
|
|
253
|
+
if fn is None:
|
|
254
|
+
continue # the name test above owns this failure
|
|
255
|
+
real = set(inspect.signature(fn).parameters) - {"fields"}
|
|
256
|
+
for kw in sorted(keywords - real):
|
|
257
|
+
if kw.startswith(_CUSTOM_FIELD_PREFIX) or kw in _PROMOTED:
|
|
258
|
+
continue
|
|
259
|
+
bad.append(f"event.{method}({kw}=...)")
|
|
260
|
+
assert not bad, (
|
|
261
|
+
f"docs/start/integrations/{page} passes keywords that are not parameters: "
|
|
262
|
+
f"{bad}. `event.*` ends in **fields, so these are accepted silently and "
|
|
263
|
+
f"stored as custom fields instead of filling the column the reader wanted."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_the_keyword_scan_actually_finds_calls():
|
|
268
|
+
"""Otherwise a change to the block format makes every page above pass vacuously."""
|
|
269
|
+
found = sum(len(list(_event_calls(_page(p)))) for p in ALL_PAGES)
|
|
270
|
+
assert found >= 10, f"only {found} event calls found across {len(ALL_PAGES)} pages"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
274
|
+
# Claims that are true of ONE adapter and were written as if universal
|
|
275
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
#: The content-capture option each adapter actually reads. `None` means the
|
|
278
|
+
#: adapter has no content switch at all, which is a fact worth stating rather
|
|
279
|
+
#: than an omission — CrewAI's only option is `session_id`, so
|
|
280
|
+
#: `instrument("crewai", capture_content=False)` raises nothing and records
|
|
281
|
+
#: everything.
|
|
282
|
+
CONTENT_OPTION = {
|
|
283
|
+
"langchain": "capture_content",
|
|
284
|
+
"pydantic_ai": "capture_content",
|
|
285
|
+
"llama_index": "capture_messages",
|
|
286
|
+
"crewai": None,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _adapter_source(name: str) -> str:
|
|
291
|
+
return (
|
|
292
|
+
Path(failproofai_sdk.__file__).resolve().parent / "integrations" / f"{name}.py"
|
|
293
|
+
).read_text(encoding="utf-8")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@pytest.mark.parametrize("adapter,option", sorted(CONTENT_OPTION.items(), key=lambda kv: kv[0]))
|
|
297
|
+
def test_the_content_option_map_matches_what_each_adapter_reads(adapter, option):
|
|
298
|
+
"""The map above is what the cross-adapter pages are checked against.
|
|
299
|
+
|
|
300
|
+
Pinned to the source so the map cannot quietly become the stale thing.
|
|
301
|
+
"""
|
|
302
|
+
source = _adapter_source(adapter)
|
|
303
|
+
for candidate in ("capture_content", "capture_messages"):
|
|
304
|
+
reads_it = f'options.get("{candidate}"' in source
|
|
305
|
+
assert reads_it == (candidate == option), (
|
|
306
|
+
f"{adapter}.py {'reads' if reads_it else 'does not read'} {candidate!r}, "
|
|
307
|
+
f"but CONTENT_OPTION says its switch is {option!r}"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def test_no_cross_adapter_page_presents_one_adapters_option_as_universal():
|
|
312
|
+
"""`how-it-works.mdx` told every reader `capture_content=False` was THE switch.
|
|
313
|
+
|
|
314
|
+
It is read by two adapters of four. LlamaIndex spells it `capture_messages`
|
|
315
|
+
and CrewAI has none — and `instrument()` drops options an adapter does not
|
|
316
|
+
read, so `instrument("crewai", capture_content=False)` raised nothing and
|
|
317
|
+
changed nothing. A reader on regulated data shipped believing prompts and
|
|
318
|
+
completions had stopped being recorded, and `collector.redact` explicitly
|
|
319
|
+
does not apply to SDK events, so nothing was behind it.
|
|
320
|
+
|
|
321
|
+
The four per-framework pages are checked elsewhere; these are the pages that
|
|
322
|
+
speak about all of them at once and so must name the difference.
|
|
323
|
+
"""
|
|
324
|
+
root = SITE.parent.parent # docs/
|
|
325
|
+
pages = [SITE / "how-it-works.mdx", SITE / "custom-agents.mdx"]
|
|
326
|
+
checked = 0
|
|
327
|
+
for page in pages:
|
|
328
|
+
if not page.is_file():
|
|
329
|
+
continue
|
|
330
|
+
checked += 1
|
|
331
|
+
text = page.read_text(encoding="utf-8")
|
|
332
|
+
if "capture_content" not in text:
|
|
333
|
+
continue
|
|
334
|
+
# If it names one adapter's option it must name the others, or a reader
|
|
335
|
+
# applies it to a framework that ignores it.
|
|
336
|
+
assert "capture_messages" in text, (
|
|
337
|
+
f"{page.name} names `capture_content` without `capture_messages`, so a "
|
|
338
|
+
"LlamaIndex reader is told to set an option that adapter does not read"
|
|
339
|
+
)
|
|
340
|
+
assert "crewai" in text.lower(), (
|
|
341
|
+
f"{page.name} presents a content switch without saying CrewAI has none"
|
|
342
|
+
)
|
|
343
|
+
if REQUIRE:
|
|
344
|
+
assert checked, "neither cross-adapter page was found; the guard checked nothing"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_no_guide_still_says_a_float_duration_is_silently_dropped():
|
|
348
|
+
"""It raises `ValueError`, and has since `_validate_promoted_numeric` landed.
|
|
349
|
+
|
|
350
|
+
The skill files were corrected when that changed and the site reference and
|
|
351
|
+
the manual guide were not, so a reader was told the worst case was an empty
|
|
352
|
+
column and got an exception out of `event.model_response()` on their first
|
|
353
|
+
instrumented model call — outside the SDK's own try/except, so it takes down
|
|
354
|
+
the agent turn.
|
|
355
|
+
"""
|
|
356
|
+
bad = re.compile(r"(stored as null|silently nulls|silently drop\w*)", re.I)
|
|
357
|
+
roots = [
|
|
358
|
+
SITE.parent.parent / "reference" / "python-sdk.mdx",
|
|
359
|
+
Path(failproofai_sdk.__file__).resolve().parent.parent / "docs",
|
|
360
|
+
]
|
|
361
|
+
hits = []
|
|
362
|
+
for root in roots:
|
|
363
|
+
files = [root] if root.is_file() else sorted(root.rglob("*.md*")) if root.is_dir() else []
|
|
364
|
+
for path in files:
|
|
365
|
+
for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
|
366
|
+
if "duration_ms" in line and bad.search(line):
|
|
367
|
+
hits.append(f"{path.name}:{i}: {line.strip()[:110]}")
|
|
368
|
+
assert not hits, (
|
|
369
|
+
"these say a float `duration_ms` is dropped or nulled; it raises ValueError "
|
|
370
|
+
"at the call site:\n " + "\n ".join(hits)
|
|
371
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""The skill's code is instructions an agent executes, so it is code under test.
|
|
2
|
+
|
|
3
|
+
An agent reading `SKILL.md` copies these blocks into someone's real agent loop.
|
|
4
|
+
A bug in a documented snippet ships to every reader, and unlike the package it
|
|
5
|
+
is never exercised by anything — which is why it gets a guard rather than a
|
|
6
|
+
proofread.
|
|
7
|
+
"""
|
|
8
|
+
import ast
|
|
9
|
+
import re
|
|
10
|
+
import textwrap
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
SKILL = Path(__file__).resolve().parents[1] / "skill"
|
|
16
|
+
SNIPPET_FILES = sorted(SKILL.rglob("*.md"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def python_blocks(path):
|
|
20
|
+
"""Every ```python fenced block in a markdown file, dedented.
|
|
21
|
+
|
|
22
|
+
Dedented because a fenced block nested inside a list item is indented in the
|
|
23
|
+
source and is still valid Python once that common prefix is removed. Without
|
|
24
|
+
this the only way to keep a snippet testable was to hoist it out of the list
|
|
25
|
+
it belongs to — so the guard was quietly shaping the prose. `test_site_docs.py`
|
|
26
|
+
already dedents; these two scans should not disagree about what a block is.
|
|
27
|
+
"""
|
|
28
|
+
text = path.read_text(encoding="utf-8")
|
|
29
|
+
blocks = re.findall(r"```python[^\n]*\n(.*?)```", text, re.DOTALL)
|
|
30
|
+
return [textwrap.dedent(b) for b in blocks]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_there_are_snippets_to_check():
|
|
34
|
+
"""A path typo would otherwise make every test below pass vacuously."""
|
|
35
|
+
blocks = [b for p in SNIPPET_FILES for b in python_blocks(p)]
|
|
36
|
+
assert len(blocks) >= 5, f"found {len(blocks)} python blocks under {SKILL}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.parametrize("path", SNIPPET_FILES, ids=lambda p: p.name)
|
|
40
|
+
def test_every_documented_snippet_parses(path):
|
|
41
|
+
"""A snippet that does not compile is worse than no snippet."""
|
|
42
|
+
for i, block in enumerate(python_blocks(path)):
|
|
43
|
+
try:
|
|
44
|
+
ast.parse(block)
|
|
45
|
+
except SyntaxError as exc: # pragma: no cover - failure path
|
|
46
|
+
pytest.fail(f"{path.name} block {i} does not parse: {exc}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("path", SNIPPET_FILES, ids=lambda p: p.name)
|
|
50
|
+
def test_lifecycle_brackets_catch_baseexception_not_exception(path):
|
|
51
|
+
"""A cancelled tool or run must still emit its closing event.
|
|
52
|
+
|
|
53
|
+
`asyncio.CancelledError` inherits straight from `BaseException`, so
|
|
54
|
+
`except Exception` does not see it — and cancellation is the ordinary way an
|
|
55
|
+
async tool ends when a timeout fires or a caller gives up, not an exotic
|
|
56
|
+
one. The result is a `tool_use` with no `tool_result`, or an `agent_start`
|
|
57
|
+
with no `agent_end`: an orphaned event that also holds its correlation slot
|
|
58
|
+
until the cap evicts it.
|
|
59
|
+
|
|
60
|
+
Only handlers that EMIT are checked. A bare `except Exception` around the
|
|
61
|
+
emit call itself is correct and must stay — swallowing `KeyboardInterrupt`
|
|
62
|
+
there would make telemetry able to block a Ctrl-C.
|
|
63
|
+
"""
|
|
64
|
+
for i, block in enumerate(python_blocks(path)):
|
|
65
|
+
try:
|
|
66
|
+
tree = ast.parse(block)
|
|
67
|
+
except SyntaxError:
|
|
68
|
+
continue
|
|
69
|
+
for node in ast.walk(tree):
|
|
70
|
+
if not isinstance(node, ast.ExceptHandler):
|
|
71
|
+
continue
|
|
72
|
+
body = ast.unparse(ast.Module(body=node.body, type_ignores=[]))
|
|
73
|
+
emits = re.search(r"\b(_emit|event\.\w+|failproofai_sdk\.event\.\w+)\s*\(", body)
|
|
74
|
+
if not emits:
|
|
75
|
+
continue
|
|
76
|
+
caught = ast.unparse(node.type) if node.type else "<bare>"
|
|
77
|
+
assert "BaseException" in caught, (
|
|
78
|
+
f"{path.name} block {i}: `except {caught}` wraps an emit — a cancelled "
|
|
79
|
+
"tool or run would skip its closing event. Use BaseException."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# --- The prose is a contract too -------------------------------------------
|
|
84
|
+
#
|
|
85
|
+
# Everything above checks that snippets PARSE and handle cancellation. Nothing
|
|
86
|
+
# checked that what the skill SAYS is true, and three claims had gone stale
|
|
87
|
+
# undetected: a correlation bug described as current after it was fixed, a float
|
|
88
|
+
# `duration_ms` described as silently dropped after it started raising, and a
|
|
89
|
+
# verify step pointing at the pre-migration spool root. An agent following that
|
|
90
|
+
# last one looks in an empty directory and reports the integration broken.
|
|
91
|
+
#
|
|
92
|
+
# These are the checks that would have caught each class.
|
|
93
|
+
|
|
94
|
+
import failproofai_sdk
|
|
95
|
+
from failproofai_sdk import _resolver
|
|
96
|
+
|
|
97
|
+
SKILL_TEXT = {p.name: p.read_text(encoding="utf-8") for p in SNIPPET_FILES}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_the_skill_only_names_event_methods_that_exist():
|
|
101
|
+
"""A method renamed in the SDK leaves the skill teaching a call that raises."""
|
|
102
|
+
named = set()
|
|
103
|
+
for text in SKILL_TEXT.values():
|
|
104
|
+
named |= set(re.findall(r"(?:failproofai_sdk\.)?event\.([a-z_]+)\s*\(", text))
|
|
105
|
+
assert named, "no event.* calls found — the scan stopped working"
|
|
106
|
+
missing = sorted(n for n in named if not hasattr(failproofai_sdk.event, n))
|
|
107
|
+
assert not missing, f"the skill names event methods that do not exist: {missing}"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_the_skill_only_names_public_api_that_exists():
|
|
111
|
+
"""Same, for the top-level surface an agent is told to import and call."""
|
|
112
|
+
named = set(re.findall(r"failproofai_sdk\.([a-z_]+[a-z_0-9]*)\s*\(", "\n".join(SKILL_TEXT.values())))
|
|
113
|
+
named -= {"event"} # a namespace, reached as failproofai_sdk.event.<method>
|
|
114
|
+
assert named, "no failproofai_sdk.* calls found — the scan stopped working"
|
|
115
|
+
missing = sorted(n for n in named if not hasattr(failproofai_sdk, n))
|
|
116
|
+
assert not missing, f"the skill names public API that does not exist: {missing}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_the_skill_verifies_against_the_current_spool_root(monkeypatch):
|
|
120
|
+
"""The verify step must send a reader to the directory the SDK actually writes.
|
|
121
|
+
|
|
122
|
+
`~/.agenteye` is still allowed in the migration notes — that is what the
|
|
123
|
+
older `agenteye-collector` reads, and the skill has to say so. What is not
|
|
124
|
+
allowed is presenting it as the path to CHECK, which is what it did: a fresh
|
|
125
|
+
integration with no env vars writes to `~/.failproofai/custom-agents/events`,
|
|
126
|
+
so the documented `ls` found nothing and read as total failure.
|
|
127
|
+
"""
|
|
128
|
+
# The suite sets FAILPROOFAI_HOME to a sandbox (conftest), and this
|
|
129
|
+
# assertion is about the SHAPE of the default rather than where this run
|
|
130
|
+
# happens to spool — so it resolves with that pointer removed.
|
|
131
|
+
monkeypatch.delenv("FAILPROOFAI_HOME", raising=False)
|
|
132
|
+
default = str(_resolver.failproofai_custom_agents_dir())
|
|
133
|
+
assert default.endswith("/.failproofai/custom-agents"), default
|
|
134
|
+
|
|
135
|
+
skill = SKILL_TEXT["SKILL.md"]
|
|
136
|
+
assert "~/.failproofai/custom-agents/events" in skill, (
|
|
137
|
+
"SKILL.md never names the current default spool directory"
|
|
138
|
+
)
|
|
139
|
+
offenders = [
|
|
140
|
+
line.strip()
|
|
141
|
+
for line in skill.splitlines()
|
|
142
|
+
if re.search(r"(ls|cat)\b[^\n]*~/\.agenteye/events", line)
|
|
143
|
+
]
|
|
144
|
+
assert not offenders, (
|
|
145
|
+
"SKILL.md tells the reader to inspect the pre-migration spool root; "
|
|
146
|
+
f"that directory is empty on a default install: {offenders}"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_the_skill_does_not_brand_the_product_with_the_retired_name():
|
|
151
|
+
"""The H1 and the description are what an agent reads before anything else.
|
|
152
|
+
|
|
153
|
+
`agenteye` stays legal in migration notes and in the collector's own name;
|
|
154
|
+
naming the PRODUCT that is what this catches.
|
|
155
|
+
"""
|
|
156
|
+
skill = SKILL_TEXT["SKILL.md"]
|
|
157
|
+
heading = next(line for line in skill.splitlines() if line.startswith("# "))
|
|
158
|
+
assert "agenteye" not in heading.lower(), f"retired product name in the H1: {heading}"
|
|
159
|
+
|
|
160
|
+
front = skill.split("---")[1] if skill.startswith("---") else ""
|
|
161
|
+
description = re.search(r"description:.*?(?=\n[a-z_]+:|\Z)", front, re.S)
|
|
162
|
+
assert description, "SKILL.md has no frontmatter description"
|
|
163
|
+
assert "to agenteye" not in description.group(0).lower(), (
|
|
164
|
+
"the frontmatter description still says events are reported to AgentEye"
|
|
165
|
+
)
|