@embedpdf/engine-core 3.0.0-next.13 → 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 (135) 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-_JRjqvoI.d.ts → DocumentSecurityService-CU1M6Q7m.d.ts} +100 -112
  12. package/dist/{DocumentSecurityService-ClTZc0zO.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/authority-DLMNR7ff.cjs +2676 -0
  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 +16753 -5247
  40. package/dist/conformance.cjs.map +1 -1
  41. package/dist/conformance.d.cts +928 -165
  42. package/dist/conformance.d.ts +928 -165
  43. package/dist/conformance.js +16731 -5268
  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 +330 -103
  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 +733 -284
  72. package/dist/runtime.cjs.map +1 -1
  73. package/dist/runtime.d.cts +5 -1348
  74. package/dist/runtime.d.ts +5 -1348
  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 +1056 -379
  104. package/dist/wire.cjs.map +1 -1
  105. package/dist/wire.d.cts +15069 -725
  106. package/dist/wire.d.ts +15069 -725
  107. package/dist/wire.js +825 -239
  108. package/dist/wire.js.map +1 -1
  109. package/package.json +10 -1
  110. package/dist/AbortError-Cq2igpZ6.cjs +0 -38
  111. package/dist/AbortError-Cq2igpZ6.cjs.map +0 -1
  112. package/dist/AbortError-DHqPBnYc.js +0 -27
  113. package/dist/AbortError-DHqPBnYc.js.map +0 -1
  114. package/dist/AnnotationListSnapshot-D3mFut1e.d.cts +0 -6871
  115. package/dist/AnnotationListSnapshot-D3mFut1e.d.ts +0 -6871
  116. package/dist/Engine-Bx0NOfmd.d.cts +0 -1264
  117. package/dist/Engine-DxtpCYcP.d.ts +0 -1264
  118. package/dist/PageInsertBlankInput-CqXBmFhN.cjs +0 -449
  119. package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +0 -1
  120. package/dist/PageInsertBlankInput-cHq9-i-z.js +0 -378
  121. package/dist/PageInsertBlankInput-cHq9-i-z.js.map +0 -1
  122. package/dist/builders-Bqm8yTpi.cjs +0 -3564
  123. package/dist/builders-Bqm8yTpi.cjs.map +0 -1
  124. package/dist/builders-Dqo2X-dt.js +0 -3121
  125. package/dist/builders-Dqo2X-dt.js.map +0 -1
  126. package/dist/fold-2V29cxCV.cjs +0 -447
  127. package/dist/fold-2V29cxCV.cjs.map +0 -1
  128. package/dist/fold-CdEJTBTn.js +0 -334
  129. package/dist/fold-CdEJTBTn.js.map +0 -1
  130. package/dist/resolver-CYVNV4hn.cjs.map +0 -1
  131. package/dist/resolver-inAVzsWt.js.map +0 -1
  132. package/dist/schemas-BLARNCgx.js +0 -3113
  133. package/dist/schemas-BLARNCgx.js.map +0 -1
  134. package/dist/schemas-DIqFMmrC.cjs +0 -4468
  135. package/dist/schemas-DIqFMmrC.cjs.map +0 -1
@@ -1,8 +1,115 @@
1
+ const require_format = require("./format-Bp3QFSH1.cjs");
2
+ const require_builders = require("./builders-Cgdp_YCn.cjs");
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 require_format.EngineError {
13
+ constructor(scope, reason) {
14
+ super(require_format.EngineErrorCode.InvalidArg, `invalid scope "${scope}": ${reason}`, { details: {
15
+ scope,
16
+ reason
17
+ } });
18
+ this.scope = scope;
19
+ require_format._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 require_format.EngineError {
33
+ constructor(required, context, anyOf, refs) {
34
+ super(require_format.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
+ require_format._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 require_format.EngineError {
55
+ constructor(scope) {
56
+ super(require_format.EngineErrorCode.InvalidArg, `scope "${scope}" requires an identity (userId and/or groups)`, { details: { scope } });
57
+ this.scope = scope;
58
+ require_format._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,117 +519,140 @@ 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 require_builders.materializePdfPermissions(b)) out.add(capability);
506
656
  }
507
657
  //#endregion
508
658
  Object.defineProperty(exports, "APPROVAL_BASELINE", {
@@ -523,6 +673,12 @@ Object.defineProperty(exports, "MissingIdentity", {
523
673
  return MissingIdentity;
524
674
  }
525
675
  });
676
+ Object.defineProperty(exports, "PROTECTABLE_CAPABILITIES", {
677
+ enumerable: true,
678
+ get: function() {
679
+ return PROTECTABLE_CAPABILITIES;
680
+ }
681
+ });
526
682
  Object.defineProperty(exports, "PermissionDenied", {
527
683
  enumerable: true,
528
684
  get: function() {
@@ -541,6 +697,12 @@ Object.defineProperty(exports, "checkAnyCapability", {
541
697
  return checkAnyCapability;
542
698
  }
543
699
  });
700
+ Object.defineProperty(exports, "checkAnyFieldAction", {
701
+ enumerable: true,
702
+ get: function() {
703
+ return checkAnyFieldAction;
704
+ }
705
+ });
544
706
  Object.defineProperty(exports, "checkCapability", {
545
707
  enumerable: true,
546
708
  get: function() {
@@ -553,18 +715,48 @@ Object.defineProperty(exports, "checkCollab", {
553
715
  return checkCollab;
554
716
  }
555
717
  });
718
+ Object.defineProperty(exports, "checkFieldAction", {
719
+ enumerable: true,
720
+ get: function() {
721
+ return checkFieldAction;
722
+ }
723
+ });
556
724
  Object.defineProperty(exports, "checkSetGroup", {
557
725
  enumerable: true,
558
726
  get: function() {
559
727
  return checkSetGroup;
560
728
  }
561
729
  });
730
+ Object.defineProperty(exports, "collabTargetOf", {
731
+ enumerable: true,
732
+ get: function() {
733
+ return collabTargetOf;
734
+ }
735
+ });
736
+ Object.defineProperty(exports, "decodeAnnotKey", {
737
+ enumerable: true,
738
+ get: function() {
739
+ return decodeAnnotKey;
740
+ }
741
+ });
562
742
  Object.defineProperty(exports, "deriveProtection", {
563
743
  enumerable: true,
564
744
  get: function() {
565
745
  return deriveProtection;
566
746
  }
567
747
  });
748
+ Object.defineProperty(exports, "describeProtection", {
749
+ enumerable: true,
750
+ get: function() {
751
+ return describeProtection;
752
+ }
753
+ });
754
+ Object.defineProperty(exports, "encodeAnnotKey", {
755
+ enumerable: true,
756
+ get: function() {
757
+ return encodeAnnotKey;
758
+ }
759
+ });
568
760
  Object.defineProperty(exports, "expandRawScope", {
569
761
  enumerable: true,
570
762
  get: function() {
@@ -583,12 +775,24 @@ Object.defineProperty(exports, "fieldLockFor", {
583
775
  return fieldLockFor;
584
776
  }
585
777
  });
778
+ Object.defineProperty(exports, "fieldRightsOf", {
779
+ enumerable: true,
780
+ get: function() {
781
+ return fieldRightsOf;
782
+ }
783
+ });
586
784
  Object.defineProperty(exports, "filterMatches", {
587
785
  enumerable: true,
588
786
  get: function() {
589
787
  return filterMatches;
590
788
  }
591
789
  });
790
+ Object.defineProperty(exports, "isProtectableCapability", {
791
+ enumerable: true,
792
+ get: function() {
793
+ return isProtectableCapability;
794
+ }
795
+ });
592
796
  Object.defineProperty(exports, "levelAllows", {
593
797
  enumerable: true,
594
798
  get: function() {
@@ -638,4 +842,4 @@ Object.defineProperty(exports, "validateScopeArray", {
638
842
  }
639
843
  });
640
844
 
641
- //# sourceMappingURL=resolver-CYVNV4hn.cjs.map
845
+ //# sourceMappingURL=resolver-D2OCtE8F.cjs.map