failproofai 1.0.2-beta.7 → 1.0.2-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/build-manifest.json +5 -5
- 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 +2 -2
- 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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- 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/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -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 +4 -3
- 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 +2 -2
- 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 +4 -3
- 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 +4 -4
- 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 +4 -4
- 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 +2 -2
- 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 +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +6 -5
- 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 +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0-9ti7z._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0sg9-3p._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0u3wrvq._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zlqo2t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1anxb5k._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +27 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/{_19uwam-._.js → _1-akg6w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -7
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +10 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_lxhg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1jjjg6g._.js → [root-of-the-server]__1_7n3xe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1uvml5y._.js → _0b6xubz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07gm3zl._.js → _1es2j7i._.js} +7 -21
- package/.next/standalone/.next/server/chunks/ssr/{_0_nsohj._.js → _1pj-r7q._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +17 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/middleware.js +1 -1
- 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 +19 -19
- package/.next/standalone/.next/static/chunks/{3swqstog1cd9u.js → 06zoac_7ffkus.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0icodhlwr9x_e.js → 0bshgqm5waivd.js} +2 -2
- package/.next/standalone/.next/static/chunks/{2-9lvhuto1iwo.js → 13grq7pifzvu5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 16pmxmg9x48ob.js} +1 -1
- package/.next/standalone/.next/static/chunks/{44oamw33aso19.js → 1da7-a7ek66uy.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1d6c85605lsgp.js → 1fg7drro5kle-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0tvmd0p_1i_g6.js → 1opyu56l4cdfk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1d7o3wnt3h6se.js → 1p-o1i0lyt9un.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vqddiy9tvlwf.js → 2mfojtsy214sf.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3jt6n16p2qpex.js → 32jj4qy40hln_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1vzyn9h2kvj-i.js → 3ww5ik9eo_1b_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1mfzsja6ftim5.css → 40nr6ehj4p3cw.css} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-2x-o099q64i8h.js → turbopack-1gehh2qpjyj5_.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +8 -1
- package/.next/standalone/app/actions/pack-actions.ts +12 -1
- package/.next/standalone/app/policies/hooks-client.tsx +41 -4
- package/.next/standalone/fp-cloud-cli/tests/test_pagination.py +204 -0
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +57 -13
- package/dist/cli.mjs +23076 -20000
- package/dist/worker.mjs +1252 -542
- package/package.json +9 -9
- package/scripts/build-policy-pack.mjs +28 -4
- package/src/audit/index.ts +24 -4
- package/src/hooks/builtin-policies.ts +933 -8
- package/src/hooks/custom-hooks-loader.ts +98 -8
- package/src/hooks/handler.ts +26 -14
- package/src/hooks/manager.ts +36 -2
- package/src/hooks/pack-cli.ts +1224 -121
- package/src/hooks/pack-failclosed.ts +100 -4
- package/src/hooks/pack-manifest.ts +63 -1
- package/src/hooks/pack-param-key.ts +36 -0
- package/src/hooks/pack-store.ts +309 -25
- package/src/hooks/policy-catalog.ts +9 -1
- package/src/hooks/policy-evaluator.ts +77 -1
- package/src/hooks/tui.ts +21 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tg4pu6._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mgrjnx._.js +0 -10
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1opxw-7._.js +0 -5
- /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""When `--all` stops early, the response must say so.
|
|
2
|
+
|
|
3
|
+
The defect these pin was measured against a live instance, on the CLI this one
|
|
4
|
+
was moved from:
|
|
5
|
+
|
|
6
|
+
fp --json sessions --since 7d --limit 200 -> 200 rows, next_cursor set
|
|
7
|
+
fp --json sessions --since 7d --all -> 50 rows (25x undercount)
|
|
8
|
+
fp --json sessions --since 7d --all --limit 5000 -> 1,274 rows
|
|
9
|
+
|
|
10
|
+
`--all` inherits `--limit`'s default of 50 — which is documented and deliberate
|
|
11
|
+
here — but the envelope then hard-coded `next_cursor: null`, so a 4% sample of a
|
|
12
|
+
customer's fleet came back with exit 0, no warning, and a response that read as
|
|
13
|
+
complete. Nothing in the output could tell you otherwise.
|
|
14
|
+
|
|
15
|
+
The fix (`Walk`, carried through `client.paginate`) is present in this repo and
|
|
16
|
+
was untested when these were written. That combination is the one worth
|
|
17
|
+
guarding: a silent-completeness bug cannot be caught by reading the code, only
|
|
18
|
+
by asserting on what the envelope claims.
|
|
19
|
+
|
|
20
|
+
So every test here checks the REPORTED completeness, not just the row count.
|
|
21
|
+
|
|
22
|
+
The fake feed is a real cursor server — it honours `limit` and `cursor` — not a
|
|
23
|
+
list of canned pages. A fixture that ignored `limit` would pass no matter how
|
|
24
|
+
few rows the CLI asked for, which is to say it could not fail on the bug.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
import httpx
|
|
32
|
+
import pytest
|
|
33
|
+
import respx
|
|
34
|
+
|
|
35
|
+
from fp_cli import client
|
|
36
|
+
from fp_cli.app import app
|
|
37
|
+
|
|
38
|
+
from .conftest import BASE_URL as BASE
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _feed(key: str, total: int, row):
|
|
42
|
+
"""respx side_effect: a cursor-paginated feed holding ``total`` rows.
|
|
43
|
+
|
|
44
|
+
The cursor is the offset of the next row (opaque to the CLI, which only
|
|
45
|
+
round-trips it). ``limit`` is honoured exactly, so the CLI's own page sizing
|
|
46
|
+
decides how much of the feed it can actually reach.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
50
|
+
params = request.url.params
|
|
51
|
+
limit = int(params["limit"])
|
|
52
|
+
offset = int(params.get("cursor") or 0)
|
|
53
|
+
rows = [row(i) for i in range(offset, min(offset + limit, total))]
|
|
54
|
+
nxt = offset + len(rows)
|
|
55
|
+
return httpx.Response(
|
|
56
|
+
200, json={key: rows, "next_cursor": str(nxt) if nxt < total else None}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return handler
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _event_row(i: int) -> dict:
|
|
63
|
+
return {
|
|
64
|
+
"id": i, "session_id": "s", "agent_id": "a", "event_type": "tool_use",
|
|
65
|
+
"ts": "2026-08-24T12:00:00Z", "environment": "prod", "summary": f"row {i}",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _session_row(i: int) -> dict:
|
|
70
|
+
return {
|
|
71
|
+
"session_id": f"sess-{i}", "agent_id": "a", "environment": "prod",
|
|
72
|
+
"last_event_at": "2026-08-24T12:00:00Z", "latest_evaluation": None,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _eval_row(i: int) -> dict:
|
|
77
|
+
return {
|
|
78
|
+
"id": f"eval-{i}", "session_id": "s", "agent_id": "a", "environment": "prod",
|
|
79
|
+
"status": "done", "scores": {"helpfulness": 0.9}, "created_at": "2026-08-24T12:00:00Z",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# (command, endpoint, server key, envelope key, row builder). Every command that
|
|
84
|
+
# takes `--all`; the bug was reported on `sessions` but lived in the shared
|
|
85
|
+
# walker, so all four had it. `errors` is the one where the two keys differ: it
|
|
86
|
+
# reads the shared errored-events feed (`{"events": …}`) and re-labels it
|
|
87
|
+
# `errors` on the way out.
|
|
88
|
+
FEEDS = [
|
|
89
|
+
("events", "/api/events/summary", "events", "events", _event_row),
|
|
90
|
+
("sessions", "/api/sessions", "sessions", "sessions", _session_row),
|
|
91
|
+
("evals", "/api/evaluations", "evaluations", "evaluations", _eval_row),
|
|
92
|
+
("errors", "/api/events/summary", "events", "errors", _event_row),
|
|
93
|
+
]
|
|
94
|
+
IDS = [f[0] for f in FEEDS]
|
|
95
|
+
|
|
96
|
+
TOTAL = 274 # > the 50 default, and not a multiple of the 200 page size
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
100
|
+
@respx.mock
|
|
101
|
+
def test_all_stopped_by_the_default_limit_is_reported_not_silent(
|
|
102
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
103
|
+
):
|
|
104
|
+
"""THE REGRESSION. `--all` stopping at 50 is the documented behaviour; saying
|
|
105
|
+
the feed was exhausted is the bug."""
|
|
106
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, TOTAL, row))
|
|
107
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all"])
|
|
108
|
+
assert res.exit_code == 0, res.output
|
|
109
|
+
out = json.loads(res.stdout)
|
|
110
|
+
assert len(out[key]) == 50, "the documented default bound still applies"
|
|
111
|
+
assert out["next_cursor"] is not None, (
|
|
112
|
+
"a walk that stopped on --limit must hand back a resumable cursor; "
|
|
113
|
+
"null here is the 4%-sample-reported-as-complete bug"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
118
|
+
@respx.mock
|
|
119
|
+
def test_all_that_exhausts_the_feed_reports_complete(
|
|
120
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
121
|
+
):
|
|
122
|
+
"""The other direction, and the reason `truncated` cannot just be "we hit the
|
|
123
|
+
limit": a walk that took every row must NOT claim there is more."""
|
|
124
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, 30, row))
|
|
125
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all", "--limit", "500"])
|
|
126
|
+
assert res.exit_code == 0, res.output
|
|
127
|
+
out = json.loads(res.stdout)
|
|
128
|
+
assert len(out[key]) == 30
|
|
129
|
+
assert out["next_cursor"] is None, "an exhausted feed must not look truncated"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@respx.mock
|
|
133
|
+
def test_an_explicit_limit_is_honoured_and_reported(logged_in, runner):
|
|
134
|
+
"""`--all --limit N` is how you actually get more than the default, so the
|
|
135
|
+
bound has to hold exactly and still report the remainder."""
|
|
136
|
+
respx.get(f"{BASE}/api/sessions").mock(side_effect=_feed("sessions", TOTAL, _session_row))
|
|
137
|
+
res = runner.invoke(
|
|
138
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
139
|
+
)
|
|
140
|
+
assert res.exit_code == 0, res.output
|
|
141
|
+
out = json.loads(res.stdout)
|
|
142
|
+
assert len(out["sessions"]) == 220
|
|
143
|
+
assert out["next_cursor"] is not None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@respx.mock
|
|
147
|
+
def test_all_pages_in_chunks_rather_than_one_giant_request(logged_in, runner):
|
|
148
|
+
"""The walk must actually WALK. Asking for 220 in a single request would work
|
|
149
|
+
against this fake and fail against a server that caps a page at 200."""
|
|
150
|
+
route = respx.get(f"{BASE}/api/sessions").mock(
|
|
151
|
+
side_effect=_feed("sessions", TOTAL, _session_row)
|
|
152
|
+
)
|
|
153
|
+
res = runner.invoke(
|
|
154
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
155
|
+
)
|
|
156
|
+
assert res.exit_code == 0, res.output
|
|
157
|
+
assert route.call_count > 1, "a 220-row walk must span more than one request"
|
|
158
|
+
for call in route.calls:
|
|
159
|
+
assert int(call.request.url.params["limit"]) <= 200
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --- the walker itself, below the CLI ---------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _pages(total: int, page: int):
|
|
166
|
+
"""A `fetch_page` over ``total`` rows, honouring the limit it is given."""
|
|
167
|
+
|
|
168
|
+
def fetch(cursor=None, limit=None):
|
|
169
|
+
offset = int(cursor or 0)
|
|
170
|
+
size = min(limit or page, page)
|
|
171
|
+
items = list(range(offset, min(offset + size, total)))
|
|
172
|
+
nxt = offset + len(items)
|
|
173
|
+
return client.Page(items=items, next_cursor=str(nxt) if nxt < total else None)
|
|
174
|
+
|
|
175
|
+
return fetch
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_walk_records_the_cursor_it_stopped_on():
|
|
179
|
+
walk = client.Walk()
|
|
180
|
+
items = list(client.paginate(_pages(500, 200), limit=250, walk=walk))
|
|
181
|
+
assert len(items) == 250
|
|
182
|
+
assert walk.truncated is True
|
|
183
|
+
assert walk.next_cursor is not None, "a bounded walk must be resumable"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_walk_that_exhausts_the_feed_is_not_flagged_truncated():
|
|
187
|
+
walk = client.Walk()
|
|
188
|
+
items = list(client.paginate(_pages(120, 200), limit=500, walk=walk))
|
|
189
|
+
assert len(items) == 120
|
|
190
|
+
assert walk.truncated is False
|
|
191
|
+
assert walk.next_cursor is None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_truncated_is_not_derivable_from_the_row_count():
|
|
195
|
+
"""The subtle case, and why `Walk` exists at all: a walk whose limit lands
|
|
196
|
+
EXACTLY on the last row is complete, and one that lands exactly on a page
|
|
197
|
+
boundary with more behind it is not. Row count cannot tell those apart."""
|
|
198
|
+
exact = client.Walk()
|
|
199
|
+
list(client.paginate(_pages(200, 200), limit=200, walk=exact))
|
|
200
|
+
assert exact.truncated is False, "taking every row is not a truncation"
|
|
201
|
+
|
|
202
|
+
boundary = client.Walk()
|
|
203
|
+
list(client.paginate(_pages(400, 200), limit=200, walk=boundary))
|
|
204
|
+
assert boundary.truncated is True, "stopping on a boundary with more behind it is"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.2-beta.
|
|
3
|
+
"version": "1.0.2-beta.9",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -120,15 +120,15 @@
|
|
|
120
120
|
"sharp": "0.35.0"
|
|
121
121
|
},
|
|
122
122
|
"optionalDependencies": {
|
|
123
|
-
"@failproofai/failproofaid-linux-x64": "1.0.2-beta.
|
|
124
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.2-beta.
|
|
125
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.2-beta.
|
|
126
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.
|
|
123
|
+
"@failproofai/failproofaid-linux-x64": "1.0.2-beta.9",
|
|
124
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.2-beta.9",
|
|
125
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.2-beta.9",
|
|
126
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.9"
|
|
127
127
|
},
|
|
128
128
|
"failproofaidBinaries": {
|
|
129
|
-
"linux-x64": "
|
|
130
|
-
"linux-arm64": "
|
|
131
|
-
"darwin-x64": "
|
|
132
|
-
"darwin-arm64": "
|
|
129
|
+
"linux-x64": "906b0c8a7502ad6a0494231a2220fff65db7ff8335ad7bdf15b58079a001562f",
|
|
130
|
+
"linux-arm64": "8b40f161cedf07ec3504dd736c906666d18bf042613af0a6a38f4daae8d7c7d4",
|
|
131
|
+
"darwin-x64": "7407d02234c24932ac9ece07bca348bd40c7a4edff72e0a8db9ac230a1e28a8c",
|
|
132
|
+
"darwin-arm64": "77131a4f0c1b9d3e5611ad3f4834542e26e32a404f1311903a1cf7783fe99fee"
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.2-beta.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.2-beta.9"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"repoRoot":"/home/runner/work/failproofai/failproofai","distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
package/bin/failproofai.mjs
CHANGED
|
@@ -1362,25 +1362,65 @@ async function runCli() {
|
|
|
1362
1362
|
{
|
|
1363
1363
|
label: "how the version is decided",
|
|
1364
1364
|
lines: [
|
|
1365
|
-
"
|
|
1366
|
-
"
|
|
1367
|
-
"
|
|
1365
|
+
"The commit you are publishing from — its short sha, twelve",
|
|
1366
|
+
"characters: a1b2c3d4e5f6. Nothing to pick, nothing to count, and it",
|
|
1367
|
+
"names exactly where the bytes came from. Publish the same source",
|
|
1368
|
+
"twice and you get the same version, because there is nothing to",
|
|
1369
|
+
"increment.",
|
|
1368
1370
|
"",
|
|
1369
|
-
"
|
|
1370
|
-
"
|
|
1371
|
-
"
|
|
1371
|
+
"Twelve rather than git's seven: seven collides in a repository with",
|
|
1372
|
+
"enough objects, and a version that stops being unique means two",
|
|
1373
|
+
"artifacts claiming one name. The full sha is recorded beside it.",
|
|
1372
1374
|
"",
|
|
1373
|
-
"
|
|
1374
|
-
"
|
|
1375
|
-
"
|
|
1375
|
+
"Read from the tree in front of you, never from the repository's",
|
|
1376
|
+
"releases, so a fresh clone and an air-gapped machine compute the",
|
|
1377
|
+
"same answer and neither has to ask GitHub what happened before.",
|
|
1378
|
+
"",
|
|
1379
|
+
"It REFUSES rather than guessing, in two cases, because the version",
|
|
1380
|
+
"claims to name a commit and must not be minted where that is false:",
|
|
1381
|
+
"",
|
|
1382
|
+
" no git checkout there is no commit to name",
|
|
1383
|
+
" uncommitted changes those bytes are not in that commit",
|
|
1384
|
+
"",
|
|
1385
|
+
"--version overrides both, and a tag on HEAD wins over the sha —",
|
|
1386
|
+
"someone who tagged v1.2.0 has SAID what this release is.",
|
|
1387
|
+
"",
|
|
1388
|
+
"A sha does not order. `policies show <owner>/<repo> --releases` is",
|
|
1389
|
+
"where you see which came first, newest at the top.",
|
|
1390
|
+
],
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
label: "what a release records",
|
|
1394
|
+
lines: [
|
|
1395
|
+
"The commit you published from, when you are in a git checkout, in the",
|
|
1396
|
+
"manifest and in the release notes. Provenance, not verification —",
|
|
1397
|
+
"the artifact digest is still the only thing that decides whether the",
|
|
1398
|
+
"bytes are the ones that were published. It answers the question a",
|
|
1399
|
+
"digest cannot: which source produced them.",
|
|
1400
|
+
"",
|
|
1401
|
+
"Which is also how anyone installs that exact release:",
|
|
1402
|
+
"",
|
|
1403
|
+
" failproofai policies add <owner>/<repo>@a1b2c3d",
|
|
1404
|
+
"",
|
|
1405
|
+
"and read the whole history with:",
|
|
1406
|
+
"",
|
|
1407
|
+
" failproofai policies show <owner>/<repo> --releases",
|
|
1376
1408
|
],
|
|
1377
1409
|
},
|
|
1378
1410
|
{
|
|
1411
|
+
// This screen said "an existing private one still publishes, and
|
|
1412
|
+
// warns" for as long as that was true. It is REFUSED now — exit 1,
|
|
1413
|
+
// nothing created or uploaded — and `--allow-private` was named
|
|
1414
|
+
// nowhere in this help, so the one documented behaviour pointed a
|
|
1415
|
+
// publisher at a command that exits 1 and gave them no way through.
|
|
1379
1416
|
label: "the repo must be public",
|
|
1380
1417
|
lines: [
|
|
1381
1418
|
"Installs are anonymous HTTPS with no credential to offer, so a private",
|
|
1382
1419
|
"repository publishes to nobody. A repo created here is public for that",
|
|
1383
|
-
"reason; an existing private one
|
|
1420
|
+
"reason; an existing private one is REFUSED, before anything is built,",
|
|
1421
|
+
"created or uploaded. --allow-private publishes to one anyway, for",
|
|
1422
|
+
"somebody who will hand the three assets over another way — it still",
|
|
1423
|
+
"says plainly that no `policies add` can reach them.",
|
|
1384
1424
|
"",
|
|
1385
1425
|
"Only the release matters. Installs read releases/download/<tag>/<asset>",
|
|
1386
1426
|
"and never touch your git tree — pushing the source is for humans.",
|
|
@@ -1405,13 +1445,14 @@ async function runCli() {
|
|
|
1405
1445
|
entries: [
|
|
1406
1446
|
["--init [file]", "Write a starter policy and stop."],
|
|
1407
1447
|
["--repo <owner>/<repo>", "Where to release it, created if missing."],
|
|
1408
|
-
["--version <version>", "
|
|
1448
|
+
["--version <version>", "Name the version, instead of the commit it was built from."],
|
|
1409
1449
|
["--id <publisher/name>", "The pack's id. Defaults to --repo."],
|
|
1410
1450
|
["--tag <tag>", "Release tag. Defaults to the version; v prefix ok."],
|
|
1411
1451
|
["--notes <text>", "Release notes."],
|
|
1412
1452
|
["--out <dir>", "Where to write the assets. Default: dist-pack."],
|
|
1413
1453
|
["--effect <effect>", "enforce or observe. observe records and blocks nothing. Default: enforce."],
|
|
1414
1454
|
["--dry-run", "Build the assets, publish nothing. No credential."],
|
|
1455
|
+
["--allow-private", "Publish to an already-private repo anyway. Nobody can install it."],
|
|
1415
1456
|
],
|
|
1416
1457
|
},
|
|
1417
1458
|
{
|
|
@@ -1475,6 +1516,7 @@ async function runCli() {
|
|
|
1475
1516
|
["remove <name>", "Turn one policy off"],
|
|
1476
1517
|
["remove <pack-id>", "Uninstall a pack"],
|
|
1477
1518
|
["show <owner>/<repo>", "What a pack contains, before you take it"],
|
|
1519
|
+
["show <owner>/<repo> --releases", "Every version it has published, and which one is here"],
|
|
1478
1520
|
],
|
|
1479
1521
|
},
|
|
1480
1522
|
{
|
|
@@ -1486,8 +1528,10 @@ async function runCli() {
|
|
|
1486
1528
|
" block-sudo a policy",
|
|
1487
1529
|
" FailproofAI/policies our pack, like any other",
|
|
1488
1530
|
" acme/deploy-guard newest release, pinned",
|
|
1489
|
-
" acme/deploy-guard@
|
|
1490
|
-
"
|
|
1531
|
+
" acme/deploy-guard@a1b2c3d4e5f6 that release",
|
|
1532
|
+
" acme/deploy-guard@a1b2c3d the release built from",
|
|
1533
|
+
" that commit",
|
|
1534
|
+
" github:acme/deploy-guard@a1b2c3d4e5f6 same, explicit",
|
|
1491
1535
|
" https://github.com/acme/x/releases/tag/v2 the URL you copied",
|
|
1492
1536
|
],
|
|
1493
1537
|
},
|