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,891 @@
|
|
|
1
|
+
"""Plain dataclasses mirroring the FailproofAI Cloud API shapes.
|
|
2
|
+
|
|
3
|
+
These are deliberately free of any I/O or framework dependency so the client
|
|
4
|
+
layer (and a future MCP server) can return them directly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Page(Generic[T]):
|
|
17
|
+
"""A single page of cursor-paginated results."""
|
|
18
|
+
|
|
19
|
+
items: List[T]
|
|
20
|
+
next_cursor: Optional[int] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class OrgMembership:
|
|
25
|
+
"""One org the operator belongs to, with the grants resolved for that org.
|
|
26
|
+
|
|
27
|
+
Mirrors the dashboard's ``OrgMembership`` (``dashboard/lib/types.ts``):
|
|
28
|
+
permissions are now *per org*, replacing the old flat global list.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
org_id: str
|
|
32
|
+
org_slug: str
|
|
33
|
+
org_name: str
|
|
34
|
+
permissions: List[str] = field(default_factory=list)
|
|
35
|
+
permission_set: Optional[str] = None
|
|
36
|
+
#: Operator-managed per-org feature flags (e.g. ``demo``). Empty when the org
|
|
37
|
+
#: has none, and also empty against a server predating the field — the two are
|
|
38
|
+
#: indistinguishable here by design, because every consumer treats "no flags"
|
|
39
|
+
#: and "flags unknown" the same way.
|
|
40
|
+
feature_flags: List[str] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_dict(cls, d: Dict[str, Any]) -> "OrgMembership":
|
|
44
|
+
return cls(
|
|
45
|
+
org_id=str(d.get("org_id", "")),
|
|
46
|
+
org_slug=str(d.get("org_slug", "")),
|
|
47
|
+
org_name=str(d.get("org_name", "")),
|
|
48
|
+
permissions=list(d.get("permissions") or []),
|
|
49
|
+
permission_set=d.get("permission_set"),
|
|
50
|
+
# from_dict is an explicit allowlist: a field absent from it is
|
|
51
|
+
# dropped silently, so `fp whoami --json` would omit the key
|
|
52
|
+
# entirely rather than report an empty set.
|
|
53
|
+
feature_flags=list(d.get("feature_flags") or []),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class SessionUser:
|
|
59
|
+
"""The authenticated operator. Multi-tenant: permissions live per-membership.
|
|
60
|
+
|
|
61
|
+
The server dropped the flat ``permissions`` field (``dashboard/lib/session.ts``);
|
|
62
|
+
a user's effective grants depend on the *active org*. ``is_instance_admin`` may
|
|
63
|
+
browse orgs without a membership but gets no data permissions there.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
id: str
|
|
67
|
+
email: str
|
|
68
|
+
is_instance_admin: bool = False
|
|
69
|
+
memberships: List[OrgMembership] = field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, d: Dict[str, Any]) -> "SessionUser":
|
|
73
|
+
return cls(
|
|
74
|
+
id=str(d.get("id", "")),
|
|
75
|
+
email=str(d.get("email", "")),
|
|
76
|
+
is_instance_admin=bool(d.get("is_instance_admin", False)),
|
|
77
|
+
memberships=[OrgMembership.from_dict(m) for m in (d.get("memberships") or [])],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def membership(self, org_slug: Optional[str]) -> Optional["OrgMembership"]:
|
|
81
|
+
if not org_slug:
|
|
82
|
+
return None
|
|
83
|
+
for m in self.memberships:
|
|
84
|
+
if m.org_slug == org_slug:
|
|
85
|
+
return m
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def permissions_for(self, org_slug: Optional[str]) -> List[str]:
|
|
89
|
+
m = self.membership(org_slug)
|
|
90
|
+
return list(m.permissions) if m else []
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def org_slugs(self) -> List[str]:
|
|
94
|
+
return [m.org_slug for m in self.memberships if m.org_slug]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class AgentEvent:
|
|
99
|
+
"""One event row. Two server sources feed this model:
|
|
100
|
+
|
|
101
|
+
* ``GET /api/events`` (full) — carries the fat ``payload`` column. Used only by the
|
|
102
|
+
opt-in heavy path (``events --full`` / ``--fields payload``).
|
|
103
|
+
* ``GET /api/events/summary`` (light) — payload-FREE. Carries the server-precomputed
|
|
104
|
+
``summary`` / ``is_error`` plus the promoted ``error_type`` / ``output_tokens``
|
|
105
|
+
columns. The default ``events`` view and all of ``errors`` use this, so their
|
|
106
|
+
responses never include the fat payload (a free-text search may still scan it
|
|
107
|
+
server-side).
|
|
108
|
+
|
|
109
|
+
Fields absent on one source default cleanly (``payload`` → ``{}`` on light rows;
|
|
110
|
+
``summary``/``is_error``/… → empty on full rows), so a single model serves both.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
id: int
|
|
114
|
+
session_id: str
|
|
115
|
+
agent_id: str
|
|
116
|
+
event_type: str
|
|
117
|
+
ts: str
|
|
118
|
+
payload: Dict[str, Any] = field(default_factory=dict)
|
|
119
|
+
environment: str = ""
|
|
120
|
+
# Light-feed columns (GET /events/summary) — server-precomputed, never derived from
|
|
121
|
+
# payload client-side.
|
|
122
|
+
summary: str = ""
|
|
123
|
+
is_error: bool = False
|
|
124
|
+
error_type: Optional[str] = None
|
|
125
|
+
output_tokens: Optional[int] = None
|
|
126
|
+
# Context-window checker: present on BOTH feeds (null for non-model events / unknown
|
|
127
|
+
# models). Now surfaced in --json instead of being silently dropped.
|
|
128
|
+
context_window: Optional[int] = None
|
|
129
|
+
context_fill: Optional[float] = None
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_dict(cls, d: Dict[str, Any]) -> "AgentEvent":
|
|
133
|
+
try:
|
|
134
|
+
event_id = int(d.get("id", 0))
|
|
135
|
+
except (TypeError, ValueError):
|
|
136
|
+
event_id = 0 # tolerate a null/non-numeric id rather than crashing the render
|
|
137
|
+
return cls(
|
|
138
|
+
id=event_id,
|
|
139
|
+
session_id=str(d.get("session_id", "")),
|
|
140
|
+
agent_id=str(d.get("agent_id", "")),
|
|
141
|
+
event_type=str(d.get("event_type", "")),
|
|
142
|
+
ts=str(d.get("ts", "")),
|
|
143
|
+
payload=d.get("payload") or {},
|
|
144
|
+
environment=str(d.get("environment", "")),
|
|
145
|
+
summary=str(d.get("summary") or ""),
|
|
146
|
+
is_error=bool(d.get("is_error", False)),
|
|
147
|
+
error_type=d.get("error_type"),
|
|
148
|
+
output_tokens=d.get("output_tokens"),
|
|
149
|
+
context_window=d.get("context_window"),
|
|
150
|
+
context_fill=d.get("context_fill"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class Evaluation:
|
|
156
|
+
id: str # evaluation_id is a UUID (Postgres), not an integer
|
|
157
|
+
session_id: str
|
|
158
|
+
agent_id: str
|
|
159
|
+
environment: str
|
|
160
|
+
status: str
|
|
161
|
+
scores: Optional[Dict[str, float]] = None
|
|
162
|
+
reasoning: Optional[Dict[str, str]] = None
|
|
163
|
+
summary: Optional[str] = None
|
|
164
|
+
error: Optional[str] = None
|
|
165
|
+
attempt_count: int = 0
|
|
166
|
+
duration_ms: Optional[int] = None
|
|
167
|
+
completed_at: str = ""
|
|
168
|
+
created_at: str = ""
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Evaluation":
|
|
172
|
+
return cls(
|
|
173
|
+
id=str(d.get("id", "")),
|
|
174
|
+
session_id=str(d.get("session_id", "")),
|
|
175
|
+
agent_id=str(d.get("agent_id", "")),
|
|
176
|
+
environment=str(d.get("environment", "")),
|
|
177
|
+
status=str(d.get("status", "")),
|
|
178
|
+
scores=d.get("scores"),
|
|
179
|
+
reasoning=d.get("reasoning"),
|
|
180
|
+
summary=d.get("summary"),
|
|
181
|
+
error=d.get("error"),
|
|
182
|
+
attempt_count=int(d.get("attempt_count", 0)),
|
|
183
|
+
duration_ms=d.get("duration_ms"),
|
|
184
|
+
completed_at=str(d.get("completed_at", "")),
|
|
185
|
+
created_at=str(d.get("created_at", "")),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class Session:
|
|
191
|
+
"""One agent run — a row from the dashboard ``/api/sessions`` endpoint (the same
|
|
192
|
+
source the dashboard's sessions page uses), so the CLI's filters match it exactly.
|
|
193
|
+
|
|
194
|
+
A session's terminal **evaluation** (if any) arrives nested under
|
|
195
|
+
``latest_evaluation``. For backward compatibility with the eval-shaped renderer and
|
|
196
|
+
with ``--json`` / ``--fields`` consumers, ``status`` and ``scores`` are **flattened
|
|
197
|
+
up** to the top level from that nested object (the full nested object is preserved as
|
|
198
|
+
``latest_evaluation`` for completeness). A session that was never evaluated has an
|
|
199
|
+
empty ``status``/``scores`` and ``latest_evaluation = None``.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
session_id: str
|
|
203
|
+
agent_id: str
|
|
204
|
+
environment: str
|
|
205
|
+
# Flattened up from latest_evaluation (back-compat: top-level status/scores).
|
|
206
|
+
status: str = ""
|
|
207
|
+
scores: Optional[Dict[str, float]] = None
|
|
208
|
+
# Full roster of every agent that ran in this session, server-sorted by
|
|
209
|
+
# event_count desc — ``[{"agent_id": str, "event_count": int}, ...]``.
|
|
210
|
+
# ``agent_id`` above is the root (first agent_start); ``agents`` is the whole
|
|
211
|
+
# cast. ``None`` on older servers that predate the multi-agent roster.
|
|
212
|
+
agents: Optional[List[Dict[str, Any]]] = None
|
|
213
|
+
# Session-level fields.
|
|
214
|
+
event_count: int = 0
|
|
215
|
+
started_at: str = ""
|
|
216
|
+
last_event_at: str = ""
|
|
217
|
+
first_event_id: Optional[int] = None
|
|
218
|
+
last_event_id: Optional[int] = None
|
|
219
|
+
# The full terminal evaluation (or None if the session was never evaluated).
|
|
220
|
+
latest_evaluation: Optional[Dict[str, Any]] = None
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Session":
|
|
224
|
+
le = d.get("latest_evaluation") or {}
|
|
225
|
+
return cls(
|
|
226
|
+
session_id=str(d.get("session_id", "")),
|
|
227
|
+
agent_id=str(d.get("agent_id", "")),
|
|
228
|
+
environment=str(d.get("environment", "")),
|
|
229
|
+
status=str(le.get("status", "")), # flattened up for the renderer + back-compat
|
|
230
|
+
scores=le.get("scores"), # flattened up
|
|
231
|
+
agents=d.get("agents"), # full agent roster (list of {agent_id, event_count})
|
|
232
|
+
event_count=int(d.get("event_count", 0)),
|
|
233
|
+
started_at=str(d.get("started_at", "")),
|
|
234
|
+
last_event_at=str(d.get("last_event_at", "")),
|
|
235
|
+
first_event_id=d.get("first_event_id"),
|
|
236
|
+
last_event_id=d.get("last_event_id"),
|
|
237
|
+
latest_evaluation=d.get("latest_evaluation"),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass
|
|
242
|
+
class ApiKey:
|
|
243
|
+
id: str
|
|
244
|
+
name: str
|
|
245
|
+
permissions: List[str] = field(default_factory=list)
|
|
246
|
+
created_at: str = ""
|
|
247
|
+
revoked_at: Optional[str] = None
|
|
248
|
+
# The server sends this; `from_dict` is an allowlist, so omitting it here
|
|
249
|
+
# silently dropped it and `fp keys list` showed an expired key as
|
|
250
|
+
# active. None = never expires.
|
|
251
|
+
expires_at: Optional[str] = None
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def from_dict(cls, d: Dict[str, Any]) -> "ApiKey":
|
|
255
|
+
return cls(
|
|
256
|
+
id=str(d.get("id", "")),
|
|
257
|
+
name=str(d.get("name", "")),
|
|
258
|
+
permissions=list(d.get("permissions") or []),
|
|
259
|
+
created_at=str(d.get("created_at", "")),
|
|
260
|
+
revoked_at=d.get("revoked_at"),
|
|
261
|
+
expires_at=d.get("expires_at"),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass
|
|
266
|
+
class SavedQuery:
|
|
267
|
+
id: str
|
|
268
|
+
name: str
|
|
269
|
+
description: str = ""
|
|
270
|
+
sql_text: str = ""
|
|
271
|
+
params: List[Dict[str, Any]] = field(default_factory=list)
|
|
272
|
+
created_by: Optional[str] = None
|
|
273
|
+
created_at: str = ""
|
|
274
|
+
updated_at: str = ""
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def from_dict(cls, d: Dict[str, Any]) -> "SavedQuery":
|
|
278
|
+
return cls(
|
|
279
|
+
id=str(d.get("id", "")),
|
|
280
|
+
name=str(d.get("name", "")),
|
|
281
|
+
description=str(d.get("description", "")),
|
|
282
|
+
sql_text=str(d.get("sql_text", "")),
|
|
283
|
+
params=list(d.get("params") or []),
|
|
284
|
+
created_by=d.get("created_by"),
|
|
285
|
+
created_at=str(d.get("created_at", "")),
|
|
286
|
+
updated_at=str(d.get("updated_at", "")),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@dataclass
|
|
291
|
+
class QueryResult:
|
|
292
|
+
columns: List[Dict[str, str]] = field(default_factory=list)
|
|
293
|
+
rows: List[List[Any]] = field(default_factory=list)
|
|
294
|
+
truncated: bool = False
|
|
295
|
+
elapsed_ms: int = 0
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def from_dict(cls, d: Dict[str, Any]) -> "QueryResult":
|
|
299
|
+
return cls(
|
|
300
|
+
columns=list(d.get("columns") or []),
|
|
301
|
+
rows=list(d.get("rows") or []),
|
|
302
|
+
truncated=bool(d.get("truncated", False)),
|
|
303
|
+
elapsed_ms=int(d.get("elapsed_ms", 0)),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@dataclass
|
|
308
|
+
class DashboardUser:
|
|
309
|
+
id: str
|
|
310
|
+
email: str
|
|
311
|
+
permissions: List[str] = field(default_factory=list)
|
|
312
|
+
permission_set: Optional[str] = None
|
|
313
|
+
permission_added: List[str] = field(default_factory=list)
|
|
314
|
+
permission_removed: List[str] = field(default_factory=list)
|
|
315
|
+
disabled_at: Optional[str] = None
|
|
316
|
+
is_protected: bool = False
|
|
317
|
+
created_at: str = ""
|
|
318
|
+
updated_at: str = ""
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def from_dict(cls, d: Dict[str, Any]) -> "DashboardUser":
|
|
322
|
+
return cls(
|
|
323
|
+
id=str(d.get("id", "")),
|
|
324
|
+
email=str(d.get("email", "")),
|
|
325
|
+
permissions=list(d.get("permissions") or []),
|
|
326
|
+
permission_set=d.get("permission_set"),
|
|
327
|
+
permission_added=list(d.get("permission_added") or []),
|
|
328
|
+
permission_removed=list(d.get("permission_removed") or []),
|
|
329
|
+
disabled_at=d.get("disabled_at"),
|
|
330
|
+
is_protected=bool(d.get("is_protected", False)),
|
|
331
|
+
created_at=str(d.get("created_at", "")),
|
|
332
|
+
updated_at=str(d.get("updated_at", "")),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@dataclass
|
|
337
|
+
class SettingRow:
|
|
338
|
+
key: str
|
|
339
|
+
value: Any = None
|
|
340
|
+
updated_at: str = ""
|
|
341
|
+
updated_by: Optional[str] = None
|
|
342
|
+
scope: Optional[str] = None
|
|
343
|
+
schema: Optional[Dict[str, Any]] = None
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def from_dict(cls, d: Dict[str, Any]) -> "SettingRow":
|
|
347
|
+
return cls(
|
|
348
|
+
key=str(d.get("key", "")),
|
|
349
|
+
value=d.get("value"),
|
|
350
|
+
updated_at=str(d.get("updated_at", "")),
|
|
351
|
+
updated_by=d.get("updated_by"),
|
|
352
|
+
scope=d.get("scope"),
|
|
353
|
+
schema=d.get("schema"),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class Alert:
|
|
359
|
+
id: str
|
|
360
|
+
name: str
|
|
361
|
+
description: Optional[str] = None
|
|
362
|
+
enabled: bool = True
|
|
363
|
+
trigger_kind: str = ""
|
|
364
|
+
trigger_spec: Dict[str, Any] = field(default_factory=dict)
|
|
365
|
+
min_breaches: int = 1
|
|
366
|
+
eval_window: int = 1
|
|
367
|
+
eval_interval_secs: int = 0
|
|
368
|
+
severity: str = ""
|
|
369
|
+
channels: List[Dict[str, Any]] = field(default_factory=list)
|
|
370
|
+
created_by: str = ""
|
|
371
|
+
created_at: str = ""
|
|
372
|
+
updated_at: str = ""
|
|
373
|
+
last_attempted_at: Optional[str] = None
|
|
374
|
+
open_incidents: int = 0
|
|
375
|
+
|
|
376
|
+
@classmethod
|
|
377
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Alert":
|
|
378
|
+
return cls(
|
|
379
|
+
id=str(d.get("id", "")),
|
|
380
|
+
name=str(d.get("name", "")),
|
|
381
|
+
description=d.get("description"),
|
|
382
|
+
enabled=bool(d.get("enabled", True)),
|
|
383
|
+
trigger_kind=str(d.get("trigger_kind", "")),
|
|
384
|
+
trigger_spec=d.get("trigger_spec") or {},
|
|
385
|
+
min_breaches=int(d.get("min_breaches", 1)),
|
|
386
|
+
eval_window=int(d.get("eval_window", 1)),
|
|
387
|
+
eval_interval_secs=int(d.get("eval_interval_secs", 0)),
|
|
388
|
+
severity=str(d.get("severity", "")),
|
|
389
|
+
channels=list(d.get("channels") or []),
|
|
390
|
+
created_by=str(d.get("created_by", "")),
|
|
391
|
+
created_at=str(d.get("created_at", "")),
|
|
392
|
+
updated_at=str(d.get("updated_at", "")),
|
|
393
|
+
last_attempted_at=d.get("last_attempted_at"),
|
|
394
|
+
open_incidents=int(d.get("open_incidents", 0)),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@dataclass
|
|
399
|
+
class Incident:
|
|
400
|
+
id: str
|
|
401
|
+
#: Short identifying line. Present on every issue since the issues redesign;
|
|
402
|
+
#: `alert_name` is only set for the minority that have a parent alert, so
|
|
403
|
+
#: this is the column that actually distinguishes rows.
|
|
404
|
+
title: Optional[str] = None
|
|
405
|
+
#: How the issue came to exist: 'manual' | 'alert' | 'audit'.
|
|
406
|
+
source: Optional[str] = None
|
|
407
|
+
#: For source='audit', the audit finding this issue was opened from.
|
|
408
|
+
source_finding_id: Optional[str] = None
|
|
409
|
+
alert_id: Optional[str] = None
|
|
410
|
+
alert_name: Optional[str] = None
|
|
411
|
+
alert_severity: str = ""
|
|
412
|
+
trigger_kind: Optional[str] = None
|
|
413
|
+
state: str = ""
|
|
414
|
+
opened_at: str = ""
|
|
415
|
+
last_breach_at: str = ""
|
|
416
|
+
acknowledged_at: Optional[str] = None
|
|
417
|
+
acknowledged_by: Optional[str] = None
|
|
418
|
+
assignees: List[str] = field(default_factory=list)
|
|
419
|
+
resolved_at: Optional[str] = None
|
|
420
|
+
breach_value: Optional[float] = None
|
|
421
|
+
breach_summary: Optional[str] = None
|
|
422
|
+
evidence: Optional[Dict[str, Any]] = None
|
|
423
|
+
notifications: Optional[List[Dict[str, Any]]] = None
|
|
424
|
+
subscribers: Optional[List[Dict[str, Any]]] = None
|
|
425
|
+
comments: Optional[List[Dict[str, Any]]] = None
|
|
426
|
+
activity: Optional[List[Dict[str, Any]]] = None
|
|
427
|
+
|
|
428
|
+
@classmethod
|
|
429
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Incident":
|
|
430
|
+
return cls(
|
|
431
|
+
id=str(d.get("id", "")),
|
|
432
|
+
title=d.get("title"),
|
|
433
|
+
source=d.get("source"),
|
|
434
|
+
source_finding_id=d.get("source_finding_id"),
|
|
435
|
+
alert_id=d.get("alert_id"),
|
|
436
|
+
alert_name=d.get("alert_name"),
|
|
437
|
+
alert_severity=str(d.get("alert_severity", "")),
|
|
438
|
+
trigger_kind=d.get("trigger_kind"),
|
|
439
|
+
state=str(d.get("state", "")),
|
|
440
|
+
opened_at=str(d.get("opened_at", "")),
|
|
441
|
+
last_breach_at=str(d.get("last_breach_at", "")),
|
|
442
|
+
acknowledged_at=d.get("acknowledged_at"),
|
|
443
|
+
acknowledged_by=d.get("acknowledged_by"),
|
|
444
|
+
assignees=list(d.get("assignees") or []),
|
|
445
|
+
resolved_at=d.get("resolved_at"),
|
|
446
|
+
breach_value=d.get("breach_value"),
|
|
447
|
+
breach_summary=d.get("breach_summary"),
|
|
448
|
+
evidence=d.get("evidence"),
|
|
449
|
+
notifications=d.get("notifications"),
|
|
450
|
+
subscribers=d.get("subscribers"),
|
|
451
|
+
comments=d.get("comments"),
|
|
452
|
+
activity=d.get("activity"),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@dataclass
|
|
457
|
+
class IncidentComment:
|
|
458
|
+
id: str
|
|
459
|
+
incident_id: str = ""
|
|
460
|
+
author_email: str = ""
|
|
461
|
+
body: Optional[str] = None
|
|
462
|
+
created_at: str = ""
|
|
463
|
+
edited_at: Optional[str] = None
|
|
464
|
+
deleted_at: Optional[str] = None
|
|
465
|
+
|
|
466
|
+
@classmethod
|
|
467
|
+
def from_dict(cls, d: Dict[str, Any]) -> "IncidentComment":
|
|
468
|
+
return cls(
|
|
469
|
+
id=str(d.get("id", "")),
|
|
470
|
+
incident_id=str(d.get("incident_id", "")),
|
|
471
|
+
author_email=str(d.get("author_email", "")),
|
|
472
|
+
body=d.get("body"),
|
|
473
|
+
created_at=str(d.get("created_at", "")),
|
|
474
|
+
edited_at=d.get("edited_at"),
|
|
475
|
+
deleted_at=d.get("deleted_at"),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@dataclass
|
|
480
|
+
class IncidentSubscriber:
|
|
481
|
+
email: str
|
|
482
|
+
source: str = ""
|
|
483
|
+
subscribed_at: str = ""
|
|
484
|
+
unsubscribed_at: Optional[str] = None
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
def from_dict(cls, d: Dict[str, Any]) -> "IncidentSubscriber":
|
|
488
|
+
return cls(
|
|
489
|
+
email=str(d.get("email", "")),
|
|
490
|
+
source=str(d.get("source", "")),
|
|
491
|
+
subscribed_at=str(d.get("subscribed_at", "")),
|
|
492
|
+
unsubscribed_at=d.get("unsubscribed_at"),
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _as_int(value: Any, default: int = 0) -> int:
|
|
497
|
+
"""A JSON value → int, falling back to ``default`` on null/garbage.
|
|
498
|
+
|
|
499
|
+
``int(d.get(k, default))`` raises on an explicit ``null`` (``int(None)``) or a
|
|
500
|
+
non-numeric string, which would crash a whole render over one bad row. The audit
|
|
501
|
+
models use this so ``from_dict`` can never raise.
|
|
502
|
+
"""
|
|
503
|
+
try:
|
|
504
|
+
return int(value)
|
|
505
|
+
except (TypeError, ValueError):
|
|
506
|
+
return default
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _as_float(value: Any, default: float = 0.0) -> float:
|
|
510
|
+
"""A JSON value → float, falling back to ``default`` on null/garbage (see :func:`_as_int`)."""
|
|
511
|
+
try:
|
|
512
|
+
return float(value)
|
|
513
|
+
except (TypeError, ValueError):
|
|
514
|
+
return default
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@dataclass
|
|
518
|
+
class Audit:
|
|
519
|
+
"""One audit definition — a scheduled sweep over a window of agent activity that
|
|
520
|
+
produces **findings**.
|
|
521
|
+
|
|
522
|
+
The definition columns (schedule, window, scope, signals, LLM settings, channels) are
|
|
523
|
+
what ``audits create``/``edit`` write; the trailing fields are server-derived read-only
|
|
524
|
+
state the list/get endpoints join in (``open_findings``, the last run's status/time, and
|
|
525
|
+
the queue row's attempt timestamps). Timestamps stay raw ISO strings — the renderers
|
|
526
|
+
humanize them, ``--json`` passes them through untouched.
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
id: str
|
|
530
|
+
name: str
|
|
531
|
+
description: Optional[str] = None
|
|
532
|
+
enabled: bool = True
|
|
533
|
+
schedule_interval_secs: int = 86400
|
|
534
|
+
# Fixed phase for the schedule: runs land on `anchor + N * interval`, so a slow
|
|
535
|
+
# run or a manual trigger can't drift the cadence. Raw ISO string, like the
|
|
536
|
+
# other timestamps. Server defaults it to the next 09:00 UTC when omitted;
|
|
537
|
+
# None only for legacy rows written before the column existed.
|
|
538
|
+
schedule_anchor: Optional[str] = None
|
|
539
|
+
window_mode: str = "since_last" # 'fixed' | 'since_last'
|
|
540
|
+
lookback_window_secs: int = 604800
|
|
541
|
+
scope: Dict[str, Any] = field(default_factory=dict)
|
|
542
|
+
ignore_error_types: List[str] = field(default_factory=list)
|
|
543
|
+
llm_enabled: bool = True
|
|
544
|
+
top_k: int = 50
|
|
545
|
+
sensitivity: str = "medium" # 'low' | 'medium' | 'high'
|
|
546
|
+
channels: List[Dict[str, Any]] = field(default_factory=list)
|
|
547
|
+
created_by: str = ""
|
|
548
|
+
created_at: str = ""
|
|
549
|
+
updated_at: str = ""
|
|
550
|
+
# Server-derived, read-only (never sent back on a write).
|
|
551
|
+
open_findings: int = 0
|
|
552
|
+
last_run_status: Optional[str] = None
|
|
553
|
+
last_run_finished_at: Optional[str] = None
|
|
554
|
+
last_attempted_at: Optional[str] = None
|
|
555
|
+
next_attempt_at: Optional[str] = None
|
|
556
|
+
last_error: Optional[str] = None
|
|
557
|
+
# Operator brief appended to the analysis prompt. READ-ONLY on this model:
|
|
558
|
+
# it is written through `fp audits context set`, never as part of a
|
|
559
|
+
# definition body, so a flag-only `audits edit` (which read-merges from
|
|
560
|
+
# _audit_to_body) can never wipe it.
|
|
561
|
+
additional_context: str = ""
|
|
562
|
+
reference_url_count: int = 0
|
|
563
|
+
|
|
564
|
+
@classmethod
|
|
565
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Audit":
|
|
566
|
+
return cls(
|
|
567
|
+
id=str(d.get("id", "")),
|
|
568
|
+
name=str(d.get("name", "")),
|
|
569
|
+
description=d.get("description"),
|
|
570
|
+
enabled=bool(d.get("enabled", True)),
|
|
571
|
+
schedule_interval_secs=_as_int(d.get("schedule_interval_secs"), 86400),
|
|
572
|
+
schedule_anchor=d.get("schedule_anchor"),
|
|
573
|
+
window_mode=str(d.get("window_mode", "") or "since_last"),
|
|
574
|
+
lookback_window_secs=_as_int(d.get("lookback_window_secs"), 604800),
|
|
575
|
+
scope=d.get("scope") or {},
|
|
576
|
+
ignore_error_types=list(d.get("ignore_error_types") or []),
|
|
577
|
+
llm_enabled=bool(d.get("llm_enabled", True)),
|
|
578
|
+
top_k=_as_int(d.get("top_k"), 50),
|
|
579
|
+
sensitivity=str(d.get("sensitivity", "") or "medium"),
|
|
580
|
+
channels=list(d.get("channels") or []),
|
|
581
|
+
created_by=str(d.get("created_by", "")),
|
|
582
|
+
created_at=str(d.get("created_at", "")),
|
|
583
|
+
updated_at=str(d.get("updated_at", "")),
|
|
584
|
+
open_findings=_as_int(d.get("open_findings"), 0),
|
|
585
|
+
last_run_status=d.get("last_run_status"),
|
|
586
|
+
last_run_finished_at=d.get("last_run_finished_at"),
|
|
587
|
+
last_attempted_at=d.get("last_attempted_at"),
|
|
588
|
+
next_attempt_at=d.get("next_attempt_at"),
|
|
589
|
+
last_error=d.get("last_error"),
|
|
590
|
+
additional_context=str(d.get("additional_context", "") or ""),
|
|
591
|
+
reference_url_count=_as_int(d.get("reference_url_count"), 0),
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
@dataclass
|
|
596
|
+
class AuditRun:
|
|
597
|
+
"""One execution of an audit — the window it swept, how it ended, and what it produced.
|
|
598
|
+
|
|
599
|
+
``stats`` is an opaque per-run counter object and ``report`` the rendered summary text
|
|
600
|
+
(both may be absent on a run that failed early), so neither is parsed here.
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
id: str
|
|
604
|
+
audit_id: str = ""
|
|
605
|
+
status: str = "" # 'running' | 'succeeded' | 'failed'
|
|
606
|
+
trigger_kind: str = ""
|
|
607
|
+
window_from: str = ""
|
|
608
|
+
window_to: str = ""
|
|
609
|
+
started_at: str = ""
|
|
610
|
+
finished_at: Optional[str] = None
|
|
611
|
+
stats: Dict[str, Any] = field(default_factory=dict)
|
|
612
|
+
findings_count: int = 0
|
|
613
|
+
new_findings_count: int = 0
|
|
614
|
+
report: Optional[str] = None
|
|
615
|
+
error: Optional[str] = None
|
|
616
|
+
|
|
617
|
+
@classmethod
|
|
618
|
+
def from_dict(cls, d: Dict[str, Any]) -> "AuditRun":
|
|
619
|
+
return cls(
|
|
620
|
+
id=str(d.get("id", "")),
|
|
621
|
+
audit_id=str(d.get("audit_id", "")),
|
|
622
|
+
status=str(d.get("status", "")),
|
|
623
|
+
trigger_kind=str(d.get("trigger_kind", "")),
|
|
624
|
+
window_from=str(d.get("window_from", "")),
|
|
625
|
+
window_to=str(d.get("window_to", "")),
|
|
626
|
+
started_at=str(d.get("started_at", "")),
|
|
627
|
+
finished_at=d.get("finished_at"),
|
|
628
|
+
stats=d.get("stats") or {},
|
|
629
|
+
findings_count=_as_int(d.get("findings_count"), 0),
|
|
630
|
+
new_findings_count=_as_int(d.get("new_findings_count"), 0),
|
|
631
|
+
report=d.get("report"),
|
|
632
|
+
error=d.get("error"),
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
@dataclass
|
|
637
|
+
class AuditFinding:
|
|
638
|
+
"""One finding — a recurring pattern an audit surfaced, carried across runs by its
|
|
639
|
+
``fingerprint`` and triaged through ``status``.
|
|
640
|
+
|
|
641
|
+
``priority`` is the server's ranking score (findings arrive priority-desc);
|
|
642
|
+
``evidence``/``evidence_queries``/``scope`` are opaque blobs shown verbatim.
|
|
643
|
+
"""
|
|
644
|
+
|
|
645
|
+
id: str
|
|
646
|
+
audit_id: str = ""
|
|
647
|
+
audit_name: str = ""
|
|
648
|
+
fingerprint: str = ""
|
|
649
|
+
title: str = ""
|
|
650
|
+
category: Optional[str] = None
|
|
651
|
+
failure_type: str = ""
|
|
652
|
+
description: Optional[str] = None
|
|
653
|
+
root_cause_hypothesis: Optional[str] = None
|
|
654
|
+
severity: str = "" # 'info' | 'warning' | 'critical'
|
|
655
|
+
magnitude: Optional[str] = None # 'small' | 'medium' | 'big'
|
|
656
|
+
priority: float = 0.0
|
|
657
|
+
status: str = "" # 'open' | 'recurring' | 'resolved' | 'dismissed' | 'muted'
|
|
658
|
+
occurrences: int = 0
|
|
659
|
+
first_seen_at: str = ""
|
|
660
|
+
last_seen_at: str = ""
|
|
661
|
+
recommendation: Optional[str] = None
|
|
662
|
+
expected_impact: Optional[str] = None
|
|
663
|
+
effort: Optional[str] = None
|
|
664
|
+
evidence: Dict[str, Any] = field(default_factory=dict)
|
|
665
|
+
evidence_queries: List[Any] = field(default_factory=list)
|
|
666
|
+
scope: Dict[str, Any] = field(default_factory=dict)
|
|
667
|
+
kind: str = "" # 'improvement' | 'policy' | 'failure'
|
|
668
|
+
assigned_to: Optional[str] = None
|
|
669
|
+
# The issue this finding graduated into, or None if it never linked.
|
|
670
|
+
# Rising nulls on open/recurring findings is how you see issue_sync
|
|
671
|
+
# degrading; `from_dict` is an allowlist, so omitting it here silently
|
|
672
|
+
# drops the field rather than erroring.
|
|
673
|
+
issue_id: Optional[str] = None
|
|
674
|
+
|
|
675
|
+
@classmethod
|
|
676
|
+
def from_dict(cls, d: Dict[str, Any]) -> "AuditFinding":
|
|
677
|
+
return cls(
|
|
678
|
+
id=str(d.get("id", "")),
|
|
679
|
+
audit_id=str(d.get("audit_id", "")),
|
|
680
|
+
audit_name=str(d.get("audit_name", "")),
|
|
681
|
+
fingerprint=str(d.get("fingerprint", "")),
|
|
682
|
+
title=str(d.get("title", "")),
|
|
683
|
+
category=d.get("category"),
|
|
684
|
+
failure_type=str(d.get("failure_type", "")),
|
|
685
|
+
description=d.get("description"),
|
|
686
|
+
root_cause_hypothesis=d.get("root_cause_hypothesis"),
|
|
687
|
+
severity=str(d.get("severity", "")),
|
|
688
|
+
magnitude=d.get("magnitude"),
|
|
689
|
+
priority=_as_float(d.get("priority"), 0.0),
|
|
690
|
+
status=str(d.get("status", "")),
|
|
691
|
+
occurrences=_as_int(d.get("occurrences"), 0),
|
|
692
|
+
first_seen_at=str(d.get("first_seen_at", "")),
|
|
693
|
+
last_seen_at=str(d.get("last_seen_at", "")),
|
|
694
|
+
recommendation=d.get("recommendation"),
|
|
695
|
+
expected_impact=d.get("expected_impact"),
|
|
696
|
+
effort=d.get("effort"),
|
|
697
|
+
evidence=d.get("evidence") or {},
|
|
698
|
+
evidence_queries=list(d.get("evidence_queries") or []),
|
|
699
|
+
scope=d.get("scope") or {},
|
|
700
|
+
kind=str(d.get("kind", "")),
|
|
701
|
+
assigned_to=d.get("assigned_to"),
|
|
702
|
+
issue_id=d.get("issue_id"),
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
# ── Cloud-managed enforcement ────────────────────────────────────────────────
|
|
707
|
+
#
|
|
708
|
+
# Three nouns, and keeping them apart is the whole model. A POLICY VERSION is
|
|
709
|
+
# written; a DEPLOYMENT says which versions a MACHINE is told to run. The
|
|
710
|
+
# dashboard splits them across three pages for the same reason — authoring is a
|
|
711
|
+
# code task, deploying is a fleet decision, and observing is neither.
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@dataclass
|
|
715
|
+
class PolicyVersion:
|
|
716
|
+
"""One published version of a policy. Versions are minted, never edited."""
|
|
717
|
+
|
|
718
|
+
id: str
|
|
719
|
+
version: int
|
|
720
|
+
description: str
|
|
721
|
+
sha256: str
|
|
722
|
+
source: Optional[str]
|
|
723
|
+
created_at: str
|
|
724
|
+
created_by: Optional[str]
|
|
725
|
+
disabled: bool
|
|
726
|
+
archived: bool
|
|
727
|
+
|
|
728
|
+
@classmethod
|
|
729
|
+
def from_dict(cls, d: Dict[str, Any]) -> "PolicyVersion":
|
|
730
|
+
return cls(
|
|
731
|
+
id=str(d.get("id", "")),
|
|
732
|
+
version=_as_int(d.get("version"), 0),
|
|
733
|
+
description=str(d.get("description", "") or ""),
|
|
734
|
+
sha256=str(d.get("sha256", "") or ""),
|
|
735
|
+
source=d.get("source"),
|
|
736
|
+
created_at=str(d.get("createdAt", d.get("created_at", "")) or ""),
|
|
737
|
+
created_by=d.get("createdBy", d.get("created_by")),
|
|
738
|
+
disabled=bool(d.get("disabled", False)),
|
|
739
|
+
archived=bool(d.get("archived", False)),
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
743
|
+
"""The server's own shape. `vars()` would leak Python snake_case into a
|
|
744
|
+
contract that is camelCase everywhere else, which is a difference a
|
|
745
|
+
harness discovers at runtime rather than in review."""
|
|
746
|
+
return {
|
|
747
|
+
"id": self.id, "version": self.version, "description": self.description,
|
|
748
|
+
"sha256": self.sha256, "source": self.source, "createdAt": self.created_at,
|
|
749
|
+
"createdBy": self.created_by, "disabled": self.disabled,
|
|
750
|
+
"archived": self.archived,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
@dataclass
|
|
755
|
+
class PolicyRef:
|
|
756
|
+
"""A policy inside a deployment: which version, and how it acts.
|
|
757
|
+
|
|
758
|
+
``effect`` is ``enforce`` or ``observe``. The server defaults an omitted
|
|
759
|
+
effect to ``enforce``; the CLI always sends it explicitly so a deployment
|
|
760
|
+
read back and written again cannot silently change meaning.
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
id: str
|
|
764
|
+
version: int
|
|
765
|
+
effect: str = "enforce"
|
|
766
|
+
|
|
767
|
+
@classmethod
|
|
768
|
+
def from_dict(cls, d: Dict[str, Any]) -> "PolicyRef":
|
|
769
|
+
return cls(
|
|
770
|
+
id=str(d.get("id", "")),
|
|
771
|
+
version=_as_int(d.get("version"), 0),
|
|
772
|
+
effect=str(d.get("effect") or "enforce"),
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
776
|
+
return {"id": self.id, "version": self.version, "effect": self.effect}
|
|
777
|
+
|
|
778
|
+
@property
|
|
779
|
+
def label(self) -> str:
|
|
780
|
+
return f"{self.id}@{self.version}:{self.effect}"
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@dataclass
|
|
784
|
+
class Deployment:
|
|
785
|
+
"""What one machine is told to enforce, and which generation that is.
|
|
786
|
+
|
|
787
|
+
``deployment`` is the generation counter. It is the CLI's only defence
|
|
788
|
+
against a concurrent write: ``PUT`` is a FULL REPLACE with no server-side
|
|
789
|
+
lock, so a deploy that returns anything other than ``base + 1`` means
|
|
790
|
+
somebody else wrote between the read and the write.
|
|
791
|
+
"""
|
|
792
|
+
|
|
793
|
+
machine_id: str
|
|
794
|
+
deployment: int
|
|
795
|
+
policies: List[PolicyRef]
|
|
796
|
+
updated_at: str
|
|
797
|
+
updated_by: Optional[str]
|
|
798
|
+
|
|
799
|
+
@classmethod
|
|
800
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Deployment":
|
|
801
|
+
return cls(
|
|
802
|
+
machine_id=str(d.get("machineId", d.get("machine_id", "")) or ""),
|
|
803
|
+
deployment=_as_int(d.get("deployment"), 0),
|
|
804
|
+
policies=[PolicyRef.from_dict(p) for p in (d.get("policies") or [])],
|
|
805
|
+
updated_at=str(d.get("updatedAt", d.get("updated_at", "")) or ""),
|
|
806
|
+
updated_by=d.get("updatedBy", d.get("updated_by")),
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
810
|
+
return {
|
|
811
|
+
"machineId": self.machine_id, "deployment": self.deployment,
|
|
812
|
+
"policies": [p.to_dict() for p in self.policies],
|
|
813
|
+
"updatedAt": self.updated_at, "updatedBy": self.updated_by,
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
@dataclass
|
|
818
|
+
class Machine:
|
|
819
|
+
"""A host that has checked in. Machines enrol themselves on their first poll.
|
|
820
|
+
|
|
821
|
+
Two generation numbers, and the gap between them is the whole point of
|
|
822
|
+
`fleet diff`: ``deployment`` is what the control plane INTENDED for this
|
|
823
|
+
machine, ``applied_deployment`` is what the machine last actually collected.
|
|
824
|
+
A machine can sit on an old set indefinitely and nothing else says so.
|
|
825
|
+
"""
|
|
826
|
+
|
|
827
|
+
machine_id: str
|
|
828
|
+
#: What the machine calls itself. May be absent — plenty never report one.
|
|
829
|
+
label: Optional[str]
|
|
830
|
+
#: What an operator called it via `fleet rename`. SEPARATE from `label` on
|
|
831
|
+
#: the server, and the reason a rename appeared to do nothing here: reading
|
|
832
|
+
#: only `label` showed the machine's own (usually null) name and silently
|
|
833
|
+
#: ignored the override. `display_label` applies the precedence.
|
|
834
|
+
label_override: Optional[str]
|
|
835
|
+
last_seen: Optional[int] # epoch ms — the server sends a number, not ISO
|
|
836
|
+
last_check_in: Optional[int]
|
|
837
|
+
deployment: Optional[int] # intended
|
|
838
|
+
applied_deployment: Optional[int] # delivered
|
|
839
|
+
applied_at: Optional[int]
|
|
840
|
+
deployed: bool
|
|
841
|
+
policy_count: int
|
|
842
|
+
event_count: int
|
|
843
|
+
|
|
844
|
+
@classmethod
|
|
845
|
+
def from_dict(cls, d: Dict[str, Any]) -> "Machine":
|
|
846
|
+
def _num(key: str) -> Optional[int]:
|
|
847
|
+
v = d.get(key)
|
|
848
|
+
return int(v) if isinstance(v, (int, float)) else None
|
|
849
|
+
|
|
850
|
+
return cls(
|
|
851
|
+
machine_id=str(d.get("machineId", d.get("machine_id", "")) or ""),
|
|
852
|
+
label=d.get("label"),
|
|
853
|
+
label_override=d.get("labelOverride"),
|
|
854
|
+
last_seen=_num("lastSeen"),
|
|
855
|
+
last_check_in=_num("lastCheckIn"),
|
|
856
|
+
deployment=_num("deployment"),
|
|
857
|
+
applied_deployment=_num("appliedDeployment"),
|
|
858
|
+
applied_at=_num("appliedAt"),
|
|
859
|
+
deployed=bool(d.get("deployed", False)),
|
|
860
|
+
policy_count=_as_int(d.get("policyCount"), 0),
|
|
861
|
+
event_count=_as_int(d.get("eventCount"), 0),
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
@property
|
|
865
|
+
def display_label(self) -> Optional[str]:
|
|
866
|
+
"""The operator's name for the machine, else its own.
|
|
867
|
+
|
|
868
|
+
Mirrors `machinePicker.ts`: `labelOverride || label || machineId`. The
|
|
869
|
+
override wins because it is the deliberate one — a machine's
|
|
870
|
+
self-asserted label is whatever it happened to send.
|
|
871
|
+
"""
|
|
872
|
+
return (self.label_override or "").strip() or (self.label or "").strip() or None
|
|
873
|
+
|
|
874
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
875
|
+
"""Server shape plus `drifted` — the one field the CLI computes."""
|
|
876
|
+
return {
|
|
877
|
+
"machineId": self.machine_id, "label": self.label,
|
|
878
|
+
"labelOverride": self.label_override,
|
|
879
|
+
"lastSeen": self.last_seen, "lastCheckIn": self.last_check_in,
|
|
880
|
+
"deployment": self.deployment, "appliedDeployment": self.applied_deployment,
|
|
881
|
+
"appliedAt": self.applied_at, "deployed": self.deployed,
|
|
882
|
+
"policyCount": self.policy_count, "eventCount": self.event_count,
|
|
883
|
+
"drifted": self.drifted,
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
@property
|
|
887
|
+
def drifted(self) -> bool:
|
|
888
|
+
"""True when the machine has not collected what it was last told to run."""
|
|
889
|
+
if self.deployment is None:
|
|
890
|
+
return False
|
|
891
|
+
return self.applied_deployment is None or self.applied_deployment < self.deployment
|