@embedpdf/engine-core 3.0.0-next.14 → 3.0.0-next.15

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 (134) hide show
  1. package/dist/AnnotationList-CkijRHet.d.cts +27163 -0
  2. package/dist/AnnotationList-CkijRHet.d.ts +27163 -0
  3. package/dist/Change-CKoxfTNY.js +889 -0
  4. package/dist/Change-CKoxfTNY.js.map +1 -0
  5. package/dist/Change-CjImsrN0.cjs +1020 -0
  6. package/dist/Change-CjImsrN0.cjs.map +1 -0
  7. package/dist/DocumentRenderService-BfFdFSwI.cjs +120 -0
  8. package/dist/DocumentRenderService-BfFdFSwI.cjs.map +1 -0
  9. package/dist/DocumentRenderService-DV3J9lMz.js +85 -0
  10. package/dist/DocumentRenderService-DV3J9lMz.js.map +1 -0
  11. package/dist/{DocumentSecurityService-Ca6EK3-3.d.ts → DocumentSecurityService-CU1M6Q7m.d.ts} +100 -112
  12. package/dist/{DocumentSecurityService-CIx1MT0T.d.cts → DocumentSecurityService-e6vp2de8.d.cts} +100 -112
  13. package/dist/LocalDocumentHandle-D02FMm-Z.cjs +53 -0
  14. package/dist/LocalDocumentHandle-D02FMm-Z.cjs.map +1 -0
  15. package/dist/LocalDocumentHandle-DAPdaOt_.js +30 -0
  16. package/dist/LocalDocumentHandle-DAPdaOt_.js.map +1 -0
  17. package/dist/LocalEngine-DFoBm4B3.d.cts +1565 -0
  18. package/dist/LocalEngine-DPriFujS.d.ts +1565 -0
  19. package/dist/PageInsertBlankInput-CXHAM7l6.cjs +17 -0
  20. package/dist/PageInsertBlankInput-CXHAM7l6.cjs.map +1 -0
  21. package/dist/PageInsertBlankInput-DhVYVJ_S.js +12 -0
  22. package/dist/PageInsertBlankInput-DhVYVJ_S.js.map +1 -0
  23. package/dist/authority-B3BklsB4.js +2239 -0
  24. package/dist/authority-B3BklsB4.js.map +1 -0
  25. package/dist/{viewport-BWeNHZmb.cjs → authority-DLMNR7ff.cjs} +820 -1647
  26. package/dist/authority-DLMNR7ff.cjs.map +1 -0
  27. package/dist/builders-CNj0POeY.js +161 -0
  28. package/dist/builders-CNj0POeY.js.map +1 -0
  29. package/dist/builders-Cgdp_YCn.cjs +184 -0
  30. package/dist/builders-Cgdp_YCn.cjs.map +1 -0
  31. package/dist/calibrate-7T3fnP8-.js +2404 -0
  32. package/dist/calibrate-7T3fnP8-.js.map +1 -0
  33. package/dist/calibrate-DJBXnhLE.cjs +2919 -0
  34. package/dist/calibrate-DJBXnhLE.cjs.map +1 -0
  35. package/dist/charmap-Cprti1ta.js +239 -0
  36. package/dist/charmap-Cprti1ta.js.map +1 -0
  37. package/dist/charmap-aYlefQP9.cjs +304 -0
  38. package/dist/charmap-aYlefQP9.cjs.map +1 -0
  39. package/dist/conformance.cjs +16466 -5247
  40. package/dist/conformance.cjs.map +1 -1
  41. package/dist/conformance.d.cts +924 -165
  42. package/dist/conformance.d.ts +924 -165
  43. package/dist/conformance.js +16429 -5252
  44. package/dist/conformance.js.map +1 -1
  45. package/dist/format-Bp3QFSH1.cjs +574 -0
  46. package/dist/format-Bp3QFSH1.cjs.map +1 -0
  47. package/dist/format-Dgg428Kb.js +461 -0
  48. package/dist/format-Dgg428Kb.js.map +1 -0
  49. package/dist/index.cjs +323 -116
  50. package/dist/index.d.cts +2 -2
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.js +14 -5
  53. package/dist/pageRefs-B3ur7Ckm.cjs +51 -0
  54. package/dist/pageRefs-B3ur7Ckm.cjs.map +1 -0
  55. package/dist/pageRefs-DkbuxSCV.js +40 -0
  56. package/dist/pageRefs-DkbuxSCV.js.map +1 -0
  57. package/dist/public.cjs +57 -0
  58. package/dist/public.d.cts +5 -0
  59. package/dist/public.d.ts +5 -0
  60. package/dist/public.js +9 -0
  61. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs +3646 -0
  62. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs.map +1 -0
  63. package/dist/resolveAnnotationDraft-CUpf0SMh.js +3167 -0
  64. package/dist/resolveAnnotationDraft-CUpf0SMh.js.map +1 -0
  65. package/dist/{resolver-inAVzsWt.js → resolver-CzxmD9Ck.js} +294 -144
  66. package/dist/resolver-CzxmD9Ck.js.map +1 -0
  67. package/dist/{resolver-CYVNV4hn.cjs → resolver-D2OCtE8F.cjs} +347 -143
  68. package/dist/resolver-D2OCtE8F.cjs.map +1 -0
  69. package/dist/runtime-Cl0v56mm.d.ts +2032 -0
  70. package/dist/runtime-p1WDzDo4.d.cts +2032 -0
  71. package/dist/runtime.cjs +726 -297
  72. package/dist/runtime.cjs.map +1 -1
  73. package/dist/runtime.d.cts +5 -1372
  74. package/dist/runtime.d.ts +5 -1372
  75. package/dist/runtime.js +396 -179
  76. package/dist/runtime.js.map +1 -1
  77. package/dist/schema-C-gU4vj7.cjs +3034 -0
  78. package/dist/schema-C-gU4vj7.cjs.map +1 -0
  79. package/dist/schema-DzlUNk38.js +2081 -0
  80. package/dist/schema-DzlUNk38.js.map +1 -0
  81. package/dist/schemas-Berf1swF.cjs +2481 -0
  82. package/dist/schemas-Berf1swF.cjs.map +1 -0
  83. package/dist/schemas-D6xOGjqg.js +1654 -0
  84. package/dist/schemas-D6xOGjqg.js.map +1 -0
  85. package/dist/subtype-CG4WpIRj.js +129 -0
  86. package/dist/subtype-CG4WpIRj.js.map +1 -0
  87. package/dist/subtype-aDt5TFmK.cjs +158 -0
  88. package/dist/subtype-aDt5TFmK.cjs.map +1 -0
  89. package/dist/validation.cjs +578 -0
  90. package/dist/validation.cjs.map +1 -0
  91. package/dist/validation.d.cts +174 -0
  92. package/dist/validation.d.ts +174 -0
  93. package/dist/validation.js +571 -0
  94. package/dist/validation.js.map +1 -0
  95. package/dist/value-BFS1R8ek.cjs +435 -0
  96. package/dist/value-BFS1R8ek.cjs.map +1 -0
  97. package/dist/value-Dhc8eYcx.js +340 -0
  98. package/dist/value-Dhc8eYcx.js.map +1 -0
  99. package/dist/viewport-BJW13EGZ.js +540 -0
  100. package/dist/viewport-BJW13EGZ.js.map +1 -0
  101. package/dist/viewport-DlL5Bxbg.cjs +671 -0
  102. package/dist/viewport-DlL5Bxbg.cjs.map +1 -0
  103. package/dist/wire.cjs +1051 -397
  104. package/dist/wire.cjs.map +1 -1
  105. package/dist/wire.d.cts +12403 -790
  106. package/dist/wire.d.ts +12403 -790
  107. package/dist/wire.js +821 -242
  108. package/dist/wire.js.map +1 -1
  109. package/package.json +10 -1
  110. package/dist/AbortError-DwuHPK_I.js +0 -27
  111. package/dist/AbortError-DwuHPK_I.js.map +0 -1
  112. package/dist/AbortError-P0RU0Mgg.cjs +0 -38
  113. package/dist/AbortError-P0RU0Mgg.cjs.map +0 -1
  114. package/dist/AnnotationListSnapshot-D9R5n2Kf.d.cts +0 -7101
  115. package/dist/AnnotationListSnapshot-D9R5n2Kf.d.ts +0 -7101
  116. package/dist/Engine-EWWHhEIi.d.ts +0 -1277
  117. package/dist/Engine-FNaAEjfb.d.cts +0 -1277
  118. package/dist/derive-BbsDk_Kz.cjs +0 -880
  119. package/dist/derive-BbsDk_Kz.cjs.map +0 -1
  120. package/dist/derive-DAFJolWu.js +0 -647
  121. package/dist/derive-DAFJolWu.js.map +0 -1
  122. package/dist/format-Bbphf4zR.js +0 -435
  123. package/dist/format-Bbphf4zR.js.map +0 -1
  124. package/dist/format-D_epWlob.cjs +0 -518
  125. package/dist/format-D_epWlob.cjs.map +0 -1
  126. package/dist/resolver-CYVNV4hn.cjs.map +0 -1
  127. package/dist/resolver-inAVzsWt.js.map +0 -1
  128. package/dist/schemas-CFQGvWiu.js +0 -3243
  129. package/dist/schemas-CFQGvWiu.js.map +0 -1
  130. package/dist/schemas-D0g6tKa9.cjs +0 -4694
  131. package/dist/schemas-D0g6tKa9.cjs.map +0 -1
  132. package/dist/viewport-BWeNHZmb.cjs.map +0 -1
  133. package/dist/viewport-CvMcadfQ.js +0 -3072
  134. package/dist/viewport-CvMcadfQ.js.map +0 -1
@@ -1,8 +1,115 @@
1
+ import { h as EngineError, v as _defineProperty, y as EngineErrorCode } from "./format-Dgg428Kb.js";
2
+ import { r as materializePdfPermissions } from "./builders-CNj0POeY.js";
3
+ //#region src/auth/scope/errors.ts
4
+ /**
5
+ * Thrown by {@link parseScope} / {@link validateScopeArray} when a scope
6
+ * string does not match any known grammar (capability, collab, virtual,
7
+ * or wildcard). An `EngineError` with code `InvalidArg`.
8
+ *
9
+ * Carries the offending scope verbatim so route/JWT-layer error
10
+ * handlers can echo it to the customer.
11
+ */
12
+ var InvalidScope = class extends EngineError {
13
+ constructor(scope, reason) {
14
+ super(EngineErrorCode.InvalidArg, `invalid scope "${scope}": ${reason}`, { details: {
15
+ scope,
16
+ reason
17
+ } });
18
+ this.scope = scope;
19
+ _defineProperty(this, "name", "InvalidScope");
20
+ }
21
+ };
22
+ /**
23
+ * Thrown by route guards and engine-local enforcement when a capability
24
+ * or collab action is denied for the current request/handle. An
25
+ * `EngineError` with code `Forbidden`, on both engines.
26
+ *
27
+ * `required` (also `details.required`) names what the caller needed (e.g.
28
+ * "doc.render" or "annotations:update"). When any of several would have
29
+ * done, `anyOf` (also `details.anyOf`) lists them and `required` is the
30
+ * first. `context` is an optional label such as "engine-local" or a route.
31
+ */
32
+ var PermissionDenied = class extends EngineError {
33
+ constructor(required, context, anyOf, refs) {
34
+ super(EngineErrorCode.Forbidden, `permission denied${context ? ` (${context})` : ""}: ${anyOf ? `one of ${anyOf.join(", ")}` : required}${refs ? ` for ${refs.length} annotation${refs.length === 1 ? "" : "s"}` : ""}`, { details: {
35
+ required,
36
+ ...context === void 0 ? {} : { context },
37
+ ...anyOf === void 0 ? {} : { anyOf: [...anyOf] },
38
+ ...refs === void 0 ? {} : { refs: [...refs] }
39
+ } });
40
+ this.required = required;
41
+ this.context = context;
42
+ this.anyOf = anyOf;
43
+ this.refs = refs;
44
+ _defineProperty(this, "name", "PermissionDenied");
45
+ }
46
+ };
47
+ /**
48
+ * Thrown at engine-local open time when the supplied scope includes
49
+ * collab filters (`:self`, `:group=...`) but the identity claims needed
50
+ * to resolve them are missing. Fails loudly at open so the configuration
51
+ * mistake is visible immediately instead of producing silent denies at
52
+ * every annotation mutation. An `EngineError` with code `InvalidArg`.
53
+ */
54
+ var MissingIdentity = class extends EngineError {
55
+ constructor(scope) {
56
+ super(EngineErrorCode.InvalidArg, `scope "${scope}" requires an identity (userId and/or groups)`, { details: { scope } });
57
+ this.scope = scope;
58
+ _defineProperty(this, "name", "MissingIdentity");
59
+ }
60
+ };
61
+ //#endregion
62
+ //#region src/identity/AnnotationRef.ts
63
+ /**
64
+ * URL-safe encoding of a ref without its page, used by the cloud HTTP
65
+ * surface as the `:annotKey` route parameter beside `:pageKey`
66
+ * (`encodePageKey`). Decoded by `decodeAnnotKey`.
67
+ *
68
+ * `{ kind: 'objectNumber', objectNumber: 42 }` -> `'obj:42'`
69
+ * `{ kind: 'baseIndex', baseIndex: 2 }` -> `'base:2'`
70
+ */
71
+ function encodeAnnotKey(ref) {
72
+ if (ref.kind === "objectNumber") {
73
+ if (!Number.isInteger(ref.objectNumber) || ref.objectNumber <= 0) throw new RangeError(`encodeAnnotKey: objectNumber must be a positive integer, got ${ref.objectNumber}`);
74
+ return `obj:${ref.objectNumber}`;
75
+ }
76
+ if (!Number.isInteger(ref.baseIndex) || ref.baseIndex < 0) throw new RangeError(`encodeAnnotKey: baseIndex must be a non-negative integer, got ${ref.baseIndex}`);
77
+ return `base:${ref.baseIndex}`;
78
+ }
79
+ /**
80
+ * Inverse of `encodeAnnotKey`, on the page the route names. Returns `null`
81
+ * for malformed input so the server can answer 400 InvalidArg with a useful
82
+ * message instead of throwing. The input is the already-`decodeURIComponent`-ed
83
+ * segment from the route path.
84
+ */
85
+ function decodeAnnotKey(page, key) {
86
+ const number = (prefix, min) => {
87
+ if (!key.startsWith(prefix)) return null;
88
+ const rest = key.slice(prefix.length);
89
+ const n = Number.parseInt(rest, 10);
90
+ return Number.isInteger(n) && n >= min && String(n) === rest ? n : null;
91
+ };
92
+ const objectNumber = number("obj:", 1);
93
+ if (objectNumber !== null) return {
94
+ kind: "objectNumber",
95
+ page,
96
+ objectNumber
97
+ };
98
+ const baseIndex = number("base:", 0);
99
+ if (baseIndex !== null) return {
100
+ kind: "baseIndex",
101
+ page,
102
+ baseIndex
103
+ };
104
+ return null;
105
+ }
106
+ //#endregion
1
107
  //#region src/signature/protection.ts
2
108
  /**
3
109
  * Bumped whenever the derivation below (or a judgement rule) changes meaning.
4
110
  * Rides every protection and every analysis, and keys the cloud's immutable
5
- * analysis URLs. v4 (the `signature-compat` corpus, 91 Acrobat observations):
111
+ * analysis URLs. The current policy is calibrated on the 91 Acrobat
112
+ * observations in `packages/engine/main/test/fixtures/signature-compat`:
6
113
  * each signature is judged on the net state of the document against the
7
114
  * revision it sealed; an object written again with the sealed value is not
8
115
  * a modification; an approval signature permits commenting; field
@@ -15,8 +122,8 @@ const SIGNATURE_POLICY_VERSION = 4;
15
122
  * changes only for certification signatures; Acrobat reads an approval
16
123
  * signature as "Form Fill-in, Signing and Commenting are allowed" (its own
17
124
  * permission text under every such signature) and keeps it valid through a
18
- * Square annotation added, deleted or recoloured afterwards (corpus
19
- * `signature-compat` v3 cases 88, 90, 91: "Annotations Created/Deleted/
125
+ * Square annotation added, deleted or recoloured afterwards (fixtures
126
+ * `signature-compat/v3/88`, `/90` and `/91`: "Annotations Created/Deleted/
20
127
  * Modified", signature valid). pyHanko judges stricter (fill only); Acrobat
21
128
  * is the validator recipients use, so the baseline is `annotate`.
22
129
  */
@@ -64,7 +171,7 @@ function lockCovers(spec, fieldName) {
64
171
  * did allow annotations.
65
172
  * fieldLocks = every signed signature's FieldMDP and the /Lock of every
66
173
  * signed field. Unsigned fields' /Lock entries describe a
67
- * FUTURE signature and lock nothing yet.
174
+ * future signature and lock nothing yet.
68
175
  */
69
176
  function deriveProtection(signatures) {
70
177
  let signed = false;
@@ -109,15 +216,37 @@ function fieldLockFor(protection, fieldName) {
109
216
  return null;
110
217
  }
111
218
  /**
219
+ * Every capability a protection can remove: the most it ever takes away.
220
+ * A check of any other capability never needs the document's protection.
221
+ */
222
+ const PROTECTABLE_CAPABILITIES = [
223
+ "doc.download.flattened",
224
+ "doc.pages.modify",
225
+ "doc.pages.assemble",
226
+ "doc.redact",
227
+ "doc.attachments.modify",
228
+ "doc.forms.modify",
229
+ "doc.forms.script",
230
+ "doc.annotate.modify",
231
+ "doc.annotate.import",
232
+ "doc.forms.fill",
233
+ "doc.forms.import"
234
+ ];
235
+ const PROTECTABLE = new Set(PROTECTABLE_CAPABILITIES);
236
+ /** Whether a protection can ever remove `capability`. */
237
+ function isProtectableCapability(capability) {
238
+ return PROTECTABLE.has(capability);
239
+ }
240
+ /**
112
241
  * The capabilities a protection removes from every caller, admin scope
113
242
  * included — document-derived authority, exactly like encryption bits.
114
243
  *
115
- * Only what was DECLARED is refused: a refusal is a promise a signer made,
244
+ * Only what was declared is refused: a refusal is a promise a signer made,
116
245
  * never a guess about a validator. A certification (or a lock with /P)
117
246
  * declares what may follow, and everything outside that — page edits,
118
247
  * redaction, field authoring, attachments, and below P=3 annotations, below
119
248
  * P=2 form fill — is refused. A plain approval signature declares nothing:
120
- * the same edits stay possible and are then JUDGED, the verdict saying the
249
+ * the same edits stay possible and are then judged, the verdict saying the
121
250
  * signature no longer holds, exactly as Acrobat does. The one exception is a
122
251
  * rewrite: it does not invalidate signatures, it erases them, so any signed
123
252
  * document refuses it. Per-field locks are enforced by the form mutator.
@@ -132,62 +261,27 @@ function protectedCapabilities(protection) {
132
261
  out.add("doc.redact");
133
262
  out.add("doc.attachments.modify");
134
263
  out.add("doc.forms.modify");
135
- if (!levelAllows(protection.enforced, "annotate")) out.add("doc.annotate.modify");
136
- if (!levelAllows(protection.enforced, "fill")) out.add("doc.forms.fill");
137
- return out;
138
- }
139
- //#endregion
140
- //#region src/auth/scope/errors.ts
141
- /**
142
- * Thrown by {@link parseScope} / {@link validateScopeArray} when a scope
143
- * string does not match any known grammar (capability, collab, virtual,
144
- * or wildcard).
145
- *
146
- * Carries the offending scope verbatim so route/JWT-layer error
147
- * handlers can echo it to the customer.
148
- */
149
- var InvalidScope = class extends Error {
150
- constructor(scope, reason) {
151
- super(`invalid scope "${scope}": ${reason}`);
152
- this.scope = scope;
153
- this.name = "InvalidScope";
264
+ out.add("doc.forms.script");
265
+ if (!levelAllows(protection.enforced, "annotate")) {
266
+ out.add("doc.annotate.modify");
267
+ out.add("doc.annotate.import");
154
268
  }
155
- };
156
- /**
157
- * Thrown by route guards and engine-local enforcement when a capability
158
- * or collab action is denied for the current request/handle.
159
- *
160
- * `required` names what the caller needed (e.g., "doc.render" or
161
- * "annotations:update"). `context` is an optional label such as "local"
162
- * or "/v1/docs/.../pages/2/render".
163
- */
164
- var PermissionDenied = class extends Error {
165
- constructor(required, context) {
166
- super(`permission denied${context ? ` (${context})` : ""}: ${required}`);
167
- this.required = required;
168
- this.context = context;
169
- this.name = "PermissionDenied";
170
- }
171
- };
172
- /**
173
- * Thrown at engine-local open time when the supplied scope includes
174
- * collab filters (`:self`, `:group=...`) but the identity claims needed
175
- * to resolve them are missing. Fails loudly at open so the configuration
176
- * mistake is visible immediately instead of producing silent denies at
177
- * every annotation mutation.
178
- */
179
- var MissingIdentity = class extends Error {
180
- constructor(scope) {
181
- super(`scope "${scope}" requires identity claims (user_id and/or groups)`);
182
- this.name = "MissingIdentity";
269
+ if (!levelAllows(protection.enforced, "fill")) {
270
+ out.add("doc.forms.fill");
271
+ out.add("doc.forms.import");
183
272
  }
184
- };
273
+ return out;
274
+ }
275
+ /** Why `protection` refuses `capability`: the message of a `ProtectedDocument` refusal. */
276
+ function describeProtection(capability, protection) {
277
+ return `the document is signed: ${protection.certification ? `certification signature ${protection.certification.signatureIndex} (permission ${protection.certification.permission})` : `an existing signature (declared level '${protection.enforced ?? "none declared"}')`} forbids '${capability}'`;
278
+ }
185
279
  //#endregion
186
280
  //#region src/auth/scope/parser.ts
187
281
  /**
188
282
  * Closed set of recognized capability strings. Membership is the
189
283
  * authoritative source of truth — adding a new capability requires
190
- * extending the `DocCapability` union AND adding it here. The parser
284
+ * extending the `DocCapability` union and adding it here. The parser
191
285
  * rejects anything outside this set.
192
286
  *
193
287
  * Removed legacy names (e.g., `doc.read`, `doc.edit-pages`, `doc.save`)
@@ -210,9 +304,12 @@ const KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
210
304
  "doc.forms.read",
211
305
  "doc.forms.fill",
212
306
  "doc.forms.modify",
307
+ "doc.forms.import",
308
+ "doc.forms.script",
213
309
  "doc.forms.submit",
214
310
  "doc.annotate.read",
215
311
  "doc.annotate.modify",
312
+ "doc.annotate.import",
216
313
  "doc.metadata.modify",
217
314
  "doc.attachments.modify",
218
315
  "doc.redact",
@@ -257,6 +354,7 @@ function parseCollab(raw) {
257
354
  const entity = raw.slice(0, idx1);
258
355
  const action = raw.slice(idx1 + 1, idx2);
259
356
  const filterStr = raw.slice(idx2 + 1);
357
+ if (entity === "fields") return parseFieldScope(raw, action, filterStr);
260
358
  if (entity !== "annotations") throw new InvalidScope(raw, `unknown collab entity: ${entity}`);
261
359
  if (action !== "create" && action !== "update" && action !== "delete" && action !== "set-group" && action !== "*") throw new InvalidScope(raw, `unknown collab action: ${action}`);
262
360
  const filter = parseFilter(filterStr, raw);
@@ -268,6 +366,22 @@ function parseCollab(raw) {
268
366
  filter
269
367
  };
270
368
  }
369
+ /**
370
+ * `fields:<fill|sign|set-group|*>:<all|group=NAME>`. A field's group is who
371
+ * fills it, and a field records no creator, so `self` and `createdBy=` are
372
+ * refused.
373
+ */
374
+ function parseFieldScope(raw, action, filterStr) {
375
+ if (action !== "fill" && action !== "sign" && action !== "set-group" && action !== "*") throw new InvalidScope(raw, `unknown fields action: ${action}`);
376
+ const filter = parseFilter(filterStr, raw);
377
+ if (filter.kind !== "all" && filter.kind !== "group") throw new InvalidScope(raw, `fields only support :all or :group=<id> filters (got :${filter.kind})`);
378
+ return {
379
+ kind: "collab",
380
+ entity: "fields",
381
+ action,
382
+ filter
383
+ };
384
+ }
271
385
  function parseFilter(s, raw) {
272
386
  if (s === "all") return { kind: "all" };
273
387
  if (s === "self") return { kind: "self" };
@@ -300,6 +414,13 @@ function parseCapability(raw) {
300
414
  }
301
415
  //#endregion
302
416
  //#region src/auth/scope/resolver.ts
417
+ /** The collab target of a record read with nullable owner fields (an annotation). */
418
+ function collabTargetOf(owner) {
419
+ return {
420
+ ...owner.userId ? { userId: owner.userId } : {},
421
+ ...owner.groupId ? { groupId: owner.groupId } : {}
422
+ };
423
+ }
303
424
  /**
304
425
  * True iff the given capability is granted by the scope array plus the
305
426
  * PDF bits visible via `pdf.permissions` expansion.
@@ -314,9 +435,9 @@ function checkCapability(capability, rawScope, pdfBits, protection = null) {
314
435
  return expandedCapabilities(parsed, pdfBits, protection).has(capability);
315
436
  }
316
437
  /**
317
- * True iff the scope grants AT LEAST ONE of `capabilities`. Convenience
318
- * for routes like `/text` (`doc.text.copy` OR `doc.text.search`) and
319
- * `/geometry` (`doc.text.select` OR `doc.text.search`).
438
+ * True iff the scope grants at least one of `capabilities`. Convenience
439
+ * for routes like `/text` (`doc.text.copy` or `doc.text.search`) and
440
+ * `/geometry` (`doc.text.select` or `doc.text.search`).
320
441
  */
321
442
  function checkAnyCapability(capabilities, rawScope, pdfBits, protection = null) {
322
443
  return capabilities.some((c) => checkCapability(c, rawScope, pdfBits, protection));
@@ -326,7 +447,7 @@ function checkAnyCapability(capabilities, rawScope, pdfBits, protection = null)
326
447
  *
327
448
  * Narrowing model. For each action independently:
328
449
  * 1. wildcard `*` → allow (global escape hatch)
329
- * 2. if any collab scope applies to this action → NARROW: only those
450
+ * 2. if any collab scope applies to this action → narrow: only those
330
451
  * collab filters decide. If none match the target, deny — even if
331
452
  * `doc.annotate.modify` is also present. This is what makes
332
453
  * `[modify, update:self]` correctly mean "edit own only" rather
@@ -337,7 +458,7 @@ function checkAnyCapability(capabilities, rawScope, pdfBits, protection = null)
337
458
  * 4. otherwise → deny.
338
459
  *
339
460
  * For create: `target` should be built by the caller from JWT identity
340
- * (`{ userId: caller.user_id, groupId: caller.group_id }`). `:self` and
461
+ * (`{ userId: caller.userId, groupId: caller.groupId }`). `:self` and
341
462
  * `:all` then trivially pass; `:group=X` is the meaningful filter (only
342
463
  * matches when the caller's default group is X).
343
464
  *
@@ -362,17 +483,22 @@ function checkCollab(action, target, rawScope, identity, pdfBits) {
362
483
  * - any annotation collab scope implies `doc.annotate.read`, because
363
484
  * mutation routes need to see the target row to evaluate the
364
485
  * collab filter against its current owner.
486
+ * - any field collab scope implies `doc.forms.read`: filling in or
487
+ * signing a field means seeing the form. Groups limit writes, never
488
+ * reads.
365
489
  *
366
- * Does NOT short-circuit on wildcard — callers do that themselves
490
+ * Does not short-circuit on wildcard — callers do that themselves
367
491
  * before calling this. Returning the expanded set is useful for the
368
492
  * `/access` response's `effectiveScope` and for advisory UI surfacing.
369
493
  */
370
494
  function expandedCapabilities(parsed, pdfBits, protection = null) {
371
495
  const out = /* @__PURE__ */ new Set();
372
496
  let hasAnnotationCollab = false;
497
+ let hasFieldCollab = false;
373
498
  for (const s of parsed) if (s.kind === "capability") out.add(s.name);
374
499
  else if (s.kind === "virtual" && s.name === "pdf.permissions") addPdfPermissions(out, pdfBits);
375
500
  else if (s.kind === "collab" && s.entity === "annotations") hasAnnotationCollab = true;
501
+ else if (s.kind === "collab" && s.entity === "fields") hasFieldCollab = true;
376
502
  if (out.has("doc.annotate.modify")) out.add("doc.annotate.read");
377
503
  if (out.has("doc.forms.modify")) {
378
504
  out.add("doc.forms.fill");
@@ -380,6 +506,7 @@ function expandedCapabilities(parsed, pdfBits, protection = null) {
380
506
  }
381
507
  if (out.has("doc.forms.fill")) out.add("doc.forms.read");
382
508
  if (hasAnnotationCollab) out.add("doc.annotate.read");
509
+ if (hasFieldCollab) out.add("doc.forms.read");
383
510
  for (const removed of protectedCapabilities(protection)) out.delete(removed);
384
511
  return out;
385
512
  }
@@ -392,119 +519,142 @@ function expandRawScope(rawScope, pdfBits, protection = null) {
392
519
  return expandedCapabilities(rawScope.map(parseScope), pdfBits, protection);
393
520
  }
394
521
  /**
395
- * Test a single collab filter against a target record + the caller's
396
- * identity. Pure function — no side effects, no implicit rules.
522
+ * Authority to put a record of `entity` in a specific group: an annotation,
523
+ * or a form field.
397
524
  *
398
- * all → always matches
399
- * self → matches if identity.user_id === target.userId
400
- * createdBy=<X> → matches if target.userId === X
401
- * group=<X> → matches if target.groupId === X
402
- * AND identity.groups includes X
403
- *
404
- * The group-membership check on `group=X` prevents a token from
405
- * matching annotations in a group it doesn't belong to, even if the
406
- * target row carries that groupId.
407
- */
408
- /**
409
- * Authority to assign a specific groupId to an annotation.
410
- *
411
- * Set-group is decoupled from `doc.annotate.modify`. The reasoning:
412
- * capabilities like `modify` describe row access — what kind of write
413
- * you can do to which existing rows — and map to PDF permission bits.
414
- * Set-group is a cloud-only *assignment authority*: which destination
415
- * group can you put an annotation into? There is no PDF-bit
416
- * counterpart, so it doesn't inherit from `modify`.
525
+ * Set-group is decoupled from the write capabilities (`doc.annotate.modify`,
526
+ * `doc.forms.modify`). The reasoning: those describe row access — what kind
527
+ * of write you can do to which existing rows — and map to PDF permission
528
+ * bits. Set-group is a cloud-only *assignment authority*: which destination
529
+ * group can you put a record into? There is no PDF-bit counterpart, so it
530
+ * doesn't inherit from them.
417
531
  *
418
532
  * Resolution order:
419
533
  * 1. newGroupId === callerDefaultGroupId → true (no real reassignment
420
- * is happening; the annotation gets the caller's default group)
534
+ * is happening; the record gets the caller's default group)
421
535
  * 2. wildcard `*` → true (global escape hatch)
422
- * 3. `annotations:set-group:all` → true
423
- * 4. `annotations:set-group:group=<newGroupId>` → true
424
- * 5. `annotations:*:all` or `annotations:*:group=<newGroupId>` → true
536
+ * 3. `<entity>:set-group:all` → true
537
+ * 4. `<entity>:set-group:group=<newGroupId>` → true
538
+ * 5. `<entity>:*:all` or `<entity>:*:group=<newGroupId>` → true
425
539
  * (action wildcard includes set-group)
426
540
  * 6. otherwise → false
427
541
  *
428
- * Note: independent from membership. A user with
429
- * `set-group:group=legal` can assign annotations to the legal group
430
- * even if they're not a member — that's the whole point.
542
+ * A user with `set-group:group=legal` can put records in the legal group
543
+ * whatever their own group is — that's the whole point.
431
544
  */
432
- function checkSetGroup(newGroupId, callerDefaultGroupId, rawScope, _pdfBits) {
545
+ function checkSetGroup(entity, newGroupId, callerDefaultGroupId, rawScope) {
433
546
  if (newGroupId === callerDefaultGroupId) return true;
434
547
  const parsed = rawScope.map(parseScope);
435
548
  if (parsed.some((s) => s.kind === "wildcard")) return true;
436
549
  for (const s of parsed) {
437
550
  if (s.kind !== "collab") continue;
438
- if (s.entity !== "annotations") continue;
551
+ if (s.entity !== entity) continue;
439
552
  if (s.action !== "set-group" && s.action !== "*") continue;
440
553
  if (s.filter.kind === "all") return true;
441
554
  if (s.filter.kind === "group" && s.filter.groupId === newGroupId) return true;
442
555
  }
443
556
  return false;
444
557
  }
558
+ /**
559
+ * True iff the scope lets the caller `action` (fill in, or sign) a form
560
+ * field in `groupId` (`null` for a field in no group).
561
+ *
562
+ * Narrowing, per action, as for annotations:
563
+ * 1. wildcard `*` → allow;
564
+ * 2. if any `fields:<action>` scope exists (or `fields:*`) → only those
565
+ * decide: the field must match one of their filters, even when the
566
+ * broad capability is also present. A field in no group then matches
567
+ * only `:all`;
568
+ * 3. otherwise the broad capability decides, for every field:
569
+ * `doc.forms.fill` to fill, `doc.sign` to sign.
570
+ *
571
+ * Nothing here narrows form design (`doc.forms.modify`).
572
+ */
573
+ function checkFieldAction(action, groupId, rawScope, pdfBits, protection = null) {
574
+ const broad = action === "fill" ? "doc.forms.fill" : "doc.sign";
575
+ if (protectedCapabilities(protection).has(broad)) return false;
576
+ const parsed = rawScope.map(parseScope);
577
+ if (parsed.some((s) => s.kind === "wildcard")) return true;
578
+ const applicable = parsed.filter((s) => s.kind === "collab" && s.entity === "fields" && (s.action === action || s.action === "*"));
579
+ if (applicable.length > 0) return applicable.some((s) => s.filter.kind === "all" || groupId !== null && s.filter.groupId === groupId);
580
+ return expandedCapabilities(parsed, pdfBits, protection).has(broad);
581
+ }
582
+ /**
583
+ * {@link checkFieldAction} and {@link checkSetGroup} as lists: for each
584
+ * action, every field or the groups the scope names, so a designer can
585
+ * offer exactly the groups a field may go in, and a filler see whose fields
586
+ * are theirs. The same rules, read the other way round.
587
+ */
588
+ function fieldRightsOf(rawScope, pdfBits, protection, callerDefaultGroupId) {
589
+ const parsed = rawScope.map(parseScope);
590
+ const wildcard = parsed.some((s) => s.kind === "wildcard");
591
+ const fieldScopes = (action) => parsed.filter((s) => s.kind === "collab" && s.entity === "fields" && (s.action === action || s.action === "*"));
592
+ const fromScopes = (scopes, extra = []) => {
593
+ if (scopes.some((s) => s.filter.kind === "all")) return { all: true };
594
+ const groups = scopes.flatMap((s) => s.filter.kind === "group" ? [s.filter.groupId] : []);
595
+ return {
596
+ all: false,
597
+ groups: [.../* @__PURE__ */ new Set([...groups, ...extra])]
598
+ };
599
+ };
600
+ const NONE = {
601
+ all: false,
602
+ groups: []
603
+ };
604
+ const writing = (action) => {
605
+ const broad = action === "fill" ? "doc.forms.fill" : "doc.sign";
606
+ if (protectedCapabilities(protection).has(broad)) return NONE;
607
+ if (wildcard) return { all: true };
608
+ const scopes = fieldScopes(action);
609
+ if (scopes.length > 0) return fromScopes(scopes);
610
+ return expandedCapabilities(parsed, pdfBits, protection).has(broad) ? { all: true } : NONE;
611
+ };
612
+ const own = callerDefaultGroupId !== void 0 ? [callerDefaultGroupId] : [];
613
+ return {
614
+ fill: writing("fill"),
615
+ sign: writing("sign"),
616
+ setGroup: wildcard ? { all: true } : fromScopes(fieldScopes("set-group"), own)
617
+ };
618
+ }
619
+ /**
620
+ * Whether the scope lets the caller `action` some form field: the broad
621
+ * capability, or any `fields:<action>` scope. What a form verb checks
622
+ * before its fields are known; each field is checked in the write.
623
+ */
624
+ function checkAnyFieldAction(action, rawScope, pdfBits, protection = null) {
625
+ const broad = action === "fill" ? "doc.forms.fill" : "doc.sign";
626
+ if (protectedCapabilities(protection).has(broad)) return false;
627
+ const parsed = rawScope.map(parseScope);
628
+ if (parsed.some((s) => s.kind === "wildcard")) return true;
629
+ if (parsed.some((s) => s.kind === "collab" && s.entity === "fields" && (s.action === action || s.action === "*"))) return true;
630
+ return expandedCapabilities(parsed, pdfBits, protection).has(broad);
631
+ }
632
+ /**
633
+ * Test a single collab filter against a target record. Every filter but
634
+ * `self` compares one fact on the record with the value the scope names:
635
+ *
636
+ * all → always matches
637
+ * self → matches if identity.userId === target.userId
638
+ * createdBy=<X> → matches if target.userId === X
639
+ * group=<X> → matches if target.groupId === X
640
+ */
445
641
  function filterMatches(filter, target, id) {
446
642
  switch (filter.kind) {
447
643
  case "all": return true;
448
- case "self": return !!id.user_id && target.userId === id.user_id;
644
+ case "self": return !!id.userId && target.userId === id.userId;
449
645
  case "createdBy": return target.userId === filter.userId;
450
- case "group": return target.groupId === filter.groupId && (id.groups?.includes(filter.groupId) ?? false);
646
+ case "group": return target.groupId === filter.groupId;
451
647
  }
452
648
  }
453
649
  /**
454
650
  * Translate `pdf.permissions` (virtual scope) into the concrete
455
- * capabilities it represents under the current PDF bit configuration.
456
- *
457
- * Always adds `doc.open` and `doc.render` — these are cloud-only
458
- * capabilities with no PDF bit, but `pdf.permissions` is meant to be
459
- * the "give the user a working session" shorthand. Without them, a
460
- * token with just `['pdf.permissions']` would be useless.
461
- *
462
- * Reads are unconditional: ISO 32000 / Acrobat let any reader see
463
- * existing annotations and form values regardless of permission bits.
464
- * Bit 6 governs *writing*, not visibility.
465
- *
466
- * Bit-derived expansions follow ISO 32000:
467
- * bit 5 → doc.text.{select, copy, search}, doc.content.copy
468
- * bit 3 → doc.print
469
- * bit 12 → doc.print.high (requires bit 3 also set)
470
- * bit 4 → doc.pages.modify, doc.redact, doc.metadata.modify, doc.attachments.modify
471
- * bit 11 → doc.pages.assemble
472
- * bit 6 → doc.annotate.modify
473
- * bit 6/9 → doc.forms.fill
474
- * bit 6+4 → doc.forms.modify
475
- *
476
- * Note: this same expansion lives in builders.ts as
477
- * `materializePdfPermissions` for SDK-side use. The two MUST stay in
478
- * sync; a test in resolver.test.ts pins them together.
651
+ * capabilities it represents under the current PDF bit configuration:
652
+ * {@link materializePdfPermissions}, the one reading of the bits.
479
653
  */
480
654
  function addPdfPermissions(out, b) {
481
- out.add("doc.open");
482
- out.add("doc.render");
483
- out.add("doc.annotate.read");
484
- out.add("doc.forms.read");
485
- if (b.bit5) {
486
- out.add("doc.text.select");
487
- out.add("doc.text.copy");
488
- out.add("doc.text.search");
489
- out.add("doc.content.copy");
490
- }
491
- if (b.bit3) out.add("doc.print");
492
- if (b.bit12 && b.bit3) out.add("doc.print.high");
493
- if (b.bit4) {
494
- out.add("doc.pages.modify");
495
- out.add("doc.redact");
496
- out.add("doc.metadata.modify");
497
- out.add("doc.attachments.modify");
498
- }
499
- if (b.bit11) out.add("doc.pages.assemble");
500
- if (b.bit6) out.add("doc.annotate.modify");
501
- if (b.bit6 || b.bit9) {
502
- out.add("doc.forms.fill");
503
- out.add("doc.sign");
504
- }
505
- if (b.bit6 && b.bit4) out.add("doc.forms.modify");
655
+ for (const capability of materializePdfPermissions(b)) out.add(capability);
506
656
  }
507
657
  //#endregion
508
- export { protectedCapabilities as S, levelAllows as _, expandRawScope as a, lockNameCovers as b, parseScope as c, MissingIdentity as d, PermissionDenied as f, fieldLockFor as g, deriveProtection as h, checkSetGroup as i, validateScopeArray as l, SIGNATURE_POLICY_VERSION as m, checkCapability as n, expandedCapabilities as o, APPROVAL_BASELINE as p, checkCollab as r, filterMatches as s, checkAnyCapability as t, InvalidScope as u, levelFromPermission as v, minLevel as x, lockCovers as y };
658
+ export { MissingIdentity as A, lockCovers as C, decodeAnnotKey as D, protectedCapabilities as E, encodeAnnotKey as O, levelFromPermission as S, minLevel as T, deriveProtection as _, checkFieldAction as a, isProtectableCapability as b, expandRawScope as c, filterMatches as d, parseScope as f, SIGNATURE_POLICY_VERSION as g, PROTECTABLE_CAPABILITIES as h, checkCollab as i, PermissionDenied as j, InvalidScope as k, expandedCapabilities as l, APPROVAL_BASELINE as m, checkAnyFieldAction as n, checkSetGroup as o, validateScopeArray as p, checkCapability as r, collabTargetOf as s, checkAnyCapability as t, fieldRightsOf as u, describeProtection as v, lockNameCovers as w, levelAllows as x, fieldLockFor as y };
509
659
 
510
- //# sourceMappingURL=resolver-inAVzsWt.js.map
660
+ //# sourceMappingURL=resolver-CzxmD9Ck.js.map