@zapier/policy-context 1.3.0 → 3.0.0-mr.39.da73fd4a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ npm install @zapier/policy-context zod
16
16
 
17
17
  ## What it provides
18
18
 
19
- Three layers, each building on the previous:
19
+ Four layers, each building on the previous:
20
20
 
21
21
  1. **Types** — Zod schemas for `HttpRequestContext`, `ActionRunContext`, and the
22
22
  `RequestContext` discriminated union. The canonical shapes stored in
@@ -25,7 +25,10 @@ Three layers, each building on the previous:
25
25
  body coercion) into validated contexts.
26
26
  3. **Conditions / Policies** — Convert stored contexts into
27
27
  [`@zapier/policy-schema`](https://www.npmjs.com/package/@zapier/policy-schema)
28
- `Condition[]` and full `Policy` documents.
28
+ `Condition[]` and full v4 policy documents with one rule tier (`rules: [[...]]`)
29
+ and randomly generated UUID rule IDs prefixed with `r_`.
30
+ 4. **Zapier policy validation** — `ZapierPolicySchema` composes the generic
31
+ `PolicySchema` with Zapier resource grammar and permission/resource rules.
29
32
 
30
33
  ## Usage
31
34
 
@@ -65,6 +68,11 @@ import { toExactPolicy } from "@zapier/policy-context";
65
68
  const policy = toExactPolicy(context);
66
69
  ```
67
70
 
71
+ Policy builders return v4 only, typed as `Extract<Policy, { version: 4 }>`, with
72
+ a single rule tier (`rules: [[...]]`) and `r_<uuid>` rule IDs. Rule content and
73
+ ordering are unchanged. Migrate `policy.statements[0][i]` to `policy.rules[0][i]`
74
+ and any statement-ID prefix handling from `s_` to `r_`.
75
+
68
76
  ### Connection-scoped policies
69
77
 
70
78
  ```ts
@@ -76,6 +84,24 @@ const policy = buildConnectionPolicy({
76
84
  });
77
85
  ```
78
86
 
87
+ ### Validate a Zapier policy
88
+
89
+ ```ts
90
+ import { ZapierPolicySchema } from "@zapier/policy-context";
91
+
92
+ const policy = ZapierPolicySchema.parse(input);
93
+ ```
94
+
95
+ `ZapierPolicySchema` output includes v4 documents as well as earlier versions.
96
+ This accepts `http`, `connection/{id|*}`, canonical
97
+ `action/{selected_api}/{action_type|*}/{action_key}` patterns, and the lone `*`
98
+ god-mode resource. It validates structure only; app, API version, and action-key
99
+ existence remain the caller's responsibility. It also rejects a statement or rule
100
+ when one higher-precedence statement or rule (`deny` > `ask` > `allow`) fully
101
+ subsumes it. Validation traverses each ordered tier of v3 `statements` and v4
102
+ `rules`, checking masking only within that tier. Same-effect redundancy and
103
+ coverage assembled from multiple statements or rules remain valid.
104
+
79
105
  ## License
80
106
 
81
107
  Published as part of Zapier's services. Use is governed by the
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AccessLevelSchema: () => AccessLevelSchema,
24
+ ActionResourcePatternSchema: () => ActionResourcePatternSchema,
24
25
  ActionRunContextInputSchema: () => ActionRunContextInputSchema,
25
26
  ActionRunContextSchema: () => ActionRunContextSchema,
26
27
  ActionTypeSchema: () => ActionTypeSchema,
@@ -33,6 +34,7 @@ __export(index_exports, {
33
34
  READ_ACTION_TYPES: () => READ_ACTION_TYPES,
34
35
  READ_HTTP_METHODS: () => READ_HTTP_METHODS,
35
36
  RequestContextSchema: () => RequestContextSchema,
37
+ ZapierPolicySchema: () => ZapierPolicySchema,
36
38
  buildActionResource: () => buildActionResource,
37
39
  buildActionRunContext: () => buildActionRunContext,
38
40
  buildBroadActionResource: () => buildBroadActionResource,
@@ -44,6 +46,7 @@ __export(index_exports, {
44
46
  buildHttpRequestContext: () => buildHttpRequestContext,
45
47
  buildUrlString: () => buildUrlString,
46
48
  globSelectedApiVersion: () => globSelectedApiVersion,
49
+ isIgnoredHeader: () => isIgnoredHeader,
47
50
  isWriteRequest: () => isWriteRequest,
48
51
  parseActionResource: () => parseActionResource,
49
52
  parseUrl: () => parseUrl,
@@ -54,6 +57,21 @@ module.exports = __toCommonJS(index_exports);
54
57
 
55
58
  // src/types.ts
56
59
  var import_zod = require("zod");
60
+ function containsUnsafeInteger(value) {
61
+ if (typeof value === "number") {
62
+ return Number.isInteger(value) && !Number.isSafeInteger(value);
63
+ }
64
+ if (Array.isArray(value)) {
65
+ return value.some(containsUnsafeInteger);
66
+ }
67
+ if (value !== null && typeof value === "object") {
68
+ return Object.values(value).some(containsUnsafeInteger);
69
+ }
70
+ return false;
71
+ }
72
+ var SafeJsonValueSchema = import_zod.z.json().refine((value) => !containsUnsafeInteger(value), {
73
+ message: "JSON integers must be within the IEEE-754 safe range; encode larger identifiers as strings"
74
+ });
57
75
  var ParsedUrlSchema = import_zod.z.object({
58
76
  scheme: import_zod.z.string().describe('URL scheme, e.g. "https"'),
59
77
  host: import_zod.z.string().describe('Hostname, e.g. "api.example.com"'),
@@ -112,7 +130,7 @@ var HttpRequestContextSchema = import_zod.z.object({
112
130
  * `z.json()` still rejects non-JSON values (`Date`, `undefined`,
113
131
  * functions) at parse time. Same inferred `JsonValue` type either way.
114
132
  */
115
- body_json: import_zod.z.unknown().pipe(import_zod.z.json()).nullable().describe("Parsed JSON body (if applicable)"),
133
+ body_json: import_zod.z.unknown().pipe(SafeJsonValueSchema).nullable().describe("Parsed JSON body (if applicable)"),
116
134
  /** Zapier connection ID used for this request */
117
135
  connection_id: import_zod.z.string().max(255).optional().describe("Zapier connection ID used for this request"),
118
136
  /** Optional human-readable reason for the request */
@@ -130,6 +148,11 @@ var ActionTypeSchema = import_zod.z.enum([
130
148
  ]).meta({ id: "action_type", description: "Canonical Zapier action type." });
131
149
  var ACTION_RESOURCE_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+(?:@[A-Za-z0-9._+-]+)?$/;
132
150
  var ACTION_KEY_SEGMENT_PATTERN = /^[A-Za-z0-9._+:-]+(?:@[A-Za-z0-9._+:-]+)?$/;
151
+ var CONNECTION_ID_CHARS = "[^/*\\x00-\\x20\\x7f-\\xa0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
152
+ var CONNECTION_ID_PATTERN = new RegExp(`^${CONNECTION_ID_CHARS}+$`);
153
+ var CONNECTION_RESOURCE_PATTERN = new RegExp(
154
+ `^connection/(?:\\*|${CONNECTION_ID_CHARS}+)$`
155
+ );
133
156
  function parseActionResource(resource) {
134
157
  const segments = resource.split("/");
135
158
  if (segments.length !== 4 || segments[0] !== "action") {
@@ -190,7 +213,7 @@ var ActionRunContextInputSchema = import_zod.z.object({
190
213
  }),
191
214
  connection_id: import_zod.z.string().max(255).optional().describe("Zapier connection ID used for this request"),
192
215
  /** Action input parameters. See body_json above for the pipe rationale. */
193
- inputs: import_zod.z.unknown().pipe(import_zod.z.json()).optional().describe("Action input parameters"),
216
+ inputs: import_zod.z.unknown().pipe(SafeJsonValueSchema).optional().describe("Action input parameters"),
194
217
  reason: import_zod.z.string().max(1024).optional().describe("Optional human-readable reason for the request")
195
218
  }).meta({
196
219
  id: "ActionRunContext",
@@ -221,7 +244,7 @@ var ActionRunContextSchema = ActionRunContextInputSchema.transform(
221
244
  ...input.connection_id !== void 0 && {
222
245
  connection_id: input.connection_id
223
246
  },
224
- ...input.inputs !== void 0 && { inputs: input.inputs },
247
+ ...input.inputs !== void 0 && input.inputs !== null && { inputs: input.inputs },
225
248
  ...input.reason !== void 0 && { reason: input.reason }
226
249
  };
227
250
  }
@@ -265,8 +288,15 @@ var IGNORED_HEADERS = /* @__PURE__ */ new Set([
265
288
  "x-real-ip",
266
289
  // Proxy internal — sdkapi-specific headers
267
290
  "x-is-interactive-session",
268
- "x-relay-callback-url"
291
+ "x-relay-callback-url",
292
+ // Subsumed by isIgnoredHeader's x-zapier- prefix rule; kept for byte-parity
293
+ // with the approvalsapi source and for consumers reading the set directly.
294
+ "x-zapier-approval-token"
269
295
  ]);
296
+ function isIgnoredHeader(name) {
297
+ const normalized = name.toLowerCase();
298
+ return IGNORED_HEADERS.has(normalized) || normalized.startsWith("zapier-") || normalized.startsWith("x-zapier-");
299
+ }
270
300
 
271
301
  // src/builders.ts
272
302
  var import_zod2 = require("zod");
@@ -289,14 +319,14 @@ function buildExactHttpConditions(context) {
289
319
  conditions.push({
290
320
  path: ["url", "query", key],
291
321
  operator: "equals",
292
- value: values
322
+ value: structuredClone(values)
293
323
  });
294
324
  }
295
325
  if (context.body_json !== null) {
296
326
  conditions.push({
297
327
  path: ["body_json"],
298
328
  operator: "equals",
299
- value: context.body_json
329
+ value: structuredClone(context.body_json)
300
330
  });
301
331
  } else if (context.body !== null) {
302
332
  conditions.push({
@@ -306,7 +336,7 @@ function buildExactHttpConditions(context) {
306
336
  });
307
337
  }
308
338
  for (const [key, value] of Object.entries(context.headers)) {
309
- if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
339
+ if (!isIgnoredHeader(key) && typeof value === "string") {
310
340
  conditions.push({
311
341
  path: ["headers", key.toLowerCase()],
312
342
  operator: "equals",
@@ -331,7 +361,7 @@ function buildExactActionConditions(context) {
331
361
  conditions.push({
332
362
  path: ["inputs"],
333
363
  operator: "equals",
334
- value: context.inputs
364
+ value: structuredClone(context.inputs)
335
365
  });
336
366
  }
337
367
  return conditions;
@@ -348,7 +378,7 @@ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
348
378
  method: import_zod2.z.string(),
349
379
  /** Raw URL string. Will be normalized with https:// if no protocol present. */
350
380
  url: import_zod2.z.string(),
351
- /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */
381
+ /** Raw headers from the request. Will be filtered through isIgnoredHeader. */
352
382
  headers: import_zod2.z.record(
353
383
  import_zod2.z.string(),
354
384
  import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.undefined()])
@@ -356,7 +386,7 @@ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
356
386
  /** Raw body string */
357
387
  body: import_zod2.z.string().nullable().optional(),
358
388
  /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */
359
- body_json: import_zod2.z.json().optional(),
389
+ body_json: SafeJsonValueSchema.optional(),
360
390
  /** Connection ID associated with this request */
361
391
  connection_id: import_zod2.z.string().optional()
362
392
  });
@@ -368,7 +398,7 @@ function buildHttpRequestContext(options) {
368
398
  if (parsed.headers) {
369
399
  for (const [key, value] of Object.entries(parsed.headers)) {
370
400
  const lowerKey = key.toLowerCase();
371
- if (!IGNORED_HEADERS.has(lowerKey) && typeof value === "string") {
401
+ if (!isIgnoredHeader(lowerKey) && typeof value === "string") {
372
402
  headers[lowerKey] = value;
373
403
  }
374
404
  }
@@ -391,7 +421,7 @@ function buildHttpRequestContext(options) {
391
421
  method: parsed.method.toUpperCase(),
392
422
  url,
393
423
  headers,
394
- body: parsed.body ?? null,
424
+ body: body_json === null ? parsed.body ?? null : null,
395
425
  body_json,
396
426
  connection_id: parsed.connection_id
397
427
  });
@@ -405,7 +435,7 @@ var BuildActionRunContextOptionsSchema = import_zod2.z.object({
405
435
  message: "action_key must contain only [A-Za-z0-9._@:+-] (no globs, slashes, or whitespace)"
406
436
  }),
407
437
  connection_id: import_zod2.z.string().optional(),
408
- inputs: import_zod2.z.json().optional(),
438
+ inputs: SafeJsonValueSchema.optional(),
409
439
  reason: import_zod2.z.string().optional()
410
440
  });
411
441
  function buildActionRunContext(options) {
@@ -426,6 +456,7 @@ function buildActionRunContext(options) {
426
456
  // src/policies.ts
427
457
  function buildAuthStatement(authenticationId) {
428
458
  return {
459
+ id: `r_${crypto.randomUUID()}`,
429
460
  effect: "allow",
430
461
  permissions: ["can_use"],
431
462
  resources: [`connection/${authenticationId}`],
@@ -433,36 +464,38 @@ function buildAuthStatement(authenticationId) {
433
464
  };
434
465
  }
435
466
  function httpContextToPolicy(context) {
436
- const statements = [];
437
- if (context.connection_id) {
438
- statements.push(buildAuthStatement(context.connection_id));
439
- }
440
467
  const conditions = buildExactHttpConditions(context);
441
468
  conditions.push({
442
469
  path: ["url", "query"],
443
470
  operator: "keys_in",
444
471
  value: Object.keys(context.url.query)
445
472
  });
446
- statements.push({
473
+ const statement = {
474
+ id: `r_${crypto.randomUUID()}`,
447
475
  effect: "allow",
448
476
  permissions: ["can_execute"],
449
477
  resources: ["http"],
450
478
  conditions
451
- });
452
- return { version: 2, statements };
479
+ };
480
+ const statements = context.connection_id ? [buildAuthStatement(context.connection_id), statement] : [statement];
481
+ return {
482
+ version: 4,
483
+ rules: [statements]
484
+ };
453
485
  }
454
486
  function actionContextToPolicy(context) {
455
- const statements = [];
456
- if (context.connection_id) {
457
- statements.push(buildAuthStatement(context.connection_id));
458
- }
459
- statements.push({
487
+ const statement = {
488
+ id: `r_${crypto.randomUUID()}`,
460
489
  effect: "allow",
461
490
  permissions: ["can_execute"],
462
491
  resources: [context.resource],
463
492
  conditions: buildExactActionConditions(context)
464
- });
465
- return { version: 2, statements };
493
+ };
494
+ const statements = context.connection_id ? [buildAuthStatement(context.connection_id), statement] : [statement];
495
+ return {
496
+ version: 4,
497
+ rules: [statements]
498
+ };
466
499
  }
467
500
  function toExactPolicy(context) {
468
501
  if (context.request_type === "http_request") {
@@ -471,9 +504,160 @@ function toExactPolicy(context) {
471
504
  return actionContextToPolicy(context);
472
505
  }
473
506
 
474
- // src/connection-policy.ts
507
+ // src/zapier-policy.ts
508
+ var import_policy = require("@zapier/policy");
509
+ var import_policy_schema = require("@zapier/policy-schema");
475
510
  var import_zod3 = require("zod");
476
- var AccessLevelSchema = import_zod3.z.enum(["read", "read_write"]);
511
+ var SELECTED_API_GLOB_PART_PATTERN = /^[A-Za-z0-9._+*-]+$/;
512
+ var ACTION_KEY_GLOB_PART_PATTERN = /^[A-Za-z0-9._+:*-]+$/;
513
+ var PERMISSIONS = /* @__PURE__ */ new Set([
514
+ "can_execute",
515
+ "can_use",
516
+ "can_view",
517
+ "can_edit",
518
+ "*"
519
+ ]);
520
+ var EFFECT_PRECEDENCE = { allow: 0, ask: 1, deny: 2 };
521
+ function isSelectedApiPattern(value) {
522
+ return isGlobSegment(
523
+ value,
524
+ ACTION_RESOURCE_SEGMENT_PATTERN,
525
+ SELECTED_API_GLOB_PART_PATTERN
526
+ );
527
+ }
528
+ function isActionKeyPattern(value) {
529
+ return isGlobSegment(
530
+ value,
531
+ ACTION_KEY_SEGMENT_PATTERN,
532
+ ACTION_KEY_GLOB_PART_PATTERN
533
+ );
534
+ }
535
+ function isGlobSegment(value, exactPattern, globPartPattern) {
536
+ if (exactPattern.test(value)) {
537
+ return true;
538
+ }
539
+ if (!value.includes("*") || value.includes("**")) {
540
+ return false;
541
+ }
542
+ const parts = value.split("@");
543
+ return parts.length <= 2 && parts.every((part) => globPartPattern.test(part));
544
+ }
545
+ function isActionResourcePattern(value) {
546
+ const segments = value.split("/");
547
+ if (segments.length !== 4 || segments[0] !== "action") {
548
+ return false;
549
+ }
550
+ const [, selectedApi, actionType, actionKey] = segments;
551
+ return selectedApi !== void 0 && actionType !== void 0 && actionKey !== void 0 && isSelectedApiPattern(selectedApi) && (actionType === "*" || ActionTypeSchema.safeParse(actionType).success) && isActionKeyPattern(actionKey);
552
+ }
553
+ var ActionResourcePatternSchema = import_zod3.z.string().refine(isActionResourcePattern, {
554
+ message: "Expected action/{selected_api}/{canonical_action_type|*}/{action_key_pattern}"
555
+ });
556
+ function resourceFamily(resource) {
557
+ if (resource === "*" || resource === "http") {
558
+ return resource;
559
+ }
560
+ if (ActionResourcePatternSchema.safeParse(resource).success) {
561
+ return "action";
562
+ }
563
+ if (CONNECTION_RESOURCE_PATTERN.test(resource)) {
564
+ return "connection";
565
+ }
566
+ return void 0;
567
+ }
568
+ function permissionAppliesTo(permission, family) {
569
+ if (permission === "*" || family === "*") {
570
+ return true;
571
+ }
572
+ return permission === "can_execute" ? family === "action" || family === "http" : family === "connection";
573
+ }
574
+ function statementGroups(policy) {
575
+ if (policy.version === 4) {
576
+ return policy.rules.map((rules, tierIndex) => ({
577
+ statements: rules,
578
+ path: ["rules", tierIndex],
579
+ entry: "rule"
580
+ }));
581
+ }
582
+ if (policy.version === 3) {
583
+ return policy.statements.map((statements, tierIndex) => ({
584
+ statements,
585
+ path: ["statements", tierIndex],
586
+ entry: "statement"
587
+ }));
588
+ }
589
+ return [
590
+ {
591
+ statements: policy.statements,
592
+ path: ["statements"],
593
+ entry: "statement"
594
+ }
595
+ ];
596
+ }
597
+ var ZapierPolicySchema = import_policy_schema.PolicySchema.superRefine((policy, ctx) => {
598
+ statementGroups(policy).forEach(({ statements, path, entry }) => {
599
+ const Entry = entry === "rule" ? "Rule" : "Statement";
600
+ statements.forEach((statement, statementIndex) => {
601
+ const families = statement.resources.map((resource, resourceIndex) => {
602
+ const family = resourceFamily(resource);
603
+ if (family === void 0) {
604
+ ctx.addIssue({
605
+ code: "custom",
606
+ message: 'Expected "http", "connection/{id|*}", a valid action pattern, or "*"',
607
+ path: [...path, statementIndex, "resources", resourceIndex]
608
+ });
609
+ }
610
+ return family;
611
+ });
612
+ statement.permissions.forEach((permission, permissionIndex) => {
613
+ if (!PERMISSIONS.has(permission)) {
614
+ ctx.addIssue({
615
+ code: "custom",
616
+ message: `Unknown Zapier permission "${permission}"`,
617
+ path: [...path, statementIndex, "permissions", permissionIndex]
618
+ });
619
+ return;
620
+ }
621
+ const incompatibleFamily = families.find(
622
+ (family) => family !== void 0 && !permissionAppliesTo(permission, family)
623
+ );
624
+ if (incompatibleFamily !== void 0) {
625
+ ctx.addIssue({
626
+ code: "custom",
627
+ message: `Permission "${permission}" cannot apply to ${incompatibleFamily} resources`,
628
+ path: [...path, statementIndex, "permissions", permissionIndex]
629
+ });
630
+ }
631
+ });
632
+ const maskingStatementIndex = statements.findIndex(
633
+ (maskingStatement, maskingIndex) => maskingIndex !== statementIndex && EFFECT_PRECEDENCE[maskingStatement.effect] > EFFECT_PRECEDENCE[statement.effect] && (0, import_policy.isStatementSubsumedBy)(statement, {
634
+ ...maskingStatement,
635
+ effect: statement.effect
636
+ })
637
+ );
638
+ if (maskingStatementIndex !== -1) {
639
+ const maskingStatement = statements[maskingStatementIndex];
640
+ const maskingId = maskingStatement.id ? ` (id "${maskingStatement.id}")` : "";
641
+ ctx.addIssue({
642
+ code: "custom",
643
+ message: `${Entry} is entirely masked by higher-precedence ${entry} at index ${maskingStatementIndex}${maskingId}`,
644
+ path: [...path, statementIndex]
645
+ });
646
+ }
647
+ });
648
+ });
649
+ });
650
+
651
+ // src/connection-policy.ts
652
+ var import_zod4 = require("zod");
653
+ var AccessLevelSchema = import_zod4.z.enum(["read", "read_write"]);
654
+ function requireAccessLevel(accessLevel) {
655
+ if (!AccessLevelSchema.safeParse(accessLevel).success) {
656
+ throw new Error(
657
+ `invalid access_level "${accessLevel}": expected "read" or "read_write"`
658
+ );
659
+ }
660
+ }
477
661
  var READ_HTTP_METHODS = ["GET", "HEAD", "OPTIONS"];
478
662
  var READ_ACTION_TYPES = [
479
663
  "read",
@@ -493,7 +677,19 @@ function isWriteRequest(context) {
493
677
  }
494
678
  function buildConnectionPolicy(options) {
495
679
  const { connectionId, selectedApi, accessLevel } = options;
680
+ requireAccessLevel(accessLevel);
681
+ if (!CONNECTION_ID_PATTERN.test(connectionId)) {
682
+ throw new Error(
683
+ `invalid connection_id "${connectionId}": must not contain "/", "*", or whitespace`
684
+ );
685
+ }
686
+ if (!ACTION_RESOURCE_SEGMENT_PATTERN.test(selectedApi)) {
687
+ throw new Error(
688
+ `invalid selected_api "${selectedApi}": must not contain "/", "*", or whitespace`
689
+ );
690
+ }
496
691
  const authStatement = {
692
+ id: `r_${crypto.randomUUID()}`,
497
693
  effect: "allow",
498
694
  permissions: ["can_use"],
499
695
  resources: [`connection/${connectionId}`],
@@ -513,6 +709,7 @@ function buildConnectionPolicy(options) {
513
709
  });
514
710
  }
515
711
  const httpStatement = {
712
+ id: `r_${crypto.randomUUID()}`,
516
713
  effect: "allow",
517
714
  permissions: ["can_execute"],
518
715
  resources: ["http"],
@@ -521,20 +718,22 @@ function buildConnectionPolicy(options) {
521
718
  const apiPattern = globSelectedApiVersion(selectedApi);
522
719
  const actionResources = accessLevel === "read" ? READ_ACTION_TYPES.map((t) => `action/${apiPattern}/${t}/*`) : [`action/${apiPattern}/*/*`];
523
720
  const actionStatement = {
721
+ id: `r_${crypto.randomUUID()}`,
524
722
  effect: "allow",
525
723
  permissions: ["can_execute"],
526
724
  resources: actionResources,
527
725
  conditions: [connectionCondition]
528
726
  };
529
727
  return {
530
- version: 2,
531
- statements: [authStatement, httpStatement, actionStatement]
728
+ version: 4,
729
+ rules: [[authStatement, httpStatement, actionStatement]]
532
730
  };
533
731
  }
534
732
 
535
733
  // src/connectionless-policy.ts
536
734
  function buildConnectionlessPolicy(options) {
537
735
  const { context, accessLevel } = options;
736
+ requireAccessLevel(accessLevel);
538
737
  if (context.request_type === "http_request") {
539
738
  const conditions = [
540
739
  {
@@ -551,27 +750,36 @@ function buildConnectionlessPolicy(options) {
551
750
  });
552
751
  }
553
752
  const statement2 = {
753
+ id: `r_${crypto.randomUUID()}`,
554
754
  effect: "allow",
555
755
  permissions: ["can_execute"],
556
756
  resources: ["http"],
557
757
  conditions
558
758
  };
559
- return { version: 2, statements: [statement2] };
759
+ return {
760
+ version: 4,
761
+ rules: [[statement2]]
762
+ };
560
763
  }
561
764
  const { selected_api } = parseActionResource(context.resource);
562
765
  const apiPattern = globSelectedApiVersion(selected_api);
563
766
  const resources = accessLevel === "read" ? READ_ACTION_TYPES.map((t) => `action/${apiPattern}/${t}/*`) : [`action/${apiPattern}/*/*`];
564
767
  const statement = {
768
+ id: `r_${crypto.randomUUID()}`,
565
769
  effect: "allow",
566
770
  permissions: ["can_execute"],
567
771
  resources,
568
772
  conditions: []
569
773
  };
570
- return { version: 2, statements: [statement] };
774
+ return {
775
+ version: 4,
776
+ rules: [[statement]]
777
+ };
571
778
  }
572
779
  // Annotate the CommonJS export names for ESM import in node:
573
780
  0 && (module.exports = {
574
781
  AccessLevelSchema,
782
+ ActionResourcePatternSchema,
575
783
  ActionRunContextInputSchema,
576
784
  ActionRunContextSchema,
577
785
  ActionTypeSchema,
@@ -584,6 +792,7 @@ function buildConnectionlessPolicy(options) {
584
792
  READ_ACTION_TYPES,
585
793
  READ_HTTP_METHODS,
586
794
  RequestContextSchema,
795
+ ZapierPolicySchema,
587
796
  buildActionResource,
588
797
  buildActionRunContext,
589
798
  buildBroadActionResource,
@@ -595,6 +804,7 @@ function buildConnectionlessPolicy(options) {
595
804
  buildHttpRequestContext,
596
805
  buildUrlString,
597
806
  globSelectedApiVersion,
807
+ isIgnoredHeader,
598
808
  isWriteRequest,
599
809
  parseActionResource,
600
810
  parseUrl,