failproofai 1.0.12-beta.1 → 1.0.12
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 +4 -4
- 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.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 +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- 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.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 +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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.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.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 +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- 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 +2 -2
- 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.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.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.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 +8 -8
- 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/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__167mipqmjprq5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.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/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
- package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
- package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
- package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
- package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
- package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
- package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
- package/.next/standalone/app/actions/update-jev-config.ts +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +1 -1
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +28 -18
- package/.next/standalone/fp-cloud-cli/README.md +127 -9
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
- package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
- package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
- package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
- package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
- package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
- package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
- package/.next/standalone/hermes-plugin/__init__.py +11 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +11 -10
- package/.next/standalone/sdk/python/uv.lock +31 -8
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +1760 -638
- package/dist/worker.mjs +426 -76
- package/hermes-plugin/__init__.py +11 -0
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +24 -7
- package/src/hooks/agent-roster.ts +9 -1
- package/src/hooks/agent-skill.ts +206 -0
- package/src/hooks/cloud-connection.ts +27 -51
- package/src/hooks/cloud-introspect.ts +5 -2
- package/src/hooks/cloud-login.ts +248 -0
- package/src/hooks/cloud-managed-policies.ts +54 -4
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +9 -3
- package/src/hooks/configure-wizard.ts +356 -32
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +14 -0
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/fp-home.ts +1 -1
- package/src/hooks/handler.ts +43 -5
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +18 -3
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +7 -9
- package/src/hooks/manager.ts +80 -46
- package/src/hooks/policy-evaluator.ts +17 -0
- package/src/hooks/policy-reviewability.ts +4 -2
- package/src/hooks/semantic/cloud-jev.ts +219 -28
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/intent.ts +127 -24
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +6 -27
- package/src/hooks/semantic/jev-review.ts +111 -4
- package/src/hooks/tui.ts +280 -0
- package/src/hooks/uninstall-cli.ts +12 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
- package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
- package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
- package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
- package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI half of "Log in to FailproofAI Cloud" — `failproofai config`'s
|
|
3
|
+
* browser login.
|
|
4
|
+
*
|
|
5
|
+
* The CLI asks the dashboard for a link, a person approves it in a browser
|
|
6
|
+
* (signing up, signing in, picking or creating an org on the way), and the CLI
|
|
7
|
+
* — polling with a secret only it holds — sees the approval and, once setup has
|
|
8
|
+
* finished, claims a freshly minted machine key. The server side is
|
|
9
|
+
* `server/src/routes/cli_login.rs` in AgentEye; the relay is the dashboard's
|
|
10
|
+
* `/api/auth/cli/*`.
|
|
11
|
+
*
|
|
12
|
+
* Three calls, each with an injectable `fetch` so tests never reach the network:
|
|
13
|
+
* start → { verificationUrl, pollSecret, expiresIn, interval }
|
|
14
|
+
* poll → pending / slow_down / approved (org + who) / denied / expired / …
|
|
15
|
+
* claim → the key, exactly once. Called at APPLY, after the daemon installs:
|
|
16
|
+
* an approval that is never claimed leaves no key behind.
|
|
17
|
+
*
|
|
18
|
+
* Every call refuses to follow a redirect, for the reason `introspectKey` gives:
|
|
19
|
+
* a dashboard that does not know a path can answer with a login page, and a 200
|
|
20
|
+
* from that must never read as success.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const LOGIN_START_PATH = "/api/auth/cli/start";
|
|
24
|
+
export const LOGIN_POLL_PATH = "/api/auth/cli/poll";
|
|
25
|
+
export const LOGIN_CLAIM_PATH = "/api/auth/cli/claim";
|
|
26
|
+
|
|
27
|
+
const TIMEOUT_MS = 10_000;
|
|
28
|
+
|
|
29
|
+
export interface LoginOrg {
|
|
30
|
+
id?: string;
|
|
31
|
+
slug?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type StartResult =
|
|
36
|
+
| { kind: "ok"; verificationUrl: string; pollSecret: string; expiresIn: number; interval: number }
|
|
37
|
+
/** This cloud predates browser login. Pasting a key still works. */
|
|
38
|
+
| { kind: "unsupported" }
|
|
39
|
+
| { kind: "refused"; reason: string }
|
|
40
|
+
| { kind: "unreachable"; reason: string };
|
|
41
|
+
|
|
42
|
+
export type PollResult =
|
|
43
|
+
| { kind: "pending"; interval?: number }
|
|
44
|
+
| { kind: "slow_down" }
|
|
45
|
+
| { kind: "approved"; org: LoginOrg; approvedBy?: string; keyName?: string; permissions: string[] }
|
|
46
|
+
| { kind: "denied" }
|
|
47
|
+
| { kind: "expired" }
|
|
48
|
+
| { kind: "claimed" }
|
|
49
|
+
/** The server no longer knows this request (swept, or never existed). */
|
|
50
|
+
| { kind: "unknown" }
|
|
51
|
+
| { kind: "unreachable"; reason: string };
|
|
52
|
+
|
|
53
|
+
export type ClaimResult =
|
|
54
|
+
| {
|
|
55
|
+
kind: "ok";
|
|
56
|
+
key: string;
|
|
57
|
+
keyId?: string;
|
|
58
|
+
keyName?: string;
|
|
59
|
+
org: LoginOrg;
|
|
60
|
+
permissions: string[];
|
|
61
|
+
approvedBy?: string;
|
|
62
|
+
}
|
|
63
|
+
| { kind: "expired" }
|
|
64
|
+
/** The person who approved can no longer grant this key. */
|
|
65
|
+
| { kind: "revoked" }
|
|
66
|
+
| { kind: "refused"; reason: string }
|
|
67
|
+
| { kind: "unreachable"; reason: string };
|
|
68
|
+
|
|
69
|
+
type Fetch = typeof fetch;
|
|
70
|
+
|
|
71
|
+
async function postJson(
|
|
72
|
+
origin: string,
|
|
73
|
+
path: string,
|
|
74
|
+
body: unknown,
|
|
75
|
+
fetchImpl: Fetch,
|
|
76
|
+
): Promise<{ status: number; json: Record<string, unknown> | null } | { error: string }> {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetchImpl(`${origin.replace(/\/+$/, "")}${path}`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify(body),
|
|
84
|
+
redirect: "manual",
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
let json: Record<string, unknown> | null = null;
|
|
88
|
+
try {
|
|
89
|
+
const parsed: unknown = await res.json();
|
|
90
|
+
json = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;
|
|
91
|
+
} catch {
|
|
92
|
+
json = null;
|
|
93
|
+
}
|
|
94
|
+
return { status: res.status, json };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// Deliberately not the URL: setup is often run while screen-sharing.
|
|
97
|
+
return { error: err instanceof Error ? (err.name === "AbortError" ? "timed out" : err.message) : String(err) };
|
|
98
|
+
} finally {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const str = (v: unknown): string | undefined => (typeof v === "string" && v.length > 0 ? v : undefined);
|
|
104
|
+
|
|
105
|
+
// Controls (a terminal executes them: clear the screen, retitle the window,
|
|
106
|
+
// write the clipboard), bidi overrides and isolates, and zero-width formatting.
|
|
107
|
+
const INVISIBLE = /[\u0000-\u001f\u007f-\u009f\u061c\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/g;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A server-supplied string made safe to print. Org names are chosen by whoever
|
|
111
|
+
* created the org and the CLI prints the one it was approved into — the line
|
|
112
|
+
* that is meant to show who approved what — so none of it may reach the
|
|
113
|
+
* terminal as anything but text.
|
|
114
|
+
*/
|
|
115
|
+
export function displayText(v: unknown, max = 200): string | undefined {
|
|
116
|
+
if (typeof v !== "string") return undefined;
|
|
117
|
+
const t = v.replace(INVISIBLE, "").trim().slice(0, max);
|
|
118
|
+
return t.length > 0 ? t : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function org(v: unknown): LoginOrg {
|
|
122
|
+
const o = v && typeof v === "object" ? (v as Record<string, unknown>) : {};
|
|
123
|
+
return { id: str(o.id), slug: displayText(o.slug), name: displayText(o.name) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function permissions(v: unknown): string[] {
|
|
127
|
+
return Array.isArray(v) ? v.map((p) => displayText(p, 64)).filter((p): p is string => p !== undefined) : [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A server's number, kept to a range this client can live with: a huge poll
|
|
131
|
+
* interval overflows `setTimeout` into a 1 ms storm, a tiny one polls flat out. */
|
|
132
|
+
const within = (v: unknown, lo: number, hi: number, fallback: number): number =>
|
|
133
|
+
typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.min(hi, Math.max(lo, v)) : fallback;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Only an http(s) link is ever handed to the browser opener. The URL comes from
|
|
137
|
+
* the server, and `xdg-open` / `open` will happily act on any scheme they know.
|
|
138
|
+
*/
|
|
139
|
+
export function isOpenableUrl(raw: string): boolean {
|
|
140
|
+
try {
|
|
141
|
+
const u = new URL(raw);
|
|
142
|
+
return u.protocol === "https:" || u.protocol === "http:";
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** What the browser page should call this machine and its OS. */
|
|
149
|
+
export function machineOs(platform: NodeJS.Platform = process.platform): string {
|
|
150
|
+
return platform === "darwin" ? "macOS" : platform === "win32" ? "windows" : platform;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function startLogin(
|
|
154
|
+
origin: string,
|
|
155
|
+
machine: { label: string; os: string; cliVersion: string },
|
|
156
|
+
fetchImpl: Fetch = fetch,
|
|
157
|
+
): Promise<StartResult> {
|
|
158
|
+
const r = await postJson(
|
|
159
|
+
origin,
|
|
160
|
+
LOGIN_START_PATH,
|
|
161
|
+
{ machine_label: machine.label, machine_os: machine.os, cli_version: machine.cliVersion },
|
|
162
|
+
fetchImpl,
|
|
163
|
+
);
|
|
164
|
+
if ("error" in r) return { kind: "unreachable", reason: r.error };
|
|
165
|
+
// No such route, the web app redirecting an unknown path, or serving a page
|
|
166
|
+
// (200, not JSON) in its place: a cloud from before this feature. Not an
|
|
167
|
+
// error — the key prompt still connects it.
|
|
168
|
+
if (r.status === 404 || (r.status >= 300 && r.status < 400) || (r.status === 200 && !r.json)) {
|
|
169
|
+
return { kind: "unsupported" };
|
|
170
|
+
}
|
|
171
|
+
if (r.status === 429) return { kind: "refused", reason: "too many login attempts from here — wait a minute" };
|
|
172
|
+
if (r.status !== 201 || !r.json) {
|
|
173
|
+
return r.status >= 500
|
|
174
|
+
? { kind: "unreachable", reason: `the server answered ${r.status}` }
|
|
175
|
+
: { kind: "refused", reason: displayText(r.json?.error, 120) ?? `the server answered ${r.status}` };
|
|
176
|
+
}
|
|
177
|
+
const verificationUrl = str(r.json.verification_url);
|
|
178
|
+
const pollSecret = str(r.json.poll_secret);
|
|
179
|
+
if (!verificationUrl || !pollSecret || !isOpenableUrl(verificationUrl)) {
|
|
180
|
+
// A 201 without the fields is something other than this API.
|
|
181
|
+
return { kind: "unsupported" };
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
kind: "ok",
|
|
185
|
+
// Re-serialised, never the raw string: it is printed, and the parser
|
|
186
|
+
// percent-encodes every control and non-ASCII character in it.
|
|
187
|
+
verificationUrl: new URL(verificationUrl).href,
|
|
188
|
+
pollSecret,
|
|
189
|
+
expiresIn: within(r.json.expires_in, 30, 3600, 600),
|
|
190
|
+
interval: within(r.json.interval, 1, 30, 2),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function pollLogin(origin: string, pollSecret: string, fetchImpl: Fetch = fetch): Promise<PollResult> {
|
|
195
|
+
const r = await postJson(origin, LOGIN_POLL_PATH, { poll_secret: pollSecret }, fetchImpl);
|
|
196
|
+
if ("error" in r) return { kind: "unreachable", reason: r.error };
|
|
197
|
+
if (r.status === 404) return { kind: "unknown" };
|
|
198
|
+
if (r.status !== 200 || !r.json) return { kind: "unreachable", reason: `the server answered ${r.status}` };
|
|
199
|
+
const j = r.json;
|
|
200
|
+
switch (j.status) {
|
|
201
|
+
case "pending":
|
|
202
|
+
return { kind: "pending", interval: typeof j.interval === "number" ? within(j.interval, 1, 30, 2) : undefined };
|
|
203
|
+
case "slow_down":
|
|
204
|
+
return { kind: "slow_down" };
|
|
205
|
+
case "approved":
|
|
206
|
+
return {
|
|
207
|
+
kind: "approved",
|
|
208
|
+
org: org(j.org),
|
|
209
|
+
approvedBy: displayText(j.approved_by),
|
|
210
|
+
keyName: displayText(j.key_name),
|
|
211
|
+
permissions: permissions(j.permissions),
|
|
212
|
+
};
|
|
213
|
+
case "denied":
|
|
214
|
+
return { kind: "denied" };
|
|
215
|
+
case "expired":
|
|
216
|
+
return { kind: "expired" };
|
|
217
|
+
case "claimed":
|
|
218
|
+
return { kind: "claimed" };
|
|
219
|
+
default:
|
|
220
|
+
return { kind: "unknown" };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function claimLogin(origin: string, pollSecret: string, fetchImpl: Fetch = fetch): Promise<ClaimResult> {
|
|
225
|
+
const r = await postJson(origin, LOGIN_CLAIM_PATH, { poll_secret: pollSecret }, fetchImpl);
|
|
226
|
+
if ("error" in r) return { kind: "unreachable", reason: r.error };
|
|
227
|
+
const j = r.json ?? {};
|
|
228
|
+
if (r.status === 201) {
|
|
229
|
+
const key = str(j.api_key);
|
|
230
|
+
if (!key) return { kind: "refused", reason: "the server answered without a key" };
|
|
231
|
+
return {
|
|
232
|
+
kind: "ok",
|
|
233
|
+
key,
|
|
234
|
+
keyId: str(j.key_id),
|
|
235
|
+
keyName: displayText(j.key_name),
|
|
236
|
+
org: org(j.org),
|
|
237
|
+
permissions: permissions(j.permissions),
|
|
238
|
+
approvedBy: displayText(j.approved_by),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (r.status === 410) return { kind: "expired" };
|
|
242
|
+
if (r.status === 403 && j.error === "approval_revoked") return { kind: "revoked" };
|
|
243
|
+
if (r.status >= 500) return { kind: "unreachable", reason: `the server answered ${r.status}` };
|
|
244
|
+
return {
|
|
245
|
+
kind: "refused",
|
|
246
|
+
reason: displayText(j.status, 40) ? `the login request is ${displayText(j.status, 40)}` : `the server answered ${r.status}`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -170,10 +170,11 @@ export function resolveManagedPath(root: string, candidate: string): string {
|
|
|
170
170
|
* Stop enforcing cloud-managed policies on this machine.
|
|
171
171
|
*
|
|
172
172
|
* Removes `desired-state.json` FIRST, then `active.json` — the manifest that
|
|
173
|
-
* says which deployment is live — both error reports
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
173
|
+
* says which deployment is live — both error reports, the Jev budget record,
|
|
174
|
+
* the load-check record and the content-addressed `artifacts/` directory.
|
|
175
|
+
* The departed organisation's policy source is removed too; a reconnect
|
|
176
|
+
* downloads the artifacts its deployment names. Cleanup runs only in the
|
|
177
|
+
* CLI-owned default directory, never a `FAILPROOFAI_CLOUD_POLICY_DIR` override.
|
|
177
178
|
*
|
|
178
179
|
* The snapshot has to go, and before the pointer. It is what the daemon's
|
|
179
180
|
* maintenance lane rebuilds a lost `active.json` from, and it used to be left
|
|
@@ -400,6 +401,55 @@ export function readCloudJevState(): CloudJevState {
|
|
|
400
401
|
return { jevMode, deployment, errors };
|
|
401
402
|
}
|
|
402
403
|
|
|
404
|
+
// ── Which Cloud Jev checks this agent has (jevPolicies) ─────────────────────
|
|
405
|
+
|
|
406
|
+
/** One `jev` / `both` assignment whose Jev checks FailproofAI Cloud asks here. */
|
|
407
|
+
export interface CloudJevAssignment {
|
|
408
|
+
id: string;
|
|
409
|
+
version: number;
|
|
410
|
+
effect: PolicyEffect;
|
|
411
|
+
agentTargets?: AgentTarget[];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* `active.json`'s `jevPolicies`: the Cloud Jev assignments of this deployment
|
|
416
|
+
* (ids, effects and scopes, never a question). `null` when the manifest does
|
|
417
|
+
* not carry the list (a server that predates it) or carries one this build
|
|
418
|
+
* cannot read: then nothing is known, and every gated call is still sent to
|
|
419
|
+
* Cloud, as before. Never throws.
|
|
420
|
+
*/
|
|
421
|
+
export function readCloudJevAssignments(): CloudJevAssignment[] | null {
|
|
422
|
+
try {
|
|
423
|
+
const active = readActiveRaw();
|
|
424
|
+
if (!active || !Array.isArray(active.jevPolicies)) return null;
|
|
425
|
+
const schema = active.schemaVersion as number;
|
|
426
|
+
const out: CloudJevAssignment[] = [];
|
|
427
|
+
for (const raw of active.jevPolicies as unknown[]) {
|
|
428
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
429
|
+
const entry = raw as Record<string, unknown>;
|
|
430
|
+
if (typeof entry.id !== "string" || !POLICY_ID_RE.test(entry.id)) return null;
|
|
431
|
+
if (!Number.isSafeInteger(entry.version) || (entry.version as number) < 0) return null;
|
|
432
|
+
if (entry.effect !== undefined && entry.effect !== "enforce" && entry.effect !== "observe") return null;
|
|
433
|
+
const targets = parseAgentTargets(entry.agentTargets, schema);
|
|
434
|
+
out.push({
|
|
435
|
+
id: entry.id,
|
|
436
|
+
version: entry.version as number,
|
|
437
|
+
effect: entry.effect === "observe" ? "observe" : "enforce",
|
|
438
|
+
...(targets ? { agentTargets: targets } : {}),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
} catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** The Cloud Jev assignments that apply to `agent`, or null when unknown (see {@link readCloudJevAssignments}). */
|
|
448
|
+
export function cloudJevAssignmentsFor(agent: AgentIdentity | null): CloudJevAssignment[] | null {
|
|
449
|
+
const all = readCloudJevAssignments();
|
|
450
|
+
return all === null ? null : all.filter((a) => agentTargetsMatch(a.agentTargets, agent));
|
|
451
|
+
}
|
|
452
|
+
|
|
403
453
|
// ── `both` policies: reviewed by their OWN Cloud checks ─────────────────────
|
|
404
454
|
|
|
405
455
|
/**
|
|
@@ -432,10 +432,16 @@ export type CodexHookTrust =
|
|
|
432
432
|
/** In hooks.json and not all trusted: Codex skips the untrusted ones. */
|
|
433
433
|
| { kind: "untrusted"; path: string; untrusted: number; total: number };
|
|
434
434
|
|
|
435
|
+
/** The Codex profile's home: `CODEX_HOME` when set, else `~/.codex`. */
|
|
436
|
+
export function codexHome(): string {
|
|
437
|
+
const dir = process.env.CODEX_HOME?.trim();
|
|
438
|
+
return dir ? resolve(dir) : resolve(homedir(), ".codex");
|
|
439
|
+
}
|
|
440
|
+
|
|
435
441
|
export interface CodexTrustDeps extends CodexSystemDeps {
|
|
436
|
-
/** `~/.codex/hooks.json
|
|
442
|
+
/** `$CODEX_HOME/hooks.json` (`~/.codex/hooks.json`). */
|
|
437
443
|
hooksJsonPath?: string;
|
|
438
|
-
/**
|
|
444
|
+
/** `$CODEX_HOME/config.toml`, where Codex keeps `[hooks.state]`. */
|
|
439
445
|
userConfigPath?: string;
|
|
440
446
|
}
|
|
441
447
|
|
|
@@ -452,7 +458,7 @@ export function codexHookTrust(deps: CodexTrustDeps = {}): CodexHookTrust {
|
|
|
452
458
|
const systemPath = resolvedPath(deps);
|
|
453
459
|
if (systemPath && codexSystemHooksPresent({ path: systemPath })) return { kind: "system", path: systemPath };
|
|
454
460
|
|
|
455
|
-
const hooksJsonPath = deps.hooksJsonPath ?? resolve(
|
|
461
|
+
const hooksJsonPath = deps.hooksJsonPath ?? resolve(codexHome(), "hooks.json");
|
|
456
462
|
const ours = failproofaiCodexHookIdentities(hooksJsonPath);
|
|
457
463
|
if (ours.length === 0) return { kind: "none" };
|
|
458
464
|
|
|
@@ -462,7 +468,7 @@ export function codexHookTrust(deps: CodexTrustDeps = {}): CodexHookTrust {
|
|
|
462
468
|
let trustedHashes = new Map<string, string>();
|
|
463
469
|
try {
|
|
464
470
|
trustedHashes = parseCodexTrustedHashes(
|
|
465
|
-
readFileSync(deps.userConfigPath ?? resolve(
|
|
471
|
+
readFileSync(deps.userConfigPath ?? resolve(codexHome(), "config.toml"), "utf8"),
|
|
466
472
|
);
|
|
467
473
|
} catch {
|
|
468
474
|
// No user config: nothing is trusted.
|
|
@@ -27,7 +27,7 @@ import { readCloudCredentials } from "./cloud-enrollment";
|
|
|
27
27
|
import { CODEX_UNTRUSTED_NOTICE, codexHookTrust, type CodexHookTrust } from "./codex-system-hooks";
|
|
28
28
|
import { describeMachine } from "./cloud-enrollment-cli";
|
|
29
29
|
import { cloudBaseFor } from "./cloud-connection";
|
|
30
|
-
import {
|
|
30
|
+
import { listActiveCloudManagedPolicies } from "./cloud-managed-policies";
|
|
31
31
|
import { readIngestCredential } from "./collector-config";
|
|
32
32
|
import { discoverPolicyFiles } from "./custom-hooks-loader";
|
|
33
33
|
import { daemonSocketPresent, isDaemonConfigured } from "./daemon-client";
|
|
@@ -259,7 +259,10 @@ export function summarizePolicySources(cwd?: string): StatusPolicies {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
try {
|
|
262
|
-
|
|
262
|
+
// Every assignment, whatever agent it targets: a hook's agent scope applied
|
|
263
|
+
// with no agent hid each targeted policy ("None installed" beside a
|
|
264
|
+
// deployment that enforces), the status twin of B4.
|
|
265
|
+
for (const artifact of listActiveCloudManagedPolicies()) {
|
|
263
266
|
if (artifact.effect === "observe") summary.observing++;
|
|
264
267
|
else summary.cloudOn++;
|
|
265
268
|
}
|
|
@@ -525,7 +528,10 @@ export function renderStatusScreen(facts: StatusFacts, opts: ScreenKitOpts & { c
|
|
|
525
528
|
return kit.caution(`No report from failproofaid for ${formatDuration(now - health.writtenAt)}`);
|
|
526
529
|
}
|
|
527
530
|
if (!health.delivering) return `${kit.off} Not delivering, failproofaid has no key`;
|
|
528
|
-
|
|
531
|
+
// Batches an earlier key had refused wait for the daemon to re-send them
|
|
532
|
+
// with the key that works now: queued, not refused.
|
|
533
|
+
const pending = queued + (refused.resending ?? 0);
|
|
534
|
+
const backlog = pending === 0 ? "nothing queued" : `${many(pending, "batch", "batches")} queued`;
|
|
529
535
|
const stuck =
|
|
530
536
|
queued > 0 &&
|
|
531
537
|
(oldestQueuedMs ?? 0) > STATUS_QUEUE_STUCK_MS &&
|