@powerhousedao/powerhouse-vetra-packages 6.2.2-dev.8 → 6.2.2-dev.80

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 (103) hide show
  1. package/dist/browser/assets/{entry-Cvoo4_c9.js → entry-5_Eh4a2Z.js} +3 -2
  2. package/dist/browser/assets/{projection-entry-DVec8YRh.js → projection-entry-DgFDnTMt.js} +4 -4
  3. package/dist/browser/{linting-Dn6pXRkK.js → button-CuO8Gh3v.js} +20 -6
  4. package/dist/browser/button-CuO8Gh3v.js.map +1 -0
  5. package/dist/browser/{connect-KEU-HR3o.js → connect-Df8YBCmF.js} +916 -200
  6. package/dist/browser/connect-Df8YBCmF.js.map +1 -0
  7. package/dist/browser/{dist-CXoKspdx.js → dist-B4FWbuEM.js} +10 -2
  8. package/dist/browser/{dist-CXoKspdx.js.map → dist-B4FWbuEM.js.map} +1 -1
  9. package/dist/{node/dist-BeZaEP6S.mjs → browser/dist-BAJ5nRDl.js} +21 -2
  10. package/dist/browser/{dist-D8H48e8N.js.map → dist-BAJ5nRDl.js.map} +1 -1
  11. package/dist/browser/{dist-D5ra2aRj.js → dist-BIpFdiLp.js} +2388 -1848
  12. package/dist/browser/dist-BIpFdiLp.js.map +1 -0
  13. package/dist/browser/{document-drive-IdFV1QXL.js → document-drive-DU75CBKf.js} +1186 -124
  14. package/dist/browser/document-drive-DU75CBKf.js.map +1 -0
  15. package/dist/browser/document-models/index.js +1 -1
  16. package/dist/browser/{documents-D4ek7Ubv.js → documents-J_l59mSV.js} +5 -604
  17. package/dist/browser/documents-J_l59mSV.js.map +1 -0
  18. package/dist/browser/{editor-DiZF3ao3.js → editor-BuYbUTIt.js} +671 -236
  19. package/dist/browser/editor-BuYbUTIt.js.map +1 -0
  20. package/dist/browser/editor-y1bqyeB_.js +25 -0
  21. package/dist/browser/editor-y1bqyeB_.js.map +1 -0
  22. package/dist/browser/editors/document-model-editor/module.js +1 -1
  23. package/dist/browser/editors/generic-drive-explorer/index.js +5 -5
  24. package/dist/browser/editors/generic-drive-explorer/module.js +1 -1
  25. package/dist/{node/folder-view-Cy-QxsDg.mjs → browser/folder-view-BzERbCoQ.js} +73 -46
  26. package/dist/browser/folder-view-BzERbCoQ.js.map +1 -0
  27. package/dist/browser/{graphql-editor-ZLRO_8Ce.js → graphql-editor-Dv1FDpax.js} +8 -7
  28. package/dist/browser/{graphql-editor-ZLRO_8Ce.js.map → graphql-editor-Dv1FDpax.js.map} +1 -1
  29. package/dist/browser/{hooks-CORHBNHb.js → hooks-KKcNp2X0.js} +2 -2
  30. package/dist/browser/{hooks-CORHBNHb.js.map → hooks-KKcNp2X0.js.map} +1 -1
  31. package/dist/browser/index.js +378 -9
  32. package/dist/browser/index.js.map +1 -1
  33. package/dist/browser/{json-editor-DBuZ6gN7.js → json-editor-ZRgRDikU.js} +4 -4
  34. package/dist/browser/{json-editor-DBuZ6gN7.js.map → json-editor-ZRgRDikU.js.map} +1 -1
  35. package/dist/browser/{lib-C-f3izXz.js → lib-ewnRj2dJ.js} +13 -4
  36. package/dist/browser/lib-ewnRj2dJ.js.map +1 -0
  37. package/dist/browser/printer-CEYr4JOg.js +604 -0
  38. package/dist/browser/printer-CEYr4JOg.js.map +1 -0
  39. package/dist/browser/{schema-context-DY-GEGnY.js → schema-context-ZyeclQwy.js} +2 -2
  40. package/dist/browser/{schema-context-DY-GEGnY.js.map → schema-context-ZyeclQwy.js.map} +1 -1
  41. package/dist/browser/{state-schemas-BjQKM498.js → state-schemas-BBkkpjRl.js} +8 -18
  42. package/dist/browser/state-schemas-BBkkpjRl.js.map +1 -0
  43. package/dist/node/{linting-Dx50GuLN.mjs → button-Da7CxMn7.mjs} +20 -7
  44. package/dist/node/button-Da7CxMn7.mjs.map +1 -0
  45. package/dist/node/{connect-KcYZcy_Z.mjs → connect-DM43aaJZ.mjs} +916 -200
  46. package/dist/node/connect-DM43aaJZ.mjs.map +1 -0
  47. package/dist/node/{dist-DJEK2WVi.mjs → dist-4gA6tFmW.mjs} +2391 -1846
  48. package/dist/node/dist-4gA6tFmW.mjs.map +1 -0
  49. package/dist/{browser/dist-D8H48e8N.js → node/dist-CzYOpeXN.mjs} +21 -2
  50. package/dist/node/{dist-BeZaEP6S.mjs.map → dist-CzYOpeXN.mjs.map} +1 -1
  51. package/dist/node/{dist-fA4EItr0.mjs → dist-DT7jI9ol.mjs} +10 -2
  52. package/dist/node/{dist-fA4EItr0.mjs.map → dist-DT7jI9ol.mjs.map} +1 -1
  53. package/dist/node/{document-drive-C8hbqQ3C.mjs → document-drive-CRYbvD0-.mjs} +1186 -124
  54. package/dist/node/document-drive-CRYbvD0-.mjs.map +1 -0
  55. package/dist/node/document-models/index.mjs +1 -1
  56. package/dist/node/{documents-BFBPmyw1.mjs → documents-DCf53bzO.mjs} +3 -3
  57. package/dist/node/{documents-BFBPmyw1.mjs.map → documents-DCf53bzO.mjs.map} +1 -1
  58. package/dist/node/{editor-B1KhoF1f.mjs → editor-B5cnYfC7.mjs} +672 -237
  59. package/dist/node/editor-B5cnYfC7.mjs.map +1 -0
  60. package/dist/node/editor-Dn0DUR0W.mjs +25 -0
  61. package/dist/node/editor-Dn0DUR0W.mjs.map +1 -0
  62. package/dist/node/editors/document-model-editor/module.mjs +1 -1
  63. package/dist/node/editors/generic-drive-explorer/index.mjs +5 -5
  64. package/dist/node/editors/generic-drive-explorer/module.mjs +1 -1
  65. package/dist/{browser/folder-view-DMFqcJhc.js → node/folder-view-BAAfODx_.mjs} +73 -46
  66. package/dist/node/folder-view-BAAfODx_.mjs.map +1 -0
  67. package/dist/node/{graphql-DskHmkMe.mjs → graphql-BfAoPeWM.mjs} +2 -2
  68. package/dist/node/{graphql-DskHmkMe.mjs.map → graphql-BfAoPeWM.mjs.map} +1 -1
  69. package/dist/node/{graphql-editor-C5VLRqEf.mjs → graphql-editor-CUrJvXrG.mjs} +7 -7
  70. package/dist/node/{graphql-editor-C5VLRqEf.mjs.map → graphql-editor-CUrJvXrG.mjs.map} +1 -1
  71. package/dist/node/{hooks-DJrjfVaS.mjs → hooks-D0DcS3MV.mjs} +2 -2
  72. package/dist/node/{hooks-DJrjfVaS.mjs.map → hooks-D0DcS3MV.mjs.map} +1 -1
  73. package/dist/node/index.mjs +378 -9
  74. package/dist/node/index.mjs.map +1 -1
  75. package/dist/node/{json-editor-iI2s9a_4.mjs → json-editor-TvKShKo-.mjs} +4 -4
  76. package/dist/node/{json-editor-iI2s9a_4.mjs.map → json-editor-TvKShKo-.mjs.map} +1 -1
  77. package/dist/node/{main-D82dkD7g.mjs → main-B1tRsYOp.mjs} +15 -6
  78. package/dist/node/main-B1tRsYOp.mjs.map +1 -0
  79. package/dist/node/{schema-context-CX5ml_9t.mjs → schema-context-DL8jhi_d.mjs} +3 -3
  80. package/dist/node/{schema-context-CX5ml_9t.mjs.map → schema-context-DL8jhi_d.mjs.map} +1 -1
  81. package/dist/node/{state-schemas-Bl7nA8bB.mjs → state-schemas-CJK5hin3.mjs} +10 -20
  82. package/dist/{browser/state-schemas-BjQKM498.js.map → node/state-schemas-CJK5hin3.mjs.map} +1 -1
  83. package/package.json +8 -7
  84. package/dist/browser/connect-KEU-HR3o.js.map +0 -1
  85. package/dist/browser/dist-D5ra2aRj.js.map +0 -1
  86. package/dist/browser/document-drive-IdFV1QXL.js.map +0 -1
  87. package/dist/browser/documents-D4ek7Ubv.js.map +0 -1
  88. package/dist/browser/editor-DiZF3ao3.js.map +0 -1
  89. package/dist/browser/editor-SqNULDP-.js +0 -29
  90. package/dist/browser/editor-SqNULDP-.js.map +0 -1
  91. package/dist/browser/folder-view-DMFqcJhc.js.map +0 -1
  92. package/dist/browser/lib-C-f3izXz.js.map +0 -1
  93. package/dist/browser/linting-Dn6pXRkK.js.map +0 -1
  94. package/dist/node/connect-KcYZcy_Z.mjs.map +0 -1
  95. package/dist/node/dist-DJEK2WVi.mjs.map +0 -1
  96. package/dist/node/document-drive-C8hbqQ3C.mjs.map +0 -1
  97. package/dist/node/editor-B1KhoF1f.mjs.map +0 -1
  98. package/dist/node/editor-yBGEvMGD.mjs +0 -29
  99. package/dist/node/editor-yBGEvMGD.mjs.map +0 -1
  100. package/dist/node/folder-view-Cy-QxsDg.mjs.map +0 -1
  101. package/dist/node/linting-Dx50GuLN.mjs.map +0 -1
  102. package/dist/node/main-D82dkD7g.mjs.map +0 -1
  103. package/dist/node/state-schemas-Bl7nA8bB.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { E as __commonJSMin, _ as string, c as any, f as lazy, h as object, j as __toESM, l as array, m as number, n as constantCase, o as _enum, p as literal, r as pascalCase, u as boolean, v as union, x as ZodError, y as unknown } from "./dist-fA4EItr0.mjs";
1
+ import { D as __commonJSMin, M as __toESM, S as ZodError, _ as strictObject, b as unknown, c as any, f as lazy, g as record, h as object, l as array, m as number, n as constantCase, o as _enum, p as literal, r as pascalCase, u as boolean, v as string, y as union } from "./dist-DT7jI9ol.mjs";
2
2
  import { createRequire } from "module";
3
3
  //#endregion
4
4
  //#region ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
@@ -590,7 +590,7 @@ function isBE() {
590
590
  return new Uint8Array(new Uint16Array([65279]).buffer)[0] === 254;
591
591
  }
592
592
  //#endregion
593
- //#region ../shared/dist/utils-DKOFCM0Q.js
593
+ //#region ../shared/dist/utils-aaTmfxFW.js
594
594
  const hashBrowser = (data, algorithm = "sha1", encoding = "base64", _params) => {
595
595
  if (!["sha1"].includes(algorithm)) throw new Error(`Hashing algorithm not supported: "${algorithm}". Available: sha1`);
596
596
  if (!["base64", "hex"].includes(encoding)) throw new Error(`Hash encoding not supported: "${encoding}". Available: base64, hex`);
@@ -637,6 +637,40 @@ function ab2hex(ab) {
637
637
  const view = ArrayBuffer.isView(ab) ? ab : new Uint8Array(ab);
638
638
  return Array.prototype.map.call(view, (x) => ("00" + x.toString(16)).slice(-2)).join("");
639
639
  }
640
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
641
+ /**
642
+ * Decodes a base58btc (Bitcoin/IPFS alphabet) string to bytes. Returns null on
643
+ * any character outside the alphabet.
644
+ */
645
+ function base58Decode(input) {
646
+ const bytes = [];
647
+ for (const ch of input) {
648
+ let carry = BASE58_ALPHABET.indexOf(ch);
649
+ if (carry === -1) return null;
650
+ for (let i = 0; i < bytes.length; i++) {
651
+ carry += bytes[i] * 58;
652
+ bytes[i] = carry & 255;
653
+ carry >>= 8;
654
+ }
655
+ while (carry > 0) {
656
+ bytes.push(carry & 255);
657
+ carry >>= 8;
658
+ }
659
+ }
660
+ for (let k = 0; k < input.length && input[k] === "1"; k++) bytes.push(0);
661
+ return Uint8Array.from(bytes.reverse());
662
+ }
663
+ /** Decodes a base64url string to bytes (portable across node, browsers, workers). */
664
+ function base64UrlToBytes(input) {
665
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
666
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
667
+ let binary;
668
+ if (typeof atob === "function") binary = atob(padded);
669
+ else binary = Buffer.from(padded, "base64").toString("binary");
670
+ const bytes = new Uint8Array(binary.length);
671
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
672
+ return bytes;
673
+ }
640
674
  function generateId(method) {
641
675
  if (method && method.toString() !== "UUIDv4") throw new Error(`Id generation method not supported: "${method.toString()}"`);
642
676
  return globalThis.crypto.randomUUID();
@@ -2963,6 +2997,103 @@ function castDraft(value) {
2963
2997
  }
2964
2998
  //#endregion
2965
2999
  //#region ../shared/dist/document-model/index.js
3000
+ /**
3001
+ * What separates a signature's params when it travels as one string.
3002
+ *
3003
+ * GraphQL declares `signatures` as a list of strings, not a list of lists, so a
3004
+ * tuple is joined for transport and split on arrival. The separator is here so
3005
+ * the two halves cannot disagree about it - they live in different packages, and
3006
+ * a mismatch would corrupt every signature that crossed the wire rather than
3007
+ * failing outright.
3008
+ */
3009
+ const SIGNATURE_PARAM_SEPARATOR = ", ";
3010
+ /** The number of params a signature carries. */
3011
+ const SIGNATURE_PARAM_COUNT = 5;
3012
+ /** Joins a signature's params for transport. Already-joined input passes through. */
3013
+ function serializeSignature(signature) {
3014
+ return Array.isArray(signature) ? signature.join(SIGNATURE_PARAM_SEPARATOR) : signature;
3015
+ }
3016
+ /**
3017
+ * Splits a transported signature back into its params. A tuple passes through.
3018
+ *
3019
+ * Short input is padded rather than refused: verification reads the params by
3020
+ * position and fails on a wrong one, which says more than a length complaint
3021
+ * raised here would.
3022
+ */
3023
+ function deserializeSignature(signature) {
3024
+ if (Array.isArray(signature)) return signature;
3025
+ const parts = signature.split(SIGNATURE_PARAM_SEPARATOR);
3026
+ return Array.from({ length: SIGNATURE_PARAM_COUNT }, (_unused, index) => parts[index] ?? "");
3027
+ }
3028
+ /**
3029
+ * Projects an action onto the fields the GraphQL `ActionInput` declares.
3030
+ *
3031
+ * A projection rather than a spread, because an input object rejects a field it
3032
+ * does not declare, and that refusal takes the whole request with it. An action
3033
+ * read back out of storage can carry fields the type no longer has - a legacy
3034
+ * `attachments` array, or the operation fields left behind by a signing helper
3035
+ * that returns an operation - and any one of them would sink an otherwise valid
3036
+ * submission.
3037
+ *
3038
+ * Only fields the action actually carries are emitted, so an unsigned action
3039
+ * sends no context at all rather than a context full of nulls.
3040
+ */
3041
+ function toTransportAction(action) {
3042
+ if (action.input === void 0 || action.input === null) throw new Error(`Action ${action.id} (${action.type}) has no input, which the wire requires`);
3043
+ const projected = {
3044
+ id: action.id,
3045
+ type: action.type,
3046
+ timestampUtcMs: action.timestampUtcMs,
3047
+ input: action.input,
3048
+ scope: action.scope
3049
+ };
3050
+ const context = toTransportContext(action.context);
3051
+ return context ? {
3052
+ ...projected,
3053
+ context
3054
+ } : projected;
3055
+ }
3056
+ function toTransportContext(context) {
3057
+ if (!context) return;
3058
+ const projected = {};
3059
+ if (context.prevOpIndex !== void 0) projected.prevOpIndex = context.prevOpIndex;
3060
+ if (context.prevOpHash !== void 0) projected.prevOpHash = context.prevOpHash;
3061
+ if (context.nonce !== void 0) projected.nonce = context.nonce;
3062
+ const signer = context.signer;
3063
+ if (signer) projected.signer = {
3064
+ ...signer.user ? { user: toTransportSignerUser(signer.user) } : {},
3065
+ ...signer.app ? { app: toTransportSignerApp(signer.app) } : {},
3066
+ signatures: (signer.signatures ?? []).map(serializeSignature)
3067
+ };
3068
+ return Object.keys(projected).length > 0 ? projected : void 0;
3069
+ }
3070
+ /**
3071
+ * The identity, projected for the same reason the context around it is.
3072
+ *
3073
+ * A signer is handed in by the app, so what reaches here is only as narrow as
3074
+ * whoever built it: an identity carrying a session's DID, credential or profile
3075
+ * alongside the address is refused by `ReactorSignerUserInput`, and that
3076
+ * refusal takes the whole submission with it.
3077
+ *
3078
+ * The compiler cannot stand in for this. `UserActionSigner` already declares
3079
+ * exactly these three fields, but excess-property checks apply to fresh object
3080
+ * literals and never to a variable of a wider type, so a wide identity assigned
3081
+ * to the narrow type passes untouched.
3082
+ */
3083
+ function toTransportSignerUser(user) {
3084
+ return {
3085
+ address: user.address,
3086
+ networkId: user.networkId,
3087
+ chainId: user.chainId
3088
+ };
3089
+ }
3090
+ /** The signing app, projected on the same rule as the identity above. */
3091
+ function toTransportSignerApp(app) {
3092
+ return {
3093
+ name: app.name,
3094
+ key: app.key
3095
+ };
3096
+ }
2966
3097
  var InvalidActionInputError = class extends Error {
2967
3098
  data;
2968
3099
  constructor(data) {
@@ -2995,6 +3126,75 @@ var DowngradeNotSupportedError = class extends Error {
2995
3126
  this.toVersion = toVersion;
2996
3127
  }
2997
3128
  };
3129
+ /**
3130
+ * Thrown when INITIALIZE_AUTH is applied to an already-initialized auth scope.
3131
+ * The genesis action is valid only at auth revision zero.
3132
+ */
3133
+ var AuthAlreadyInitializedError = class extends Error {
3134
+ documentId;
3135
+ constructor(documentId) {
3136
+ super(`Auth scope already initialized for document ${documentId}: INITIALIZE_AUTH is valid only at auth revision zero`);
3137
+ this.name = "AuthAlreadyInitializedError";
3138
+ this.documentId = documentId;
3139
+ }
3140
+ };
3141
+ /**
3142
+ * Thrown when INITIALIZE_AUTH is not signed by the document creator (its signer
3143
+ * does not match `header.sig.publicKey`), so it cannot set the auth policy.
3144
+ */
3145
+ var AuthInitializerNotCreatorError = class extends Error {
3146
+ documentId;
3147
+ constructor(documentId) {
3148
+ super(`INITIALIZE_AUTH for document ${documentId} must be signed by the document creator`);
3149
+ this.name = "AuthInitializerNotCreatorError";
3150
+ this.documentId = documentId;
3151
+ }
3152
+ };
3153
+ /**
3154
+ * Thrown when INITIALIZE_AUTH carries a version below 1. Version 0 is reserved
3155
+ * for the uninitialized auth scope.
3156
+ */
3157
+ var InvalidAuthVersionError = class extends Error {
3158
+ documentId;
3159
+ version;
3160
+ constructor(documentId, version) {
3161
+ super(`Invalid auth policy version ${version} for document ${documentId}: INITIALIZE_AUTH requires an integer version >= 1`);
3162
+ this.name = "InvalidAuthVersionError";
3163
+ this.documentId = documentId;
3164
+ this.version = version;
3165
+ }
3166
+ };
3167
+ /** Thrown when a duplicate would lose the source policy's version or creator binding. */
3168
+ var AuthPolicyNotPreservedError = class extends Error {
3169
+ documentId;
3170
+ constructor(documentId) {
3171
+ super(`Duplicating document ${documentId} would not preserve its auth policy: the copy loses the policy version or its creator binding`);
3172
+ this.name = "AuthPolicyNotPreservedError";
3173
+ this.documentId = documentId;
3174
+ }
3175
+ };
3176
+ /**
3177
+ * Thrown when a grant referenced by REMOVE_GRANT or MOVE_GRANT does not exist.
3178
+ */
3179
+ var GrantNotFoundError = class extends Error {
3180
+ grantId;
3181
+ constructor(grantId) {
3182
+ super(`Grant not found in auth scope: ${grantId}`);
3183
+ this.name = "GrantNotFoundError";
3184
+ this.grantId = grantId;
3185
+ }
3186
+ };
3187
+ /**
3188
+ * Thrown when a disallowed action (UNDO, REDO, PRUNE) targets the auth scope.
3189
+ */
3190
+ var AuthActionNotAllowedError = class extends Error {
3191
+ actionType;
3192
+ constructor(actionType) {
3193
+ super(`${actionType} is not permitted on the auth scope`);
3194
+ this.name = "AuthActionNotAllowedError";
3195
+ this.actionType = actionType;
3196
+ }
3197
+ };
2998
3198
  var HashMismatchError = class extends Error {
2999
3199
  _scope;
3000
3200
  _document;
@@ -3021,6 +3221,25 @@ var HashMismatchError = class extends Error {
3021
3221
  return this._operation;
3022
3222
  }
3023
3223
  };
3224
+ /**
3225
+ * Thrown when replay or import requires a document model version that is not
3226
+ * registered. Carries the data the UI needs to explain the mismatch.
3227
+ */
3228
+ var UnsupportedDocumentModelVersionError = class extends Error {
3229
+ documentType;
3230
+ requiredVersion;
3231
+ availableVersions;
3232
+ constructor(documentType, requiredVersion, availableVersions) {
3233
+ super(`No reducer registered for document version ${requiredVersion}. Available versions: ${availableVersions.join(", ")}`);
3234
+ this.name = "UnsupportedDocumentModelVersionError";
3235
+ this.documentType = documentType;
3236
+ this.requiredVersion = requiredVersion;
3237
+ this.availableVersions = availableVersions;
3238
+ }
3239
+ static isError(error) {
3240
+ return Error.isError(error) && error.name === "UnsupportedDocumentModelVersionError";
3241
+ }
3242
+ };
3024
3243
  const isDefinedNonNullAny$1 = (v) => v !== void 0 && v !== null;
3025
3244
  any().refine((v) => isDefinedNonNullAny$1(v));
3026
3245
  const Load_StateSchema = _enum(["LOAD_STATE"]);
@@ -3461,6 +3680,88 @@ const ConfigEntrySchema = object({
3461
3680
  required: boolean().optional(),
3462
3681
  default: boolean().optional()
3463
3682
  });
3683
+ const PwaUrlPatternSchema = union([string(), strictObject({
3684
+ source: string(),
3685
+ flags: string().optional()
3686
+ }).refine((p) => {
3687
+ try {
3688
+ new RegExp(p.source, p.flags);
3689
+ return true;
3690
+ } catch {
3691
+ return false;
3692
+ }
3693
+ }, { message: "source/flags do not compile to a valid RegExp" })]);
3694
+ const PwaRuntimeCachingSchema = strictObject({
3695
+ urlPattern: PwaUrlPatternSchema,
3696
+ handler: _enum([
3697
+ "CacheFirst",
3698
+ "CacheOnly",
3699
+ "NetworkFirst",
3700
+ "NetworkOnly",
3701
+ "StaleWhileRevalidate"
3702
+ ]),
3703
+ method: _enum([
3704
+ "GET",
3705
+ "POST",
3706
+ "PUT",
3707
+ "DELETE",
3708
+ "HEAD",
3709
+ "PATCH"
3710
+ ]).optional(),
3711
+ options: strictObject({
3712
+ cacheName: string().optional(),
3713
+ networkTimeoutSeconds: number().optional(),
3714
+ expiration: strictObject({
3715
+ maxEntries: number().optional(),
3716
+ maxAgeSeconds: number().optional()
3717
+ }).optional(),
3718
+ cacheableResponse: strictObject({
3719
+ statuses: array(number()).optional(),
3720
+ headers: record(string(), string()).optional()
3721
+ }).optional()
3722
+ }).optional()
3723
+ });
3724
+ const PwaIconSchema = strictObject({
3725
+ src: string(),
3726
+ sizes: string().optional(),
3727
+ type: string().optional(),
3728
+ purpose: string().optional()
3729
+ });
3730
+ const PwaFileHandlerSchema = strictObject({
3731
+ accept: record(string(), array(string().regex(/^\./, "file extensions must start with '.'"))),
3732
+ icons: array(PwaIconSchema).optional(),
3733
+ launch_type: _enum(["single-client", "multiple-clients"]).optional()
3734
+ });
3735
+ const PwaConfigSchema = strictObject({
3736
+ manifest: strictObject({
3737
+ name: string().optional(),
3738
+ short_name: string().optional(),
3739
+ description: string().optional(),
3740
+ theme_color: string().optional(),
3741
+ background_color: string().optional(),
3742
+ display: _enum([
3743
+ "fullscreen",
3744
+ "standalone",
3745
+ "minimal-ui",
3746
+ "browser"
3747
+ ]).optional(),
3748
+ start_url: string().optional(),
3749
+ scope: string().optional(),
3750
+ icons: array(PwaIconSchema).optional(),
3751
+ file_handlers: array(PwaFileHandlerSchema).optional(),
3752
+ launch_handler: strictObject({ client_mode: _enum([
3753
+ "auto",
3754
+ "focus-existing",
3755
+ "navigate-existing",
3756
+ "navigate-new"
3757
+ ]) }).optional()
3758
+ }).optional(),
3759
+ globPatterns: array(string()).optional(),
3760
+ globIgnores: array(string()).optional(),
3761
+ maximumFileSizeToCacheInBytes: number().optional(),
3762
+ runtimeCaching: array(PwaRuntimeCachingSchema).optional(),
3763
+ navigateFallbackDenylist: array(PwaUrlPatternSchema).optional()
3764
+ });
3464
3765
  object({
3465
3766
  name: string(),
3466
3767
  description: string().optional(),
@@ -3472,7 +3773,8 @@ object({
3472
3773
  editors: PowerhouseModulesSchema,
3473
3774
  processors: PowerhouseModulesSchema,
3474
3775
  subgraphs: PowerhouseModulesSchema,
3475
- config: array(ConfigEntrySchema).optional()
3776
+ config: array(ConfigEntrySchema).optional(),
3777
+ pwa: PwaConfigSchema.optional()
3476
3778
  });
3477
3779
  /**
3478
3780
  * Cancels the last `count` operations.
@@ -3743,6 +4045,505 @@ const actions = {
3743
4045
  ...baseActions,
3744
4046
  ...documentModelActions
3745
4047
  };
4048
+ const documentModelDocumentType = "powerhouse/document-model";
4049
+ /**
4050
+ * Thrown when a grant violates the v1 validation rules. The message is stored
4051
+ * on error operations, so it must be a pure function of the input.
4052
+ */
4053
+ var InvalidGrantError = class extends Error {
4054
+ grantId;
4055
+ constructor(grantId, problem) {
4056
+ super(`Invalid grant "${grantId}": ${problem}`);
4057
+ this.name = "InvalidGrantError";
4058
+ this.grantId = grantId;
4059
+ }
4060
+ };
4061
+ /** Thrown for a `{ group }` principal on a group document: references never chain. */
4062
+ var GroupPrincipalNotAllowedError = class extends Error {
4063
+ grantId;
4064
+ constructor(grantId) {
4065
+ super(`Grant "${grantId}" uses a group principal on a group document: a group's auth scope cannot reference other groups`);
4066
+ this.name = "GroupPrincipalNotAllowedError";
4067
+ this.grantId = grantId;
4068
+ }
4069
+ };
4070
+ /**
4071
+ * Thrown when a change would leave a creator-less policy with no grant
4072
+ * permitting execute on the auth scope. Without the creator carve-out no
4073
+ * subject could ever administer such a policy again.
4074
+ */
4075
+ var AuthAdministrationLockoutError = class extends Error {
4076
+ grantId;
4077
+ constructor(grantId) {
4078
+ super(`Change to grant "${grantId}" would leave no reachable grant permitting execute on the auth scope: a policy with no creator must always retain one`);
4079
+ this.name = "AuthAdministrationLockoutError";
4080
+ this.grantId = grantId;
4081
+ }
4082
+ };
4083
+ /**
4084
+ * Thrown when INITIALIZE_AUTH would create a creator-less policy with no grant
4085
+ * permitting execute on the auth scope. Such a policy would be born with no
4086
+ * subject able to administer it.
4087
+ */
4088
+ var AuthAdministrationMissingError = class extends Error {
4089
+ constructor() {
4090
+ super("Initial grants include no reachable grant permitting execute on the auth scope: a policy with no creator must always include one");
4091
+ this.name = "AuthAdministrationMissingError";
4092
+ }
4093
+ };
4094
+ const GRANT_KEYS = new Set([
4095
+ "id",
4096
+ "description",
4097
+ "effect",
4098
+ "principal",
4099
+ "capability",
4100
+ "where"
4101
+ ]);
4102
+ const PRINCIPAL_KINDS = new Set([
4103
+ "anyone",
4104
+ "address",
4105
+ "group",
4106
+ "match"
4107
+ ]);
4108
+ const COMPARISON_CONDITION_KINDS = new Set([
4109
+ "eq",
4110
+ "ne",
4111
+ "lt",
4112
+ "lte",
4113
+ "gt",
4114
+ "gte"
4115
+ ]);
4116
+ function isPlainValue(value) {
4117
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4118
+ }
4119
+ function operandProblem(value, capabilityScope, budget) {
4120
+ budget.nodes -= 1;
4121
+ if (budget.nodes < 0) return `condition exceeds 100 nodes`;
4122
+ if (!isPlainValue(value)) return "operand must be an object";
4123
+ const keys = Object.keys(value);
4124
+ if (keys.length !== 1) return "operand must have exactly one of attr or lit";
4125
+ if (keys[0] === "attr") {
4126
+ const attr = value.attr;
4127
+ if (typeof attr !== "string" || attr.length === 0) return "attr must be a non-empty string";
4128
+ if (capabilityScope !== void 0 && capabilityScope !== "*" && attr.startsWith("doc.")) {
4129
+ const pathScope = attr.split(".")[1] ?? "";
4130
+ if (pathScope !== capabilityScope) return `condition path "${attr}" reads scope "${pathScope}" but the capability covers only scope "${capabilityScope}"`;
4131
+ }
4132
+ return null;
4133
+ }
4134
+ if (keys[0] === "lit") {
4135
+ const lit = value.lit;
4136
+ if (lit !== null && typeof lit !== "string" && typeof lit !== "number" && typeof lit !== "boolean") return "lit must be a string, number, boolean, or null";
4137
+ if (typeof lit === "number" && !Number.isFinite(lit)) return "lit must be a finite number";
4138
+ if (typeof lit === "number" && Object.is(lit, -0)) return "lit must not be negative zero";
4139
+ return null;
4140
+ }
4141
+ return `unknown operand kind "${keys[0]}"`;
4142
+ }
4143
+ function conditionProblem(value, capabilityScope, depth, budget) {
4144
+ if (depth > 10) return `condition exceeds depth 10`;
4145
+ budget.nodes -= 1;
4146
+ if (budget.nodes < 0) return `condition exceeds 100 nodes`;
4147
+ if (!isPlainValue(value)) return "condition must be an object";
4148
+ const keys = Object.keys(value);
4149
+ if (keys.length !== 1) return "condition must have exactly one operator";
4150
+ const kind = keys[0];
4151
+ const body = value[kind];
4152
+ if (COMPARISON_CONDITION_KINDS.has(kind)) {
4153
+ if (!Array.isArray(body) || body.length !== 2) return `${kind} requires a pair of operands`;
4154
+ for (const operand of body) {
4155
+ const problem = operandProblem(operand, capabilityScope, budget);
4156
+ if (problem !== null) return problem;
4157
+ }
4158
+ return null;
4159
+ }
4160
+ if (kind === "in" || kind === "notIn") {
4161
+ if (!Array.isArray(body) || body.length !== 2 || !Array.isArray(body[1])) return `${kind} requires an operand and an operand list`;
4162
+ const first = operandProblem(body[0], capabilityScope, budget);
4163
+ if (first !== null) return first;
4164
+ for (const operand of body[1]) {
4165
+ const problem = operandProblem(operand, capabilityScope, budget);
4166
+ if (problem !== null) return problem;
4167
+ }
4168
+ return null;
4169
+ }
4170
+ if (kind === "exists") return operandProblem(body, capabilityScope, budget);
4171
+ if (kind === "and" || kind === "or") {
4172
+ if (!Array.isArray(body)) return `${kind} requires a condition list`;
4173
+ for (const child of body) {
4174
+ const problem = conditionProblem(child, capabilityScope, depth + 1, budget);
4175
+ if (problem !== null) return problem;
4176
+ }
4177
+ return null;
4178
+ }
4179
+ if (kind === "not") return conditionProblem(body, capabilityScope, depth + 1, budget);
4180
+ return `unknown condition operator "${kind}"`;
4181
+ }
4182
+ function principalProblem(value, capabilityScope) {
4183
+ if (!isPlainValue(value)) return "principal must be an object";
4184
+ const keys = Object.keys(value);
4185
+ if (keys.length !== 1 || !PRINCIPAL_KINDS.has(keys[0])) return "principal must have exactly one of anyone, address, group, or match";
4186
+ const kind = keys[0];
4187
+ if (kind === "anyone" && value.anyone !== true) return "anyone must be true";
4188
+ if (kind === "address") {
4189
+ const address = value.address;
4190
+ if (typeof address !== "string" || address.length === 0) return "address must be a non-empty string";
4191
+ }
4192
+ if (kind === "group") {
4193
+ const group = value.group;
4194
+ if (typeof group !== "string" || group.length === 0) return "group must be a non-empty string";
4195
+ }
4196
+ if (kind === "match") return conditionProblem(value.match, capabilityScope, 1, { nodes: 100 });
4197
+ return null;
4198
+ }
4199
+ function capabilityProblem(value) {
4200
+ if (!isPlainValue(value)) return "capability must be an object";
4201
+ const can = value.can;
4202
+ if (can !== "read" && can !== "execute") return "capability.can must be read or execute";
4203
+ const allowedKeys = can === "execute" ? [
4204
+ "can",
4205
+ "scope",
4206
+ "operation"
4207
+ ] : ["can", "scope"];
4208
+ const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key)).sort();
4209
+ if (unknownKeys.length > 0) return `unknown capability key "${unknownKeys[0]}"`;
4210
+ if (value.scope !== void 0) {
4211
+ if (typeof value.scope !== "string" || value.scope.length === 0) return "capability.scope must be a non-empty string";
4212
+ }
4213
+ if (can === "execute" && value.operation !== void 0) {
4214
+ const operation = value.operation;
4215
+ if (!Array.isArray(operation)) return "capability.operation must be an array";
4216
+ if (operation.length > 100) return `capability.operation exceeds 100 entries`;
4217
+ for (const entry of operation) if (typeof entry !== "string" || entry.length === 0) return "capability.operation entries must be non-empty strings";
4218
+ }
4219
+ return null;
4220
+ }
4221
+ /** Returns the first v1-rule violation, or null. Pure, total, deterministic. */
4222
+ function grantProblem(value) {
4223
+ if (!isPlainValue(value)) return "grant must be an object";
4224
+ const unknownKeys = Object.keys(value).filter((key) => !GRANT_KEYS.has(key)).sort();
4225
+ if (unknownKeys.length > 0) return `unknown grant key "${unknownKeys[0]}"`;
4226
+ if (typeof value.id !== "string" || value.id.length === 0) return "id must be a non-empty string";
4227
+ if (typeof value.description !== "string") return "description must be a string";
4228
+ if (value.effect !== "allow" && value.effect !== "deny") return "effect must be allow or deny";
4229
+ const capabilityValue = value.capability;
4230
+ const capability = capabilityProblem(capabilityValue);
4231
+ if (capability !== null) return capability;
4232
+ const capabilityScope = capabilityValue.scope;
4233
+ const principal = principalProblem(value.principal, capabilityScope);
4234
+ if (principal !== null) return principal;
4235
+ if (value.where !== void 0) return conditionProblem(value.where, capabilityScope, 1, { nodes: 100 });
4236
+ return null;
4237
+ }
4238
+ /** V1 shape rules plus the group-document group-principal ban. */
4239
+ function assertValidGrant(grant, documentType) {
4240
+ const grantId = isPlainValue(grant) && typeof grant.id === "string" ? grant.id : "";
4241
+ const problem = grantProblem(grant);
4242
+ if (problem !== null) throw new InvalidGrantError(grantId, problem);
4243
+ if (documentType === "powerhouse/reactor-group" && "group" in grant.principal) throw new GroupPrincipalNotAllowedError(grantId);
4244
+ }
4245
+ /**
4246
+ * Validates an initial grant list: the count cap, every grant, and — on a
4247
+ * creator-less policy — that some grant keeps the auth scope administrable.
4248
+ */
4249
+ function assertValidInitialGrants(grants, documentType, creator) {
4250
+ if (grants.length > 100) throw new InvalidGrantError("", `policy exceeds 100 grants`);
4251
+ for (const grant of grants) assertValidGrant(grant, documentType);
4252
+ if (creator === void 0 && !administrationReachable(grants)) throw new AuthAdministrationMissingError();
4253
+ }
4254
+ /**
4255
+ * Validates a grant upsert: the grant itself, the count cap on append, and
4256
+ * administration retention. Retention is checked on an append as well as an
4257
+ * in-place replace, because a grant appended after the administration grant can
4258
+ * shadow it (evaluation is last-applicable-grant-wins) and so take
4259
+ * administration away without removing anything.
4260
+ */
4261
+ function assertValidGrantUpsert(grant, existing, documentType, creator) {
4262
+ assertValidGrant(grant, documentType);
4263
+ const exists = existing.some((g) => g.id === grant.id);
4264
+ if (!exists && existing.length >= 100) throw new InvalidGrantError(grant.id, `policy exceeds 100 grants`);
4265
+ assertAuthAdministrationRetained(creator, existing, exists ? existing.map((g) => g.id === grant.id ? grant : g) : [...existing, grant], grant.id);
4266
+ }
4267
+ /**
4268
+ * The request whose coverage keeps a policy administrable: a subject who may
4269
+ * SET_GRANT can upsert any grant, so every other repair stays reachable.
4270
+ */
4271
+ const AUTH_ADMINISTRATION_REQUEST = {
4272
+ verb: "execute",
4273
+ scope: "auth",
4274
+ operation: "SET_GRANT"
4275
+ };
4276
+ /**
4277
+ * Whether some subject can still administer the auth scope under this grant list.
4278
+ *
4279
+ * Asks the evaluator rather than pattern-matching a single grant: evaluation is
4280
+ * last-applicable-grant-wins, so an allow that some later deny shadows keeps
4281
+ * nothing reachable even though it is still present in the list. Only anyone and
4282
+ * address principals are candidates, because those are the ones v1 can match at
4283
+ * all; a `where` condition or a group or match principal never applies.
4284
+ */
4285
+ function administrationReachable(grants) {
4286
+ return grants.some((grant) => {
4287
+ const subject = administrationCandidate(grant);
4288
+ if (subject === void 0) return false;
4289
+ return evaluateGrantStack(grants, subject, AUTH_ADMINISTRATION_REQUEST).decision === "allow";
4290
+ });
4291
+ }
4292
+ /**
4293
+ * The subject to test this grant with, or undefined when the grant could never
4294
+ * carry administration for anyone.
4295
+ */
4296
+ function administrationCandidate(grant) {
4297
+ if (grant.effect !== "allow" || grant.where !== void 0 || !capabilityCovers(grant.capability, AUTH_ADMINISTRATION_REQUEST)) return;
4298
+ if ("address" in grant.principal) return {
4299
+ address: grant.principal.address,
4300
+ key: void 0
4301
+ };
4302
+ if ("anyone" in grant.principal) return {
4303
+ address: void 0,
4304
+ key: void 0
4305
+ };
4306
+ }
4307
+ /**
4308
+ * A creator-less policy must always retain a grant permitting execute on the
4309
+ * auth scope; on a signed document the creator carve-out keeps administration
4310
+ * reachable instead. Rejects a change that takes the last such grant away. A
4311
+ * policy already without one is left alone: the change is not what locks it.
4312
+ */
4313
+ function assertAuthAdministrationRetained(creator, previous, next, grantId) {
4314
+ if (creator !== void 0) return;
4315
+ if (administrationReachable(previous) && !administrationReachable(next)) throw new AuthAdministrationLockoutError(grantId);
4316
+ }
4317
+ /**
4318
+ * An operand whose shape validation would have rejected. Distinguished from
4319
+ * an unresolved path so a structurally malformed operand poisons its whole
4320
+ * condition to false rather than reading as "absent", which `not` would
4321
+ * otherwise widen to true.
4322
+ */
4323
+ const INVALID_OPERAND = Symbol("invalid-operand");
4324
+ /**
4325
+ * Narrows to the values conditions compare. An object, array, or non-finite
4326
+ * number resolves to undefined, and every comparison involving undefined is
4327
+ * false.
4328
+ */
4329
+ function asConditionValue(value) {
4330
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
4331
+ if (typeof value === "number" && Number.isFinite(value)) return value;
4332
+ }
4333
+ /**
4334
+ * Resolves one operand. Attr roots: `subject.*`, `doc.<scope>.*` where the
4335
+ * scope must be the executing scope (validation already rejects any other,
4336
+ * but resolution stays total), and `action.input.*`. Path steps read own
4337
+ * properties only, so prototype members can never influence a verdict.
4338
+ */
4339
+ function resolveOperand(operand, subject, request, conditions) {
4340
+ if (!isPlainValue(operand)) return INVALID_OPERAND;
4341
+ const keys = Object.keys(operand);
4342
+ if (keys.length !== 1) return INVALID_OPERAND;
4343
+ if (keys[0] === "lit") {
4344
+ const value = asConditionValue(operand.lit);
4345
+ return value === void 0 ? INVALID_OPERAND : value;
4346
+ }
4347
+ if (keys[0] !== "attr") return INVALID_OPERAND;
4348
+ const attr = operand.attr;
4349
+ if (typeof attr !== "string" || attr.length === 0) return INVALID_OPERAND;
4350
+ const path = attr.split(".");
4351
+ let value;
4352
+ let rest;
4353
+ if (path[0] === "subject") {
4354
+ value = subject;
4355
+ rest = path.slice(1);
4356
+ } else if (path[0] === "doc") {
4357
+ if (path[1] !== request.scope) return;
4358
+ value = conditions.scopeState;
4359
+ rest = path.slice(2);
4360
+ } else if (path[0] === "action" && path[1] === "input") {
4361
+ value = conditions.actionInput;
4362
+ rest = path.slice(2);
4363
+ } else return;
4364
+ for (const segment of rest) {
4365
+ if (!isPlainValue(value) || !Object.hasOwn(value, segment)) return;
4366
+ value = value[segment];
4367
+ }
4368
+ return asConditionValue(value);
4369
+ }
4370
+ /**
4371
+ * Total order within one type: numbers numerically, strings by code point.
4372
+ * Everything else, including mixed types, does not order.
4373
+ */
4374
+ function compareValues(left, right) {
4375
+ if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
4376
+ if (typeof left === "string" && typeof right === "string") {
4377
+ const leftPoints = Array.from(left);
4378
+ const rightPoints = Array.from(right);
4379
+ const shared = Math.min(leftPoints.length, rightPoints.length);
4380
+ for (let i = 0; i < shared; i++) {
4381
+ const a = leftPoints[i].codePointAt(0) ?? 0;
4382
+ const b = rightPoints[i].codePointAt(0) ?? 0;
4383
+ if (a !== b) return a < b ? -1 : 1;
4384
+ }
4385
+ return leftPoints.length === rightPoints.length ? 0 : leftPoints.length < rightPoints.length ? -1 : 1;
4386
+ }
4387
+ }
4388
+ /**
4389
+ * Tri-state evaluation: undefined marks a structurally invalid node, which
4390
+ * poisons the whole tree to false at the top — and a structurally malformed
4391
+ * operand poisons its condition the same way. Both are distinct from an
4392
+ * operand whose path fails to resolve, which is a valid comparison that is
4393
+ * false. The distinction keeps `not` from widening over malformed input.
4394
+ */
4395
+ function evaluateNode(node, subject, request, conditions) {
4396
+ if (!isPlainValue(node)) return;
4397
+ const keys = Object.keys(node);
4398
+ if (keys.length !== 1) return;
4399
+ const kind = keys[0];
4400
+ const body = node[kind];
4401
+ switch (kind) {
4402
+ case "eq":
4403
+ case "ne":
4404
+ case "lt":
4405
+ case "lte":
4406
+ case "gt":
4407
+ case "gte": {
4408
+ if (!Array.isArray(body) || body.length !== 2) return;
4409
+ const left = resolveOperand(body[0], subject, request, conditions);
4410
+ const right = resolveOperand(body[1], subject, request, conditions);
4411
+ if (left === INVALID_OPERAND || right === INVALID_OPERAND) return;
4412
+ if (left === void 0 || right === void 0) return false;
4413
+ if (kind === "eq") return left === right;
4414
+ if (kind === "ne") return left !== right;
4415
+ const order = compareValues(left, right);
4416
+ if (order === void 0) return false;
4417
+ switch (kind) {
4418
+ case "lt": return order < 0;
4419
+ case "lte": return order <= 0;
4420
+ case "gt": return order > 0;
4421
+ case "gte": return order >= 0;
4422
+ }
4423
+ return;
4424
+ }
4425
+ case "in":
4426
+ case "notIn": {
4427
+ if (!Array.isArray(body) || body.length !== 2 || !Array.isArray(body[1])) return;
4428
+ const left = resolveOperand(body[0], subject, request, conditions);
4429
+ if (left === INVALID_OPERAND) return;
4430
+ const elements = body[1].map((element) => resolveOperand(element, subject, request, conditions));
4431
+ if (elements.some((value) => value === INVALID_OPERAND)) return;
4432
+ if (left === void 0) return false;
4433
+ const found = elements.some((value) => value !== void 0 && value === left);
4434
+ return kind === "in" ? found : !found;
4435
+ }
4436
+ case "exists": {
4437
+ const value = resolveOperand(body, subject, request, conditions);
4438
+ if (value === INVALID_OPERAND) return;
4439
+ return value !== void 0;
4440
+ }
4441
+ case "and":
4442
+ case "or": {
4443
+ if (!Array.isArray(body)) return;
4444
+ let result = kind === "and";
4445
+ for (const child of body) {
4446
+ const value = evaluateNode(child, subject, request, conditions);
4447
+ if (value === void 0) return;
4448
+ if (kind === "and") result = result && value;
4449
+ else result = result || value;
4450
+ }
4451
+ return result;
4452
+ }
4453
+ case "not": {
4454
+ const value = evaluateNode(body, subject, request, conditions);
4455
+ return value === void 0 ? void 0 : !value;
4456
+ }
4457
+ default: return;
4458
+ }
4459
+ }
4460
+ /**
4461
+ * Evaluates a version-1 condition. Deterministic, total, and pure: any input
4462
+ * shape yields a boolean and never throws, and a malformed condition is
4463
+ * false. These are consensus semantics, versioned by `PHAuthState.version`;
4464
+ * changing them requires a new version.
4465
+ */
4466
+ function evaluateCondition(condition, subject, request, conditions) {
4467
+ return evaluateNode(condition, subject, request, conditions) === true;
4468
+ }
4469
+ /** Whether a capability's scope reaches the requested one. */
4470
+ function scopeCovers(scope, requested) {
4471
+ return scope === void 0 || scope === "*" || scope === requested;
4472
+ }
4473
+ /**
4474
+ * Whether one grant answers this request.
4475
+ *
4476
+ * A grant allowing execute on a scope also allows reading it: executing an
4477
+ * operation means reading the state it applies to, so permitting the write while
4478
+ * withholding the read would describe an access nobody could use. The converse
4479
+ * does not hold -- a read grant confers no write.
4480
+ *
4481
+ * Only an allow carries across. A deny on execute withholds the write and says
4482
+ * nothing about the read, so a policy locking writes down does not silently
4483
+ * revoke a read grant sitting before it. The operation list is not consulted
4484
+ * either: it restricts which operations may be executed, not whether the scope
4485
+ * is visible, and a read carries no operation to match against.
4486
+ *
4487
+ * Reads are not consensus -- no replica records a read, and the two read call
4488
+ * sites are both inside the read gate -- so this rule is not part of what makes
4489
+ * an operation valid, and does not need a policy version of its own.
4490
+ */
4491
+ function grantAnswers(grant, request) {
4492
+ if (capabilityCovers(grant.capability, request)) return true;
4493
+ return request.verb === "read" && grant.effect === "allow" && grant.capability.can === "execute" && scopeCovers(grant.capability.scope, request.scope);
4494
+ }
4495
+ function capabilityCovers(capability, request) {
4496
+ if (capability.can !== request.verb) return false;
4497
+ if (!scopeCovers(capability.scope, request.scope)) return false;
4498
+ if (capability.can === "execute") {
4499
+ if (capability.operation === void 0) return true;
4500
+ return request.operation !== void 0 && capability.operation.includes(request.operation);
4501
+ }
4502
+ return true;
4503
+ }
4504
+ function principalMatches(principal, subject, request, groups, conditions) {
4505
+ if ("anyone" in principal) return true;
4506
+ if ("address" in principal) return subject.address !== void 0 && subject.address.toLowerCase() === principal.address.toLowerCase();
4507
+ if ("group" in principal) {
4508
+ if (groups === void 0 || subject.address === void 0) return false;
4509
+ const group = groups[principal.group];
4510
+ if (group === void 0 || !Array.isArray(group.members)) return false;
4511
+ const address = subject.address.toLowerCase();
4512
+ return group.members.some((member) => member.toLowerCase() === address);
4513
+ }
4514
+ if ("match" in principal) {
4515
+ if (conditions === void 0) return false;
4516
+ return evaluateCondition(principal.match, subject, request, conditions);
4517
+ }
4518
+ return false;
4519
+ }
4520
+ /**
4521
+ * Evaluates a v1 grant stack: default deny, last applicable grant wins, and
4522
+ * reports which grant decided it. Group principals match only against a
4523
+ * supplied groups map, and `where` clauses and { match } principals evaluate
4524
+ * only against a supplied condition context; a grant that uses an unsupplied
4525
+ * feature never applies.
4526
+ */
4527
+ function evaluateGrantStack(grants, subject, request, groups, conditions) {
4528
+ let applicable;
4529
+ for (const grant of grants) {
4530
+ if (grant.where !== void 0) {
4531
+ if (conditions === void 0) continue;
4532
+ if (!evaluateCondition(grant.where, subject, request, conditions)) continue;
4533
+ }
4534
+ if (grantAnswers(grant, request) && principalMatches(grant.principal, subject, request, groups, conditions)) applicable = grant;
4535
+ }
4536
+ if (applicable === void 0) return {
4537
+ decision: "deny",
4538
+ refusal: "no-applicable-grant"
4539
+ };
4540
+ if (applicable.effect === "deny") return {
4541
+ decision: "deny",
4542
+ refusal: "denied-by-grant",
4543
+ grantId: applicable.id
4544
+ };
4545
+ return { decision: "allow" };
4546
+ }
3746
4547
  const documentModelFileExtension = "phdm";
3747
4548
  const documentModelInitialLocalState = {};
3748
4549
  const documentModelInitialGlobalState = {
@@ -4324,7 +5125,306 @@ const documentModelGlobalState = {
4324
5125
  };
4325
5126
  const HASH_ALGORITHM_SHA1 = "sha1";
4326
5127
  const HASH_ENCODING_BASE64 = "base64";
4327
- const documentModelDocumentType = "powerhouse/document-model";
5128
+ /**
5129
+ * Creates a default PHAuthState
5130
+ */
5131
+ function defaultAuthState() {
5132
+ return {
5133
+ version: 0,
5134
+ grants: []
5135
+ };
5136
+ }
5137
+ /**
5138
+ * Creates a default PHDocumentState
5139
+ */
5140
+ function defaultDocumentState() {
5141
+ return {
5142
+ version: 0,
5143
+ hash: {
5144
+ algorithm: HASH_ALGORITHM_SHA1,
5145
+ encoding: HASH_ENCODING_BASE64
5146
+ }
5147
+ };
5148
+ }
5149
+ /**
5150
+ * Creates a default PHBaseState with auth and document properties
5151
+ */
5152
+ function defaultBaseState() {
5153
+ return {
5154
+ auth: defaultAuthState(),
5155
+ document: defaultDocumentState()
5156
+ };
5157
+ }
5158
+ /**
5159
+ * Creates a PHAuthState with the given properties
5160
+ */
5161
+ function createAuthState(auth) {
5162
+ return {
5163
+ ...defaultAuthState(),
5164
+ ...auth
5165
+ };
5166
+ }
5167
+ /**
5168
+ * Creates a PHDocumentState with the given properties
5169
+ */
5170
+ function createDocumentState(document) {
5171
+ return {
5172
+ ...defaultDocumentState(),
5173
+ ...document
5174
+ };
5175
+ }
5176
+ /**
5177
+ * Creates a PHBaseState with the given auth and document properties
5178
+ */
5179
+ function createBaseState(auth, document) {
5180
+ return {
5181
+ auth: createAuthState(auth),
5182
+ document: createDocumentState(document)
5183
+ };
5184
+ }
5185
+ /**
5186
+ * Backfills the auth scope to the default for legacy documents serialized with
5187
+ * an empty `auth`. Replaces only `state.auth`. Idempotent.
5188
+ */
5189
+ function backfillAuthState(state) {
5190
+ return {
5191
+ ...state,
5192
+ auth: createAuthState(state.auth)
5193
+ };
5194
+ }
5195
+ function defaultGlobalState() {
5196
+ return {
5197
+ ...defaultBaseState(),
5198
+ author: {
5199
+ name: "",
5200
+ website: ""
5201
+ },
5202
+ description: "",
5203
+ extension: "",
5204
+ id: "",
5205
+ name: "",
5206
+ specifications: []
5207
+ };
5208
+ }
5209
+ function defaultLocalState() {
5210
+ return {};
5211
+ }
5212
+ function createGlobalState(state) {
5213
+ return {
5214
+ ...defaultGlobalState(),
5215
+ ...state || {}
5216
+ };
5217
+ }
5218
+ function createLocalState(state) {
5219
+ return {
5220
+ ...defaultLocalState(),
5221
+ ...state || {}
5222
+ };
5223
+ }
5224
+ function createState(baseState, globalState, localState) {
5225
+ return {
5226
+ ...createBaseState(baseState?.auth, baseState?.document),
5227
+ global: createGlobalState(globalState),
5228
+ local: createLocalState(localState)
5229
+ };
5230
+ }
5231
+ /**
5232
+ * Destructuring a null input (reachable via raw synced operations) would
5233
+ * store an engine-specific TypeError message on the error operation.
5234
+ */
5235
+ function assertActionInputShape(input) {
5236
+ if (!isPlainValue(input)) throw new InvalidActionInputError({ input: "must be an object" });
5237
+ }
5238
+ function withGrants(document, grants) {
5239
+ return {
5240
+ ...document,
5241
+ state: {
5242
+ ...document.state,
5243
+ auth: {
5244
+ ...document.state.auth,
5245
+ grants
5246
+ }
5247
+ }
5248
+ };
5249
+ }
5250
+ const P256_PUBKEY_MULTICODEC = [128, 36];
5251
+ const DID_KEY_PREFIX = "did:key:z";
5252
+ function bytesEqual(a, b) {
5253
+ if (a.length !== b.length) return false;
5254
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
5255
+ return true;
5256
+ }
5257
+ /**
5258
+ * True when `signerKey` (an ActionSigner app key, a did:key) identifies the same
5259
+ * key recorded as the document creator. Returns false
5260
+ * when there is no creator (empty JWK) or no signer key.
5261
+ */
5262
+ function isDocumentCreator(creatorKey, signerKey) {
5263
+ if (!creatorKey?.x || !creatorKey.y) return false;
5264
+ if (!signerKey || !signerKey.startsWith(DID_KEY_PREFIX)) return false;
5265
+ const decoded = base58Decode(signerKey.slice(9));
5266
+ if (!decoded || decoded.length !== 35) return false;
5267
+ if (decoded[0] !== P256_PUBKEY_MULTICODEC[0] || decoded[1] !== P256_PUBKEY_MULTICODEC[1]) return false;
5268
+ const parityPrefix = decoded[2];
5269
+ if (parityPrefix !== 2 && parityPrefix !== 3) return false;
5270
+ const didX = decoded.subarray(3, 35);
5271
+ const jwkX = base64UrlToBytes(creatorKey.x);
5272
+ const jwkY = base64UrlToBytes(creatorKey.y);
5273
+ if (jwkX.length !== 32 || jwkY.length !== 32) return false;
5274
+ if (!bytesEqual(didX, jwkX)) return false;
5275
+ return (jwkY[31] & 1) === 1 === (parityPrefix === 3);
5276
+ }
5277
+ /**
5278
+ * Sets the initial policy. Valid only while the auth scope is uninitialized
5279
+ * (version 0). The input version is the policy language version and must be an
5280
+ * integer >= 1; 0 is reserved for the uninitialized state. On a signed-header
5281
+ * document it must be signed by the document creator (`header.sig.publicKey`).
5282
+ * A creator-less policy must include a grant permitting execute on the auth
5283
+ * scope, or it would be born locked out.
5284
+ */
5285
+ function applyInitializeAuthAction(document, action) {
5286
+ assertActionInputShape(action.input);
5287
+ const { version, grants } = action.input;
5288
+ if (!Number.isInteger(version) || version < 1) throw new InvalidAuthVersionError(document.header.id, version);
5289
+ if (document.state.auth.version !== 0) throw new AuthAlreadyInitializedError(document.header.id);
5290
+ if (!Array.isArray(grants)) throw new InvalidActionInputError({ grants: "must be an array" });
5291
+ const creatorKey = document.header.sig.publicKey;
5292
+ const signerKey = action.context?.signer?.app.key;
5293
+ const hasCreator = Boolean(creatorKey.kty || creatorKey.x || creatorKey.y);
5294
+ if (hasCreator && !isDocumentCreator(creatorKey, signerKey)) throw new AuthInitializerNotCreatorError(document.header.id);
5295
+ const creator = hasCreator ? signerKey : void 0;
5296
+ assertValidInitialGrants(grants, document.header.documentType, creator);
5297
+ return {
5298
+ ...document,
5299
+ state: {
5300
+ ...document.state,
5301
+ auth: createAuthState(creator ? {
5302
+ version,
5303
+ grants,
5304
+ creator
5305
+ } : {
5306
+ version,
5307
+ grants
5308
+ })
5309
+ }
5310
+ };
5311
+ }
5312
+ /** Upserts a grant by id: replaces in place if present, otherwise appends. */
5313
+ function applySetGrantAction(document, action) {
5314
+ assertActionInputShape(action.input);
5315
+ const { grant } = action.input;
5316
+ const grants = document.state.auth.grants;
5317
+ assertValidGrantUpsert(grant, grants, document.header.documentType, document.state.auth.creator);
5318
+ return withGrants(document, grants.some((g) => g.id === grant.id) ? grants.map((g) => g.id === grant.id ? grant : g) : [...grants, grant]);
5319
+ }
5320
+ /**
5321
+ * Removes a grant by id; throws if the id is not present or if the removal
5322
+ * would leave a creator-less policy with no auth-administration grant.
5323
+ */
5324
+ function applyRemoveGrantAction(document, action) {
5325
+ assertActionInputShape(action.input);
5326
+ const { id } = action.input;
5327
+ const { grants, creator } = document.state.auth;
5328
+ if (!grants.some((g) => g.id === id)) throw new GrantNotFoundError(id);
5329
+ const next = grants.filter((g) => g.id !== id);
5330
+ assertAuthAdministrationRetained(creator, grants, next, id);
5331
+ return withGrants(document, next);
5332
+ }
5333
+ /**
5334
+ * Moves a grant by id to a new index. Order is load-bearing (the last
5335
+ * applicable grant wins), so the relative order of the other grants is kept.
5336
+ * The target index is clamped to the valid range; an unknown id throws.
5337
+ *
5338
+ * Order alone decides which grant wins, so a move can take administration away
5339
+ * without changing the list's contents. It carries the same retention rule as
5340
+ * the two mutation paths.
5341
+ */
5342
+ function applyMoveGrantAction(document, action) {
5343
+ assertActionInputShape(action.input);
5344
+ const { id, index } = action.input;
5345
+ const { grants, creator } = document.state.auth;
5346
+ const from = grants.findIndex((g) => g.id === id);
5347
+ if (from === -1) throw new GrantNotFoundError(id);
5348
+ const next = [...grants];
5349
+ const [moved] = next.splice(from, 1);
5350
+ const to = Math.max(0, Math.min(index, next.length));
5351
+ next.splice(to, 0, moved);
5352
+ assertAuthAdministrationRetained(creator, grants, next, id);
5353
+ return withGrants(document, next);
5354
+ }
5355
+ /**
5356
+ * Dispatches an auth-scope action to its handler. This is the auth scope's
5357
+ * dedicated reducer: it is applied by the base reducer instead of the model
5358
+ * reducer, mirroring the document-scope platform handlers. Unknown types are a
5359
+ * no-op, matching the model-reducer default.
5360
+ */
5361
+ function applyAuthAction(document, action) {
5362
+ switch (action.type) {
5363
+ case "INITIALIZE_AUTH": return applyInitializeAuthAction(document, action);
5364
+ case "SET_GRANT": return applySetGrantAction(document, action);
5365
+ case "REMOVE_GRANT": return applyRemoveGrantAction(document, action);
5366
+ case "MOVE_GRANT": return applyMoveGrantAction(document, action);
5367
+ default: return document;
5368
+ }
5369
+ }
5370
+ /**
5371
+ * Because only creators can initialize auth scopes, we must verify that either
5372
+ * the document has no auth or the version and creator match.
5373
+ */
5374
+ function assertAuthPreservedOnDuplicate(documentId, source, duplicated) {
5375
+ if (!source || source.version === 0) return;
5376
+ if (duplicated === void 0 || duplicated.version !== source.version || duplicated.creator !== source.creator) throw new AuthPolicyNotPreservedError(documentId);
5377
+ }
5378
+ /**
5379
+ * The auth scope a state snapshot may install, given the policy already there.
5380
+ *
5381
+ * `applyAuthAction` is the validated door onto `state.auth`, but a whole-state
5382
+ * snapshot (UPGRADE_DOCUMENT's `initialState`, LOAD_STATE's `data`) replaces the
5383
+ * scope wholesale and is authorized as a `document`-scope write. Without this,
5384
+ * a subject holding `execute` on `document` and no auth grant at all can install
5385
+ * a policy of its choosing, name itself `creator` (which exempts the policy from
5386
+ * the retention rule for good), or wipe an existing policy by carrying the
5387
+ * default uninitialized one.
5388
+ *
5389
+ * Three cases:
5390
+ *
5391
+ * - the snapshot carries no policy, or an uninitialized one: the document
5392
+ * keeps the policy it has, so a default-state upgrade cannot reset it;
5393
+ * - the document is uninitialized and the snapshot carries a policy: the
5394
+ * policy is installed after the same validation genesis applies, so it
5395
+ * cannot be born locked out;
5396
+ * - both carry a policy: they must agree. A duplicate preserves its source's
5397
+ * policy (see {@link assertAuthPreservedOnDuplicate}), and anything else is
5398
+ * an attempt to replace one policy with another.
5399
+ */
5400
+ function resolveSnapshotAuth(documentId, documentType, current, incoming) {
5401
+ const currentAuth = current ?? createAuthState({
5402
+ version: 0,
5403
+ grants: []
5404
+ });
5405
+ if (!incoming || !incoming.version) return currentAuth;
5406
+ if (currentAuth.version !== 0) {
5407
+ if (stringify$1(incoming) !== stringify$1(currentAuth)) throw new AuthPolicyNotPreservedError(documentId);
5408
+ return incoming;
5409
+ }
5410
+ if (!Array.isArray(incoming.grants)) throw new InvalidActionInputError({ grants: "must be an array" });
5411
+ assertValidInitialGrants(incoming.grants, documentType, incoming.creator);
5412
+ return incoming;
5413
+ }
5414
+ /** UNDO, REDO and PRUNE are rejected on the auth scope. */
5415
+ function assertAuthScopeActionAllowed(action) {
5416
+ if (action.scope === "auth" && [
5417
+ "UNDO",
5418
+ "REDO",
5419
+ "PRUNE"
5420
+ ].includes(action.type)) throw new AuthActionNotAllowedError(action.type);
5421
+ }
5422
+ /**
5423
+ * True iff authorization rejected the action.
5424
+ */
5425
+ function isDenied(operation) {
5426
+ return operation.deniedReason !== void 0;
5427
+ }
4328
5428
  const BaseDocumentHeaderSchema = object({
4329
5429
  id: string(),
4330
5430
  name: string(),
@@ -4455,6 +5555,7 @@ function createDocumentScopeOperations(header, state) {
4455
5555
  function baseCreateDocument(createState, initialState, documentType = "") {
4456
5556
  const state = createState(initialState);
4457
5557
  const header = createPresignedHeader(generateId(), documentType);
5558
+ header.protocolVersions = { "base-reducer": 2 };
4458
5559
  return {
4459
5560
  header,
4460
5561
  state,
@@ -4478,6 +5579,18 @@ function hashDocumentStateForScope(document, scope = "global") {
4478
5579
  * Uses the V2 model where consecutive NOOPs form chains.
4479
5580
  * Unlike V1 garbageCollect, this preserves ALL operations but marks which to apply.
4480
5581
  */
5582
+ /**
5583
+ * The base-reducer protocol version a document is written against. Every
5584
+ * document carries one, set from the CREATE_DOCUMENT input. A header without
5585
+ * one was built outside that path, and choosing a version on its behalf would
5586
+ * replay the document through a reducer it was not written for, so it is an
5587
+ * error rather than a default.
5588
+ */
5589
+ function baseReducerVersion(header) {
5590
+ const version = header.protocolVersions?.["base-reducer"];
5591
+ if (typeof version !== "number") throw new Error(`Document ${header.id} carries no base-reducer protocol version`);
5592
+ return version;
5593
+ }
4481
5594
  function garbageCollectV2(sortedOperations) {
4482
5595
  const result = [];
4483
5596
  let noopChainLength = 0;
@@ -4494,9 +5607,26 @@ function garbageCollectV2(sortedOperations) {
4494
5607
  const defaultCreateState = (state) => {
4495
5608
  return state;
4496
5609
  };
5610
+ /**
5611
+ * Records an operation in the history without applying it, which is what a
5612
+ * denied operation needs: it occupies its index and contributes no state.
5613
+ *
5614
+ * The scope defaults to the action's own, and is passed explicitly by a rebuild
5615
+ * that is walking one stream and does not want to trust the action's copy.
5616
+ */
5617
+ function appendWithoutApplying(document, operation, scope = operation.action.scope) {
5618
+ return {
5619
+ ...document,
5620
+ operations: {
5621
+ ...document.operations,
5622
+ [scope]: [...document.operations[scope] ?? [], operation]
5623
+ }
5624
+ };
5625
+ }
4497
5626
  function replayDocument(initialState, operations, reducer, header, dispatch, skipHeaderOperations = {}, options) {
4498
5627
  const { checkHashes = true, reuseOperationResultingState, operationResultingStateParser = parseResultingState, skipIndexValidation } = options || {};
4499
- let documentState = initialState;
5628
+ const backfilledInitialState = backfillAuthState(initialState);
5629
+ let documentState = backfilledInitialState;
4500
5630
  const operationsToReplay = [];
4501
5631
  const allScopes = new Set([
4502
5632
  ...Object.keys(operations),
@@ -4531,12 +5661,13 @@ function replayDocument(initialState, operations, reducer, header, dispatch, ski
4531
5661
  const document = {
4532
5662
  header,
4533
5663
  state: defaultCreateState(documentState),
4534
- initialState,
5664
+ initialState: backfilledInitialState,
4535
5665
  operations: initialOperations,
4536
5666
  clipboard: []
4537
5667
  };
4538
5668
  let result = document;
4539
5669
  if (operationsToReplay.length) result = operationsToReplay.reduce((document, operation) => {
5670
+ if (isDenied(operation)) return updateHeaderRevision(appendWithoutApplying(document, operation), operation.action.scope, operation.timestampUtcMs);
4540
5671
  return reducer(document, operation.action, dispatch, {
4541
5672
  ignoreSkipOperations: true,
4542
5673
  checkHashes,
@@ -4818,13 +5949,15 @@ function redoOperation(document, action, skip) {
4818
5949
  });
4819
5950
  }
4820
5951
  function loadStateOperation(document, action) {
5952
+ const loaded = backfillAuthState(action.state.data);
5953
+ loaded.auth = resolveSnapshotAuth(document.header.id, document.header.documentType, backfillAuthState({ ...document.state }).auth, loaded.auth);
4821
5954
  return {
4822
5955
  ...document,
4823
5956
  header: {
4824
5957
  ...document.header,
4825
5958
  name: action.state.name
4826
5959
  },
4827
- state: action.state.data
5960
+ state: loaded
4828
5961
  };
4829
5962
  }
4830
5963
  function replayOperations(initialState, clearedOperations, stateReducer, header, dispatch, documentReducer = baseReducer, skipHeaderOperations = {}, options) {
@@ -4998,12 +6131,13 @@ function processUndoOperation(document, scope, customReducer, reuseOperationResu
4998
6131
  function baseReducer(document, action, customReducer, dispatch, options = {}) {
4999
6132
  const { skip, ignoreSkipOperations = false, reuseOperationResultingState = false, operationResultingStateParser, pruneOnSkip = true, branch = "main" } = options;
5000
6133
  let _action = actionFromAction(action);
6134
+ assertAuthScopeActionAllowed(_action);
5001
6135
  let skipValue = skip ?? options.replayOptions?.operation.skip ?? 0;
5002
6136
  let newDocument = { ...document };
5003
6137
  let reuseLastOperationIndex = false;
5004
6138
  const shouldProcessSkipOperation = !ignoreSkipOperations && skipValue > 0;
5005
6139
  if (isUndoRedo(_action)) {
5006
- const { skip: calculatedSkip, action: transformedAction, document: processedDocument, reuseLastOperationIndex: reuseIndex } = processUndoRedo(document, _action, skipValue, options.protocolVersion ?? 1);
6140
+ const { skip: calculatedSkip, action: transformedAction, document: processedDocument, reuseLastOperationIndex: reuseIndex } = processUndoRedo(document, _action, skipValue, options.protocolVersion ?? baseReducerVersion(document.header));
5007
6141
  _action = transformedAction;
5008
6142
  skipValue = calculatedSkip;
5009
6143
  newDocument = processedDocument;
@@ -5019,7 +6153,7 @@ function baseReducer(document, action, customReducer, dispatch, options = {}) {
5019
6153
  branch
5020
6154
  };
5021
6155
  newDocument = updateDocument(newDocument, _action, reuseLastOperationIndex, skipValue, operationContext, options.replayOptions?.operation, options.skipIndexValidation);
5022
- const protocolVersion = options.protocolVersion ?? 1;
6156
+ const protocolVersion = options.protocolVersion ?? baseReducerVersion(document.header);
5023
6157
  if (isUndo(action) && protocolVersion < 2) return processUndoOperation(newDocument, action.scope, customReducer);
5024
6158
  const isNoopWithSkip = _action.type === "NOOP" && skipValue > 0;
5025
6159
  if ((isUndo(action) || isNoopWithSkip) && protocolVersion >= 2) {
@@ -5045,6 +6179,13 @@ function baseReducer(document, action, customReducer, dispatch, options = {}) {
5045
6179
  }
5046
6180
  newDocument = create(newDocument, (draft) => {
5047
6181
  try {
6182
+ if (_action.scope === "auth") {
6183
+ const authState = applyAuthAction(newDocument, _action).state;
6184
+ unsafe(() => {
6185
+ draft.state = castDraft(authState);
6186
+ });
6187
+ return;
6188
+ }
5048
6189
  const newState = customReducer(draft.state, _action, dispatch);
5049
6190
  if (newState) unsafe(() => {
5050
6191
  draft.state = castDraft(newState);
@@ -5074,8 +6215,8 @@ function baseReducer(document, action, customReducer, dispatch, options = {}) {
5074
6215
  "PRUNE"
5075
6216
  ].includes(_action.type)) return newDocument;
5076
6217
  const scope = _action.scope || "global";
5077
- let hash = hashDocumentStateForScope(newDocument, scope);
5078
- if (options.replayOptions?.operation.hash && options.replayOptions.operation.hash !== "") hash = options.replayOptions.operation.hash;
6218
+ const replayHash = options.replayOptions?.operation.hash;
6219
+ const hash = replayHash ? replayHash : hashDocumentStateForScope(newDocument, scope);
5079
6220
  const lastOperation = newDocument.operations[scope]?.at(-1);
5080
6221
  if (lastOperation) {
5081
6222
  lastOperation.hash = hash;
@@ -5834,14 +6975,27 @@ const documentModelStateReducer = (state, action) => {
5834
6975
  }
5835
6976
  };
5836
6977
  const documentModelReducer = createReducer(documentModelStateReducer);
6978
+ /**
6979
+ * Canonical document-model version normalization: documents stamped with 0
6980
+ * or nothing at all predate versioning and are treated as version 1, the
6981
+ * same version the registry assigns unversioned modules. Every consumer
6982
+ * that resolves a module or compares versions must use this rule; resolving
6983
+ * 0 to "latest" instead re-pins a legacy document's history to whichever
6984
+ * module happens to be newest.
6985
+ */
6986
+ function normalizeDocumentModelVersion(version) {
6987
+ return version && version > 0 ? version : 1;
6988
+ }
5837
6989
  function applyInitialState(document, action) {
5838
6990
  const input = action.input;
5839
6991
  const newState = input.initialState || input.state;
5840
6992
  if (newState) {
5841
- document.state = {
6993
+ const merged = backfillAuthState({
5842
6994
  ...document.state,
5843
6995
  ...newState
5844
- };
6996
+ });
6997
+ merged.auth = resolveSnapshotAuth(document.header.id, document.header.documentType, backfillAuthState({ ...document.state }).auth, merged.auth);
6998
+ document.state = merged;
5845
6999
  document.initialState = document.state;
5846
7000
  }
5847
7001
  }
@@ -5901,7 +7055,6 @@ function computeUpgradeTransitions(manifest, fromVersion, toVersion) {
5901
7055
  }
5902
7056
  return transitions;
5903
7057
  }
5904
- const NON_DOMAIN_SCOPES$1 = new Set(["auth", "document"]);
5905
7058
  function highestReducerVersion(reducers) {
5906
7059
  const keys = Object.keys(reducers).map(Number);
5907
7060
  if (keys.length === 0) throw new Error("VersionedReplayConfig.reducers must not be empty");
@@ -5929,15 +7082,15 @@ function highestReducerVersion(reducers) {
5929
7082
  */
5930
7083
  function replayDocumentVersioned(initialState, operations, config, header, dispatch, options) {
5931
7084
  const { checkHashes = true, skipIndexValidation } = options || {};
5932
- const protocolVersion = header.protocolVersions?.["base-reducer"] ?? 1;
7085
+ const protocolVersion = baseReducerVersion(header);
5933
7086
  const spine = (operations["document"] ?? []).slice().sort((a, b) => a.index - b.index);
5934
7087
  const upgrades = spine.filter((op) => op.action.type === "UPGRADE_DOCUMENT");
5935
7088
  const legacyFallback = () => {
5936
- const domainOps = Object.fromEntries(Object.entries(operations).filter(([s]) => !NON_DOMAIN_SCOPES$1.has(s)));
7089
+ const replayOps = Object.fromEntries(Object.entries(operations).filter(([s]) => s !== "document"));
5937
7090
  const latestVersion = highestReducerVersion(config.reducers);
5938
7091
  const reducer = config.reducers[latestVersion];
5939
7092
  return {
5940
- ...replayDocument(initialState, domainOps, reducer, header, dispatch, {}, options),
7093
+ ...replayDocument(initialState, replayOps, reducer, header, dispatch, {}, options),
5941
7094
  operations
5942
7095
  };
5943
7096
  };
@@ -5961,14 +7114,14 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
5961
7114
  const a = op.action;
5962
7115
  return a.input.fromVersion > 0 && a.input.fromVersion < a.input.toVersion;
5963
7116
  });
5964
- const domainScopes = Object.keys(operations).filter((s) => !NON_DOMAIN_SCOPES$1.has(s));
7117
+ const replayScopes = Object.keys(operations).filter((s) => s !== "document");
5965
7118
  const scopeOps = {};
5966
- for (const s of domainScopes) scopeOps[s] = (operations[s] ?? []).slice().sort((a, b) => a.index - b.index);
7119
+ for (const s of replayScopes) scopeOps[s] = (operations[s] ?? []).slice().sort((a, b) => a.index - b.index);
5967
7120
  const boundaries = validatedUpgrades.map((upgradeOp) => {
5968
7121
  const revisionSnapshot = upgradeOp.action.input.revision;
5969
7122
  const upgradeTimestamp = upgradeOp.timestampUtcMs;
5970
7123
  const boundary = {};
5971
- for (const s of domainScopes) {
7124
+ for (const s of replayScopes) {
5972
7125
  const ops = scopeOps[s] ?? [];
5973
7126
  if (revisionSnapshot !== void 0) {
5974
7127
  const rev = revisionSnapshot[s] ?? 0;
@@ -5986,7 +7139,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
5986
7139
  }
5987
7140
  return boundary;
5988
7141
  });
5989
- for (let i = 1; i < boundaries.length; i++) for (const s of domainScopes) {
7142
+ for (let i = 1; i < boundaries.length; i++) for (const s of replayScopes) {
5990
7143
  const prev = boundaries[i - 1]?.[s] ?? 0;
5991
7144
  const curr = boundaries[i]?.[s] ?? 0;
5992
7145
  if (boundaries[i]) boundaries[i][s] = Math.max(prev, curr);
@@ -5998,10 +7151,11 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
5998
7151
  ]);
5999
7152
  const initialOperations = {};
6000
7153
  for (const s of allScopes) initialOperations[s] = [];
7154
+ const backfilledSeed = backfillAuthState(seedState);
6001
7155
  let document = {
6002
7156
  header,
6003
- state: seedState,
6004
- initialState: seedState,
7157
+ state: backfilledSeed,
7158
+ initialState: backfilledSeed,
6005
7159
  operations: initialOperations,
6006
7160
  clipboard: []
6007
7161
  };
@@ -6009,17 +7163,15 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
6009
7163
  const segmentEndHashPerScope = /* @__PURE__ */ new Map();
6010
7164
  for (let k = 0; k <= validatedUpgrades.length; k++) {
6011
7165
  const reducer = config.reducers[currentVersion];
6012
- if (!reducer) {
6013
- const available = Object.keys(config.reducers).join(", ");
6014
- throw new Error(`No reducer registered for document version ${currentVersion}. Available versions: ${available}`);
6015
- }
6016
- for (const s of domainScopes) {
7166
+ if (!reducer) throw new UnsupportedDocumentModelVersionError(header.documentType, currentVersion, Object.keys(config.reducers).map(Number).sort((a, b) => a - b));
7167
+ for (const s of replayScopes) {
6017
7168
  const ops = scopeOps[s] ?? [];
6018
7169
  const segStart = k === 0 ? 0 : boundaries[k - 1]?.[s] ?? 0;
6019
7170
  const segEnd = k < validatedUpgrades.length ? boundaries[k]?.[s] ?? ops.length : ops.length;
6020
7171
  const segOps = ops.slice(segStart, segEnd);
6021
7172
  for (const op of segOps) {
6022
- document = reducer(document, op.action, dispatch, {
7173
+ if (isDenied(op)) document = updateHeaderRevision(appendWithoutApplying(document, op, s), s, op.timestampUtcMs);
7174
+ else document = reducer(document, op.action, dispatch, {
6023
7175
  ignoreSkipOperations: true,
6024
7176
  checkHashes,
6025
7177
  skipIndexValidation,
@@ -6036,6 +7188,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
6036
7188
  if (!spineOp) continue;
6037
7189
  const spineActionType = spineOp.action.type;
6038
7190
  if (spineActionType === "CREATE_DOCUMENT" || spineActionType === "UPGRADE_DOCUMENT") continue;
7191
+ if (isDenied(spineOp)) continue;
6039
7192
  if (spineActionType === "DELETE_DOCUMENT") document = applyDeleteDocumentAction(document, spineOp.action);
6040
7193
  else document = reducer(document, spineOp.action, dispatch, {
6041
7194
  ignoreSkipOperations: true,
@@ -6066,7 +7219,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
6066
7219
  }
6067
7220
  };
6068
7221
  if (!checkHashes) {
6069
- const allReplayedOps = domainScopes.flatMap((s) => scopeOps[s] ?? []);
7222
+ const allReplayedOps = replayScopes.flatMap((s) => scopeOps[s] ?? []);
6070
7223
  for (const scope of Object.keys(document.state)) {
6071
7224
  const capturedHash = segmentEndHashPerScope.get(scope);
6072
7225
  const scopeHash = capturedHash !== void 0 ? capturedHash : hashDocumentStateForScope(document, scope);
@@ -6108,7 +7261,6 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
6108
7261
  }
6109
7262
  };
6110
7263
  }
6111
- const NON_DOMAIN_SCOPES = new Set(["auth", "document"]);
6112
7264
  function zipAsync(data) {
6113
7265
  return new Promise((resolve, reject) => {
6114
7266
  zip(data, (err, out) => err ? reject(err) : resolve(out));
@@ -6187,7 +7339,7 @@ async function parseZipData(data) {
6187
7339
  async function loadFromZipData(data, reducer, options) {
6188
7340
  const { initialState, header, clearedOperations } = await parseZipData(data);
6189
7341
  return {
6190
- ...replayDocument(initialState, Object.fromEntries(Object.entries(clearedOperations).filter(([scope]) => !NON_DOMAIN_SCOPES.has(scope))), reducer, header, void 0, {}, options),
7342
+ ...replayDocument(initialState, Object.fromEntries(Object.entries(clearedOperations).filter(([scope]) => scope !== "document")), reducer, header, void 0, {}, options),
6191
7343
  operations: clearedOperations
6192
7344
  };
6193
7345
  }
@@ -6210,96 +7362,6 @@ const documentModelLoadFromInput = (input) => {
6210
7362
  const documentModelSaveToFileHandle = (document, input) => {
6211
7363
  return baseSaveToFileHandle(document, input);
6212
7364
  };
6213
- /**
6214
- * Creates a default PHAuthState
6215
- */
6216
- function defaultAuthState() {
6217
- return {};
6218
- }
6219
- /**
6220
- * Creates a default PHDocumentState
6221
- */
6222
- function defaultDocumentState() {
6223
- return {
6224
- version: 0,
6225
- hash: {
6226
- algorithm: HASH_ALGORITHM_SHA1,
6227
- encoding: HASH_ENCODING_BASE64
6228
- }
6229
- };
6230
- }
6231
- /**
6232
- * Creates a default PHBaseState with auth and document properties
6233
- */
6234
- function defaultBaseState() {
6235
- return {
6236
- auth: defaultAuthState(),
6237
- document: defaultDocumentState()
6238
- };
6239
- }
6240
- /**
6241
- * Creates a PHAuthState with the given properties
6242
- */
6243
- function createAuthState(auth) {
6244
- return {
6245
- ...defaultAuthState(),
6246
- ...auth
6247
- };
6248
- }
6249
- /**
6250
- * Creates a PHDocumentState with the given properties
6251
- */
6252
- function createDocumentState(document) {
6253
- return {
6254
- ...defaultDocumentState(),
6255
- ...document
6256
- };
6257
- }
6258
- /**
6259
- * Creates a PHBaseState with the given auth and document properties
6260
- */
6261
- function createBaseState(auth, document) {
6262
- return {
6263
- auth: createAuthState(auth),
6264
- document: createDocumentState(document)
6265
- };
6266
- }
6267
- function defaultGlobalState() {
6268
- return {
6269
- ...defaultBaseState(),
6270
- author: {
6271
- name: "",
6272
- website: ""
6273
- },
6274
- description: "",
6275
- extension: "",
6276
- id: "",
6277
- name: "",
6278
- specifications: []
6279
- };
6280
- }
6281
- function defaultLocalState() {
6282
- return {};
6283
- }
6284
- function createGlobalState(state) {
6285
- return {
6286
- ...defaultGlobalState(),
6287
- ...state || {}
6288
- };
6289
- }
6290
- function createLocalState(state) {
6291
- return {
6292
- ...defaultLocalState(),
6293
- ...state || {}
6294
- };
6295
- }
6296
- function createState(baseState, globalState, localState) {
6297
- return {
6298
- ...createBaseState(baseState?.auth, baseState?.document),
6299
- global: createGlobalState(globalState),
6300
- local: createLocalState(localState)
6301
- };
6302
- }
6303
7365
  //#endregion
6304
7366
  //#region ../document-model/dist/index.js
6305
7367
  const isStateOfType$1 = (state) => {
@@ -7378,6 +8440,6 @@ const driveDocumentModelModule = {
7378
8440
  }
7379
8441
  };
7380
8442
  //#endregion
7381
- export { redo as A, setName as B, createZip as C, deleteOperationError as D, deleteOperation as E, setModelDescription as F, setStateSchema as G, setOperationErrorName as H, setModelExtension as I, validateModules as J, undo as K, setModelId as L, setAuthorName as M, setAuthorWebsite as N, documentModelDocumentType as O, setInitialState as P, setModelName as R, createState as S, deleteModule as T, setOperationName as U, setOperationDescription as V, setOperationSchema as W, generateId as X, validateStateSchemaName as Y, baseLoadFromInputVersioned as _, handleTargetNameCollisions as a, createPresignedHeader as b, moveNode as c, logger as d, addModule as f, baseLoadFromInput as g, baseCreateDocument as h, generateNodesCopy as i, replayDocumentVersioned as j, isDocumentAction as k, updateNode as l, addOperationError as m, copyNode as n, isFileNode as o, addOperation as p, validateInitialState as q, driveDocumentModelModule as r, isFolderNode as s, addFolder as t, documentModelDocumentModelModule as u, baseSaveToFileHandle as v, defaultBaseState as w, createReducer as x, buildSignedAction as y, setModuleName as z };
8443
+ export { setOperationDescription as $, createState as A, normalizeDocumentModelVersion as B, baseLoadFromInputVersioned as C, createBaseState as D, createAction as E, deleteOperationError as F, setAuthorWebsite as G, releaseNewVersion as H, deserializeSignature as I, setModelExtension as J, setInitialState as K, documentModelDocumentType as L, defaultBaseState as M, deleteModule as N, createPresignedHeader as O, deleteOperation as P, setName as Q, hashDocumentStateForScope as R, baseLoadFromInput as S, buildSignedAction as T, replayDocumentVersioned as U, redo as V, setAuthorName as W, setModelName as X, setModelId as Y, setModuleName as Z, addOperation as _, handleTargetNameCollisions as a, undo as at, baseActions as b, isValidName as c, validateStateSchemaName as ct, documentModelDocumentModelModule as d, setOperationErrorName as et, logger as f, addModule as g, UnsupportedDocumentModelVersionError as h, generateNodesCopy as i, toTransportAction as it, createZip as j, createReducer as k, moveNode as l, generateId as lt, BaseDocumentStateSchema as m, copyNode as n, setOperationSchema as nt, isFileNode as o, validateInitialState as ot, BaseDocumentHeaderSchema as p, setModelDescription as q, driveDocumentModelModule as r, setStateSchema as rt, isFolderNode as s, validateModules as st, addFolder as t, setOperationName as tt, updateNode as u, addOperationError as v, baseSaveToFileHandle as w, baseCreateDocument as x, assertAuthPreservedOnDuplicate as y, isDocumentAction as z };
7382
8444
 
7383
- //# sourceMappingURL=document-drive-C8hbqQ3C.mjs.map
8445
+ //# sourceMappingURL=document-drive-CRYbvD0-.mjs.map