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.
Files changed (389) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +6 -6
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  39. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/index.html +1 -1
  42. package/.next/standalone/.next/server/app/index.rsc +15 -15
  43. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
  51. package/.next/standalone/.next/server/app/policies/page.js +5 -4
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  68. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  69. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
  72. package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
  84. package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
  85. 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
  86. 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
  87. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
  89. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  114. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  115. package/.next/standalone/.next/server/middleware.js +1 -1
  116. package/.next/standalone/.next/server/pages/404.html +1 -1
  117. package/.next/standalone/.next/server/pages/500.html +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
  120. package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
  122. package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
  123. package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
  125. package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
  131. package/.next/standalone/SECURITY.md +5 -4
  132. package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
  133. package/.next/standalone/app/actions/pack-actions.ts +153 -0
  134. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  135. package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
  136. package/.next/standalone/app/policies/hooks-client.tsx +320 -14
  137. package/.next/standalone/components/navbar.tsx +2 -28
  138. package/.next/standalone/components/reach-developers.tsx +8 -1
  139. package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
  140. package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
  141. package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
  142. package/.next/standalone/docs-old/i18n/README.de.md +1 -0
  143. package/.next/standalone/docs-old/i18n/README.es.md +1 -0
  144. package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
  145. package/.next/standalone/docs-old/i18n/README.he.md +1 -0
  146. package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
  147. package/.next/standalone/docs-old/i18n/README.it.md +1 -0
  148. package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
  149. package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
  150. package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
  151. package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
  152. package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
  153. package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
  154. package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
  155. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
  156. package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
  157. package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
  158. package/.next/standalone/fp-cloud-cli/README.md +259 -0
  159. package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
  160. package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
  164. package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
  166. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
  167. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
  168. package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
  169. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
  170. package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
  171. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
  172. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
  173. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
  174. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
  175. package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
  176. package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
  177. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
  178. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
  179. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
  180. package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
  181. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
  182. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
  183. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
  184. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
  185. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
  186. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
  187. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
  188. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
  189. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
  190. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
  191. package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
  192. package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
  193. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
  194. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
  195. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
  196. package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
  197. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
  198. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
  199. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
  200. package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
  201. package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
  202. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
  203. package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
  204. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
  205. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
  206. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
  207. package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
  208. package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
  209. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
  210. package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
  211. package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
  212. package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
  213. package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
  214. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
  215. package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
  216. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
  217. package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
  218. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
  219. package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
  220. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
  221. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
  222. package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
  223. package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
  224. package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
  225. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
  226. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
  227. package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
  228. package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
  229. package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
  230. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
  231. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
  232. package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
  233. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
  234. package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
  235. package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
  236. package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
  237. package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
  238. package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
  239. package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
  240. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
  241. package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
  242. package/.next/standalone/package.json +29 -22
  243. package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
  244. package/.next/standalone/sdk/python/LICENSE +42 -0
  245. package/.next/standalone/sdk/python/MANIFEST.in +21 -0
  246. package/.next/standalone/sdk/python/README.md +466 -0
  247. package/.next/standalone/sdk/python/docs/README.md +249 -0
  248. package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
  249. package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
  250. package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
  251. package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
  252. package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
  253. package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
  254. package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
  255. package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
  256. package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
  257. package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
  258. package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
  259. package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
  260. package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
  261. package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
  262. package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
  263. package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
  264. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
  265. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
  266. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
  267. package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
  268. package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
  269. package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
  270. package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
  271. package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
  272. package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
  273. package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
  274. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
  275. package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
  276. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
  277. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
  278. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
  279. package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
  280. package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
  281. package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
  282. package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
  283. package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
  284. package/.next/standalone/sdk/python/pyproject.toml +92 -0
  285. package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
  286. package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
  287. package/.next/standalone/sdk/python/skill/references/events.md +234 -0
  288. package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
  289. package/.next/standalone/sdk/python/skill/references/install.md +100 -0
  290. package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
  291. package/.next/standalone/sdk/python/tests/__init__.py +0 -0
  292. package/.next/standalone/sdk/python/tests/conftest.py +169 -0
  293. package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
  294. package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
  295. package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
  296. package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
  297. package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
  298. package/.next/standalone/sdk/python/tests/test_context.py +410 -0
  299. package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
  300. package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
  301. package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
  302. package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
  303. package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
  304. package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
  305. package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
  306. package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
  307. package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
  308. package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
  309. package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
  310. package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
  311. package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
  312. package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
  313. package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
  314. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
  315. package/.next/standalone/sdk/python/uv.lock +6005 -0
  316. package/.next/standalone/server.js +1 -1
  317. package/README.md +77 -21
  318. package/bin/failproofai.mjs +145 -11
  319. package/dist/cli.mjs +4482 -2205
  320. package/dist/index.js +5 -5
  321. package/dist/worker.mjs +2399 -1978
  322. package/package.json +29 -22
  323. package/policy-pack/SHA256SUMS +2 -0
  324. package/policy-pack/failproofai-pack.json +732 -0
  325. package/policy-pack/failproofai-pack.mjs +2017 -0
  326. package/scripts/build-policy-pack.mjs +121 -0
  327. package/scripts/changelog-section.py +77 -0
  328. package/scripts/prune-standalone.mjs +3 -0
  329. package/scripts/python-version.py +232 -0
  330. package/scripts/translate-docs/cli.ts +21 -0
  331. package/scripts/translate-docs/mintlify-nav.ts +85 -13
  332. package/scripts/translate-docs/translator.ts +66 -11
  333. package/src/audit/cache.ts +40 -3
  334. package/src/audit/cli.ts +1 -1
  335. package/src/audit/index.ts +1 -1
  336. package/src/audit/replay.ts +89 -3
  337. package/src/audit/report.ts +5 -5
  338. package/src/audit/schedule-cli.ts +49 -53
  339. package/src/hooks/builtin-policies.ts +217 -579
  340. package/src/hooks/cloud-enrollment-cli.ts +66 -27
  341. package/src/hooks/cloud-managed-policies.ts +6 -1
  342. package/src/hooks/configure-wizard.ts +1 -1
  343. package/src/hooks/custom-hooks-loader.ts +89 -6
  344. package/src/hooks/fp-home.ts +92 -4
  345. package/src/hooks/fp-reset.ts +26 -2
  346. package/src/hooks/handler.ts +181 -11
  347. package/src/hooks/harness-cli.ts +79 -25
  348. package/src/hooks/hook-activity-store.ts +13 -3
  349. package/src/hooks/install-prompt.ts +25 -3
  350. package/src/hooks/manager.ts +418 -172
  351. package/src/hooks/migrations.ts +2 -2
  352. package/src/hooks/pack-cli.ts +505 -0
  353. package/src/hooks/pack-failclosed.ts +189 -0
  354. package/src/hooks/pack-manifest.ts +344 -0
  355. package/src/hooks/pack-store.ts +930 -0
  356. package/src/hooks/policy-catalog.ts +535 -0
  357. package/src/hooks/policy-evaluator.ts +24 -16
  358. package/src/hooks/policy-presets.ts +4 -2
  359. package/src/hooks/policy-registry.ts +8 -2
  360. package/src/hooks/policy-types.ts +26 -0
  361. package/src/hooks/session-pause-cli.ts +33 -9
  362. package/src/hooks/tui.ts +570 -0
  363. package/src/hooks/types.ts +15 -1
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
  373. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
  380. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
  381. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
  382. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
  383. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
  384. package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
  385. package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
  386. package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
  387. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
  388. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
  389. /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