@fayz-ai/core 0.8.4 → 0.8.5

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 (110) hide show
  1. package/dist/access/engine.d.ts +8 -1
  2. package/dist/access/engine.d.ts.map +1 -1
  3. package/dist/access/index.d.ts +1 -1
  4. package/dist/access/index.d.ts.map +1 -1
  5. package/dist/access/index.js +6 -1
  6. package/dist/access/index.js.map +1 -1
  7. package/dist/chunk-HN7PTOSH.js +1149 -0
  8. package/dist/chunk-HN7PTOSH.js.map +1 -0
  9. package/dist/{chunk-KL2XT2TV.cjs → chunk-V2MSAUHO.js} +166 -29
  10. package/dist/chunk-V2MSAUHO.js.map +1 -0
  11. package/dist/data/archetype.d.ts.map +1 -1
  12. package/dist/data/bulk.d.ts +17 -0
  13. package/dist/data/bulk.d.ts.map +1 -0
  14. package/dist/data/filters.d.ts +29 -0
  15. package/dist/data/filters.d.ts.map +1 -0
  16. package/dist/data/index.d.ts +2 -1
  17. package/dist/data/index.d.ts.map +1 -1
  18. package/dist/data/index.js +1 -1
  19. package/dist/data/mock.d.ts.map +1 -1
  20. package/dist/data/platform-api.d.ts.map +1 -1
  21. package/dist/data/refresh.d.ts.map +1 -1
  22. package/dist/data/resolve.d.ts +10 -0
  23. package/dist/data/resolve.d.ts.map +1 -1
  24. package/dist/data/supabase.d.ts.map +1 -1
  25. package/dist/data/types.d.ts +22 -0
  26. package/dist/data/types.d.ts.map +1 -1
  27. package/dist/i18n/index.d.ts +7 -0
  28. package/dist/i18n/index.d.ts.map +1 -1
  29. package/dist/i18n/index.js +1 -1
  30. package/dist/i18n/shell-translations.en.d.ts +2 -0
  31. package/dist/i18n/shell-translations.en.d.ts.map +1 -0
  32. package/dist/i18n/shell-translations.pt-br.d.ts +2 -0
  33. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -0
  34. package/dist/index.d.ts +6 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +10 -6
  37. package/dist/index.js.map +1 -1
  38. package/dist/search/engine.d.ts.map +1 -1
  39. package/dist/shell-translations.pt-br-2GWRQRRP.js +875 -0
  40. package/dist/shell-translations.pt-br-2GWRQRRP.js.map +1 -0
  41. package/dist/tenant/index.d.ts +10 -0
  42. package/dist/tenant/index.d.ts.map +1 -1
  43. package/dist/types/org.d.ts +9 -3
  44. package/dist/types/org.d.ts.map +1 -1
  45. package/package.json +21 -29
  46. package/src/access/engine.ts +16 -1
  47. package/src/access/index.ts +1 -0
  48. package/src/app/render.tsx +1 -1
  49. package/src/data/archetype.ts +23 -0
  50. package/src/data/bulk.ts +35 -0
  51. package/src/data/filters.test.ts +70 -0
  52. package/src/data/filters.ts +90 -0
  53. package/src/data/index.ts +2 -1
  54. package/src/data/mock.ts +8 -2
  55. package/src/data/platform-api.ts +12 -3
  56. package/src/data/refresh.ts +11 -1
  57. package/src/data/resolve.ts +16 -4
  58. package/src/data/supabase.ts +60 -1
  59. package/src/data/types.ts +24 -0
  60. package/src/i18n/index.ts +56 -6
  61. package/src/i18n/shell-translations.en.ts +875 -0
  62. package/src/i18n/shell-translations.pt-br.ts +873 -0
  63. package/src/index.ts +11 -2
  64. package/src/search/engine.ts +8 -0
  65. package/src/tenant/index.ts +20 -0
  66. package/src/types/org.ts +9 -3
  67. package/dist/access/index.cjs +0 -60
  68. package/dist/access/index.cjs.map +0 -1
  69. package/dist/chunk-56GH3WC2.cjs +0 -35
  70. package/dist/chunk-56GH3WC2.cjs.map +0 -1
  71. package/dist/chunk-5TKIZVVJ.cjs +0 -16
  72. package/dist/chunk-5TKIZVVJ.cjs.map +0 -1
  73. package/dist/chunk-AXTWKF5X.cjs +0 -38
  74. package/dist/chunk-AXTWKF5X.cjs.map +0 -1
  75. package/dist/chunk-GULZD7XW.js +0 -709
  76. package/dist/chunk-GULZD7XW.js.map +0 -1
  77. package/dist/chunk-HN66DF4E.cjs +0 -2024
  78. package/dist/chunk-HN66DF4E.cjs.map +0 -1
  79. package/dist/chunk-KL2XT2TV.cjs.map +0 -1
  80. package/dist/chunk-MJ4ML56F.js +0 -1994
  81. package/dist/chunk-MJ4ML56F.js.map +0 -1
  82. package/dist/chunk-P5OJUH25.cjs +0 -81
  83. package/dist/chunk-P5OJUH25.cjs.map +0 -1
  84. package/dist/chunk-UKU4RIWM.cjs +0 -32
  85. package/dist/chunk-UKU4RIWM.cjs.map +0 -1
  86. package/dist/chunk-ZOPJB5FN.cjs +0 -369
  87. package/dist/chunk-ZOPJB5FN.cjs.map +0 -1
  88. package/dist/data/index.cjs +0 -40
  89. package/dist/data/index.cjs.map +0 -1
  90. package/dist/entity/index.cjs +0 -28
  91. package/dist/entity/index.cjs.map +0 -1
  92. package/dist/i18n/index.cjs +0 -44
  93. package/dist/i18n/index.cjs.map +0 -1
  94. package/dist/i18n/shell-translations.d.ts +0 -2
  95. package/dist/i18n/shell-translations.d.ts.map +0 -1
  96. package/dist/index.cjs +0 -2450
  97. package/dist/index.cjs.map +0 -1
  98. package/dist/integrations/index.cjs +0 -12
  99. package/dist/integrations/index.cjs.map +0 -1
  100. package/dist/plugin/index.cjs +0 -32
  101. package/dist/plugin/index.cjs.map +0 -1
  102. package/dist/router/index.cjs +0 -16
  103. package/dist/router/index.cjs.map +0 -1
  104. package/dist/runtime/index.cjs +0 -16
  105. package/dist/runtime/index.cjs.map +0 -1
  106. package/dist/testing/index.cjs +0 -20
  107. package/dist/testing/index.cjs.map +0 -1
  108. package/dist/types/index.cjs +0 -4
  109. package/dist/types/index.cjs.map +0 -1
  110. package/src/i18n/shell-translations.ts +0 -1752
@@ -1,4 +1,4 @@
1
- import type { PermissionProfile, PermissionAction } from '../types/permissions';
1
+ import type { PermissionProfile, PermissionAction, SystemPermission } from '../types/permissions';
2
2
  import type { Plan } from '../types/billing';
3
3
  /** Why an access request was denied. `limit` only occurs on guarded writes. */
4
4
  export type DenyReason = 'role' | 'plan' | 'limit';
@@ -27,6 +27,13 @@ export declare function isOwnerProfile(profile: PermissionProfile): boolean;
27
27
  * semantics.
28
28
  */
29
29
  export declare function profileHasPermission(profile: PermissionProfile | null, feature: string, action?: string): boolean;
30
+ /**
31
+ * The org-management side of a profile: managing team, billing, settings and
32
+ * permissions themselves. Separate axis from `grants` — these gate the settings
33
+ * tabs, not a feature's rows — and it follows the same owner bypass, so the one
34
+ * person who can hand out access is never locked out of doing it.
35
+ */
36
+ export declare function profileHasSystemPermission(profile: PermissionProfile | null, permission: SystemPermission): boolean;
30
37
  /**
31
38
  * Does the current plan entitle `feature`? A plan gates a feature ONLY with an
32
39
  * explicit `false`; absent means "not gated by this plan" (allowed). This keeps
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAe5C,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACjC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GAAG,IAAI,EACjC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAQhB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IACxG,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;CACnB,CAOA"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACjG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAe5C,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACjC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GAAG,IAAI,EACjC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,GAAG,IAAI,EACjC,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAIT;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAQhB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IACxG,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;CACnB,CAOA"}
@@ -1,4 +1,4 @@
1
- export { isOwnerProfile, profileHasPermission, isEntitledByPlan, resolveAccess, resolveLimit, } from './engine';
1
+ export { isOwnerProfile, profileHasPermission, profileHasSystemPermission, isEntitledByPlan, resolveAccess, resolveLimit, } from './engine';
2
2
  export type { DenyReason, AccessDecision, AccessSubject } from './engine';
3
3
  export { UPGRADE_URL, agentDenial } from './denial';
4
4
  export type { AgentDenial, AgentDenialLimit, AgentGuardResult } from './denial';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,YAAY,GACb,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,gBAAgB,EAChB,aAAa,EACb,YAAY,GACb,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA"}
@@ -10,6 +10,11 @@ function profileHasPermission(profile, feature, action) {
10
10
  if (!action) return actions.length > 0;
11
11
  return actions.includes(action) || actions.includes("manage");
12
12
  }
13
+ function profileHasSystemPermission(profile, permission) {
14
+ if (!profile) return true;
15
+ if (isOwnerProfile(profile)) return true;
16
+ return profile.systemPermissions?.includes(permission) ?? false;
17
+ }
13
18
  function isEntitledByPlan(plan, feature) {
14
19
  return plan?.entitlements?.features?.[feature] !== false;
15
20
  }
@@ -46,6 +51,6 @@ function mergeLimitDeclarations(...layers) {
46
51
  return Array.from(byKey.values());
47
52
  }
48
53
 
49
- export { UPGRADE_URL, agentDenial, isEntitledByPlan, isOwnerProfile, mergeLimitDeclarations, profileHasPermission, resolveAccess, resolveLimit };
54
+ export { UPGRADE_URL, agentDenial, isEntitledByPlan, isOwnerProfile, mergeLimitDeclarations, profileHasPermission, profileHasSystemPermission, resolveAccess, resolveLimit };
50
55
  //# sourceMappingURL=index.js.map
51
56
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/access/engine.ts","../../src/access/denial.ts","../../src/access/limits.ts"],"names":[],"mappings":";AAuCO,SAAS,eAAe,OAAA,EAAqC;AAClE,EAAA,OAAO,QAAQ,EAAA,KAAO,OAAA,IAAW,OAAA,CAAQ,IAAA,EAAM,aAAY,KAAM,OAAA;AACnE;AAOO,SAAS,oBAAA,CACd,OAAA,EACA,OAAA,EACA,MAAA,EACS;AACT,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAMrB,EAAA,IAAI,cAAA,CAAe,OAAO,CAAA,EAAG,OAAO,IAAA;AAEpC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA;AACtC,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AAErB,EAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA;AAErC,EAAA,OAAO,QAAQ,QAAA,CAAS,MAAM,CAAA,IAAK,OAAA,CAAQ,SAAS,QAAQ,CAAA;AAC9D;AAQO,SAAS,gBAAA,CAAiB,MAAmB,OAAA,EAA0B;AAC5E,EAAA,OAAO,IAAA,EAAM,YAAA,EAAc,QAAA,GAAW,OAAO,CAAA,KAAM,KAAA;AACrD;AAWO,SAAS,aAAA,CACd,OAAA,EACA,OAAA,EACA,MAAA,EACgB;AAChB,EAAA,IAAI,CAAC,oBAAA,CAAqB,OAAA,CAAQ,OAAA,EAAS,OAAA,EAAS,MAAM,CAAA,EAAG;AAC3D,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAO;AAAA,EAC1C;AACA,EAAA,IAAI,CAAC,gBAAA,CAAiB,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,EAAG;AAC5C,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAO;AAAA,EAC1C;AACA,EAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AACzB;AAOO,SAAS,aAAa,MAAA,EAM3B;AACA,EAAA,MAAM,EAAE,GAAA,EAAK,cAAA,EAAgB,IAAA,EAAK,GAAI,MAAA;AACtC,EAAA,MAAM,SAAA,GAAY,CAAC,cAAA,IAAkB,GAAA,KAAQ,UAAa,GAAA,KAAQ,EAAA;AAClE,EAAA,MAAM,GAAA,GAAM,YAAY,QAAA,GAAW,GAAA;AACnC,EAAA,MAAM,YAAY,SAAA,GAAY,QAAA,GAAW,KAAK,GAAA,CAAI,CAAA,EAAG,MAAM,IAAI,CAAA;AAC/D,EAAA,MAAM,OAAA,GAAU,CAAC,SAAA,IAAa,IAAA,IAAQ,GAAA;AACtC,EAAA,OAAO,EAAE,GAAA,EAAK,IAAA,EAAM,SAAA,EAAW,SAAS,SAAA,EAAU;AACpD;;;AC7GO,IAAM,WAAA,GAAc;AAmBpB,SAAS,WAAA,CAAY,QAAoB,KAAA,EAAuC;AACrF,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAM,GAAI,EAAC,EAAI,UAAA,EAAY,WAAA,EAAY;AACxF;;;ACvBO,SAAS,0BACX,MAAA,EACiB;AACpB,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA8B;AAChD,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,KAAA,MAAW,IAAA,IAAQ,SAAS,EAAC,QAAS,GAAA,CAAI,IAAA,CAAK,KAAK,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,CAAA;AAClC","file":"index.js","sourcesContent":["import type { PermissionProfile, PermissionAction } from '../types/permissions'\nimport type { Plan } from '../types/billing'\n\n// ---------------------------------------------------------------------------\n// The access engine — the ONE implementation of the role × plan × limit\n// decision, shared by every runtime that gates anything:\n//\n// - browser (saas AccessProvider / PermissionsProvider re-export from here)\n// - headless client-plane (saas access/headless.ts)\n// - the Fayz broker (server-side agent authorization imports this package)\n//\n// Hard constraint: this module (and everything under core/src/access) must stay\n// free of React, zustand and supabase — pure functions over plain data — so the\n// exact same code runs in Node/edge. Guarded by scripts/check-access-purity.mjs.\n// ---------------------------------------------------------------------------\n\n/** Why an access request was denied. `limit` only occurs on guarded writes. */\nexport type DenyReason = 'role' | 'plan' | 'limit'\n\nexport interface AccessDecision {\n allowed: boolean\n reason?: DenyReason\n}\n\n/** The minimal subject a decision needs — a projection of any richer session. */\nexport interface AccessSubject {\n /** Effective RBAC profile — the previewed role during impersonation. */\n profile: PermissionProfile | null\n /** The tenant's currently-active plan. */\n plan: Plan | null\n}\n\n/**\n * The org owner has every permission implicitly. Owner is represented by the\n * canonical role id `owner` across adapters (see org/adapters/supabase.ts\n * buildPermissionProfiles, which also grants owner all catalog perms, and the\n * mock adapter's `owner` fallback). We match by id first, falling back to the\n * (possibly localized) name for safety.\n */\nexport function isOwnerProfile(profile: PermissionProfile): boolean {\n return profile.id === 'owner' || profile.name?.toLowerCase() === 'owner'\n}\n\n/**\n * The single role-side permission check. The plan axis composes ON TOP of it in\n * {@link resolveAccess} instead of reimplementing owner-bypass / `manage`\n * semantics.\n */\nexport function profileHasPermission(\n profile: PermissionProfile | null,\n feature: string,\n action?: string,\n): boolean {\n if (!profile) return true\n\n // Owner bypass: never lock the owner out, even if the RBAC catalog is empty\n // or still loading. Because permission checks read the *effective* profile,\n // an owner previewing a non-owner role correctly loses this bypass — the\n // preview stays honest.\n if (isOwnerProfile(profile)) return true\n\n const actions = profile.grants[feature]\n if (!actions) return false\n\n if (!action) return actions.length > 0\n\n return actions.includes(action) || actions.includes('manage')\n}\n\n/**\n * Does the current plan entitle `feature`? A plan gates a feature ONLY with an\n * explicit `false`; absent means \"not gated by this plan\" (allowed). This keeps\n * entitlements additive and safe: a feature the plan has never heard of stays\n * open rather than silently locking.\n */\nexport function isEntitledByPlan(plan: Plan | null, feature: string): boolean {\n return plan?.entitlements?.features?.[feature] !== false\n}\n\n/**\n * The single access decision: role FIRST, then plan.\n *\n * 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics\n * live there, and impersonation composes for free because `profile` is the\n * previewed role). Denied → `{ allowed:false, reason:'role' }`.\n * 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's\n * decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.\n */\nexport function resolveAccess(\n subject: AccessSubject,\n feature: string,\n action?: PermissionAction,\n): AccessDecision {\n if (!profileHasPermission(subject.profile, feature, action)) {\n return { allowed: false, reason: 'role' }\n }\n if (!isEntitledByPlan(subject.plan, feature)) {\n return { allowed: false, reason: 'plan' }\n }\n return { allowed: true }\n}\n\n/**\n * Pure numeric side of a limit: given the plan cap, whether a countable\n * declaration exists, and the current usage, derive the limit math. Unlimited\n * when there's no declaration to count, no cap on the plan, or the cap is `-1`.\n */\nexport function resolveLimit(params: { cap: number | undefined; hasDeclaration: boolean; used: number }): {\n max: number\n used: number\n remaining: number\n atLimit: boolean\n unlimited: boolean\n} {\n const { cap, hasDeclaration, used } = params\n const unlimited = !hasDeclaration || cap === undefined || cap === -1\n const max = unlimited ? Infinity : cap\n const remaining = unlimited ? Infinity : Math.max(0, max - used)\n const atLimit = !unlimited && used >= max\n return { max, used, remaining, atLimit, unlimited }\n}\n","import type { DenyReason } from './engine'\n\n// ---------------------------------------------------------------------------\n// The structured denial an AGENT surface returns instead of failing silently\n// (docs/ENTITLEMENTS.md § \"Superfície de agentes de IA\"). One shape for every\n// executor — FAB client-plane, the Fayz broker, and the pool RPCs (which emit\n// its jsonb mirror in their `denial` field) — so the model can explain the\n// denial and offer the upgrade path conversationally.\n// ---------------------------------------------------------------------------\n\n/** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */\nexport const UPGRADE_URL = '/settings/subscription'\n\nexport interface AgentDenialLimit {\n key: string\n max: number\n used: number\n}\n\nexport interface AgentDenial {\n allowed: false\n reason: DenyReason\n /** Present when `reason === 'limit'`. */\n limit?: AgentDenialLimit\n upgradeUrl: string\n}\n\nexport type AgentGuardResult = { allowed: true } | AgentDenial\n\n/** Build a denial with the canonical upgrade URL attached. */\nexport function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial {\n return { allowed: false, reason, ...(limit ? { limit } : {}), upgradeUrl: UPGRADE_URL }\n}\n","import type { LimitDeclaration } from '../types/entitlements'\n\n/**\n * The 4-layer limit-declaration merge, extracted so the browser AccessProvider\n * and the manifest derivation (`fayz manifest emit`) resolve the EXACT same\n * final set: core built-ins < entity-derived < plugin `declaredLimits` < app\n * overrides. Later layers win by key. Pure: pass the layers in, get the merge\n * out — deriving a declaration FROM an entity stays at the call site.\n */\nexport function mergeLimitDeclarations(\n ...layers: Array<LimitDeclaration[] | undefined>\n): LimitDeclaration[] {\n const byKey = new Map<string, LimitDeclaration>()\n for (const layer of layers) {\n for (const decl of layer ?? []) byKey.set(decl.key, decl)\n }\n return Array.from(byKey.values())\n}\n"]}
1
+ {"version":3,"sources":["../../src/access/engine.ts","../../src/access/denial.ts","../../src/access/limits.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PermissionProfile, PermissionAction, SystemPermission } from '../types/permissions'\nimport type { Plan } from '../types/billing'\n\n// ---------------------------------------------------------------------------\n// The access engine — the ONE implementation of the role × plan × limit\n// decision, shared by every runtime that gates anything:\n//\n// - browser (saas AccessProvider / PermissionsProvider re-export from here)\n// - headless client-plane (saas access/headless.ts)\n// - the Fayz broker (server-side agent authorization imports this package)\n//\n// Hard constraint: this module (and everything under core/src/access) must stay\n// free of React, zustand and supabase — pure functions over plain data — so the\n// exact same code runs in Node/edge. Guarded by scripts/check-access-purity.mjs.\n// ---------------------------------------------------------------------------\n\n/** Why an access request was denied. `limit` only occurs on guarded writes. */\nexport type DenyReason = 'role' | 'plan' | 'limit'\n\nexport interface AccessDecision {\n allowed: boolean\n reason?: DenyReason\n}\n\n/** The minimal subject a decision needs — a projection of any richer session. */\nexport interface AccessSubject {\n /** Effective RBAC profile — the previewed role during impersonation. */\n profile: PermissionProfile | null\n /** The tenant's currently-active plan. */\n plan: Plan | null\n}\n\n/**\n * The org owner has every permission implicitly. Owner is represented by the\n * canonical role id `owner` across adapters (see org/adapters/supabase.ts\n * buildPermissionProfiles, which also grants owner all catalog perms, and the\n * mock adapter's `owner` fallback). We match by id first, falling back to the\n * (possibly localized) name for safety.\n */\nexport function isOwnerProfile(profile: PermissionProfile): boolean {\n return profile.id === 'owner' || profile.name?.toLowerCase() === 'owner'\n}\n\n/**\n * The single role-side permission check. The plan axis composes ON TOP of it in\n * {@link resolveAccess} instead of reimplementing owner-bypass / `manage`\n * semantics.\n */\nexport function profileHasPermission(\n profile: PermissionProfile | null,\n feature: string,\n action?: string,\n): boolean {\n if (!profile) return true\n\n // Owner bypass: never lock the owner out, even if the RBAC catalog is empty\n // or still loading. Because permission checks read the *effective* profile,\n // an owner previewing a non-owner role correctly loses this bypass — the\n // preview stays honest.\n if (isOwnerProfile(profile)) return true\n\n const actions = profile.grants[feature]\n if (!actions) return false\n\n if (!action) return actions.length > 0\n\n return actions.includes(action) || actions.includes('manage')\n}\n\n/**\n * The org-management side of a profile: managing team, billing, settings and\n * permissions themselves. Separate axis from `grants` — these gate the settings\n * tabs, not a feature's rows — and it follows the same owner bypass, so the one\n * person who can hand out access is never locked out of doing it.\n */\nexport function profileHasSystemPermission(\n profile: PermissionProfile | null,\n permission: SystemPermission,\n): boolean {\n if (!profile) return true\n if (isOwnerProfile(profile)) return true\n return profile.systemPermissions?.includes(permission) ?? false\n}\n\n/**\n * Does the current plan entitle `feature`? A plan gates a feature ONLY with an\n * explicit `false`; absent means \"not gated by this plan\" (allowed). This keeps\n * entitlements additive and safe: a feature the plan has never heard of stays\n * open rather than silently locking.\n */\nexport function isEntitledByPlan(plan: Plan | null, feature: string): boolean {\n return plan?.entitlements?.features?.[feature] !== false\n}\n\n/**\n * The single access decision: role FIRST, then plan.\n *\n * 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics\n * live there, and impersonation composes for free because `profile` is the\n * previewed role). Denied → `{ allowed:false, reason:'role' }`.\n * 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's\n * decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.\n */\nexport function resolveAccess(\n subject: AccessSubject,\n feature: string,\n action?: PermissionAction,\n): AccessDecision {\n if (!profileHasPermission(subject.profile, feature, action)) {\n return { allowed: false, reason: 'role' }\n }\n if (!isEntitledByPlan(subject.plan, feature)) {\n return { allowed: false, reason: 'plan' }\n }\n return { allowed: true }\n}\n\n/**\n * Pure numeric side of a limit: given the plan cap, whether a countable\n * declaration exists, and the current usage, derive the limit math. Unlimited\n * when there's no declaration to count, no cap on the plan, or the cap is `-1`.\n */\nexport function resolveLimit(params: { cap: number | undefined; hasDeclaration: boolean; used: number }): {\n max: number\n used: number\n remaining: number\n atLimit: boolean\n unlimited: boolean\n} {\n const { cap, hasDeclaration, used } = params\n const unlimited = !hasDeclaration || cap === undefined || cap === -1\n const max = unlimited ? Infinity : cap\n const remaining = unlimited ? Infinity : Math.max(0, max - used)\n const atLimit = !unlimited && used >= max\n return { max, used, remaining, atLimit, unlimited }\n}\n","import type { DenyReason } from './engine'\n\n// ---------------------------------------------------------------------------\n// The structured denial an AGENT surface returns instead of failing silently\n// (docs/ENTITLEMENTS.md § \"Superfície de agentes de IA\"). One shape for every\n// executor — FAB client-plane, the Fayz broker, and the pool RPCs (which emit\n// its jsonb mirror in their `denial` field) — so the model can explain the\n// denial and offer the upgrade path conversationally.\n// ---------------------------------------------------------------------------\n\n/** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */\nexport const UPGRADE_URL = '/settings/subscription'\n\nexport interface AgentDenialLimit {\n key: string\n max: number\n used: number\n}\n\nexport interface AgentDenial {\n allowed: false\n reason: DenyReason\n /** Present when `reason === 'limit'`. */\n limit?: AgentDenialLimit\n upgradeUrl: string\n}\n\nexport type AgentGuardResult = { allowed: true } | AgentDenial\n\n/** Build a denial with the canonical upgrade URL attached. */\nexport function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial {\n return { allowed: false, reason, ...(limit ? { limit } : {}), upgradeUrl: UPGRADE_URL }\n}\n","import type { LimitDeclaration } from '../types/entitlements'\n\n/**\n * The 4-layer limit-declaration merge, extracted so the browser AccessProvider\n * and the manifest derivation (`fayz manifest emit`) resolve the EXACT same\n * final set: core built-ins < entity-derived < plugin `declaredLimits` < app\n * overrides. Later layers win by key. Pure: pass the layers in, get the merge\n * out — deriving a declaration FROM an entity stays at the call site.\n */\nexport function mergeLimitDeclarations(\n ...layers: Array<LimitDeclaration[] | undefined>\n): LimitDeclaration[] {\n const byKey = new Map<string, LimitDeclaration>()\n for (const layer of layers) {\n for (const decl of layer ?? []) byKey.set(decl.key, decl)\n }\n return Array.from(byKey.values())\n}\n"]}