failproofai 1.0.0-beta.2 → 1.0.0-beta.4
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 +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/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.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.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +2 -2
- 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.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/reminder/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 +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 +1 -1
- 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.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 +30 -18
- 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 +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/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 +1 -1
- 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 +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/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-dh5jy._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h3lfmp._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l4vuqm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0longh-._.js → [root-of-the-server]__0szqzl8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10oa-v9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18d-9oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__1p7s8-d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0nb0syg._.js → [root-of-the-server]__0cmdyn_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ath6v_._.js → [root-of-the-server]__0ls0gsr._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0uno39i._.js → [root-of-the-server]__0n_uvv0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__0rag5dg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0rtdfof._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1psjybo._.js → [root-of-the-server]__0sdrz-j._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0114ewg._.js → [root-of-the-server]__1j4u3yd._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- 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/{node_modules_html-to-image_es_index_0oy_-36.js → node_modules_html-to-image_es_index_1q9xxca.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- 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 +32 -20
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 06jakt_xhoolk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +6 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 0ue9ak_xexiqh.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +1 -0
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/2c90h1g6x8z8y.js +1 -0
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 2zb3m1tzq6h98.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 31cqiv_cpgj9m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 3h5_3u46gu9jx.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 43a1ywqsg9bq9.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/Cargo.lock +1992 -198
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/policies/hooks-client.tsx +81 -6
- package/.next/standalone/crates/CLOUD_POLICIES.md +136 -0
- package/.next/standalone/crates/failproofaid/Cargo.toml +7 -0
- package/.next/standalone/crates/failproofaid/src/cloud_client.rs +484 -0
- package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +921 -0
- package/.next/standalone/crates/failproofaid/src/main.rs +452 -3
- package/.next/standalone/crates/failproofaid/src/paths.rs +29 -0
- package/.next/standalone/crates/fpai-collect/Cargo.toml +43 -0
- package/.next/standalone/crates/fpai-collect/src/config.rs +412 -0
- package/.next/standalone/crates/fpai-collect/src/cursor.rs +520 -0
- package/.next/standalone/crates/fpai-collect/src/delivery.rs +472 -0
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +484 -0
- package/.next/standalone/crates/fpai-collect/src/health.rs +369 -0
- package/.next/standalone/crates/fpai-collect/src/lib.rs +34 -0
- package/.next/standalone/crates/fpai-collect/src/redact.rs +516 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +279 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +619 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +156 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +715 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +158 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +512 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +511 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +586 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +318 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +579 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +310 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +448 -0
- package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +23 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +176 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +500 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +315 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +493 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +201 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +533 -0
- package/.next/standalone/crates/fpai-collect/src/spool.rs +458 -0
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +387 -0
- package/.next/standalone/crates/fpai-collect/src/supervisor.rs +423 -0
- package/.next/standalone/crates/fpai-collect/src/uploader.rs +600 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +738 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +393 -0
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +975 -0
- package/.next/standalone/crates/fpai-collect/tests/config.rs +286 -0
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1019 -0
- package/.next/standalone/crates/fpai-collect/tests/delivery.rs +281 -0
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1008 -0
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1129 -0
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +621 -0
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +854 -0
- package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +1478 -0
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1191 -0
- package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +290 -0
- package/.next/standalone/crates/fpai-collect/tests/uploader.rs +506 -0
- package/.next/standalone/package.json +15 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +129 -0
- package/dist/cli.mjs +2255 -779
- package/dist/worker.mjs +365 -52
- package/package.json +15 -9
- package/scripts/build-daemon-packages.mjs +210 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/prune-standalone.mjs +6 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/src/hooks/builtin-policies.ts +139 -0
- package/src/hooks/cloud-connection.ts +205 -0
- package/src/hooks/cloud-enrollment-cli.ts +227 -0
- package/src/hooks/cloud-enrollment.ts +173 -0
- package/src/hooks/cloud-managed-policies.ts +138 -0
- package/src/hooks/collector-config.ts +226 -0
- package/src/hooks/configure-wizard.ts +221 -5
- package/src/hooks/custom-hooks-loader.ts +69 -25
- package/src/hooks/daemon-client.ts +18 -0
- package/src/hooks/daemon-download.ts +137 -27
- package/src/hooks/daemon-service.ts +23 -8
- package/src/hooks/handler.ts +144 -6
- package/src/hooks/hook-activity-store.ts +50 -0
- package/src/hooks/loader-utils.ts +6 -4
- package/src/hooks/policy-types.ts +35 -0
- package/src/hooks/session-pause-cli.ts +157 -0
- package/src/hooks/session-pause.ts +236 -0
- package/src/hooks/tui.ts +115 -0
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/31feojxhjyjpl.js +0 -1
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
//! Contract tests for the collector supervisor.
|
|
2
|
+
//!
|
|
3
|
+
//! These are the tests that justify running collection inside the daemon at
|
|
4
|
+
//! all. `failproofai` fails closed — an unreachable daemon denies every tool
|
|
5
|
+
//! call on the machine — so "a collector bug cannot take the daemon down" is
|
|
6
|
+
//! not a nice property, it is the precondition for the whole design. Each test
|
|
7
|
+
//! below pins one half of that claim.
|
|
8
|
+
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
11
|
+
use std::time::{Duration, Instant};
|
|
12
|
+
|
|
13
|
+
use fpai_collect::{TaskError, TaskSpec, spawn_supervised};
|
|
14
|
+
|
|
15
|
+
/// Poll `cond` until true or `budget` elapses. Returns whether it became true.
|
|
16
|
+
/// Used instead of a fixed sleep so the tests are neither flaky nor slower
|
|
17
|
+
/// than they need to be.
|
|
18
|
+
fn wait_until(budget: Duration, mut cond: impl FnMut() -> bool) -> bool {
|
|
19
|
+
let deadline = Instant::now() + budget;
|
|
20
|
+
while Instant::now() < deadline {
|
|
21
|
+
if cond() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
std::thread::sleep(Duration::from_millis(10));
|
|
25
|
+
}
|
|
26
|
+
cond()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[test]
|
|
30
|
+
fn no_tasks_means_no_thread_and_no_runtime() {
|
|
31
|
+
// The normal state on a machine that has not opted in to collection. It
|
|
32
|
+
// must cost nothing at all — not an idle thread, not a Tokio runtime.
|
|
33
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
34
|
+
let handle = spawn_supervised(vec![], shutdown);
|
|
35
|
+
assert!(
|
|
36
|
+
handle.is_none(),
|
|
37
|
+
"an empty task list must not start a runtime"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn a_panicking_task_is_contained_and_restarted() {
|
|
43
|
+
// The core claim. A transform that panics on some agent's malformed log
|
|
44
|
+
// line must not unwind into the runtime, must not abort the process, and
|
|
45
|
+
// must be tried again rather than silently dropped.
|
|
46
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
47
|
+
let starts = Arc::new(AtomicUsize::new(0));
|
|
48
|
+
|
|
49
|
+
let s = starts.clone();
|
|
50
|
+
let handle = spawn_supervised(
|
|
51
|
+
vec![TaskSpec::new("panicky", move |_sd| {
|
|
52
|
+
let s = s.clone();
|
|
53
|
+
async move {
|
|
54
|
+
s.fetch_add(1, Ordering::SeqCst);
|
|
55
|
+
panic!("simulated transform bug");
|
|
56
|
+
}
|
|
57
|
+
})],
|
|
58
|
+
shutdown.clone(),
|
|
59
|
+
)
|
|
60
|
+
.expect("supervisor should start");
|
|
61
|
+
|
|
62
|
+
// Backoff starts at 1s, so a second start proves a real restart happened
|
|
63
|
+
// rather than the first attempt being counted twice.
|
|
64
|
+
assert!(
|
|
65
|
+
wait_until(Duration::from_secs(5), || starts.load(Ordering::SeqCst)
|
|
66
|
+
>= 2),
|
|
67
|
+
"a panicking task must be restarted, got {} start(s)",
|
|
68
|
+
starts.load(Ordering::SeqCst)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
let m = handle.metrics();
|
|
72
|
+
assert!(
|
|
73
|
+
m.panics.load(Ordering::Relaxed) >= 1,
|
|
74
|
+
"the panic must be counted"
|
|
75
|
+
);
|
|
76
|
+
assert_eq!(
|
|
77
|
+
m.failures.load(Ordering::Relaxed),
|
|
78
|
+
0,
|
|
79
|
+
"a panic is not an Err — they are counted separately so a bug is distinguishable from a flaky environment"
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
assert!(handle.join_with_flush(Duration::from_secs(5)));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[test]
|
|
86
|
+
fn a_panic_in_one_task_does_not_disturb_another() {
|
|
87
|
+
// Isolation between tasks, not just between the collector and the daemon.
|
|
88
|
+
// One badly-behaved source must not stop the others from shipping.
|
|
89
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
90
|
+
let healthy_ticks = Arc::new(AtomicUsize::new(0));
|
|
91
|
+
|
|
92
|
+
let ticks = healthy_ticks.clone();
|
|
93
|
+
let handle = spawn_supervised(
|
|
94
|
+
vec![
|
|
95
|
+
TaskSpec::new("panicky", |_sd| async {
|
|
96
|
+
panic!("bad source");
|
|
97
|
+
}),
|
|
98
|
+
TaskSpec::new("healthy", move |sd| {
|
|
99
|
+
let ticks = ticks.clone();
|
|
100
|
+
// Shaped like a real source: loop until asked to stop, and
|
|
101
|
+
// use the interruptible sleep so shutdown is prompt.
|
|
102
|
+
async move {
|
|
103
|
+
while !sd.is_set() {
|
|
104
|
+
ticks.fetch_add(1, Ordering::SeqCst);
|
|
105
|
+
sd.sleep(Duration::from_millis(10)).await;
|
|
106
|
+
}
|
|
107
|
+
Ok(())
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
],
|
|
111
|
+
shutdown.clone(),
|
|
112
|
+
)
|
|
113
|
+
.expect("supervisor should start");
|
|
114
|
+
|
|
115
|
+
assert!(
|
|
116
|
+
wait_until(Duration::from_secs(5), || healthy_ticks
|
|
117
|
+
.load(Ordering::SeqCst)
|
|
118
|
+
>= 5),
|
|
119
|
+
"the healthy task must keep running while its sibling panics"
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
assert!(handle.join_with_flush(Duration::from_secs(5)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn a_failing_task_is_restarted_and_counted_as_a_failure_not_a_panic() {
|
|
127
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
128
|
+
let starts = Arc::new(AtomicUsize::new(0));
|
|
129
|
+
|
|
130
|
+
let s = starts.clone();
|
|
131
|
+
let handle = spawn_supervised(
|
|
132
|
+
vec![TaskSpec::new("flaky", move |_sd| {
|
|
133
|
+
let s = s.clone();
|
|
134
|
+
async move {
|
|
135
|
+
s.fetch_add(1, Ordering::SeqCst);
|
|
136
|
+
Err(TaskError::from("upstream refused the connection"))
|
|
137
|
+
}
|
|
138
|
+
})],
|
|
139
|
+
shutdown.clone(),
|
|
140
|
+
)
|
|
141
|
+
.expect("supervisor should start");
|
|
142
|
+
|
|
143
|
+
assert!(
|
|
144
|
+
wait_until(Duration::from_secs(5), || starts.load(Ordering::SeqCst)
|
|
145
|
+
>= 2),
|
|
146
|
+
"a failing task must be restarted"
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
let m = handle.metrics();
|
|
150
|
+
assert!(m.failures.load(Ordering::Relaxed) >= 1);
|
|
151
|
+
assert_eq!(
|
|
152
|
+
m.panics.load(Ordering::Relaxed),
|
|
153
|
+
0,
|
|
154
|
+
"an Err is the environment, not a bug — it must not inflate the panic count"
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
assert!(handle.join_with_flush(Duration::from_secs(5)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn a_task_that_finishes_cleanly_is_not_restarted() {
|
|
162
|
+
// A one-shot task (a backfill pass, say) returning Ok is done. Restarting
|
|
163
|
+
// it would turn "finished" into an infinite loop.
|
|
164
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
165
|
+
let starts = Arc::new(AtomicUsize::new(0));
|
|
166
|
+
|
|
167
|
+
let s = starts.clone();
|
|
168
|
+
let handle = spawn_supervised(
|
|
169
|
+
vec![TaskSpec::new("oneshot", move |_sd| {
|
|
170
|
+
let s = s.clone();
|
|
171
|
+
async move {
|
|
172
|
+
s.fetch_add(1, Ordering::SeqCst);
|
|
173
|
+
Ok(())
|
|
174
|
+
}
|
|
175
|
+
})],
|
|
176
|
+
shutdown.clone(),
|
|
177
|
+
)
|
|
178
|
+
.expect("supervisor should start");
|
|
179
|
+
|
|
180
|
+
// Well past the 1s backoff a restart would have used.
|
|
181
|
+
std::thread::sleep(Duration::from_millis(2500));
|
|
182
|
+
assert_eq!(
|
|
183
|
+
starts.load(Ordering::SeqCst),
|
|
184
|
+
1,
|
|
185
|
+
"a completed task must run exactly once"
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
assert!(handle.join_with_flush(Duration::from_secs(5)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn the_daemons_shutdown_flag_stops_the_collector() {
|
|
193
|
+
// The collector observes the same flag the socket server does, so one
|
|
194
|
+
// SIGTERM stops both. If this regressed, `systemctl stop` would hang until
|
|
195
|
+
// its own timeout and then SIGKILL, losing whatever was buffered.
|
|
196
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
197
|
+
|
|
198
|
+
let handle = spawn_supervised(
|
|
199
|
+
vec![TaskSpec::new("long-runner", |sd| async move {
|
|
200
|
+
sd.sleep(Duration::from_secs(3600)).await;
|
|
201
|
+
Ok(())
|
|
202
|
+
})],
|
|
203
|
+
shutdown.clone(),
|
|
204
|
+
)
|
|
205
|
+
.expect("supervisor should start");
|
|
206
|
+
|
|
207
|
+
shutdown.store(true, Ordering::Relaxed);
|
|
208
|
+
|
|
209
|
+
let drained = handle.join_with_flush(Duration::from_secs(5));
|
|
210
|
+
assert!(
|
|
211
|
+
drained,
|
|
212
|
+
"the collector must stop when the daemon's shutdown flag is set"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn shutdown_interrupts_a_backoff_rather_than_serving_it_out() {
|
|
218
|
+
// A task failing repeatedly climbs to a 60s backoff. Process exit must not
|
|
219
|
+
// wait for that: without an interruptible sleep, stopping the daemon while
|
|
220
|
+
// a source was backing off would block for up to a minute.
|
|
221
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
222
|
+
let starts = Arc::new(AtomicUsize::new(0));
|
|
223
|
+
|
|
224
|
+
let s = starts.clone();
|
|
225
|
+
let handle = spawn_supervised(
|
|
226
|
+
vec![TaskSpec::new("always-fails", move |_sd| {
|
|
227
|
+
let s = s.clone();
|
|
228
|
+
async move {
|
|
229
|
+
s.fetch_add(1, Ordering::SeqCst);
|
|
230
|
+
Err(TaskError::from("nope"))
|
|
231
|
+
}
|
|
232
|
+
})],
|
|
233
|
+
shutdown.clone(),
|
|
234
|
+
)
|
|
235
|
+
.expect("supervisor should start");
|
|
236
|
+
|
|
237
|
+
// Let it fail once and enter its backoff.
|
|
238
|
+
assert!(wait_until(Duration::from_secs(2), || starts
|
|
239
|
+
.load(Ordering::SeqCst)
|
|
240
|
+
>= 1));
|
|
241
|
+
|
|
242
|
+
let t0 = Instant::now();
|
|
243
|
+
shutdown.store(true, Ordering::Relaxed);
|
|
244
|
+
let drained = handle.join_with_flush(Duration::from_secs(5));
|
|
245
|
+
let elapsed = t0.elapsed();
|
|
246
|
+
|
|
247
|
+
assert!(drained, "must drain");
|
|
248
|
+
assert!(
|
|
249
|
+
elapsed < Duration::from_millis(1500),
|
|
250
|
+
"shutdown waited out the backoff instead of interrupting it: took {elapsed:?}"
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn join_with_flush_gives_up_on_a_wedged_task_instead_of_hanging() {
|
|
256
|
+
// The bound we actually promise. A task that ignores shutdown — stuck in a
|
|
257
|
+
// blocking syscall, say — delays process exit by the flush budget and no
|
|
258
|
+
// more. Hanging here is precisely the failure this module exists to
|
|
259
|
+
// prevent, because the daemon would never exit and the service manager
|
|
260
|
+
// would SIGKILL it mid-write.
|
|
261
|
+
let shutdown = Arc::new(AtomicBool::new(false));
|
|
262
|
+
|
|
263
|
+
let handle = spawn_supervised(
|
|
264
|
+
vec![TaskSpec::new("wedged", |_sd| async {
|
|
265
|
+
// `spawn_blocking` is not cancellable, which is exactly the shape
|
|
266
|
+
// of a real wedge (a synchronous read on a stalled filesystem).
|
|
267
|
+
tokio::task::spawn_blocking(|| std::thread::sleep(Duration::from_secs(30)))
|
|
268
|
+
.await
|
|
269
|
+
.ok();
|
|
270
|
+
Ok(())
|
|
271
|
+
})],
|
|
272
|
+
shutdown.clone(),
|
|
273
|
+
)
|
|
274
|
+
.expect("supervisor should start");
|
|
275
|
+
|
|
276
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
277
|
+
|
|
278
|
+
let t0 = Instant::now();
|
|
279
|
+
let drained = handle.join_with_flush(Duration::from_millis(600));
|
|
280
|
+
let elapsed = t0.elapsed();
|
|
281
|
+
|
|
282
|
+
assert!(
|
|
283
|
+
!drained,
|
|
284
|
+
"a wedged task should report that it did not drain"
|
|
285
|
+
);
|
|
286
|
+
assert!(
|
|
287
|
+
elapsed < Duration::from_secs(3),
|
|
288
|
+
"join must respect its budget rather than waiting for the wedged task: took {elapsed:?}"
|
|
289
|
+
);
|
|
290
|
+
}
|