failproofai 1.0.5 → 1.0.6-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +5 -5
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/index.html +1 -1
  36. package/.next/standalone/.next/server/app/index.rsc +14 -14
  37. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  38. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  40. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/[externals]__0sds_16._.js +3 -0
  67. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +3 -3
  69. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15k-dqb._.js → [root-of-the-server]__1i26dsw._.js} +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sz_5na._.js → [root-of-the-server]__1ouobn4._.js} +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/{_1b78kcp._.js → _09hs6ms._.js} +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/{_0guaevg._.js → _0lbwa8m._.js} +2 -2
  90. package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +3 -3
  94. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
  97. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  104. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  105. package/.next/standalone/.next/server/pages/404.html +1 -1
  106. package/.next/standalone/.next/server/pages/500.html +1 -1
  107. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/server-reference-manifest.json +19 -19
  109. package/.next/standalone/.next/static/chunks/{3w46euc60dx39.js → 03n_qjih7-w6q.js} +1 -1
  110. package/.next/standalone/.next/static/chunks/{2zafgs90r_leq.js → 06jxjnw8bfczc.js} +1 -1
  111. package/.next/standalone/.next/static/chunks/{252xreaecy80h.js → 0vanab2g65op_.js} +1 -1
  112. package/.next/standalone/.next/static/chunks/17pjz_h45j10c.js +1 -0
  113. package/.next/standalone/.next/static/chunks/1ekh-hj_iom84.js +1 -0
  114. package/.next/standalone/.next/static/chunks/{021ydrq6rhpsi.js → 1so_3s0q4c4j1.js} +1 -1
  115. package/.next/standalone/.next/static/chunks/1xacnwjkbekn7.js +1 -0
  116. package/.next/standalone/.next/static/chunks/2hg_bp8z_vm2g.js +1 -0
  117. package/.next/standalone/.next/static/chunks/{3bleyy40s99f2.js → 2u0kol_boahtv.js} +1 -1
  118. package/.next/standalone/.next/static/chunks/{1vb33xa6j9y2q.js → 3_vesf83-0903.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/3d3hzhcnns6ww.js +1 -0
  120. package/.next/standalone/.next/static/chunks/3iasus89n8mg1.js +6 -0
  121. package/.next/standalone/.next/static/chunks/{11s_v03ub6nss.js → 44_cj1_i_aa2z.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/{turbopack-3p73obe0xmrxm.js → turbopack-38nzuvp95_2rv.js} +1 -1
  123. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  124. package/.next/standalone/hermes-plugin/README.md +133 -0
  125. package/.next/standalone/hermes-plugin/__init__.py +248 -0
  126. package/.next/standalone/hermes-plugin/client.py +134 -0
  127. package/.next/standalone/hermes-plugin/ledger.py +187 -0
  128. package/.next/standalone/hermes-plugin/plugin.yaml +37 -0
  129. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  130. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  131. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
  132. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
  133. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  134. package/.next/standalone/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +2 -1
  135. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  136. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  137. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  138. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +14 -0
  139. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  140. package/.next/standalone/node_modules/next/dist/server/lib/disk-lru-cache.external.js +7 -1
  141. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  142. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +6 -1
  143. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  144. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  145. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  146. package/.next/standalone/node_modules/next/package.json +10 -10
  147. package/.next/standalone/node_modules/react/cjs/react.development.js +92 -46
  148. package/.next/standalone/node_modules/react/cjs/react.production.js +112 -89
  149. package/.next/standalone/node_modules/react/package.json +1 -1
  150. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1165 -790
  151. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1171 -791
  152. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +1294 -862
  153. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +1305 -868
  154. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +1315 -896
  155. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +24 -5
  156. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  157. package/.next/standalone/package.json +13 -12
  158. package/.next/standalone/sdk/python/uv.lock +18 -18
  159. package/.next/standalone/server.js +1 -1
  160. package/README.md +29 -13
  161. package/bin/failproofai.mjs +3 -1
  162. package/dist/cli.mjs +420 -178
  163. package/dist/worker.mjs +17 -3
  164. package/hermes-plugin/README.md +133 -0
  165. package/hermes-plugin/__init__.py +248 -0
  166. package/hermes-plugin/client.py +134 -0
  167. package/hermes-plugin/ledger.py +187 -0
  168. package/hermes-plugin/plugin.yaml +37 -0
  169. package/package.json +13 -12
  170. package/scripts/translate-docs/validate-translation.ts +48 -0
  171. package/src/hooks/handler.ts +29 -1
  172. package/src/hooks/integrations.ts +264 -73
  173. package/src/hooks/manager.ts +17 -5
  174. package/src/hooks/types.ts +8 -8
  175. package/src/hooks/worker-server.ts +1 -0
  176. package/.next/standalone/.next/server/chunks/[externals]__157pf8j._.js +0 -3
  177. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +0 -3
  178. package/.next/standalone/.next/static/chunks/0y8_1s45gbqmn.js +0 -6
  179. package/.next/standalone/.next/static/chunks/14b3ezufe5411.js +0 -1
  180. package/.next/standalone/.next/static/chunks/1oskk7pdo8aon.js +0 -1
  181. package/.next/standalone/.next/static/chunks/2xycd6mjg00fb.js +0 -1
  182. package/.next/standalone/.next/static/chunks/3l36d73kj18zw.js +0 -1
  183. package/.next/standalone/.next/static/chunks/40lpt2o2b283e.js +0 -1
  184. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_buildManifest.js +0 -0
  185. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_clientMiddlewareManifest.js +0 -0
  186. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_ssgManifest.js +0 -0
@@ -0,0 +1,134 @@
1
+ """Versioned Unix-socket client for failproofaid policy evaluation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import socket
8
+ import struct
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Mapping, Sequence
12
+
13
+ PROTOCOL_VERSION = 1
14
+ MAX_FRAME_BYTES = 16 * 1024 * 1024
15
+ MAX_REQUEST_BYTES = 1024 * 1024
16
+
17
+
18
+ class EvaluationError(RuntimeError):
19
+ """The daemon could not return a trustworthy policy verdict."""
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class PolicyVerdict:
24
+ decision: str
25
+ policy_names: tuple[str, ...]
26
+ reason: str | None
27
+ matched_policies: tuple[str, ...]
28
+ duration_ms: int
29
+ tool_name: str | None
30
+
31
+
32
+ def daemon_socket_path() -> Path:
33
+ override = os.environ.get("FAILPROOFAI_DAEMON_SOCKET", "").strip()
34
+ if override:
35
+ return Path(override).expanduser()
36
+ root = os.environ.get("FAILPROOFAI_HOME", "").strip()
37
+ home = Path(root).expanduser() if root else Path.home() / ".failproofai"
38
+ return home / "run" / "failproofaid.sock"
39
+
40
+
41
+ def _read_exact(sock: socket.socket, length: int) -> bytes:
42
+ chunks: list[bytes] = []
43
+ remaining = length
44
+ while remaining:
45
+ chunk = sock.recv(remaining)
46
+ if not chunk:
47
+ raise EvaluationError("failproofaid closed the connection before returning a verdict")
48
+ chunks.append(chunk)
49
+ remaining -= len(chunk)
50
+ return b"".join(chunks)
51
+
52
+
53
+ def _string_list(value: object, field: str) -> tuple[str, ...]:
54
+ if not isinstance(value, Sequence) or isinstance(value, (str, bytes, bytearray)):
55
+ raise EvaluationError(f"failproofaid returned an invalid {field}")
56
+ if not all(isinstance(item, str) for item in value):
57
+ raise EvaluationError(f"failproofaid returned an invalid {field}")
58
+ return tuple(value)
59
+
60
+
61
+ def evaluate_policy(
62
+ *,
63
+ event: str,
64
+ payload: Mapping[str, Any],
65
+ cwd: str | None,
66
+ connect_timeout_ms: int = 250,
67
+ evaluation_timeout_ms: int = 12_000,
68
+ ) -> PolicyVerdict:
69
+ request = {
70
+ "type": "policyEvaluation",
71
+ "protocolVersion": PROTOCOL_VERSION,
72
+ "integration": "hermes",
73
+ "event": event,
74
+ "payload": dict(payload),
75
+ "cwd": cwd,
76
+ }
77
+ try:
78
+ body = json.dumps(
79
+ request,
80
+ ensure_ascii=False,
81
+ separators=(",", ":"),
82
+ default=lambda value: repr(value),
83
+ ).encode("utf-8")
84
+ except (TypeError, ValueError) as exc:
85
+ raise EvaluationError(f"could not encode policy request: {exc}") from exc
86
+ if len(body) > MAX_REQUEST_BYTES:
87
+ raise EvaluationError("policy request exceeds the 1 MiB limit")
88
+
89
+ try:
90
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
91
+ sock.settimeout(max(connect_timeout_ms, 1) / 1000)
92
+ sock.connect(str(daemon_socket_path()))
93
+ sock.settimeout(max(evaluation_timeout_ms, 1) / 1000)
94
+ sock.sendall(struct.pack(">I", len(body)) + body)
95
+ declared_length = struct.unpack(">I", _read_exact(sock, 4))[0]
96
+ if declared_length > MAX_FRAME_BYTES:
97
+ raise EvaluationError("failproofaid response exceeds the 16 MiB limit")
98
+ response = json.loads(_read_exact(sock, declared_length).decode("utf-8"))
99
+ except EvaluationError:
100
+ raise
101
+ except (OSError, UnicodeError, ValueError, struct.error) as exc:
102
+ raise EvaluationError(f"failproofaid evaluation failed: {exc}") from exc
103
+
104
+ if not isinstance(response, dict):
105
+ raise EvaluationError("failproofaid returned a non-object response")
106
+ if response.get("protocolVersion") != PROTOCOL_VERSION:
107
+ raise EvaluationError("failproofaid protocol version mismatch")
108
+ if response.get("type") == "error":
109
+ message = response.get("message")
110
+ raise EvaluationError(str(message or "failproofaid could not evaluate the policy"))
111
+ if response.get("type") != "policyResult":
112
+ raise EvaluationError("failproofaid does not support native policy evaluation")
113
+
114
+ decision = response.get("decision")
115
+ if decision not in {"allow", "deny", "instruct"}:
116
+ raise EvaluationError("failproofaid returned an invalid policy decision")
117
+ reason = response.get("reason")
118
+ if reason is not None and not isinstance(reason, str):
119
+ raise EvaluationError("failproofaid returned an invalid policy reason")
120
+ duration_ms = response.get("durationMs", 0)
121
+ if not isinstance(duration_ms, int) or duration_ms < 0:
122
+ raise EvaluationError("failproofaid returned an invalid evaluation duration")
123
+ tool_name = response.get("toolName")
124
+ if tool_name is not None and not isinstance(tool_name, str):
125
+ raise EvaluationError("failproofaid returned an invalid tool name")
126
+
127
+ return PolicyVerdict(
128
+ decision=decision,
129
+ policy_names=_string_list(response.get("policyNames", []), "policyNames"),
130
+ reason=reason,
131
+ matched_policies=_string_list(response.get("matchedPolicies", []), "matchedPolicies"),
132
+ duration_ms=duration_ms,
133
+ tool_name=tool_name,
134
+ )
@@ -0,0 +1,187 @@
1
+ """Persistent, bounded delivery state for FailproofAI instruct decisions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import sqlite3
8
+ import threading
9
+ import time
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Sequence
13
+
14
+
15
+ class LedgerError(RuntimeError):
16
+ """Instruction state could not be read or updated safely."""
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class InstructionAction:
21
+ block: bool
22
+ status: str
23
+
24
+
25
+ class InstructionLedger:
26
+ def __init__(self, path: Path, *, ttl_seconds: int = 3600, max_rounds: int = 2) -> None:
27
+ self.path = path
28
+ self.ttl_seconds = max(60, int(ttl_seconds))
29
+ self.max_rounds = max(0, int(max_rounds))
30
+ self._init_lock = threading.Lock()
31
+ self._initialized = False
32
+
33
+ def _connect(self) -> sqlite3.Connection:
34
+ self.path.parent.mkdir(parents=True, exist_ok=True)
35
+ connection = sqlite3.connect(self.path, timeout=0.25, isolation_level=None)
36
+ connection.execute("PRAGMA busy_timeout = 250")
37
+ connection.execute("PRAGMA journal_mode = WAL")
38
+ connection.execute("PRAGMA synchronous = NORMAL")
39
+ return connection
40
+
41
+ def _ensure_schema(self) -> None:
42
+ if self._initialized:
43
+ return
44
+ with self._init_lock:
45
+ if self._initialized:
46
+ return
47
+ try:
48
+ with self._connect() as connection:
49
+ connection.execute(
50
+ """
51
+ CREATE TABLE IF NOT EXISTS instruction_deliveries (
52
+ delivery_key TEXT PRIMARY KEY,
53
+ profile TEXT NOT NULL,
54
+ session_id TEXT NOT NULL,
55
+ task_id TEXT NOT NULL,
56
+ turn_id TEXT NOT NULL,
57
+ policy_fingerprint TEXT NOT NULL,
58
+ scope_key TEXT NOT NULL,
59
+ blocked_api_request_id TEXT NOT NULL,
60
+ acknowledged_api_request_id TEXT,
61
+ state TEXT NOT NULL,
62
+ created_at_ms INTEGER NOT NULL,
63
+ expires_at_ms INTEGER NOT NULL
64
+ )
65
+ """
66
+ )
67
+ connection.execute(
68
+ """
69
+ CREATE INDEX IF NOT EXISTS instruction_deliveries_turn
70
+ ON instruction_deliveries(profile, session_id, task_id, turn_id, expires_at_ms)
71
+ """
72
+ )
73
+ except sqlite3.Error as exc:
74
+ raise LedgerError(f"could not initialize instruction state: {exc}") from exc
75
+ self._initialized = True
76
+
77
+ @staticmethod
78
+ def _digest(parts: Sequence[str]) -> str:
79
+ encoded = json.dumps(list(parts), ensure_ascii=False, separators=(",", ":")).encode("utf-8")
80
+ return hashlib.sha256(encoded).hexdigest()
81
+
82
+ def decide(
83
+ self,
84
+ *,
85
+ profile: str,
86
+ session_id: str,
87
+ task_id: str,
88
+ turn_id: str,
89
+ api_request_id: str,
90
+ policy_names: Sequence[str],
91
+ reason: str,
92
+ scope_key: str,
93
+ now_ms: int | None = None,
94
+ ) -> InstructionAction:
95
+ if not session_id or not turn_id or not api_request_id:
96
+ return InstructionAction(False, "missing-correlation-id")
97
+
98
+ self._ensure_schema()
99
+ now = int(time.time() * 1000) if now_ms is None else int(now_ms)
100
+ expires_at = now + self.ttl_seconds * 1000
101
+ task = task_id or "root"
102
+ policy_fingerprint = self._digest([*sorted(policy_names), reason])
103
+ delivery_key = self._digest(
104
+ [profile, session_id, task, turn_id, policy_fingerprint, scope_key]
105
+ )
106
+
107
+ try:
108
+ with self._connect() as connection:
109
+ connection.execute("BEGIN IMMEDIATE")
110
+ connection.execute(
111
+ "DELETE FROM instruction_deliveries WHERE expires_at_ms <= ?", (now,)
112
+ )
113
+ row = connection.execute(
114
+ """
115
+ SELECT blocked_api_request_id, state
116
+ FROM instruction_deliveries
117
+ WHERE delivery_key = ?
118
+ """,
119
+ (delivery_key,),
120
+ ).fetchone()
121
+ if row is not None:
122
+ blocked_request, state = row
123
+ if state == "issued" and blocked_request == api_request_id:
124
+ connection.commit()
125
+ return InstructionAction(True, "same-api-request")
126
+ if state == "issued":
127
+ connection.execute(
128
+ """
129
+ UPDATE instruction_deliveries
130
+ SET state = 'acknowledged', acknowledged_api_request_id = ?
131
+ WHERE delivery_key = ?
132
+ """,
133
+ (api_request_id, delivery_key),
134
+ )
135
+ connection.commit()
136
+ return InstructionAction(False, "acknowledged")
137
+
138
+ rounds = connection.execute(
139
+ """
140
+ SELECT COUNT(*)
141
+ FROM instruction_deliveries
142
+ WHERE profile = ? AND session_id = ? AND task_id = ? AND turn_id = ?
143
+ AND expires_at_ms > ?
144
+ """,
145
+ (profile, session_id, task, turn_id, now),
146
+ ).fetchone()[0]
147
+ if rounds >= self.max_rounds:
148
+ connection.commit()
149
+ return InstructionAction(False, "turn-cap")
150
+
151
+ connection.execute(
152
+ """
153
+ INSERT INTO instruction_deliveries (
154
+ delivery_key, profile, session_id, task_id, turn_id,
155
+ policy_fingerprint, scope_key, blocked_api_request_id,
156
+ acknowledged_api_request_id, state, created_at_ms, expires_at_ms
157
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'issued', ?, ?)
158
+ """,
159
+ (
160
+ delivery_key,
161
+ profile,
162
+ session_id,
163
+ task,
164
+ turn_id,
165
+ policy_fingerprint,
166
+ scope_key,
167
+ api_request_id,
168
+ now,
169
+ expires_at,
170
+ ),
171
+ )
172
+ connection.commit()
173
+ return InstructionAction(True, "issued")
174
+ except (sqlite3.Error, OSError) as exc:
175
+ raise LedgerError(f"could not update instruction state: {exc}") from exc
176
+
177
+ def clear_session(self, session_id: str) -> None:
178
+ if not session_id:
179
+ return
180
+ self._ensure_schema()
181
+ try:
182
+ with self._connect() as connection:
183
+ connection.execute(
184
+ "DELETE FROM instruction_deliveries WHERE session_id = ?", (session_id,)
185
+ )
186
+ except (sqlite3.Error, OSError) as exc:
187
+ raise LedgerError(f"could not clear instruction state: {exc}") from exc
@@ -0,0 +1,37 @@
1
+ name: failproofai
2
+ version: 1.0.6-beta.0
3
+ description: Real-time FailproofAI policy enforcement for Hermes
4
+ manifest_version: 2
5
+ api_version: 1
6
+ homepage: https://github.com/FailproofAI/failproofai
7
+ tags: [security, policy, observability]
8
+ provides_hooks:
9
+ - pre_tool_call
10
+ - post_tool_call
11
+ - pre_llm_call
12
+ - on_session_start
13
+ - on_session_end
14
+ - on_session_reset
15
+ - on_session_finalize
16
+ - subagent_stop
17
+ config_schema:
18
+ failure_mode:
19
+ type: str
20
+ default: deny
21
+ description: Block or allow when the local FailproofAI evaluator is unavailable.
22
+ connect_timeout_ms:
23
+ type: int
24
+ default: 250
25
+ description: Maximum time to connect to the local FailproofAI daemon.
26
+ evaluation_timeout_ms:
27
+ type: int
28
+ default: 12000
29
+ description: Maximum total time allowed for one policy evaluation.
30
+ instruction_ttl_seconds:
31
+ type: int
32
+ default: 3600
33
+ description: Maximum lifetime of an instruction-delivery record.
34
+ max_instruction_rounds:
35
+ type: int
36
+ default: 2
37
+ description: Maximum distinct instruction interruptions in one Hermes turn.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/env",
3
- "version": "16.3.4",
3
+ "version": "16.3.5",
4
4
  "keywords": [
5
5
  "react",
6
6
  "next",
@@ -139,7 +139,7 @@ var HmrTarget = /*#__PURE__*/ function(HmrTarget) {
139
139
  HmrTarget["Server"] = "server";
140
140
  return HmrTarget;
141
141
  }({});
142
- const nextVersion = "16.3.4";
142
+ const nextVersion = "16.3.5";
143
143
  const ArchName = (0, _os.arch)();
144
144
  const PlatformName = (0, _os.platform)();
145
145
  function infoLog(...args) {