failproofai 1.0.6-beta.1 → 1.0.7-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 +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
- 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 +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +7 -5
- 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 +6 -6
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +13 -11
- 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 +8 -7
- 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 +7 -7
- 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 +7 -6
- 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 +41 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -9
- 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]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__1j-zsg5._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1x3j9mj._.js → [root-of-the-server]__1p28m0u._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0qlygw6._.js → _0cf1olg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1_ddpm3._.js → _135-c3y._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +4 -4
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_0az0sfq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -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/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +56 -23
- package/.next/standalone/.next/static/chunks/{06fjx8-0_qufs.js → 00i9thnpht4l1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ht0xsco46pul.js → 0289oapk24zql.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1qf2uiipatr80.js → 0pyy9m5cm8zku.js} +1 -1
- package/.next/standalone/.next/static/chunks/15heikmy3yjzc.js +1 -0
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
- package/.next/standalone/.next/static/chunks/{02vwr5js1x28j.js → 2plg9qae1yejv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/{2x776s0x3n_tu.js → 2tqszh84mp_2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
- package/.next/standalone/.next/static/chunks/3cdkxrukvlvul.js +1 -0
- package/.next/standalone/.next/static/chunks/3dnd-_29uiaj3.js +6 -0
- package/.next/standalone/.next/static/chunks/{0y227_ohdqwvn.js → 3vp7c3knkh8mr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{27_bwbg8sv6vj.js → 3vwnzfx9q80yl.js} +1 -1
- package/.next/standalone/app/actions/get-jev-config.ts +409 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +420 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +9 -0
- package/.next/standalone/app/settings/jev-panel.tsx +469 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +51 -3
- package/.next/standalone/fp-cloud-cli/README.md +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
- package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/package.json +11 -11
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
- package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
- package/.next/standalone/sdk/python/uv.lock +15 -15
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +115 -2
- package/dist/cli.mjs +10982 -3787
- package/dist/index.js +19 -1
- package/dist/worker.mjs +13761 -7834
- package/package.json +11 -11
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +39 -2
- package/src/hooks/builtin-policies.ts +21 -3
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/custom-hooks-loader.ts +45 -7
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/daemon-client.ts +128 -50
- package/src/hooks/daemon-service.ts +30 -0
- package/src/hooks/effective-reviewers.ts +79 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/handler.ts +265 -6
- package/src/hooks/hook-activity-store.ts +105 -1
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/integrations.ts +3 -2
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +1193 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +4 -4
- package/src/hooks/pack-cli.ts +237 -11
- package/src/hooks/pack-manifest.ts +479 -7
- package/src/hooks/pack-store.ts +156 -10
- package/src/hooks/policy-authority.ts +333 -0
- package/src/hooks/policy-catalog.ts +65 -0
- package/src/hooks/policy-evaluator.ts +940 -796
- package/src/hooks/policy-registry.ts +25 -0
- package/src/hooks/policy-reviewability.ts +229 -0
- package/src/hooks/policy-types.ts +126 -0
- package/src/hooks/semantic/combine.ts +541 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +392 -0
- package/src/hooks/semantic/envelope.ts +1296 -0
- package/src/hooks/semantic/evaluator.ts +547 -0
- package/src/hooks/semantic/facts.ts +292 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +643 -0
- package/src/hooks/semantic/jev-config.ts +594 -0
- package/src/hooks/semantic/jev-review.ts +374 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +421 -0
- package/src/hooks/semantic/pack-policies.ts +251 -0
- package/src/hooks/semantic/policies.ts +596 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2910 -0
- package/src/hooks/semantic/types.ts +145 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/worker-server.ts +119 -26
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +0 -10
- package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +0 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
- package/.next/standalone/.next/static/chunks/1p4xauf-zu6zk.js +0 -1
- package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_ssgManifest.js +0 -0
|
@@ -642,3 +642,187 @@ def test_issues_show_malformed_id_is_not_found(logged_in, runner):
|
|
|
642
642
|
result = runner.invoke(app, ["--json", "issues", "show", "not-a-uuid"])
|
|
643
643
|
assert result.exit_code == 6
|
|
644
644
|
assert "no issue not-a-uuid" in json.loads(result.stdout)["error"]
|
|
645
|
+
|
|
646
|
+
# --- close / archive / clear ------------------------------------------------
|
|
647
|
+
#
|
|
648
|
+
# The issue lifecycle grew a second terminal state (`closed`) and an orthogonal
|
|
649
|
+
# archived flag. The CLI restates the server's contract by hand — the state
|
|
650
|
+
# allowlist, the paths, the response keys — with nothing checking the two agree,
|
|
651
|
+
# so these pin the CLI half.
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@respx.mock
|
|
655
|
+
def test_issues_close_posts_to_close_not_resolve(logged_in, runner):
|
|
656
|
+
# The whole point of `close` is that it is NOT `resolve`: a resolved issue
|
|
657
|
+
# reopens when its audit finding recurs and a closed one does not, so a
|
|
658
|
+
# close that quietly hit /resolve would silently give the opposite
|
|
659
|
+
# behaviour with an identical success line.
|
|
660
|
+
resolve = respx.post(f"{BASE}/api/issues/i1/resolve").mock(return_value=httpx.Response(200, json={}))
|
|
661
|
+
close = respx.post(f"{BASE}/api/issues/i1/close").mock(
|
|
662
|
+
return_value=httpx.Response(200, json={"id": "i1", "state": "closed", "closed_at": "t"}))
|
|
663
|
+
result = runner.invoke(app, ["--json", "issues", "close", "i1", "--yes"])
|
|
664
|
+
assert result.exit_code == 0, result.output
|
|
665
|
+
assert json.loads(result.stdout) == {"closed": True, "id": "i1"}
|
|
666
|
+
assert close.called
|
|
667
|
+
assert not resolve.called
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
@respx.mock
|
|
671
|
+
def test_issues_close_human_line(logged_in, runner):
|
|
672
|
+
respx.post(f"{BASE}/api/issues/i1/close").mock(return_value=httpx.Response(200, json={}))
|
|
673
|
+
result = runner.invoke(app, ["issues", "close", "i1", "--yes"])
|
|
674
|
+
assert result.exit_code == 0, result.output
|
|
675
|
+
assert "closed issue" in result.stderr
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
@respx.mock
|
|
679
|
+
def test_issues_close_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
|
|
680
|
+
monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
|
|
681
|
+
monkeypatch.setattr(output, "confirm_incident_close", lambda *a, **k: False)
|
|
682
|
+
respx.get(f"{BASE}/api/issues/i1").mock(
|
|
683
|
+
return_value=httpx.Response(200, json={"id": "i1", "alert_name": "p95"}))
|
|
684
|
+
close = respx.post(f"{BASE}/api/issues/i1/close").mock(return_value=httpx.Response(200, json={}))
|
|
685
|
+
result = runner.invoke(app, ["--json", "issues", "close", "i1"])
|
|
686
|
+
assert result.exit_code == 0, result.output
|
|
687
|
+
assert json.loads(result.stdout) == {"cancelled": True}
|
|
688
|
+
assert not close.called
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@respx.mock
|
|
692
|
+
def test_issues_archive_and_unarchive_hit_their_own_verbs(logged_in, runner):
|
|
693
|
+
arch = respx.post(f"{BASE}/api/issues/i1/archive").mock(return_value=httpx.Response(200, json={}))
|
|
694
|
+
unarch = respx.post(f"{BASE}/api/issues/i1/unarchive").mock(return_value=httpx.Response(200, json={}))
|
|
695
|
+
|
|
696
|
+
result = runner.invoke(app, ["--json", "issues", "archive", "i1"])
|
|
697
|
+
assert result.exit_code == 0, result.output
|
|
698
|
+
assert json.loads(result.stdout) == {"archived": True, "id": "i1"}
|
|
699
|
+
assert arch.called and not unarch.called
|
|
700
|
+
|
|
701
|
+
result = runner.invoke(app, ["--json", "issues", "unarchive", "i1"])
|
|
702
|
+
assert result.exit_code == 0, result.output
|
|
703
|
+
assert json.loads(result.stdout) == {"archived": False, "id": "i1"}
|
|
704
|
+
assert unarch.called
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
@respx.mock
|
|
708
|
+
def test_issues_archive_does_not_prompt(logged_in, runner, monkeypatch):
|
|
709
|
+
# Archiving destroys nothing and `unarchive` undoes it, so it must not ask —
|
|
710
|
+
# a prompt here would be the only blocking one in a cleanup loop.
|
|
711
|
+
monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
|
|
712
|
+
route = respx.post(f"{BASE}/api/issues/i1/archive").mock(return_value=httpx.Response(200, json={}))
|
|
713
|
+
result = runner.invoke(app, ["--json", "issues", "archive", "i1"])
|
|
714
|
+
assert result.exit_code == 0, result.output
|
|
715
|
+
assert route.called
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def test_issues_clear_requires_exactly_one_scope(logged_in, runner):
|
|
719
|
+
# The three scopes have very different blast radii, so there is deliberately
|
|
720
|
+
# no default and no way to pass two.
|
|
721
|
+
none_given = runner.invoke(app, ["--json", "issues", "clear"])
|
|
722
|
+
assert none_given.exit_code == 2, none_given.output
|
|
723
|
+
|
|
724
|
+
two_given = runner.invoke(app, ["--json", "issues", "clear", "--all-audits", "--everything"])
|
|
725
|
+
assert two_given.exit_code == 2, two_given.output
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
@respx.mock
|
|
729
|
+
def test_issues_clear_dry_run_writes_nothing(logged_in, runner):
|
|
730
|
+
calls = []
|
|
731
|
+
|
|
732
|
+
def record(request):
|
|
733
|
+
calls.append(json.loads(request.content))
|
|
734
|
+
return httpx.Response(200, json={"issues": 12, "findings": 9, "dry_run": True, "scope": "all_audits"})
|
|
735
|
+
|
|
736
|
+
respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
|
|
737
|
+
result = runner.invoke(app, ["--json", "issues", "clear", "--all-audits", "--dry-run"])
|
|
738
|
+
assert result.exit_code == 0, result.output
|
|
739
|
+
assert json.loads(result.stdout)["issues"] == 12
|
|
740
|
+
# Exactly one request, and it carried dry_run — a second (writing) call here
|
|
741
|
+
# would mean --dry-run cleared the board.
|
|
742
|
+
assert len(calls) == 1
|
|
743
|
+
assert calls[0] == {"scope": "all_audits", "dry_run": True}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
@respx.mock
|
|
747
|
+
def test_issues_clear_previews_then_writes(logged_in, runner):
|
|
748
|
+
calls = []
|
|
749
|
+
|
|
750
|
+
def record(request):
|
|
751
|
+
body = json.loads(request.content)
|
|
752
|
+
calls.append(body)
|
|
753
|
+
return httpx.Response(200, json={
|
|
754
|
+
"issues": 3, "findings": 2, "dry_run": body["dry_run"], "scope": "audit"})
|
|
755
|
+
|
|
756
|
+
respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
|
|
757
|
+
result = runner.invoke(app, ["--json", "issues", "clear", "--audit", "a1", "--yes"])
|
|
758
|
+
assert result.exit_code == 0, result.output
|
|
759
|
+
# Preview first (so the confirm can name a number), then the real write.
|
|
760
|
+
assert [c["dry_run"] for c in calls] == [True, False]
|
|
761
|
+
assert all(c["audit_id"] == "a1" and c["scope"] == "audit" for c in calls)
|
|
762
|
+
assert json.loads(result.stdout)["issues"] == 3
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
@respx.mock
|
|
766
|
+
def test_issues_clear_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
|
|
767
|
+
monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
|
|
768
|
+
monkeypatch.setattr(output, "confirm_issues_clear", lambda *a, **k: False)
|
|
769
|
+
calls = []
|
|
770
|
+
|
|
771
|
+
def record(request):
|
|
772
|
+
calls.append(json.loads(request.content))
|
|
773
|
+
return httpx.Response(200, json={"issues": 5, "findings": 4, "dry_run": True, "scope": "everything"})
|
|
774
|
+
|
|
775
|
+
respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
|
|
776
|
+
result = runner.invoke(app, ["--json", "issues", "clear", "--everything"])
|
|
777
|
+
assert result.exit_code == 0, result.output
|
|
778
|
+
assert json.loads(result.stdout) == {"cancelled": True}
|
|
779
|
+
# Only the preview ran. A declined confirm that had already written would be
|
|
780
|
+
# the worst possible version of this bug.
|
|
781
|
+
assert [c["dry_run"] for c in calls] == [True]
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@respx.mock
|
|
785
|
+
def test_issues_clear_on_an_empty_scope_does_not_prompt_or_write(logged_in, runner, monkeypatch):
|
|
786
|
+
monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
|
|
787
|
+
|
|
788
|
+
def boom(*a, **k): # pragma: no cover - asserts it is never reached
|
|
789
|
+
raise AssertionError("prompted to confirm clearing zero issues")
|
|
790
|
+
|
|
791
|
+
monkeypatch.setattr(output, "confirm_issues_clear", boom)
|
|
792
|
+
calls = []
|
|
793
|
+
|
|
794
|
+
def record(request):
|
|
795
|
+
calls.append(json.loads(request.content))
|
|
796
|
+
return httpx.Response(200, json={"issues": 0, "findings": 0, "dry_run": True, "scope": "all_audits"})
|
|
797
|
+
|
|
798
|
+
respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
|
|
799
|
+
result = runner.invoke(app, ["--json", "issues", "clear", "--all-audits"])
|
|
800
|
+
assert result.exit_code == 0, result.output
|
|
801
|
+
assert json.loads(result.stdout)["issues"] == 0
|
|
802
|
+
assert [c["dry_run"] for c in calls] == [True]
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def test_issues_list_accepts_closed_as_a_state(logged_in, runner):
|
|
806
|
+
# `--state` is validated client-side (exit 2) against a hand-copied list. A
|
|
807
|
+
# state missing from it is rejected here for something the server accepts.
|
|
808
|
+
from fp_cli.commands import incidents_cmds
|
|
809
|
+
|
|
810
|
+
assert "closed" in incidents_cmds._STATES
|
|
811
|
+
bad = runner.invoke(app, ["--json", "issues", "list", "--state", "nope"])
|
|
812
|
+
assert bad.exit_code == 2, bad.output
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
@respx.mock
|
|
816
|
+
def test_issues_show_surfaces_closed_at_and_archived_at(logged_in, runner):
|
|
817
|
+
# `Incident.from_dict` is an ALLOWLIST — a key it does not name is dropped
|
|
818
|
+
# with no error, which renders as a permanently blank column.
|
|
819
|
+
respx.get(f"{BASE}/api/issues/i1").mock(return_value=httpx.Response(200, json={
|
|
820
|
+
"id": "i1", "state": "closed", "closed_at": "2026-09-22T10:00:00Z",
|
|
821
|
+
"archived_at": "2026-09-22T11:00:00Z", "alert_severity": "warning",
|
|
822
|
+
}))
|
|
823
|
+
from fp_cli import client as api
|
|
824
|
+
|
|
825
|
+
inc = api.get_incident(api.ClientContext(base_url=BASE, token="s"), "i1")
|
|
826
|
+
assert inc.closed_at == "2026-09-22T10:00:00Z"
|
|
827
|
+
assert inc.archived_at == "2026-09-22T11:00:00Z"
|
|
828
|
+
assert inc.state == "closed"
|
|
@@ -372,7 +372,7 @@ wheels = [
|
|
|
372
372
|
|
|
373
373
|
[[package]]
|
|
374
374
|
name = "posthog"
|
|
375
|
-
version = "7.
|
|
375
|
+
version = "7.58.0"
|
|
376
376
|
source = { registry = "https://pypi.org/simple" }
|
|
377
377
|
dependencies = [
|
|
378
378
|
{ name = "backoff" },
|
|
@@ -380,9 +380,9 @@ dependencies = [
|
|
|
380
380
|
{ name = "requests" },
|
|
381
381
|
{ name = "typing-extensions" },
|
|
382
382
|
]
|
|
383
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
383
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ef/d9/6e351a6c739fec6e78c1be494f4d39512271167d3eb5905ccafa783c3234/posthog-7.58.0.tar.gz", hash = "sha256:43dcdeea3af4dbdece3322c5e37864cbdad6d0e507c20182bb33a960900bb077", size = 668207, upload-time = "2026-09-18T15:00:06.96Z" }
|
|
384
384
|
wheels = [
|
|
385
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
385
|
+
{ url = "https://files.pythonhosted.org/packages/24/60/997629e929e96641d297f7bf67543026eeaf5f8a3de66363f1cbe366c0ee/posthog-7.58.0-py3-none-any.whl", hash = "sha256:2ac581ce52c63acb3a551c9066652c3d29e1de1ea06e70655ebe8b866e80f334", size = 781569, upload-time = "2026-09-18T15:00:04.587Z" },
|
|
386
386
|
]
|
|
387
387
|
|
|
388
388
|
[[package]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7-beta.0",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -81,14 +81,14 @@
|
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@anthropic-ai/sdk": "^0.
|
|
84
|
+
"@anthropic-ai/sdk": "^0.127.0",
|
|
85
85
|
"@mdx-js/mdx": "^3.1.1",
|
|
86
86
|
"@tailwindcss/postcss": "^4.3.1",
|
|
87
87
|
"@tanstack/react-virtual": "^3.14.10",
|
|
88
88
|
"@testing-library/jest-dom": "^7.0.0",
|
|
89
89
|
"@testing-library/react": "^16.3.2",
|
|
90
90
|
"@testing-library/user-event": "^14.6.5",
|
|
91
|
-
"@types/node": "26.
|
|
91
|
+
"@types/node": "26.6.1",
|
|
92
92
|
"@types/react": "19.3.0",
|
|
93
93
|
"@types/react-dom": "^19.2.3",
|
|
94
94
|
"@vitejs/plugin-react": "^6.1.0",
|
|
@@ -122,15 +122,15 @@
|
|
|
122
122
|
"browserslist": "4.28.8"
|
|
123
123
|
},
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@failproofai/failproofaid-linux-x64": "1.0.
|
|
126
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.
|
|
127
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.
|
|
128
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.
|
|
125
|
+
"@failproofai/failproofaid-linux-x64": "1.0.7-beta.0",
|
|
126
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.7-beta.0",
|
|
127
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.7-beta.0",
|
|
128
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.7-beta.0"
|
|
129
129
|
},
|
|
130
130
|
"failproofaidBinaries": {
|
|
131
|
-
"linux-x64": "
|
|
132
|
-
"linux-arm64": "
|
|
133
|
-
"darwin-x64": "
|
|
134
|
-
"darwin-arm64": "
|
|
131
|
+
"linux-x64": "3ac234868fe9149179c32981da9ae1209371e98df7ffb09c6a4947664a3d6857",
|
|
132
|
+
"linux-arm64": "3603faac5741c321a63ba5a963e0cb4bb38c84663a63b53f764faae020116f90",
|
|
133
|
+
"darwin-x64": "c8a5b32a1e97b5a7f76ce4e42c78f0d566d108be65fdca3c6cd59c405f39b946",
|
|
134
|
+
"darwin-arm64": "255ac741c3ee3069591b1eb8bbf397baa0722ef240b83a8540b73f3f1b35722c"
|
|
135
135
|
}
|
|
136
136
|
}
|
|
@@ -9,6 +9,13 @@ in `failproofai_sdk/_version.py` becomes that release's GitHub Release body. A
|
|
|
9
9
|
release whose section is missing or empty is refused before anything is built —
|
|
10
10
|
see `scripts/changelog-section.py`.
|
|
11
11
|
|
|
12
|
+
## 0.0.1b5 — 2026-09-22
|
|
13
|
+
|
|
14
|
+
Open for the next release. `0.0.1b4` published on 2026-09-22 and the `bump` job
|
|
15
|
+
moved the version here automatically; nothing has landed against `0.0.1b5` yet.
|
|
16
|
+
Add entries as changes merge — this section becomes the GitHub Release body when
|
|
17
|
+
it ships.
|
|
18
|
+
|
|
12
19
|
## 0.0.1b4 — 2026-09-12
|
|
13
20
|
|
|
14
21
|
Open for the next release. `0.0.1b3` published on 2026-09-12 and the `bump` job
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.1b5"
|
|
@@ -10,6 +10,7 @@ from failproofai_sdk.evaluator.authoring import (
|
|
|
10
10
|
EvalDefinition,
|
|
11
11
|
EvalResult,
|
|
12
12
|
Evaluator,
|
|
13
|
+
ManagedCompiler,
|
|
13
14
|
Metric,
|
|
14
15
|
Score,
|
|
15
16
|
)
|
|
@@ -67,6 +68,7 @@ __all__ = [
|
|
|
67
68
|
"EvalSelection",
|
|
68
69
|
"ExecutionMode",
|
|
69
70
|
"Evaluator",
|
|
71
|
+
"ManagedCompiler",
|
|
70
72
|
"EvaluatorAPIError",
|
|
71
73
|
"EvaluatorClient",
|
|
72
74
|
"EvaluatorKind",
|
|
@@ -36,6 +36,17 @@ ConditionFunction = Callable[
|
|
|
36
36
|
[SessionTranscript], "bool | ConditionResult | Awaitable[bool | ConditionResult]"
|
|
37
37
|
]
|
|
38
38
|
CancellationFunction = Callable[[SessionTranscript], "Any | Awaitable[Any]"]
|
|
39
|
+
# Compiles server-authored (managed) source into something callable with a
|
|
40
|
+
# session. Signature mirrors `source.compile_evaluator`, which is the default.
|
|
41
|
+
#
|
|
42
|
+
# A host that serves managed definitions whose source is NOT a restricted Python
|
|
43
|
+
# expression — FailproofAI's own managed worker, whose source may instead be a
|
|
44
|
+
# declarative judge document — installs its own compiler here. The SDK stays
|
|
45
|
+
# agnostic: it never inspects the source, it just hands it to whoever compiles.
|
|
46
|
+
# Returning an `async def` is supported and is the right shape for a compiler
|
|
47
|
+
# whose work is IO-bound (see `WorkerRuntime._invoke`, which awaits an awaitable
|
|
48
|
+
# result on the event loop rather than parking a thread on it).
|
|
49
|
+
ManagedCompiler = Callable[..., EvalFunction]
|
|
39
50
|
|
|
40
51
|
|
|
41
52
|
def _bounded(value: str, *, field_name: str, maximum: int) -> str:
|
|
@@ -284,11 +295,23 @@ class EvalDefinition:
|
|
|
284
295
|
class Evaluator:
|
|
285
296
|
"""A process-local collection of explicitly versioned evaluations."""
|
|
286
297
|
|
|
287
|
-
def __init__(
|
|
298
|
+
def __init__(
|
|
299
|
+
self,
|
|
300
|
+
*,
|
|
301
|
+
name: str,
|
|
302
|
+
version: str,
|
|
303
|
+
managed_compiler: ManagedCompiler | None = None,
|
|
304
|
+
) -> None:
|
|
288
305
|
self.name = _bounded(name, field_name="name", maximum=MAX_DISPLAY_NAME_BYTES)
|
|
289
306
|
self.version = _bounded(
|
|
290
307
|
version, field_name="version", maximum=MAX_VERSION_BYTES
|
|
291
308
|
)
|
|
309
|
+
# `None` keeps the default: managed source is compiled and sandboxed by
|
|
310
|
+
# `source.compile_evaluator`. Customer workers never set this — their
|
|
311
|
+
# definitions are LOCAL and never carry source at all.
|
|
312
|
+
if managed_compiler is not None and not callable(managed_compiler):
|
|
313
|
+
raise TypeError("managed_compiler must be callable")
|
|
314
|
+
self.managed_compiler = managed_compiler
|
|
292
315
|
self._definitions: dict[str, EvalDefinition] = {}
|
|
293
316
|
|
|
294
317
|
def eval(
|
|
@@ -88,7 +88,12 @@ def _utc_now() -> str:
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def _deferred_managed_eval(
|
|
91
|
+
def _deferred_managed_eval(
|
|
92
|
+
source: str,
|
|
93
|
+
timeout_seconds: int | None,
|
|
94
|
+
eval_key: str,
|
|
95
|
+
compiler: Any = None,
|
|
96
|
+
):
|
|
92
97
|
"""Compile server-authored source lazily, at invocation time.
|
|
93
98
|
|
|
94
99
|
Compilation can reject unsafe or malformed source (``UnsafeEvaluatorSource``).
|
|
@@ -98,10 +103,19 @@ def _deferred_managed_eval(source: str, timeout_seconds: int | None, eval_key: s
|
|
|
98
103
|
dead-letters cleanly as one failed run instead of raising out of assignment
|
|
99
104
|
setup, crashing the task, and forcing the whole assignment to be reclaimed
|
|
100
105
|
and retried until its attempt budget is exhausted.
|
|
106
|
+
|
|
107
|
+
``compiler`` defaults to :func:`compile_evaluator` — the restricted-expression
|
|
108
|
+
sandbox. A host serving managed definitions in another shape installs its own
|
|
109
|
+
via ``Evaluator(managed_compiler=...)``; the SDK never inspects the source.
|
|
110
|
+
The thunk stays SYNCHRONOUS either way, so a sandboxed compile still runs in
|
|
111
|
+
the runtime's sized executor exactly as before. A compiler that returns an
|
|
112
|
+
``async def`` simply makes this thunk return a coroutine, which ``_invoke``
|
|
113
|
+
awaits on the event loop — the right place for IO-bound work.
|
|
101
114
|
"""
|
|
115
|
+
compile_source = compiler if compiler is not None else compile_evaluator
|
|
102
116
|
|
|
103
117
|
def evaluate(session: Any) -> Any:
|
|
104
|
-
return
|
|
118
|
+
return compile_source(
|
|
105
119
|
source, timeout_seconds=timeout_seconds, eval_key=eval_key
|
|
106
120
|
)(session)
|
|
107
121
|
|
|
@@ -551,6 +565,7 @@ class WorkerRuntime:
|
|
|
551
565
|
run.evaluator_source,
|
|
552
566
|
run.timeout_seconds or descriptor.timeout_seconds,
|
|
553
567
|
descriptor.eval_key,
|
|
568
|
+
self.evaluator.managed_compiler,
|
|
554
569
|
),
|
|
555
570
|
condition=None,
|
|
556
571
|
on_cancel=None,
|
|
@@ -597,9 +612,15 @@ class WorkerRuntime:
|
|
|
597
612
|
started = time.monotonic()
|
|
598
613
|
# A synchronous evaluator runs in the executor thread; if it overruns the
|
|
599
614
|
# wall-clock timeout below, the thread cannot be cancelled and is orphaned.
|
|
600
|
-
|
|
615
|
+
#
|
|
616
|
+
# Decided AFTER the fact, not from the function object: a deferred managed
|
|
617
|
+
# thunk is a sync callable that may return a coroutine, which `_invoke`
|
|
618
|
+
# awaits on the loop. That case is cancellable and leaves no orphan, so
|
|
619
|
+
# counting it as one would send an operator hunting a hung thread that
|
|
620
|
+
# does not exist.
|
|
621
|
+
awaited_on_loop: list[bool] = []
|
|
601
622
|
try:
|
|
602
|
-
invocation = self._invoke(definition.function, session)
|
|
623
|
+
invocation = self._invoke(definition.function, session, awaited_on_loop)
|
|
603
624
|
# Always bound the evaluation. A definition with no declared
|
|
604
625
|
# timeout_seconds falls back to DEFAULT_EVAL_TIMEOUT_SECONDS rather
|
|
605
626
|
# than awaiting unbounded — an unbounded local eval that hangs would
|
|
@@ -630,7 +651,7 @@ class WorkerRuntime:
|
|
|
630
651
|
# the forked managed sandbox was killed by its CPU/memory/time budget —
|
|
631
652
|
# the real, thread-uncancellable case. Both are a timed-out run.
|
|
632
653
|
await self._cancel_hook(definition, session)
|
|
633
|
-
if isinstance(timeout_error, asyncio.TimeoutError) and
|
|
654
|
+
if isinstance(timeout_error, asyncio.TimeoutError) and not awaited_on_loop:
|
|
634
655
|
# The awaiter gave up while a SYNCHRONOUS evaluator was still
|
|
635
656
|
# running in the executor. CPython cannot interrupt that thread,
|
|
636
657
|
# so it is now orphaned — it runs to completion (or forever)
|
|
@@ -824,12 +845,26 @@ class WorkerRuntime:
|
|
|
824
845
|
remaining = min(remaining, float(timeout_seconds))
|
|
825
846
|
return remaining
|
|
826
847
|
|
|
827
|
-
async def _invoke(self, function, session):
|
|
848
|
+
async def _invoke(self, function, session, awaited: list[bool] | None = None):
|
|
849
|
+
"""Run an evaluation or condition, on the loop or in the executor.
|
|
850
|
+
|
|
851
|
+
``awaited`` is an out-parameter: append-only, and appended to exactly when
|
|
852
|
+
the work ends up being awaited on the EVENT LOOP rather than occupying an
|
|
853
|
+
executor thread. The timeout handler needs that distinction — a coroutine
|
|
854
|
+
is cancelled cleanly, a running thread is not — and it cannot be known
|
|
855
|
+
from the function object alone, because a deferred managed thunk is a
|
|
856
|
+
plain sync callable that may still RETURN a coroutine.
|
|
857
|
+
"""
|
|
828
858
|
if inspect.iscoroutinefunction(function):
|
|
859
|
+
if awaited is not None:
|
|
860
|
+
awaited.append(True)
|
|
829
861
|
return await function(session)
|
|
830
862
|
loop = asyncio.get_running_loop()
|
|
831
863
|
result = await loop.run_in_executor(self._eval_executor, function, session)
|
|
832
864
|
if inspect.isawaitable(result):
|
|
865
|
+
# The thread has already returned; only the coroutine is outstanding.
|
|
866
|
+
if awaited is not None:
|
|
867
|
+
awaited.append(True)
|
|
833
868
|
return await result
|
|
834
869
|
return result
|
|
835
870
|
|
|
@@ -1101,3 +1101,91 @@ def test_condition_phase_deadline_and_budget_are_lease_bounded():
|
|
|
1101
1101
|
assert runtime._condition_budget(deadline, None) == pytest.approx(95, abs=2)
|
|
1102
1102
|
assert runtime._condition_budget(deadline, 10) == pytest.approx(10, abs=0.05)
|
|
1103
1103
|
assert runtime._condition_budget(time.monotonic(), None) < 0
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def test_managed_compiler_hook_receives_source_verbatim_and_may_be_async():
|
|
1107
|
+
# The SDK must never inspect managed source. FailproofAI's hosted worker
|
|
1108
|
+
# serves definitions whose source is a declarative judge document rather than
|
|
1109
|
+
# a restricted Python expression; it installs its own compiler, and the SDK's
|
|
1110
|
+
# only job is to hand the string over unchanged. The compiler returns an
|
|
1111
|
+
# `async def` here because judge work is IO-bound — `_invoke` must await that
|
|
1112
|
+
# on the event loop rather than parking an executor thread on it.
|
|
1113
|
+
source = '{"kind": "judge", "criteria": "was it helpful", "threshold": 0.5}'
|
|
1114
|
+
seen: dict[str, object] = {}
|
|
1115
|
+
|
|
1116
|
+
def compiler(raw, *, timeout_seconds, eval_key):
|
|
1117
|
+
seen["source"] = raw
|
|
1118
|
+
seen["timeout_seconds"] = timeout_seconds
|
|
1119
|
+
seen["eval_key"] = eval_key
|
|
1120
|
+
|
|
1121
|
+
async def judge(session):
|
|
1122
|
+
seen["session_id"] = session.session_id
|
|
1123
|
+
return EvalResult(score=Score(0.9, passed=True), summary="judged")
|
|
1124
|
+
|
|
1125
|
+
return judge
|
|
1126
|
+
|
|
1127
|
+
class HostedClient(FakeClient):
|
|
1128
|
+
def __init__(self):
|
|
1129
|
+
super().__init__()
|
|
1130
|
+
self.assignment = replace(
|
|
1131
|
+
self.assignment,
|
|
1132
|
+
definitions_url=f"/v1/evaluator/assignments/{self.assignment.assignment_id}/definitions",
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
def definitions(self, assignment, *, worker_id):
|
|
1136
|
+
return DefinitionsResponse(
|
|
1137
|
+
assignment_id=assignment.assignment_id,
|
|
1138
|
+
catalog_revision="sha256:hosted",
|
|
1139
|
+
definitions=(
|
|
1140
|
+
AssignmentDefinition(
|
|
1141
|
+
eval_key="answer_quality",
|
|
1142
|
+
display_name="Answer quality",
|
|
1143
|
+
eval_version="1",
|
|
1144
|
+
result_kind=ResultKind.SCORE,
|
|
1145
|
+
execution_mode=ExecutionMode.PYTHON,
|
|
1146
|
+
source_checksum=source_checksum(None, source),
|
|
1147
|
+
),
|
|
1148
|
+
),
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
def plan(self, assignment_id, request):
|
|
1152
|
+
self.plans.append(request)
|
|
1153
|
+
return PlanResponse(
|
|
1154
|
+
assignment_id=assignment_id,
|
|
1155
|
+
assignment_status="planned",
|
|
1156
|
+
runs=(
|
|
1157
|
+
PlannedRun(
|
|
1158
|
+
"run-judge",
|
|
1159
|
+
"answer_quality",
|
|
1160
|
+
"1",
|
|
1161
|
+
execution_mode=ExecutionMode.PYTHON,
|
|
1162
|
+
evaluator_source=source,
|
|
1163
|
+
source_checksum=source_checksum(None, source),
|
|
1164
|
+
timeout_seconds=30,
|
|
1165
|
+
),
|
|
1166
|
+
),
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
client = HostedClient()
|
|
1170
|
+
evaluator = Evaluator(name="managed", version="1", managed_compiler=compiler)
|
|
1171
|
+
asyncio.run(_runtime(evaluator, client).process_assignment(client.assignment))
|
|
1172
|
+
|
|
1173
|
+
# Verbatim: not parsed, not normalised, not validated by the SDK.
|
|
1174
|
+
assert seen["source"] == source
|
|
1175
|
+
assert seen["eval_key"] == "answer_quality"
|
|
1176
|
+
assert seen["timeout_seconds"] == 30
|
|
1177
|
+
assert seen["session_id"] == client.assignment.session_id
|
|
1178
|
+
|
|
1179
|
+
assert len(client.submissions) == 1
|
|
1180
|
+
run_id, result = client.submissions[0]
|
|
1181
|
+
assert run_id == "run-judge"
|
|
1182
|
+
assert result.status.value == "succeeded"
|
|
1183
|
+
assert result.results[0].numeric_value == 0.9
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def test_managed_compiler_defaults_to_the_sandbox_and_rejects_non_callables():
|
|
1187
|
+
# No compiler installed => the restricted-expression sandbox, unchanged. This
|
|
1188
|
+
# is what every customer worker and every existing deployment gets.
|
|
1189
|
+
assert Evaluator(name="default", version="1").managed_compiler is None
|
|
1190
|
+
with pytest.raises(TypeError):
|
|
1191
|
+
Evaluator(name="bad", version="1", managed_compiler="not-callable")
|
|
@@ -751,7 +751,7 @@ wheels = [
|
|
|
751
751
|
|
|
752
752
|
[[package]]
|
|
753
753
|
name = "crewai"
|
|
754
|
-
version = "1.15.
|
|
754
|
+
version = "1.15.22"
|
|
755
755
|
source = { registry = "https://pypi.org/simple" }
|
|
756
756
|
dependencies = [
|
|
757
757
|
{ name = "aiofiles" },
|
|
@@ -786,14 +786,14 @@ dependencies = [
|
|
|
786
786
|
{ name = "tomli" },
|
|
787
787
|
{ name = "tomli-w" },
|
|
788
788
|
]
|
|
789
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
789
|
+
sdist = { url = "https://files.pythonhosted.org/packages/38/3e/e79f59badc4194f5d80e9ae5b07d35d96206d705270a2b2a2b5508b0a54f/crewai-1.15.22.tar.gz", hash = "sha256:0b1c654f35e07dfe41720337c56542b334644a32c5e4498a6e38e5e7da7dd82e", size = 8057957, upload-time = "2026-09-16T22:10:43.476Z" }
|
|
790
790
|
wheels = [
|
|
791
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
791
|
+
{ url = "https://files.pythonhosted.org/packages/f0/92/b19a70b6109ce2c2bd24dc63f8b4022a7097816ef236a1d9de1733738db8/crewai-1.15.22-py3-none-any.whl", hash = "sha256:7675c2bb1166933c60d67596936cd8b4ce63d77361dd3aeb925b210180298e5a", size = 1189089, upload-time = "2026-09-16T22:10:41.471Z" },
|
|
792
792
|
]
|
|
793
793
|
|
|
794
794
|
[[package]]
|
|
795
795
|
name = "crewai-cli"
|
|
796
|
-
version = "1.15.
|
|
796
|
+
version = "1.15.22"
|
|
797
797
|
source = { registry = "https://pypi.org/simple" }
|
|
798
798
|
dependencies = [
|
|
799
799
|
{ name = "appdirs" },
|
|
@@ -813,14 +813,14 @@ dependencies = [
|
|
|
813
813
|
{ name = "tomli-w" },
|
|
814
814
|
{ name = "uv" },
|
|
815
815
|
]
|
|
816
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
816
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/ed/f143440caca0fd6c5cb985710f79f0a6ebffbbb5468ccf842d55b5500ce6/crewai_cli-1.15.22.tar.gz", hash = "sha256:b7e00daedfbee9102ce4fcaffa10224b27c9c02e2b976c98a25acf214730acae", size = 244293, upload-time = "2026-09-16T22:10:46.178Z" }
|
|
817
817
|
wheels = [
|
|
818
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
818
|
+
{ url = "https://files.pythonhosted.org/packages/d6/af/5e06129c84ba60a7f48ee181d6b0c3f270f00e2dcebaf48556325cc1a59b/crewai_cli-1.15.22-py3-none-any.whl", hash = "sha256:b44abcc69b90f30c4d3b93657542879f5e2ac83506757a38a1e2e404a9d84300", size = 202381, upload-time = "2026-09-16T22:10:44.901Z" },
|
|
819
819
|
]
|
|
820
820
|
|
|
821
821
|
[[package]]
|
|
822
822
|
name = "crewai-core"
|
|
823
|
-
version = "1.15.
|
|
823
|
+
version = "1.15.22"
|
|
824
824
|
source = { registry = "https://pypi.org/simple" }
|
|
825
825
|
dependencies = [
|
|
826
826
|
{ name = "appdirs" },
|
|
@@ -836,9 +836,9 @@ dependencies = [
|
|
|
836
836
|
{ name = "rich" },
|
|
837
837
|
{ name = "tomli" },
|
|
838
838
|
]
|
|
839
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
839
|
+
sdist = { url = "https://files.pythonhosted.org/packages/95/27/1ed033b746506039e21291eb64fd4624e9de0b0489dfd5128e7efb936e92/crewai_core-1.15.22.tar.gz", hash = "sha256:6b85c609536bdca62e93eca59933f3609f79b8e207e8f6d3c59a8365a5ed5d78", size = 43090, upload-time = "2026-09-16T22:10:48.274Z" }
|
|
840
840
|
wheels = [
|
|
841
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
841
|
+
{ url = "https://files.pythonhosted.org/packages/02/45/f2eb1866dc98d75617ebe942e9575b6fa1f2f2b7c6352ea22cb2faf75931/crewai_core-1.15.22-py3-none-any.whl", hash = "sha256:8b97a45a35e663f17af65ed2a318bf428d5806ce20a54b0fce726ebe53eedb75", size = 44375, upload-time = "2026-09-16T22:10:47.06Z" },
|
|
842
842
|
]
|
|
843
843
|
|
|
844
844
|
[[package]]
|
|
@@ -3906,7 +3906,7 @@ wheels = [
|
|
|
3906
3906
|
|
|
3907
3907
|
[[package]]
|
|
3908
3908
|
name = "pydantic-ai-slim"
|
|
3909
|
-
version = "2.
|
|
3909
|
+
version = "2.45.0"
|
|
3910
3910
|
source = { registry = "https://pypi.org/simple" }
|
|
3911
3911
|
dependencies = [
|
|
3912
3912
|
{ name = "anyio" },
|
|
@@ -3919,9 +3919,9 @@ dependencies = [
|
|
|
3919
3919
|
{ name = "pydantic-graph" },
|
|
3920
3920
|
{ name = "typing-inspection" },
|
|
3921
3921
|
]
|
|
3922
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
3922
|
+
sdist = { url = "https://files.pythonhosted.org/packages/70/ad/d39e46c192e04df2e620871592054185aed4e212558d90bf89436aa7628d/pydantic_ai_slim-2.45.0.tar.gz", hash = "sha256:34cf5216ebe05cb3d343129bb24a1bd011c66e89dd620f00afa982bd2484e3bf", size = 1562650, upload-time = "2026-09-18T04:50:15.315Z" }
|
|
3923
3923
|
wheels = [
|
|
3924
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
3924
|
+
{ url = "https://files.pythonhosted.org/packages/de/5b/1397c8c65eaeae781ec489db648ab24125268b749d1f256ba7c13b07891e/pydantic_ai_slim-2.45.0-py3-none-any.whl", hash = "sha256:0914f4e307c64401b676bbba2d613da69f74dbeaab4e2e8516d0d9b53f4ab483", size = 1857668, upload-time = "2026-09-18T04:50:07.395Z" },
|
|
3925
3925
|
]
|
|
3926
3926
|
|
|
3927
3927
|
[[package]]
|
|
@@ -4044,7 +4044,7 @@ wheels = [
|
|
|
4044
4044
|
|
|
4045
4045
|
[[package]]
|
|
4046
4046
|
name = "pydantic-graph"
|
|
4047
|
-
version = "2.
|
|
4047
|
+
version = "2.45.0"
|
|
4048
4048
|
source = { registry = "https://pypi.org/simple" }
|
|
4049
4049
|
dependencies = [
|
|
4050
4050
|
{ name = "anyio" },
|
|
@@ -4052,9 +4052,9 @@ dependencies = [
|
|
|
4052
4052
|
{ name = "pydantic" },
|
|
4053
4053
|
{ name = "typing-inspection" },
|
|
4054
4054
|
]
|
|
4055
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
4055
|
+
sdist = { url = "https://files.pythonhosted.org/packages/56/ee/d4f4c73eb91241af7a06e4c36ef58c18d3622dc03d8343825464ec06cb2e/pydantic_graph-2.45.0.tar.gz", hash = "sha256:c07e8cee55f25d79ea2ea55de67fc67d0353ec2807e3a1328f42995edf022c23", size = 45192, upload-time = "2026-09-18T04:50:17.706Z" }
|
|
4056
4056
|
wheels = [
|
|
4057
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
4057
|
+
{ url = "https://files.pythonhosted.org/packages/27/56/d040149787ae9ade628f05c1e7f30657437731b0e2bb3a2cdb6cb979b7af/pydantic_graph-2.45.0-py3-none-any.whl", hash = "sha256:60fef7eab40a103510f0baf6d17cdd11418672cb64a7524089fbaf845f45aa1a", size = 52701, upload-time = "2026-09-18T04:50:10.72Z" },
|
|
4058
4058
|
]
|
|
4059
4059
|
|
|
4060
4060
|
[[package]]
|