failproofai 0.0.11-beta.7 → 0.0.11-beta.9
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/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- 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 +7 -7
- 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 +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- 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/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +7 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +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 +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +2 -2
- 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/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- 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/build-manifest.json +3 -3
- 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 +3 -3
- 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/build-manifest.json +3 -3
- 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 +3 -3
- 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/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- 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-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_audit_invite_route_actions_0-2n5sy.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_0ttxbz7._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1epycqa._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1wpdcgo._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +8 -8
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1cd25c7._.js → [root-of-the-server]__0e4-6d8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08gyb62._.js → [root-of-the-server]__0e446gb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-scthx._.js → [root-of-the-server]__0f62vu9._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l13qf2._.js → [root-of-the-server]__0k65l27._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08v7sn2._.js → [root-of-the-server]__0wprfyc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0989_dx._.js → [root-of-the-server]__1e-x7j4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0il3fl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +21 -21
- 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/node_modules_html-to-image_es_index_0y4a-0q.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/functions-config-manifest.json +0 -3
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- 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 +10 -10
- package/.next/standalone/.next/static/chunks/{0hmxjseydp6as.js → 07_d165p5h5ys.js} +1 -1
- package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +1 -0
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +1 -0
- package/.next/standalone/.next/static/chunks/168k-8z6k7e8z.css +1 -0
- package/.next/standalone/.next/static/chunks/{3n28elq6kwpy3.js → 1kvadxkgnapyj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fpw1jzeg66_x.js → 277oc363p56n6.js} +2 -2
- package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +1 -0
- package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +41 -0
- package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +2 -0
- package/.next/standalone/.next/static/chunks/{41vwoxpkauj1w.js → 29tg7deqmq32l.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1eq7f419t6ln3.js → 2z42u62k-8-_q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0avgb78edu0i-.js → 3nj6g3xu9uy78.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0xkxbaa-oo180.js → 3ty6dhcuogout.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-00qy7zfa7m--m.js → turbopack-3lrm4f20fz89b.js} +1 -1
- package/.next/standalone/app/api/audit/_state.ts +40 -33
- package/.next/standalone/app/api/audit/invite/route.ts +183 -0
- package/.next/standalone/app/api/audit/run/route.ts +30 -16
- package/.next/standalone/app/api/audit/status/route.ts +1 -0
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +33 -65
- package/.next/standalone/app/audit/_components/audit-poster.tsx +322 -0
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +21 -49
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +316 -0
- package/.next/standalone/app/audit/_components/empty-state.tsx +8 -2
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
- package/.next/standalone/app/audit/_components/invite-dialog.tsx +227 -0
- package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
- package/.next/standalone/app/audit/_components/rerun-button.tsx +43 -14
- package/.next/standalone/app/audit/_components/run-progress.tsx +2 -2
- package/.next/standalone/app/audit/_components/share-templates.ts +23 -22
- package/.next/standalone/app/audit/_components/sigil.tsx +9 -66
- package/.next/standalone/app/audit/_components/strengths-section.tsx +20 -32
- package/.next/standalone/app/audit/audit-styles.css +781 -1784
- package/.next/standalone/app/components/sessions-list.tsx +77 -80
- package/.next/standalone/app/globals.css +214 -32
- package/.next/standalone/app/layout.tsx +1 -10
- package/.next/standalone/app/policies/hooks-client.tsx +12 -4
- package/.next/standalone/app/project/[name]/page.tsx +23 -79
- package/.next/standalone/app/projects/page.tsx +14 -23
- package/.next/standalone/assets/audit/poster-styles.css +1 -1
- package/.next/standalone/assets/audit/styles.css +11 -11
- package/.next/standalone/components/navbar.tsx +2 -37
- package/.next/standalone/lib/auth/api-server-client.ts +25 -0
- package/.next/standalone/node_modules/@next/env/package.json +2 -2
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +15 -15
- package/.next/standalone/package.json +18 -15
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +2 -2
- package/lib/auth/api-server-client.ts +25 -0
- package/package.json +18 -15
- package/src/audit/dashboard-cache.ts +13 -6
- package/src/audit/social-proof.ts +34 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07tgnzi._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oeun7z._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12pit4m._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13ra2jq._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b9z5-i._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ixjiy8._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1-1804f._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00jkjmt._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013du6r._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e85wxv._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gfxvb1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hlrq6y._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ihxdo5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1vvfde2._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_0q4m0_h.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1n-0xws.js +0 -3
- package/.next/standalone/.next/static/chunks/09ueq8s1as8xs.css +0 -2
- package/.next/standalone/.next/static/chunks/0qassxjx1ef04.js +0 -1
- package/.next/standalone/.next/static/chunks/2452blhu4bcdr.js +0 -41
- package/.next/standalone/.next/static/chunks/2so39wg7mjbi7.js +0 -1
- package/.next/standalone/.next/static/chunks/3gti1qdk5epqn.js +0 -1
- package/.next/standalone/.next/static/chunks/3osmt-t2hsrng.js +0 -1
- package/.next/standalone/.next/static/chunks/3wycox197ouus.css +0 -1
- package/.next/standalone/app/audit/_components/findings-section.tsx +0 -135
- package/.next/standalone/app/audit/_components/identity-section.tsx +0 -126
- package/.next/standalone/app/audit/_components/policies-section.tsx +0 -194
- package/.next/standalone/app/audit/_components/return-section.tsx +0 -416
- package/.next/standalone/app/audit/_components/score-section.tsx +0 -179
- package/.next/standalone/app/audit/_components/share-dock.tsx +0 -265
- package/.next/standalone/app/audit/_components/show-off-cta.tsx +0 -135
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section 04 — HOW TO IMPROVE. Calm row list, one per prescribed
|
|
5
|
+
* policy:
|
|
6
|
+
*
|
|
7
|
+
* <policy-name> $ failproofai policy add <slug> [📋]
|
|
8
|
+
* <one-line explanation>
|
|
9
|
+
*
|
|
10
|
+
* A single "install all" button at the section header copies the
|
|
11
|
+
* combined install command for every prescribed policy.
|
|
12
|
+
*/
|
|
13
|
+
import React, { useMemo, useState } from "react";
|
|
14
|
+
import type { AuditResult } from "@/src/audit/types";
|
|
15
|
+
import { type Grade, tierName } from "@/src/audit/scoring";
|
|
16
|
+
import { usePostHog } from "@/contexts/PostHogContext";
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
result: AuditResult;
|
|
20
|
+
projected: number;
|
|
21
|
+
projectedGrade: Grade;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DETECTOR_TO_PRIMARY_POLICY: Record<string, string> = {
|
|
25
|
+
"redundant-cd-cwd": "warn-repeated-tool-calls",
|
|
26
|
+
"prefer-edit-over-read-cat": "block-read-outside-cwd",
|
|
27
|
+
"prefer-edit-over-sed-awk": "warn-repeated-tool-calls",
|
|
28
|
+
"prefer-write-over-heredoc": "block-env-files",
|
|
29
|
+
"sleep-polling-loop": "warn-background-process",
|
|
30
|
+
"find-from-root": "block-read-outside-cwd",
|
|
31
|
+
"git-commit-no-verify": "warn-git-amend",
|
|
32
|
+
"reread-after-edit": "warn-repeated-tool-calls",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const POLICY_DESC: Record<string, string> = {
|
|
36
|
+
"warn-repeated-tool-calls": "warns when the same tool is called 3+ times with identical parameters.",
|
|
37
|
+
"block-read-outside-cwd": "denies any file read outside the project root, including symlinks.",
|
|
38
|
+
"block-env-files": "blocks reads and writes of .env files at the tool layer.",
|
|
39
|
+
"block-secrets-write": "blocks writes to .pem, id_rsa, credentials.json, and other secret-key files.",
|
|
40
|
+
"warn-background-process": "warns before starting nohup / & / screen / tmux processes.",
|
|
41
|
+
"warn-git-amend": "warns before amending git commits.",
|
|
42
|
+
"require-ci-green-before-stop": "requires CI checks to pass on HEAD before the agent declares the task done.",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function shortName(name: string): string {
|
|
46
|
+
const slash = name.indexOf("/");
|
|
47
|
+
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FixRow {
|
|
51
|
+
name: string;
|
|
52
|
+
desc: string;
|
|
53
|
+
hits: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildFixes(result: AuditResult): FixRow[] {
|
|
57
|
+
const enabledSet = new Set(result.enabledBuiltinNames ?? []);
|
|
58
|
+
const buckets = new Map<string, number>();
|
|
59
|
+
|
|
60
|
+
for (const row of result.results) {
|
|
61
|
+
if (row.hits === 0) continue;
|
|
62
|
+
|
|
63
|
+
let target: string;
|
|
64
|
+
if (row.source === "audit-detector") {
|
|
65
|
+
const mapped = DETECTOR_TO_PRIMARY_POLICY[shortName(row.name)];
|
|
66
|
+
if (!mapped) continue;
|
|
67
|
+
target = mapped;
|
|
68
|
+
} else if (row.source === "builtin" && !row.enabledInConfig) {
|
|
69
|
+
target = shortName(row.name);
|
|
70
|
+
} else {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (enabledSet.has(target)) continue;
|
|
75
|
+
buckets.set(target, (buckets.get(target) ?? 0) + row.hits);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [...buckets.entries()]
|
|
79
|
+
.sort((a, b) => b[1] - a[1])
|
|
80
|
+
.map(([name, hits]) => ({
|
|
81
|
+
name,
|
|
82
|
+
desc: POLICY_DESC[name] ?? "enable this builtin policy to close the gap.",
|
|
83
|
+
hits,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function bulkInstall(fixes: FixRow[]): string {
|
|
88
|
+
if (fixes.length === 0) return "";
|
|
89
|
+
if (fixes.length === 1) return `failproofai policy add ${fixes[0]!.name}`;
|
|
90
|
+
return `failproofai policy add ${fixes.map((f) => f.name).join(" ")}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function HowToImproveSection({ result, projected, projectedGrade }: Props) {
|
|
94
|
+
const { capture } = usePostHog();
|
|
95
|
+
const fixes = useMemo(() => buildFixes(result), [result]);
|
|
96
|
+
const installAllCmd = useMemo(() => bulkInstall(fixes), [fixes]);
|
|
97
|
+
const [copiedAll, setCopiedAll] = useState(false);
|
|
98
|
+
|
|
99
|
+
if (fixes.length === 0) return null;
|
|
100
|
+
|
|
101
|
+
const handleInstallAll = async () => {
|
|
102
|
+
try {
|
|
103
|
+
await navigator.clipboard.writeText(installAllCmd);
|
|
104
|
+
setCopiedAll(true);
|
|
105
|
+
capture("audit_copy_clicked", {
|
|
106
|
+
source: "how_to_improve_section_install_all",
|
|
107
|
+
item_type: "bulk_install_command",
|
|
108
|
+
policy_count: fixes.length,
|
|
109
|
+
});
|
|
110
|
+
setTimeout(() => setCopiedAll(false), 1500);
|
|
111
|
+
} catch { /* ignore */ }
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<section className="audit-sec" data-screen-label="04 How to improve">
|
|
116
|
+
<div className="audit-sec-head">
|
|
117
|
+
<span className="audit-sec-eyebrow">
|
|
118
|
+
<span className="ix">04</span>{"// how to improve"}
|
|
119
|
+
</span>
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className="install-all-btn"
|
|
123
|
+
onClick={handleInstallAll}
|
|
124
|
+
aria-label="Copy install-all command"
|
|
125
|
+
>
|
|
126
|
+
{copiedAll ? "copied" : "install all"}
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
<h2 className="audit-sec-title">install or configure</h2>
|
|
130
|
+
<div className="audit-sec-sub">
|
|
131
|
+
enable all {fixes.length === 1 ? "one" : fixes.length} → projected{" "}
|
|
132
|
+
<strong>{projected}</strong> · {tierName(projectedGrade).toLowerCase()}
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div className="fix-list">
|
|
136
|
+
{fixes.map((f, i) => (
|
|
137
|
+
<FixRow key={f.name} fix={f} idx={i} />
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function FixRow({ fix, idx }: { fix: FixRow; idx: number }) {
|
|
145
|
+
const { capture } = usePostHog();
|
|
146
|
+
const [copied, setCopied] = useState(false);
|
|
147
|
+
const install = `failproofai policy add ${fix.name}`;
|
|
148
|
+
|
|
149
|
+
const handleCopy = async () => {
|
|
150
|
+
try {
|
|
151
|
+
await navigator.clipboard.writeText(install);
|
|
152
|
+
setCopied(true);
|
|
153
|
+
capture("audit_copy_clicked", {
|
|
154
|
+
source: "how_to_improve_section",
|
|
155
|
+
item_type: "single_policy_install_command",
|
|
156
|
+
policy_name: fix.name,
|
|
157
|
+
policy_rank: idx + 1,
|
|
158
|
+
});
|
|
159
|
+
setTimeout(() => setCopied(false), 1500);
|
|
160
|
+
} catch { /* ignore */ }
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="fix-row">
|
|
165
|
+
<div className="fix-row-info">
|
|
166
|
+
<div className="fix-name">{fix.name}</div>
|
|
167
|
+
<div className="fix-desc">{fix.desc}</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="fix-row-cmd">
|
|
170
|
+
<code className="fix-cmd-code">{install}</code>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
className="copy-icon-btn"
|
|
174
|
+
onClick={handleCopy}
|
|
175
|
+
aria-label={`Copy install command for ${fix.name}`}
|
|
176
|
+
>
|
|
177
|
+
{copied ? "✓" : (
|
|
178
|
+
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
|
|
179
|
+
<rect x="3" y="3" width="9" height="11" fill="none" stroke="currentColor" strokeWidth="1.2" />
|
|
180
|
+
<rect x="5.5" y="0.5" width="9" height="11" fill="none" stroke="currentColor" strokeWidth="1.2" />
|
|
181
|
+
</svg>
|
|
182
|
+
)}
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Invite dialog — modal that takes a comma/newline-separated list of friend
|
|
5
|
+
* emails and POSTs them to /api/audit/invite. The api-server composes the
|
|
6
|
+
* actual invite email (Cc'ing the sender) using the same email infrastructure
|
|
7
|
+
* that backs the OTP flow.
|
|
8
|
+
*
|
|
9
|
+
* Anonymous users get bounced through the AuthDialog by the caller; this
|
|
10
|
+
* component assumes the session is already established.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { usePostHog } from "@/contexts/PostHogContext";
|
|
15
|
+
import { toast } from "@/app/components/toast";
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
/** Source tag for PostHog so we can split "invited from come-back-better"
|
|
21
|
+
* from any future entry points. */
|
|
22
|
+
source: string;
|
|
23
|
+
/** Called when the proxy returns 401 (session expired between probe and
|
|
24
|
+
* submit). The parent should re-open the AuthDialog so the user can
|
|
25
|
+
* re-authenticate; without this, a 401 dead-ends with an inline error. */
|
|
26
|
+
onUnauthorized?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
30
|
+
const MAX_RECIPIENTS = 10;
|
|
31
|
+
|
|
32
|
+
function parseEmails(input: string): { valid: string[]; invalid: string[] } {
|
|
33
|
+
const tokens = input
|
|
34
|
+
.split(/[\s,;]+/)
|
|
35
|
+
.map((t) => t.trim().toLowerCase())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
const valid: string[] = [];
|
|
38
|
+
const invalid: string[] = [];
|
|
39
|
+
const seen = new Set<string>();
|
|
40
|
+
for (const t of tokens) {
|
|
41
|
+
if (seen.has(t)) continue;
|
|
42
|
+
seen.add(t);
|
|
43
|
+
if (EMAIL_RE.test(t)) valid.push(t);
|
|
44
|
+
else invalid.push(t);
|
|
45
|
+
}
|
|
46
|
+
return { valid, invalid };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function InviteDialog({ open, onClose, source, onUnauthorized }: Props): React.ReactElement | null {
|
|
50
|
+
const { capture } = usePostHog();
|
|
51
|
+
const [value, setValue] = useState("");
|
|
52
|
+
const [busy, setBusy] = useState(false);
|
|
53
|
+
const [error, setError] = useState<string | null>(null);
|
|
54
|
+
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (open) {
|
|
58
|
+
setValue("");
|
|
59
|
+
setBusy(false);
|
|
60
|
+
setError(null);
|
|
61
|
+
capture("audit_invite_dialog_opened", { source });
|
|
62
|
+
}
|
|
63
|
+
}, [capture, open, source]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!open) return;
|
|
67
|
+
const t = setTimeout(() => inputRef.current?.focus(), 50);
|
|
68
|
+
return () => clearTimeout(t);
|
|
69
|
+
}, [open]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!open) return;
|
|
73
|
+
const onKey = (e: KeyboardEvent): void => {
|
|
74
|
+
if (e.key === "Escape" && !busy) onClose();
|
|
75
|
+
};
|
|
76
|
+
window.addEventListener("keydown", onKey);
|
|
77
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
78
|
+
}, [open, busy, onClose]);
|
|
79
|
+
|
|
80
|
+
const { valid, invalid } = useMemo(() => parseEmails(value), [value]);
|
|
81
|
+
|
|
82
|
+
const submit = useCallback(
|
|
83
|
+
async (e: React.FormEvent<HTMLFormElement>) => {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
if (busy) return;
|
|
86
|
+
if (valid.length === 0) {
|
|
87
|
+
setError("add at least one valid email address.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (valid.length > MAX_RECIPIENTS) {
|
|
91
|
+
setError(`up to ${MAX_RECIPIENTS} emails at a time. send the rest in a follow-up.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setBusy(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
capture("audit_invite_submitted", {
|
|
97
|
+
source,
|
|
98
|
+
recipient_count: valid.length,
|
|
99
|
+
had_invalid: invalid.length > 0,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
const res = await fetch("/api/audit/invite", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "content-type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ to: valid }),
|
|
106
|
+
});
|
|
107
|
+
const body = (await res.json().catch(() => ({}))) as {
|
|
108
|
+
sent?: string[];
|
|
109
|
+
failed?: string[];
|
|
110
|
+
code?: string;
|
|
111
|
+
message?: string;
|
|
112
|
+
};
|
|
113
|
+
if (res.status === 401) {
|
|
114
|
+
// Session expired between probe and submit — route back through auth.
|
|
115
|
+
// Without this, repeated submits would dead-end with the same 401.
|
|
116
|
+
if (onUnauthorized) {
|
|
117
|
+
onClose();
|
|
118
|
+
onUnauthorized();
|
|
119
|
+
} else {
|
|
120
|
+
setError("session expired. please sign in again.");
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
const msg = body.message ?? "couldn't send invites.";
|
|
126
|
+
setError(msg);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const sent = body.sent?.length ?? 0;
|
|
130
|
+
const failed = body.failed?.length ?? 0;
|
|
131
|
+
toast(
|
|
132
|
+
failed > 0
|
|
133
|
+
? `📨 sent ${sent}, ${failed} bounced — copy the bounce and try again.`
|
|
134
|
+
: `📨 sent ${sent} ${sent === 1 ? "invite" : "invites"}. thanks for spreading the word.`,
|
|
135
|
+
);
|
|
136
|
+
onClose();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
+
setError(`network error: ${message}`);
|
|
140
|
+
} finally {
|
|
141
|
+
setBusy(false);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[busy, valid, invalid, capture, source, onClose, onUnauthorized],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (!open) return null;
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
className="auth-dialog-backdrop"
|
|
152
|
+
role="dialog"
|
|
153
|
+
aria-modal="true"
|
|
154
|
+
aria-labelledby="invite-dialog-title"
|
|
155
|
+
onClick={(e) => {
|
|
156
|
+
if (!busy && e.target === e.currentTarget) onClose();
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<div className="auth-dialog">
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
className="auth-close"
|
|
163
|
+
onClick={onClose}
|
|
164
|
+
disabled={busy}
|
|
165
|
+
aria-label="close"
|
|
166
|
+
>
|
|
167
|
+
×
|
|
168
|
+
</button>
|
|
169
|
+
|
|
170
|
+
<h2 id="invite-dialog-title" className="auth-headline">
|
|
171
|
+
invite friends to audit theirs
|
|
172
|
+
</h2>
|
|
173
|
+
<p className="auth-sub">
|
|
174
|
+
paste emails separated by commas, spaces, or newlines. you'll be Cc'd on every invite so they know it's from you.
|
|
175
|
+
</p>
|
|
176
|
+
|
|
177
|
+
<form onSubmit={submit} className="auth-form">
|
|
178
|
+
<textarea
|
|
179
|
+
ref={inputRef}
|
|
180
|
+
name="emails"
|
|
181
|
+
placeholder="alice@x.com, bob@y.com carol@z.com"
|
|
182
|
+
disabled={busy}
|
|
183
|
+
className="auth-input invite-textarea"
|
|
184
|
+
value={value}
|
|
185
|
+
onChange={(e) => setValue(e.target.value)}
|
|
186
|
+
rows={4}
|
|
187
|
+
required
|
|
188
|
+
/>
|
|
189
|
+
<div className="invite-summary">
|
|
190
|
+
{valid.length > 0 && (
|
|
191
|
+
<span className="invite-valid">{valid.length} valid</span>
|
|
192
|
+
)}
|
|
193
|
+
{invalid.length > 0 && (
|
|
194
|
+
<span className="invite-invalid">
|
|
195
|
+
{invalid.length} invalid: {invalid.slice(0, 3).join(", ")}
|
|
196
|
+
{invalid.length > 3 ? `, +${invalid.length - 3} more` : ""}
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
199
|
+
{valid.length > MAX_RECIPIENTS && (
|
|
200
|
+
<span className="invite-invalid">
|
|
201
|
+
only {MAX_RECIPIENTS} per send — first {MAX_RECIPIENTS} will go.
|
|
202
|
+
</span>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
{error && <div className="auth-error">{error}</div>}
|
|
206
|
+
<div className="auth-actions">
|
|
207
|
+
<button
|
|
208
|
+
type="submit"
|
|
209
|
+
className="auth-btn primary"
|
|
210
|
+
disabled={busy || valid.length === 0}
|
|
211
|
+
>
|
|
212
|
+
{busy ? "sending…" : `send ${valid.length || ""} invite${valid.length === 1 ? "" : "s"}`.trim()}
|
|
213
|
+
</button>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
className="auth-btn"
|
|
217
|
+
onClick={onClose}
|
|
218
|
+
disabled={busy}
|
|
219
|
+
>
|
|
220
|
+
cancel
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
</form>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section 03 — QUIRKS. "what to improve."
|
|
5
|
+
*
|
|
6
|
+
* 4-column table: when · what slipped (+ policy that would've caught it) ·
|
|
7
|
+
* severity pill · recurrence. Each row corresponds to a triggered
|
|
8
|
+
* detector. No per-finding card chrome, no 4-quad body, no corner
|
|
9
|
+
* crosshairs — evidence and fix live in section 04 (How to improve).
|
|
10
|
+
*/
|
|
11
|
+
import React from "react";
|
|
12
|
+
import type { FindingCard } from "@/src/audit/findings";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
findings: FindingCard[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Severity = "low" | "medium" | "high";
|
|
19
|
+
|
|
20
|
+
/** Heuristic severity from occurrence count — until the audit pipeline
|
|
21
|
+
* carries a real severity field. Tuned so a single occurrence reads as
|
|
22
|
+
* low and >5 reads as high. */
|
|
23
|
+
function severityFromCount(count: number): Severity {
|
|
24
|
+
if (count >= 6) return "high";
|
|
25
|
+
if (count >= 2) return "medium";
|
|
26
|
+
return "low";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function recurrenceLabel(count: number): string {
|
|
30
|
+
if (count <= 1) return "new";
|
|
31
|
+
if (count < 10) return `${count}× seen`;
|
|
32
|
+
return "recurring";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function QuirksSection({ findings }: Props) {
|
|
36
|
+
if (findings.length === 0) return null;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<section className="audit-sec" data-screen-label="03 Quirks">
|
|
40
|
+
<div className="audit-sec-head">
|
|
41
|
+
<span className="audit-sec-eyebrow">
|
|
42
|
+
<span className="ix">03</span>{"// quirks"}
|
|
43
|
+
</span>
|
|
44
|
+
<span className="audit-sec-meta">{findings.length} slipped through</span>
|
|
45
|
+
</div>
|
|
46
|
+
<h2 className="audit-sec-title">what to improve</h2>
|
|
47
|
+
|
|
48
|
+
<div className="quirks-table">
|
|
49
|
+
<div className="quirks-thead">
|
|
50
|
+
<span>when</span>
|
|
51
|
+
<span>what slipped</span>
|
|
52
|
+
<span>severity</span>
|
|
53
|
+
<span>seen</span>
|
|
54
|
+
</div>
|
|
55
|
+
{findings.map((f) => {
|
|
56
|
+
const sev = severityFromCount(f.count);
|
|
57
|
+
return (
|
|
58
|
+
<div key={f.sourceSlug} className="quirks-row">
|
|
59
|
+
<span className="q-when">{f.lastSeen}</span>
|
|
60
|
+
<span className="q-what">
|
|
61
|
+
<span className="q-title">{f.title}</span>
|
|
62
|
+
<span className="q-policy">
|
|
63
|
+
would've been caught by:{" "}
|
|
64
|
+
<code>{f.policy}</code>
|
|
65
|
+
</span>
|
|
66
|
+
</span>
|
|
67
|
+
<span className={`q-pill q-pill-${sev}`}>{sev}</span>
|
|
68
|
+
<span className="q-recur">{recurrenceLabel(f.count)}</span>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -30,7 +30,13 @@ export interface ScanParams {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const POLL_INTERVAL_MS = 1000;
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
/** Give up polling only after this many *consecutive* status-fetch failures.
|
|
35
|
+
* There is no duration cap on a run (a cold all-history scan is unbounded), so
|
|
36
|
+
* the only thing that should stop a live poll is losing the server — not the
|
|
37
|
+
* run taking a while. At POLL_INTERVAL_MS, this is ~10s of an unreachable
|
|
38
|
+
* server before we surface a network error. */
|
|
39
|
+
const MAX_CONSECUTIVE_POLL_FAILURES = 10;
|
|
34
40
|
|
|
35
41
|
/** Exported for unit testing the option-threading. */
|
|
36
42
|
export function paramsToBody(p: ScanParams) {
|
|
@@ -51,7 +57,10 @@ export class RerunError extends Error {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
export async function triggerRun(scanParams: ScanParams): Promise<void> {
|
|
54
|
-
//
|
|
60
|
+
// Start the run. The route is fire-and-forget — it kicks off runAudit() in
|
|
61
|
+
// the background and returns 202 in milliseconds — so the default fast fetch
|
|
62
|
+
// timeout is correct here: it bounds the *kickoff request*, not the run.
|
|
63
|
+
// 409 (already running) is fine — we just poll the in-flight run.
|
|
55
64
|
try {
|
|
56
65
|
const res = await fetchWithTimeout("/api/audit/run", {
|
|
57
66
|
method: "POST",
|
|
@@ -69,22 +78,42 @@ export async function triggerRun(scanParams: ScanParams): Promise<void> {
|
|
|
69
78
|
throw new RerunError(isAbortError(err) ? "timeout" : "network", "audit run request failed");
|
|
70
79
|
}
|
|
71
80
|
|
|
72
|
-
// Poll status until
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
// Poll /api/audit/status until the run finishes. There is deliberately NO
|
|
82
|
+
// duration cap: a cold, all-history scan can run arbitrarily long and must
|
|
83
|
+
// not be guillotined client-side. The only failure mode that stops polling is
|
|
84
|
+
// losing the server, tracked as consecutive status-fetch failures.
|
|
85
|
+
let consecutiveFailures = 0;
|
|
86
|
+
for (;;) {
|
|
78
87
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
88
|
+
|
|
89
|
+
let status: { running: boolean; error?: string | null } | null = null;
|
|
79
90
|
try {
|
|
80
91
|
const sres = await fetchWithTimeout("/api/audit/status", { cache: "no-store" });
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
if (sres.ok) {
|
|
93
|
+
status = (await sres.json()) as { running: boolean; error?: string | null };
|
|
94
|
+
}
|
|
84
95
|
} catch {
|
|
85
|
-
// Transient (including per-request timeout) —
|
|
86
|
-
//
|
|
96
|
+
// Transient fetch/JSON failure (including a per-request timeout) — handled
|
|
97
|
+
// by the connectivity backstop below.
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (status === null) {
|
|
101
|
+
// Status unreachable or non-OK. Only give up once the server has been
|
|
102
|
+
// unreachable for MAX_CONSECUTIVE_POLL_FAILURES polls in a row; a single
|
|
103
|
+
// blip must not kill a long, healthy run.
|
|
104
|
+
if (++consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
105
|
+
throw new RerunError("network", "audit status poll lost the server");
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
consecutiveFailures = 0;
|
|
111
|
+
if (!status.running) {
|
|
112
|
+
// Run finished. A non-null error means the background runAudit() threw —
|
|
113
|
+
// surface it (the dashboard cache was NOT updated). Otherwise it's a clean
|
|
114
|
+
// success: writeDashboardCache ran before finishRun(null).
|
|
115
|
+
if (status.error) throw new RerunError("post_failed", status.error);
|
|
116
|
+
return;
|
|
87
117
|
}
|
|
88
118
|
}
|
|
89
|
-
throw new RerunError("timeout", "audit poll loop timed out");
|
|
90
119
|
}
|
|
@@ -66,7 +66,7 @@ export function RunProgress() {
|
|
|
66
66
|
<div className="panel running-panel">
|
|
67
67
|
<div className="running-header">
|
|
68
68
|
<span className="running-prompt">$</span>
|
|
69
|
-
<span className="running-cmd">failproofai audit
|
|
69
|
+
<span className="running-cmd">failproofai audit</span>
|
|
70
70
|
<span className="running-cursor" aria-hidden="true">▮</span>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
@@ -112,7 +112,7 @@ export function RunProgress() {
|
|
|
112
112
|
</div>
|
|
113
113
|
|
|
114
114
|
<p className="running-foot">
|
|
115
|
-
this
|
|
115
|
+
this can take a while depending on how much session history you have.
|
|
116
116
|
</p>
|
|
117
117
|
</div>
|
|
118
118
|
</section>
|
|
@@ -22,32 +22,33 @@ export interface ShareCtx {
|
|
|
22
22
|
|
|
23
23
|
const pol = (n: number) => (n === 1 ? "policy" : "policies");
|
|
24
24
|
|
|
25
|
-
/** Quirky, emoji-forward — for X.
|
|
25
|
+
/** Quirky, emoji-forward — for X. No grade-tier framing (sounds bad at
|
|
26
|
+
* the low end). Lean on the persona name + score number instead. */
|
|
26
27
|
export const X_TEMPLATES: ((c: ShareCtx) => string)[] = [
|
|
27
|
-
({ score, arch,
|
|
28
|
-
`my AI coding agent just got profiled and it's "${arch}" 🤠\n\
|
|
29
|
-
({ score, arch
|
|
30
|
-
`turns out my coding agent has a whole personality and it's ${arch} 💀\n\
|
|
31
|
-
({ score, arch
|
|
32
|
-
|
|
33
|
-
({ score, arch,
|
|
34
|
-
`plot twist: my AI agent is ${arch} 🎭\n\n${score}/100
|
|
35
|
-
({ score, arch
|
|
36
|
-
`i let failproofai audit my coding agent and it called me ${arch} ${
|
|
28
|
+
({ score, arch, missing }) =>
|
|
29
|
+
`my AI coding agent just got profiled and it's "${arch}" 🤠\n\nscored ${score}/100${missing > 0 ? ` · ${missing} ${pol(missing)} between me and a clean run` : ` · every guardrail live`}\n\nwhat's yours? → ${SITE_URL}`,
|
|
30
|
+
({ score, arch }) =>
|
|
31
|
+
`turns out my coding agent has a whole personality and it's ${arch} 💀\n\n${score}/100. the audit does not miss.\n\nrun yours in 30s → ${SITE_URL}`,
|
|
32
|
+
({ score, arch }) =>
|
|
33
|
+
`archetype: ${arch} 👀\n\n${score}/100 on the failproofai audit. it reverse-engineered my agent's entire vibe from its own session logs.\n\n${SITE_URL}`,
|
|
34
|
+
({ score, arch, missing }) =>
|
|
35
|
+
`plot twist: my AI agent is ${arch} 🎭\n\n${score}/100${missing > 0 ? ` · ${missing} ${pol(missing)} away from behaving` : ` · spotless, somehow`}\n\nbefailproof.ai`,
|
|
36
|
+
({ score, arch }) =>
|
|
37
|
+
`i let failproofai audit my coding agent and it called me ${arch} ${score >= 80 ? "😎" : "😬"}\n\n${score}/100. brutally accurate. no notes.\n\n${SITE_URL}`,
|
|
37
38
|
];
|
|
38
39
|
|
|
39
|
-
/** Measured, professional — for LinkedIn. */
|
|
40
|
+
/** Measured, professional — for LinkedIn. No grade-tier framing. */
|
|
40
41
|
export const LI_TEMPLATES: ((c: ShareCtx) => string)[] = [
|
|
41
|
-
({ score, arch,
|
|
42
|
-
`I ran a failproofai audit on our AI coding agents.\n\nResult: ${score}/100
|
|
43
|
-
({ score, arch,
|
|
44
|
-
`Security posture check on our coding-agent stack, via failproofai.\n\nScore: ${score}/100
|
|
45
|
-
({ score, arch
|
|
46
|
-
`Most teams can't answer a simple question: how do our AI agents actually behave when no one is watching?\n\nfailproofai audited ours and profiled it as "${arch}" — ${score}/100
|
|
47
|
-
({ score, arch,
|
|
48
|
-
`Agent security isn't a vibe — it's measurable.\n\nWe scored ${score}/100
|
|
49
|
-
({ score, arch
|
|
50
|
-
`Ran our coding agents through a failproofai behavioural audit.\n\n${score}/100,
|
|
42
|
+
({ score, arch, missing }) =>
|
|
43
|
+
`I ran a failproofai audit on our AI coding agents.\n\nResult: ${score}/100, behavioural archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} would close the remaining gaps.` : `Every prescribed policy is already live.`}\n\nUnderstanding how agents actually behave across real sessions is the first step to securing them. Free and open-source: ${SITE_URL}`,
|
|
44
|
+
({ score, arch, missing }) =>
|
|
45
|
+
`Security posture check on our coding-agent stack, via failproofai.\n\nScore: ${score}/100. Behavioural profile: "${arch}". ${missing > 0 ? `${missing} ${pol(missing)} flagged as real, addressable attack surface.` : `No open policy gaps.`}\n\nThirty seconds to run your own assessment: ${SITE_URL}`,
|
|
46
|
+
({ score, arch }) =>
|
|
47
|
+
`Most teams can't answer a simple question: how do our AI agents actually behave when no one is watching?\n\nfailproofai audited ours and profiled it as "${arch}" — ${score}/100. Concrete, evidence-backed, and mapped to the exact policies that close each gap.\n\n${SITE_URL}`,
|
|
48
|
+
({ score, arch, missing }) =>
|
|
49
|
+
`Agent security isn't a vibe — it's measurable.\n\nWe scored ${score}/100 on the failproofai audit, archetype "${arch}". ${missing > 0 ? `${missing} prescribed ${pol(missing)} remain to harden the stack.` : `Full coverage on every prescribed policy.`}\n\nWorth 30 seconds for any team shipping with AI agents: ${SITE_URL}`,
|
|
50
|
+
({ score, arch }) =>
|
|
51
|
+
`Ran our coding agents through a failproofai behavioural audit.\n\n${score}/100, archetype "${arch}". The report maps every risky pattern to the policy that prevents it, so remediation becomes a checklist rather than a guess.\n\nOpen-source, free to run: ${SITE_URL}`,
|
|
51
52
|
];
|
|
52
53
|
|
|
53
54
|
/** djb2 hash — stable per seed so the template choice is deterministic. */
|