@bash-app/bash-common 30.360.0 → 30.361.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.
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "@jest/globals";
2
- import { organizationHasVanitySubdomain } from "../organizationEntitlements.js";
2
+ import { organizationHasApiAccess, organizationHasAuditLogAccess, organizationHasComplianceAccess, organizationHasEnterprisePlatformAccess, organizationHasProcurementAccess, organizationHasSsoAccess, organizationHasVanitySubdomain, } from "../organizationEntitlements.js";
3
3
  describe("organizationHasVanitySubdomain", () => {
4
4
  test("null / undefined return false", () => {
5
5
  expect(organizationHasVanitySubdomain(null)).toBe(false);
@@ -39,4 +39,37 @@ describe("organizationHasVanitySubdomain", () => {
39
39
  }
40
40
  });
41
41
  });
42
+ describe("enterprise platform gates (audit logs, SSO, procurement, API access)", () => {
43
+ const enterpriseActive = {
44
+ subscriptionTier: "Enterprise",
45
+ subscriptionStatus: "Active",
46
+ };
47
+ const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
48
+ test("organizationHasEnterprisePlatformAccess mirrors organizationHasVanitySubdomain", () => {
49
+ expect(organizationHasEnterprisePlatformAccess(enterpriseActive)).toBe(true);
50
+ expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
51
+ expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
52
+ expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
53
+ });
54
+ test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
55
+ expect(organizationHasAuditLogAccess(enterpriseActive)).toBe(true);
56
+ expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
57
+ });
58
+ test("organizationHasSsoAccess follows the shared enterprise gate", () => {
59
+ expect(organizationHasSsoAccess(enterpriseActive)).toBe(true);
60
+ expect(organizationHasSsoAccess(growthActive)).toBe(false);
61
+ });
62
+ test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
63
+ expect(organizationHasProcurementAccess(enterpriseActive)).toBe(true);
64
+ expect(organizationHasProcurementAccess(growthActive)).toBe(false);
65
+ });
66
+ test("organizationHasApiAccess follows the shared enterprise gate", () => {
67
+ expect(organizationHasApiAccess(enterpriseActive)).toBe(true);
68
+ expect(organizationHasApiAccess(growthActive)).toBe(false);
69
+ });
70
+ test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
71
+ expect(organizationHasComplianceAccess(enterpriseActive)).toBe(true);
72
+ expect(organizationHasComplianceAccess(growthActive)).toBe(false);
73
+ });
74
+ });
42
75
  //# sourceMappingURL=organizationEntitlements.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAEhF,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,CAAC,8BAA8B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACnD,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;YACnE,MAAM,CACJ,8BAA8B,CAAC;gBAC7B,gBAAgB,EAAE,IAAI;gBACtB,kBAAkB,EAAE,QAAQ;aAC7B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QACjE,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,QAAQ;SAC7B,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,UAAU;SAC/B,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACnF,MAAM,CACJ,8BAA8B,CAAC,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAC,CACnE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,IAAI;SACzB,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,KAAK,MAAM,MAAM,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,CAAC;YAC1D,MAAM,CACJ,8BAA8B,CAAC;gBAC7B,gBAAgB,EAAE,YAAY;gBAC9B,kBAAkB,EAAE,MAAM;aAC3B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EACL,wBAAwB,EACxB,6BAA6B,EAC7B,+BAA+B,EAC/B,uCAAuC,EACvC,gCAAgC,EAChC,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,gCAAgC,CAAC;AAExC,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,CAAC,8BAA8B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACnD,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;YACnE,MAAM,CACJ,8BAA8B,CAAC;gBAC7B,gBAAgB,EAAE,IAAI;gBACtB,kBAAkB,EAAE,QAAQ;aAC7B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QACjE,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,QAAQ;SAC7B,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,UAAU;SAC/B,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACnF,MAAM,CACJ,8BAA8B,CAAC,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAC,CACnE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,8BAA8B,CAAC;YAC7B,gBAAgB,EAAE,YAAY;YAC9B,kBAAkB,EAAE,IAAI;SACzB,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,KAAK,MAAM,MAAM,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,CAAC;YAC1D,MAAM,CACJ,8BAA8B,CAAC;gBAC7B,gBAAgB,EAAE,YAAY;gBAC9B,kBAAkB,EAAE,MAAM;aAC3B,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sEAAsE,EAAE,GAAG,EAAE;IACpF,MAAM,gBAAgB,GAAG;QACvB,gBAAgB,EAAE,YAAY;QAC9B,kBAAkB,EAAE,QAAQ;KAC7B,CAAC;IACF,MAAM,YAAY,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC;IAElF,IAAI,CAAC,gFAAgF,EAAE,GAAG,EAAE;QAC1F,MAAM,CAAC,uCAAuC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7E,MAAM,CAAC,uCAAuC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1E,MAAM,CAAC,uCAAuC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,uCAAuC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,6BAA6B,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,CAAC,6BAA6B,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,wBAAwB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,CAAC,wBAAwB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,gCAAgC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtE,MAAM,CAAC,gCAAgC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,wBAAwB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,CAAC,wBAAwB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,+BAA+B,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,+BAA+B,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -17,4 +17,23 @@ export interface OrganizationEntitlementInput {
17
17
  * (or trialing) subscription so a lapsed Enterprise org falls back to the path.
18
18
  */
19
19
  export declare function organizationHasVanitySubdomain(org: OrganizationEntitlementInput | null | undefined): boolean;
20
+ /**
21
+ * Shared gate for the enterprise platform surface: org-scoped audit logs,
22
+ * SSO/identity setup, procurement approval policy, API keys, webhooks, and
23
+ * third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
24
+ * perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
25
+ * as a distinct name so the two can diverge later (e.g. Growth gets audit
26
+ * logs before SSO) without touching every call site.
27
+ */
28
+ export declare function organizationHasEnterprisePlatformAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
29
+ /** Org-admin audit log viewer + CSV export. */
30
+ export declare function organizationHasAuditLogAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
31
+ /** SSO/identity setup. */
32
+ export declare function organizationHasSsoAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
33
+ /** Procurement approval workflows (PO-gated billing/spend). */
34
+ export declare function organizationHasProcurementAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
35
+ /** API keys, outbound webhooks, and third-party integrations. */
36
+ export declare function organizationHasApiAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
37
+ /** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
38
+ export declare function organizationHasComplianceAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
20
39
  //# sourceMappingURL=organizationEntitlements.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAUT"}
1
+ {"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAUT;AAED;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+CAA+C;AAC/C,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,0BAA0B;AAC1B,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+DAA+D;AAC/D,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+EAA+E;AAC/E,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET"}
@@ -27,3 +27,35 @@ export function organizationHasVanitySubdomain(org) {
27
27
  return true;
28
28
  return ACTIVE_ORG_SUBSCRIPTION_STATUSES.includes(org.subscriptionStatus);
29
29
  }
30
+ /**
31
+ * Shared gate for the enterprise platform surface: org-scoped audit logs,
32
+ * SSO/identity setup, procurement approval policy, API keys, webhooks, and
33
+ * third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
34
+ * perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
35
+ * as a distinct name so the two can diverge later (e.g. Growth gets audit
36
+ * logs before SSO) without touching every call site.
37
+ */
38
+ export function organizationHasEnterprisePlatformAccess(org) {
39
+ return organizationHasVanitySubdomain(org);
40
+ }
41
+ /** Org-admin audit log viewer + CSV export. */
42
+ export function organizationHasAuditLogAccess(org) {
43
+ return organizationHasEnterprisePlatformAccess(org);
44
+ }
45
+ /** SSO/identity setup. */
46
+ export function organizationHasSsoAccess(org) {
47
+ return organizationHasEnterprisePlatformAccess(org);
48
+ }
49
+ /** Procurement approval workflows (PO-gated billing/spend). */
50
+ export function organizationHasProcurementAccess(org) {
51
+ return organizationHasEnterprisePlatformAccess(org);
52
+ }
53
+ /** API keys, outbound webhooks, and third-party integrations. */
54
+ export function organizationHasApiAccess(org) {
55
+ return organizationHasEnterprisePlatformAccess(org);
56
+ }
57
+ /** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
58
+ export function organizationHasComplianceAccess(org) {
59
+ return organizationHasEnterprisePlatformAccess(org);
60
+ }
61
+ //# sourceMappingURL=organizationEntitlements.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAOX;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,4EAA4E;IAC5E,4BAA4B;IAC5B,IAAI,GAAG,CAAC,kBAAkB,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAChD,OAAQ,gCAAsD,CAAC,QAAQ,CACrE,GAAG,CAAC,kBAAkB,CACvB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAOX;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,4EAA4E;IAC5E,4BAA4B;IAC5B,IAAI,GAAG,CAAC,kBAAkB,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAChD,OAAQ,gCAAsD,CAAC,QAAQ,CACrE,GAAG,CAAC,kBAAkB,CACvB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC,CACrD,GAAoD;IAEpD,OAAO,8BAA8B,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,6BAA6B,CAC3C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gCAAgC,CAC9C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,+BAA+B,CAC7C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash-app/bash-common",
3
- "version": "30.360.0",
3
+ "version": "30.361.0",
4
4
  "description": "Common data and scripts to use on the frontend and backend",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -4457,6 +4457,20 @@ model User {
4457
4457
  submittedBudgets Budget[] @relation("BudgetSubmitter")
4458
4458
  ownedBudgets Budget[] @relation("UserBudgets")
4459
4459
  departmentsHeaded Department[] @relation("DepartmentHead")
4460
+ /// Enterprise platform (Phase 0-4): internal team members assigned as a named
4461
+ /// account manager, and org members who submit/approve procurement requests
4462
+ /// or create API keys.
4463
+ accountManagerForOrganizations Organization[] @relation("OrganizationAccountManager")
4464
+ procurementRequestsSubmitted ProcurementRequest[] @relation("ProcurementRequestSubmitter")
4465
+ procurementRequestsApproved ProcurementRequest[] @relation("ProcurementRequestApprover")
4466
+ organizationApiKeysCreated OrganizationApiKey[] @relation("OrganizationApiKeyCreator")
4467
+ /// SSO (Phase 2) + compliance (Phase 5): who configured a connection, and
4468
+ /// who submitted/reviewed a W-9 or certificate of insurance.
4469
+ organizationSsoConnectionsConfigured OrganizationSsoConnection[] @relation("OrganizationSsoConfiguredBy")
4470
+ organizationTaxDocumentsSubmitted OrganizationTaxDocument[] @relation("OrgTaxDocumentSubmittedBy")
4471
+ organizationTaxDocumentsReviewed OrganizationTaxDocument[] @relation("OrgTaxDocumentReviewedBy")
4472
+ organizationInsuranceCertsSubmitted OrganizationInsuranceCertificate[] @relation("OrgInsuranceCertSubmittedBy")
4473
+ organizationInsuranceCertsReviewed OrganizationInsuranceCertificate[] @relation("OrgInsuranceCertReviewedBy")
4460
4474
  favoriteOrganizations Organization[] @relation("FavoriteOrganizations")
4461
4475
  organizationEmailSubscriptions OrganizationSubscriber[]
4462
4476
  eventSeriesCreated EventSeries[] @relation("EventSeriesCreator")
@@ -5909,10 +5923,55 @@ model Organization {
5909
5923
  agencyBookingSettlements AgencyBookingSettlement[]
5910
5924
  representedBookings ServiceBooking[]
5911
5925
 
5926
+ /// Enterprise billing + support (ops-provisioned after a signed pilot/annual
5927
+ /// deal — not self-serve). See docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md.
5928
+ billingPoNumber String? // Purchase order number for invoiced billing
5929
+ billingContactEmail String? // AP / finance contact, distinct from pocEmail
5930
+ invoiceNetDays Int? // e.g. 30 for Net 30 terms; null = card/Stripe billing only
5931
+ enterpriseContractStartAt DateTime?
5932
+ enterpriseContractEndAt DateTime?
5933
+ accountManagerUserId String?
5934
+ accountManager User? @relation("OrganizationAccountManager", fields: [accountManagerUserId], references: [id], onDelete: SetNull)
5935
+ /// "Standard" | "Priority" | "Dedicated" — drives named-AM banner + support routing copy.
5936
+ supportTier String @default("Standard")
5937
+
5938
+ /// SSO / identity (Organization Enterprise only). `ssoEnforced` blocks password
5939
+ /// login for members whose email matches `ssoDomain` once `ssoConnection.status`
5940
+ /// is Active. Denormalized here (rather than requiring a join) so the
5941
+ /// pre-login domain check on every sign-in attempt stays a single indexed
5942
+ /// lookup; `ssoConnection` holds the protocol-specific IdP configuration.
5943
+ ssoProvider String? // "saml" | "oidc" — mirrors ssoConnection.protocol; drives login routing
5944
+ ssoDomain String? // Verified email domain that triggers SSO redirect
5945
+ ssoEnforced Boolean @default(false)
5946
+ ssoConfiguredAt DateTime?
5947
+ ssoConnection OrganizationSsoConnection?
5948
+
5949
+ /// Procurement approval policy (nullable JSON). Null = no gate, billing/spend
5950
+ /// proceeds without a PO. Shape when set:
5951
+ /// { requirePoForSubscription: boolean, approverUserIds: string[], minAmountCentsForApproval: number }
5952
+ procurementApprovalPolicy Json?
5953
+ procurementRequests ProcurementRequest[]
5954
+
5955
+ /// Enterprise integrations + developer platform (API keys, outbound webhooks,
5956
+ /// third-party connectors like Mailchimp/Salesforce).
5957
+ integrations OrganizationIntegration[]
5958
+ apiKeys OrganizationApiKey[]
5959
+ webhooks OrganizationWebhook[]
5960
+
5961
+ /// Compliance (deal-driven, Phase 5): W-9 collection for institutional
5962
+ /// finance + certificate-of-insurance tracking. Ops reviews both manually.
5963
+ taxDocuments OrganizationTaxDocument[]
5964
+ insuranceCertificates OrganizationInsuranceCertificate[]
5965
+
5966
+ /// Org-scoped slice of the platform audit trail (member approvals, budget
5967
+ /// approvals, settings changes, SSO logins, API key/webhook/integration events).
5968
+ auditLogs AuditLog[] @relation("OrganizationAuditLogs")
5969
+
5912
5970
  @@index([ownerId])
5913
5971
  @@index([slug])
5914
5972
  @@index([parentOrganizationId])
5915
5973
  @@index([nonprofitVerifiedAt])
5974
+ @@index([accountManagerUserId])
5916
5975
  }
5917
5976
 
5918
5977
  /// Email-first followers of a public organizer (org landing). Authenticated users who
@@ -10840,6 +10899,12 @@ model AuditLog {
10840
10899
  userId String?
10841
10900
  user User? @relation("UserAuditLogs", fields: [userId], references: [id], onDelete: SetNull)
10842
10901
 
10902
+ /// Set when the action happened in the context of an Organization (member
10903
+ /// approvals, budget approvals, settings changes, SSO logins, API keys,
10904
+ /// webhooks, integrations, procurement). Null for platform-wide/user actions.
10905
+ organizationId String?
10906
+ organization Organization? @relation("OrganizationAuditLogs", fields: [organizationId], references: [id], onDelete: SetNull)
10907
+
10843
10908
  // Action details
10844
10909
  action AuditAction // LOGIN_SUCCESS, LOGIN_FAILED, PAYMENT_PROCESSED, etc.
10845
10910
  category AuditCategory // Authentication, Payment, Admin, DataAccess, etc.
@@ -10864,6 +10929,7 @@ model AuditLog {
10864
10929
  @@index([category, severity, createdAt])
10865
10930
  @@index([resourceType, resourceId])
10866
10931
  @@index([ipAddress])
10932
+ @@index([organizationId, createdAt])
10867
10933
  }
10868
10934
 
10869
10935
  /// Token Revocation System
@@ -10959,6 +11025,30 @@ enum AuditAction {
10959
11025
  RATE_LIMIT_EXCEEDED
10960
11026
  IP_BLOCKED
10961
11027
  TOKEN_REVOKED
11028
+
11029
+ // Organization (enterprise platform — member/budget approvals, settings,
11030
+ // SSO, procurement, API keys, webhooks, integrations)
11031
+ ORG_MEMBER_APPROVED
11032
+ ORG_MEMBER_REJECTED
11033
+ ORG_BUDGET_APPROVED
11034
+ ORG_SETTINGS_CHANGED
11035
+ ORG_BILLING_UPDATED
11036
+ ORG_SSO_CONFIGURED
11037
+ ORG_SSO_LOGIN
11038
+ ORG_PROCUREMENT_SUBMITTED
11039
+ ORG_PROCUREMENT_APPROVED
11040
+ ORG_PROCUREMENT_REJECTED
11041
+ ORG_API_KEY_CREATED
11042
+ ORG_API_KEY_REVOKED
11043
+ ORG_WEBHOOK_CREATED
11044
+ ORG_WEBHOOK_DELETED
11045
+ ORG_INTEGRATION_CONNECTED
11046
+ ORG_INTEGRATION_DISCONNECTED
11047
+ ORG_AUDIT_LOG_EXPORTED
11048
+ ORG_TAX_DOCUMENT_SUBMITTED
11049
+ ORG_TAX_DOCUMENT_REVIEWED
11050
+ ORG_INSURANCE_CERTIFICATE_SUBMITTED
11051
+ ORG_INSURANCE_CERTIFICATE_REVIEWED
10962
11052
  }
10963
11053
 
10964
11054
  enum AuditCategory {
@@ -10973,6 +11063,7 @@ enum AuditCategory {
10973
11063
  DataAccess
10974
11064
  Compliance
10975
11065
  Security
11066
+ Organization
10976
11067
  }
10977
11068
 
10978
11069
  enum AuditSeverity {
@@ -10981,6 +11072,260 @@ enum AuditSeverity {
10981
11072
  Critical // Security incidents
10982
11073
  }
10983
11074
 
11075
+ /// ─── Enterprise platform: procurement, API keys, webhooks, integrations ────
11076
+ /// Shared by universities/nonprofits/venues/brands buying Organization
11077
+ /// Enterprise. See docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md for sales framing.
11078
+
11079
+ model ProcurementRequest {
11080
+ id String @id @default(cuid())
11081
+ organizationId String
11082
+
11083
+ /// "OrgSubscription" | "EventSpend" | "VendorContract" | "Other"
11084
+ type String
11085
+
11086
+ amountCents Int
11087
+ poNumber String?
11088
+ description String?
11089
+
11090
+ /// "Draft" | "Submitted" | "Approved" | "Rejected" | "Cancelled"
11091
+ status String @default("Draft")
11092
+
11093
+ submittedById String?
11094
+ /// Snapshot of who is entitled to approve at submit time (org's
11095
+ /// `procurementApprovalPolicy.approverUserIds`, or Owner/Admin if unset).
11096
+ approverIds String[] @default([])
11097
+ approvedById String?
11098
+ approvedAt DateTime?
11099
+ rejectedById String?
11100
+ rejectedAt DateTime?
11101
+ rejectionReason String?
11102
+
11103
+ /// Free-form context: linked subscription tier, budget id, vendor name, etc.
11104
+ metadata Json?
11105
+
11106
+ createdAt DateTime @default(now())
11107
+ updatedAt DateTime @updatedAt
11108
+
11109
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11110
+ submittedBy User? @relation("ProcurementRequestSubmitter", fields: [submittedById], references: [id], onDelete: SetNull)
11111
+ approvedBy User? @relation("ProcurementRequestApprover", fields: [approvedById], references: [id], onDelete: SetNull)
11112
+
11113
+ @@index([organizationId, status])
11114
+ @@index([submittedById])
11115
+ }
11116
+
11117
+ model OrganizationApiKey {
11118
+ id String @id @default(cuid())
11119
+ organizationId String
11120
+ name String // Host-chosen label, e.g. "Zapier sync"
11121
+
11122
+ /// SHA-256 hash of the secret; the plaintext key is shown once at creation
11123
+ /// and never persisted. Prefix is safe to display for identification.
11124
+ keyHash String @unique
11125
+ keyPrefix String // e.g. "bsh_live_ab12" — shown in the UI list
11126
+
11127
+ scopes String[] @default([]) // e.g. ["read:members", "read:events"]
11128
+ createdById String?
11129
+ lastUsedAt DateTime?
11130
+ revokedAt DateTime?
11131
+ expiresAt DateTime?
11132
+
11133
+ createdAt DateTime @default(now())
11134
+
11135
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11136
+ createdBy User? @relation("OrganizationApiKeyCreator", fields: [createdById], references: [id], onDelete: SetNull)
11137
+
11138
+ @@index([organizationId])
11139
+ }
11140
+
11141
+ model OrganizationWebhook {
11142
+ id String @id @default(cuid())
11143
+ organizationId String
11144
+
11145
+ url String
11146
+ /// Event types this subscription receives, e.g. "ticket.checked_in",
11147
+ /// "member.approved", "procurement.approved".
11148
+ eventTypes String[] @default([])
11149
+ /// HMAC-SHA256 signing secret (shown once at creation) — verify the
11150
+ /// `X-Bash-Signature` header before trusting the payload.
11151
+ signingSecret String
11152
+
11153
+ isActive Boolean @default(true)
11154
+ lastDeliveryAt DateTime?
11155
+ lastDeliveryStatus String? // "success" | "failed"
11156
+ failureCount Int @default(0)
11157
+
11158
+ createdAt DateTime @default(now())
11159
+ updatedAt DateTime @updatedAt
11160
+
11161
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11162
+
11163
+ @@index([organizationId])
11164
+ }
11165
+
11166
+ model OrganizationIntegration {
11167
+ id String @id @default(cuid())
11168
+ organizationId String
11169
+
11170
+ /// "Mailchimp" | "Salesforce" — extend as connectors ship.
11171
+ provider String
11172
+
11173
+ /// "Connected" | "Error" | "Disconnected"
11174
+ status String @default("Connected")
11175
+
11176
+ /// Provider credentials, AES-256-CBC encrypted at rest (see
11177
+ /// api/src/utils/organization/organizationIntegrationCrypto.ts). Never
11178
+ /// return this field to the client.
11179
+ encryptedCredentials String?
11180
+
11181
+ /// Non-secret provider config, e.g. { audienceId: "abc123" } for Mailchimp.
11182
+ settings Json?
11183
+
11184
+ lastSyncAt DateTime?
11185
+ lastSyncStatus String? // "success" | "failed"
11186
+ lastSyncError String?
11187
+ connectedById String?
11188
+
11189
+ createdAt DateTime @default(now())
11190
+ updatedAt DateTime @updatedAt
11191
+
11192
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11193
+
11194
+ @@unique([organizationId, provider])
11195
+ @@index([organizationId])
11196
+ }
11197
+
11198
+ enum SsoProtocol {
11199
+ Saml
11200
+ Oidc
11201
+ }
11202
+
11203
+ enum SsoConnectionStatus {
11204
+ Draft // Admin has started filling in IdP details; not yet validated.
11205
+ Testing // "Test connection" is in flight or failed; ssoEnforced cannot be true.
11206
+ Active // Test succeeded; org can enforce SSO for its verified domain.
11207
+ Disabled // Was Active; admin turned it off without deleting the config.
11208
+ }
11209
+
11210
+ /// Generic SAML 2.0 (as Service Provider) or OIDC (as Relying Party) connection
11211
+ /// for one Organization. No third-party IdP broker (e.g. WorkOS) — Bash acts as
11212
+ /// its own SP/RP against whatever IdP the customer supplies (Okta, Entra ID,
11213
+ /// Google Workspace, generic OIDC). One connection per org for v1.
11214
+ model OrganizationSsoConnection {
11215
+ id String @id @default(cuid())
11216
+ organizationId String @unique
11217
+ protocol SsoProtocol
11218
+ status SsoConnectionStatus @default(Draft)
11219
+
11220
+ /// SAML 2.0 — values copied from the customer's IdP app configuration.
11221
+ samlIdpEntityId String?
11222
+ samlIdpSsoUrl String? // IdP's Single Sign-On (AuthnRequest destination) URL
11223
+ samlIdpCertificate String? // IdP's public signing cert (PEM), used to verify the SAMLResponse signature
11224
+
11225
+ /// OIDC — values copied from the customer's IdP app configuration. The
11226
+ /// authorization/token/jwks endpoints are filled in by discovery against
11227
+ /// `oidcIssuer` when the connection is tested, not entered by the admin.
11228
+ oidcIssuer String?
11229
+ oidcClientId String?
11230
+ /// AES-256-CBC encrypted (see organizationIntegrationCrypto.ts). Never
11231
+ /// returned to the client.
11232
+ oidcClientSecretEncrypted String?
11233
+ oidcAuthorizationEndpoint String?
11234
+ oidcTokenEndpoint String?
11235
+ oidcJwksUri String?
11236
+
11237
+ lastTestedAt DateTime?
11238
+ lastTestStatus String? // "Success" | "Failed"
11239
+ lastTestError String?
11240
+
11241
+ configuredByUserId String?
11242
+ configuredBy User? @relation("OrganizationSsoConfiguredBy", fields: [configuredByUserId], references: [id], onDelete: SetNull)
11243
+
11244
+ createdAt DateTime @default(now())
11245
+ updatedAt DateTime @updatedAt
11246
+
11247
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11248
+
11249
+ @@index([organizationId])
11250
+ }
11251
+
11252
+ enum OrgComplianceDocStatus {
11253
+ Pending
11254
+ Approved
11255
+ Rejected
11256
+ }
11257
+
11258
+ enum OrgTaxDocumentType {
11259
+ W9
11260
+ W8BEN
11261
+ }
11262
+
11263
+ /// Institutional tax documentation (deal-driven, Phase 5). Ops reviews the
11264
+ /// uploaded PDF manually — same "admin approves after out-of-band review"
11265
+ /// shape as nonprofit verification, just with a real self-serve upload
11266
+ /// (presigned S3, see AwsS3Utils.createUrlFromAssetKey) instead of an
11267
+ /// ops-entered doc id.
11268
+ model OrganizationTaxDocument {
11269
+ id String @id @default(cuid())
11270
+ organizationId String
11271
+ type OrgTaxDocumentType @default(W9)
11272
+
11273
+ legalBusinessName String
11274
+ taxClassification String? // e.g. "C Corporation", "LLC", "Nonprofit" — free text, mirrors the W-9 form
11275
+ taxIdLast4 String? // Last 4 of EIN/SSN only; never store the full TIN
11276
+ docUrl String // Uploaded W-9 PDF
11277
+
11278
+ status OrgComplianceDocStatus @default(Pending)
11279
+ rejectionReason String?
11280
+
11281
+ submittedByUserId String?
11282
+ reviewedByUserId String?
11283
+ reviewedAt DateTime?
11284
+
11285
+ createdAt DateTime @default(now())
11286
+ updatedAt DateTime @updatedAt
11287
+
11288
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11289
+ submittedBy User? @relation("OrgTaxDocumentSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
11290
+ reviewedBy User? @relation("OrgTaxDocumentReviewedBy", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
11291
+
11292
+ @@index([organizationId])
11293
+ @@index([status])
11294
+ }
11295
+
11296
+ /// Certificate of insurance tracking at the Organization level (deal-driven,
11297
+ /// Phase 5) — e.g. a university requiring proof of general liability coverage
11298
+ /// before an annual agreement. Distinct from the per-`Vendor` insurance flags
11299
+ /// on `VendorInsurance`, which cover marketplace vendors, not the org itself.
11300
+ model OrganizationInsuranceCertificate {
11301
+ id String @id @default(cuid())
11302
+ organizationId String
11303
+
11304
+ insurerName String
11305
+ policyNumber String?
11306
+ coverageAmountCents Int?
11307
+ docUrl String // Uploaded certificate-of-insurance PDF
11308
+ expiresAt DateTime
11309
+
11310
+ status OrgComplianceDocStatus @default(Pending)
11311
+ rejectionReason String?
11312
+
11313
+ submittedByUserId String?
11314
+ reviewedByUserId String?
11315
+ reviewedAt DateTime?
11316
+
11317
+ createdAt DateTime @default(now())
11318
+ updatedAt DateTime @updatedAt
11319
+
11320
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
11321
+ submittedBy User? @relation("OrgInsuranceCertSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
11322
+ reviewedBy User? @relation("OrgInsuranceCertReviewedBy", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
11323
+
11324
+ @@index([organizationId])
11325
+ @@index([status])
11326
+ @@index([expiresAt])
11327
+ }
11328
+
10984
11329
  // Service Analytics Tracking
10985
11330
  model ServiceAnalytics {
10986
11331
  id String @id @default(cuid())
@@ -1,6 +1,14 @@
1
1
  import { describe, expect, test } from "@jest/globals";
2
2
 
3
- import { organizationHasVanitySubdomain } from "../organizationEntitlements.js";
3
+ import {
4
+ organizationHasApiAccess,
5
+ organizationHasAuditLogAccess,
6
+ organizationHasComplianceAccess,
7
+ organizationHasEnterprisePlatformAccess,
8
+ organizationHasProcurementAccess,
9
+ organizationHasSsoAccess,
10
+ organizationHasVanitySubdomain,
11
+ } from "../organizationEntitlements.js";
4
12
 
5
13
  describe("organizationHasVanitySubdomain", () => {
6
14
  test("null / undefined return false", () => {
@@ -57,3 +65,43 @@ describe("organizationHasVanitySubdomain", () => {
57
65
  }
58
66
  });
59
67
  });
68
+
69
+ describe("enterprise platform gates (audit logs, SSO, procurement, API access)", () => {
70
+ const enterpriseActive = {
71
+ subscriptionTier: "Enterprise",
72
+ subscriptionStatus: "Active",
73
+ };
74
+ const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
75
+
76
+ test("organizationHasEnterprisePlatformAccess mirrors organizationHasVanitySubdomain", () => {
77
+ expect(organizationHasEnterprisePlatformAccess(enterpriseActive)).toBe(true);
78
+ expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
79
+ expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
80
+ expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
81
+ });
82
+
83
+ test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
84
+ expect(organizationHasAuditLogAccess(enterpriseActive)).toBe(true);
85
+ expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
86
+ });
87
+
88
+ test("organizationHasSsoAccess follows the shared enterprise gate", () => {
89
+ expect(organizationHasSsoAccess(enterpriseActive)).toBe(true);
90
+ expect(organizationHasSsoAccess(growthActive)).toBe(false);
91
+ });
92
+
93
+ test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
94
+ expect(organizationHasProcurementAccess(enterpriseActive)).toBe(true);
95
+ expect(organizationHasProcurementAccess(growthActive)).toBe(false);
96
+ });
97
+
98
+ test("organizationHasApiAccess follows the shared enterprise gate", () => {
99
+ expect(organizationHasApiAccess(enterpriseActive)).toBe(true);
100
+ expect(organizationHasApiAccess(growthActive)).toBe(false);
101
+ });
102
+
103
+ test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
104
+ expect(organizationHasComplianceAccess(enterpriseActive)).toBe(true);
105
+ expect(organizationHasComplianceAccess(growthActive)).toBe(false);
106
+ });
107
+ });
@@ -35,3 +35,52 @@ export function organizationHasVanitySubdomain(
35
35
  org.subscriptionStatus
36
36
  );
37
37
  }
38
+
39
+ /**
40
+ * Shared gate for the enterprise platform surface: org-scoped audit logs,
41
+ * SSO/identity setup, procurement approval policy, API keys, webhooks, and
42
+ * third-party integrations (Mailchimp/Salesforce). All are Enterprise-tier
43
+ * perks today — same predicate as {@link organizationHasVanitySubdomain}, kept
44
+ * as a distinct name so the two can diverge later (e.g. Growth gets audit
45
+ * logs before SSO) without touching every call site.
46
+ */
47
+ export function organizationHasEnterprisePlatformAccess(
48
+ org: OrganizationEntitlementInput | null | undefined
49
+ ): boolean {
50
+ return organizationHasVanitySubdomain(org);
51
+ }
52
+
53
+ /** Org-admin audit log viewer + CSV export. */
54
+ export function organizationHasAuditLogAccess(
55
+ org: OrganizationEntitlementInput | null | undefined
56
+ ): boolean {
57
+ return organizationHasEnterprisePlatformAccess(org);
58
+ }
59
+
60
+ /** SSO/identity setup. */
61
+ export function organizationHasSsoAccess(
62
+ org: OrganizationEntitlementInput | null | undefined
63
+ ): boolean {
64
+ return organizationHasEnterprisePlatformAccess(org);
65
+ }
66
+
67
+ /** Procurement approval workflows (PO-gated billing/spend). */
68
+ export function organizationHasProcurementAccess(
69
+ org: OrganizationEntitlementInput | null | undefined
70
+ ): boolean {
71
+ return organizationHasEnterprisePlatformAccess(org);
72
+ }
73
+
74
+ /** API keys, outbound webhooks, and third-party integrations. */
75
+ export function organizationHasApiAccess(
76
+ org: OrganizationEntitlementInput | null | undefined
77
+ ): boolean {
78
+ return organizationHasEnterprisePlatformAccess(org);
79
+ }
80
+
81
+ /** Compliance docs (W-9 tax collection, certificate-of-insurance tracking). */
82
+ export function organizationHasComplianceAccess(
83
+ org: OrganizationEntitlementInput | null | undefined
84
+ ): boolean {
85
+ return organizationHasEnterprisePlatformAccess(org);
86
+ }