failproofai 0.0.6-beta.5 → 0.0.6

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 (88) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  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 +2 -2
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/index.html +1 -1
  27. package/.next/standalone/.next/server/app/index.rsc +15 -15
  28. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  30. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  31. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  32. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  33. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  34. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  37. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  44. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
  51. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
  52. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
  53. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
  54. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
  55. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
  56. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0u49i20._.js → [root-of-the-server]__0ow37ro._.js} +2 -2
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0xezw2w._.js → [root-of-the-server]__0t3ka1q._.js} +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
  64. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  65. package/.next/standalone/.next/server/pages/404.html +2 -2
  66. package/.next/standalone/.next/server/pages/500.html +1 -1
  67. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
  69. package/.next/standalone/.next/static/chunks/{0af6vx3nh6v77.js → 0-igg2k65fzo_.js} +1 -1
  70. package/.next/standalone/.next/static/chunks/{0q~7ymasjp~n7.js → 04iuhj_-h-21-.js} +1 -1
  71. package/.next/standalone/.next/static/chunks/{0hrob6m-1.n1d.js → 061hxr2b-j.6q.js} +1 -1
  72. package/.next/standalone/.next/static/chunks/{11vga8q3t~527.js → 0k5t-n0s8p2nr.js} +1 -1
  73. package/.next/standalone/.next/static/chunks/{0ms9sfkvw5h5i.js → 0lq8ary5l4s8t.js} +1 -1
  74. package/.next/standalone/.next/static/chunks/{03xexm0jftw.x.js → 0ubv3x~0zdd_w.js} +1 -1
  75. package/.next/standalone/.next/static/chunks/{0on5naeqa0w.x.js → 0v23ca5xty1n~.js} +2 -2
  76. package/.next/standalone/.next/static/chunks/{0_-7qmtiagi-~.js → 13cot7j99xkb~.js} +1 -1
  77. package/.next/standalone/failproofai-hq.gif +0 -0
  78. package/.next/standalone/package.json +1 -1
  79. package/.next/standalone/server.js +1 -1
  80. package/README.md +6 -2
  81. package/dist/cli.mjs +30 -12
  82. package/package.json +1 -1
  83. package/src/hooks/builtin-policies.ts +13 -5
  84. package/src/hooks/policy-evaluator.ts +31 -7
  85. package/src/hooks/policy-registry.ts +19 -2
  86. /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_buildManifest.js +0 -0
  87. /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_clientMiddlewareManifest.js +0 -0
  88. /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_ssgManifest.js +0 -0
@@ -7,7 +7,7 @@ import { execSync, execFileSync } from "node:child_process";
7
7
  import { homedir } from "node:os";
8
8
  import type { BuiltinPolicyDefinition, PolicyContext, PolicyResult, PolicyParamsSchema } from "./policy-types";
9
9
  import { allow, deny, instruct } from "./policy-helpers";
10
- import { registerPolicy } from "./policy-registry";
10
+ import { normalizePolicyName, registerPolicy } from "./policy-registry";
11
11
  import { hookLogWarn } from "./hook-logger";
12
12
 
13
13
  function isClaudeInternalPath(resolved: string): boolean {
@@ -1254,7 +1254,7 @@ function requireNoConflictsBeforeStop(ctx: PolicyContext): PolicyResult {
1254
1254
 
1255
1255
  let prJson: string;
1256
1256
  try {
1257
- prJson = execSync("gh pr view --json mergeable,number,url", {
1257
+ prJson = execSync("gh pr view --json mergeable,number,url,state", {
1258
1258
  cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000,
1259
1259
  }).trim();
1260
1260
  } catch {
@@ -1265,13 +1265,19 @@ function requireNoConflictsBeforeStop(ctx: PolicyContext): PolicyResult {
1265
1265
  );
1266
1266
  }
1267
1267
 
1268
- let pr: { mergeable: string; number: number; url: string };
1268
+ let pr: { mergeable: string; number: number; url: string; state: string };
1269
1269
  try {
1270
1270
  pr = JSON.parse(prJson);
1271
1271
  } catch {
1272
1272
  return allow("Could not parse gh pr view output, skipping PR mergeability check.");
1273
1273
  }
1274
1274
 
1275
+ // GitHub stops computing mergeability for non-OPEN PRs (returns UNKNOWN forever).
1276
+ // Skip the check entirely so a merged or closed PR doesn't trap Stop in a wait loop.
1277
+ if (pr.state !== "OPEN") {
1278
+ return allow(`PR #${pr.number} is ${pr.state.toLowerCase()}; skipping conflict check.`);
1279
+ }
1280
+
1275
1281
  if (pr.mergeable === "CONFLICTING") {
1276
1282
  return deny(
1277
1283
  `PR #${pr.number} has merge conflicts per GitHub (${pr.url}). ` +
@@ -1718,9 +1724,11 @@ export const BUILTIN_POLICIES: BuiltinPolicyDefinition[] = [
1718
1724
  ];
1719
1725
 
1720
1726
  export function registerBuiltinPolicies(enabledNames: string[]): void {
1721
- const enabledSet = new Set(enabledNames);
1727
+ // Tolerate both flat ("sanitize-jwt") and qualified ("exospherehost/sanitize-jwt")
1728
+ // forms in the user's enabledPolicies config — canonicalize both sides.
1729
+ const enabledSet = new Set(enabledNames.map(normalizePolicyName));
1722
1730
  for (const policy of BUILTIN_POLICIES) {
1723
- if (enabledSet.has(policy.name)) {
1731
+ if (enabledSet.has(normalizePolicyName(policy.name))) {
1724
1732
  registerPolicy(policy.name, policy.description, policy.fn, policy.match);
1725
1733
  }
1726
1734
  }
@@ -5,7 +5,7 @@
5
5
  import type { HookEventType, SessionMetadata } from "./types";
6
6
  import type { PolicyContext, HooksConfig } from "./policy-types";
7
7
  import { BUILTIN_POLICIES } from "./builtin-policies";
8
- import { getPoliciesForEvent } from "./policy-registry";
8
+ import { DEFAULT_POLICY_NAMESPACE, getPoliciesForEvent, normalizePolicyName } from "./policy-registry";
9
9
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
10
10
 
11
11
  function appendHint(baseReason: string, hint: unknown): string {
@@ -26,11 +26,33 @@ export interface EvaluationResult {
26
26
  decision: "allow" | "deny" | "instruct";
27
27
  }
28
28
 
29
- // Build a map from policy name to its params schema (for injecting defaults)
29
+ // Build a map from canonical policy name to its params schema (for injecting defaults).
30
+ // Keyed by canonical name because registered policies always carry the canonical form.
30
31
  const POLICY_PARAMS_MAP = new Map(
31
- BUILTIN_POLICIES.filter((p) => p.params).map((p) => [p.name, p.params!]),
32
+ BUILTIN_POLICIES.filter((p) => p.params).map((p) => [normalizePolicyName(p.name), p.params!]),
32
33
  );
33
34
 
35
+ /**
36
+ * Look up policy params for a canonical policy name in the user config,
37
+ * tolerating either flat ("block-force-push") or qualified
38
+ * ("exospherehost/block-force-push") config keys for built-in policies.
39
+ *
40
+ * The flat-key fallback is intentionally limited to the default namespace
41
+ * so namespace isolation is preserved: `policyParams.foo` only matches
42
+ * `exospherehost/foo`, never `myorg/foo` or `custom/foo`.
43
+ */
44
+ function getConfigParamsFor(
45
+ config: HooksConfig | undefined,
46
+ canonicalName: string,
47
+ ): Record<string, unknown> | undefined {
48
+ if (!config?.policyParams) return undefined;
49
+ const canonicalParams = config.policyParams[canonicalName];
50
+ if (canonicalParams) return canonicalParams;
51
+ const defaultPrefix = `${DEFAULT_POLICY_NAMESPACE}/`;
52
+ if (!canonicalName.startsWith(defaultPrefix)) return undefined;
53
+ return config.policyParams[canonicalName.slice(defaultPrefix.length)];
54
+ }
55
+
34
56
  export async function evaluatePolicies(
35
57
  eventType: HookEventType,
36
58
  payload: Record<string, unknown>,
@@ -63,11 +85,13 @@ export async function evaluatePolicies(
63
85
  const allowEntries: Array<{ policyName: string; reason: string }> = [];
64
86
 
65
87
  for (const policy of policies) {
66
- // Inject params: merge policyParams[policy.name] over schema defaults
88
+ // Inject params: merge policyParams[policy.name] over schema defaults.
89
+ // policy.name is canonical (e.g. "exospherehost/block-force-push"); user
90
+ // config keys may be flat or canonical — getConfigParamsFor accepts both.
67
91
  const schema = POLICY_PARAMS_MAP.get(policy.name);
68
92
  let ctx: PolicyContext;
69
93
  if (schema) {
70
- const userParams = config?.policyParams?.[policy.name] ?? {};
94
+ const userParams = getConfigParamsFor(config, policy.name) ?? {};
71
95
  const resolvedParams: Record<string, unknown> = {};
72
96
  for (const [key, spec] of Object.entries(schema)) {
73
97
  resolvedParams[key] = key in userParams ? userParams[key] : spec.default;
@@ -89,7 +113,7 @@ export async function evaluatePolicies(
89
113
  if (result.decision === "deny") {
90
114
  const reason = appendHint(
91
115
  result.reason ?? `Blocked by policy: ${policy.name}`,
92
- config?.policyParams?.[policy.name]?.hint,
116
+ getConfigParamsFor(config, policy.name)?.hint,
93
117
  );
94
118
  hookLogInfo(`deny by "${policy.name}": ${reason}`);
95
119
 
@@ -156,7 +180,7 @@ export async function evaluatePolicies(
156
180
  if (result.decision === "instruct") {
157
181
  const reason = appendHint(
158
182
  result.reason ?? `Instruction from policy: ${policy.name}`,
159
- config?.policyParams?.[policy.name]?.hint,
183
+ getConfigParamsFor(config, policy.name)?.hint,
160
184
  );
161
185
  instructEntries.push({ policyName: policy.name, reason });
162
186
  hookLogInfo(`instruct by "${policy.name}": ${reason}`);
@@ -11,6 +11,22 @@ import type { PolicyFunction, PolicyMatcher, RegisteredPolicy } from "./policy-t
11
11
  const REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__";
12
12
  const INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__";
13
13
 
14
+ /**
15
+ * The default namespace applied to any policy name registered without a
16
+ * `<namespace>/` prefix. Builtins live under this namespace; custom hooks
17
+ * loaded by the handler get their own prefixes (e.g. `custom/foo`).
18
+ */
19
+ export const DEFAULT_POLICY_NAMESPACE = "exospherehost";
20
+
21
+ /**
22
+ * Canonicalize a policy name. If the name already contains a `/`, it is
23
+ * treated as already-namespaced and returned unchanged. Otherwise the
24
+ * default namespace is prepended.
25
+ */
26
+ export function normalizePolicyName(name: string): string {
27
+ return name.includes("/") ? name : `${DEFAULT_POLICY_NAMESPACE}/${name}`;
28
+ }
29
+
14
30
  interface GlobalWithRegistry {
15
31
  [REGISTRY_KEY]?: RegisteredPolicy[];
16
32
  }
@@ -42,9 +58,10 @@ export function registerPolicy(
42
58
  match: PolicyMatcher,
43
59
  priority: number = 0,
44
60
  ): void {
61
+ const canonical = normalizePolicyName(name);
45
62
  const registry = getRegistry();
46
- const idx = registry.findIndex((p) => p.name === name);
47
- const entry: RegisteredPolicy = { name, description, fn, match, priority };
63
+ const idx = registry.findIndex((p) => p.name === canonical);
64
+ const entry: RegisteredPolicy = { name: canonical, description, fn, match, priority };
48
65
  if (idx >= 0) {
49
66
  registry[idx] = entry;
50
67
  } else {