@powerhousedao/shared 6.2.2-dev.5 → 6.2.2-dev.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clis/args/access-token.mjs +1 -1
- package/dist/clis/args/{common-dbsvksvf.mjs → common-CcYbXrBx.mjs} +7 -2
- package/dist/clis/args/common-CcYbXrBx.mjs.map +1 -0
- package/dist/clis/args/{common-C5n5ai5C.d.mts → common-jbDGxlex.d.mts} +7 -4
- package/dist/clis/args/{common-C5n5ai5C.d.mts.map → common-jbDGxlex.d.mts.map} +1 -1
- package/dist/clis/args/common.d.mts +2 -2
- package/dist/clis/args/common.mjs +2 -2
- package/dist/clis/args/connect.d.mts +14 -5
- package/dist/clis/args/connect.d.mts.map +1 -1
- package/dist/clis/args/connect.mjs +2 -1
- package/dist/clis/args/connect.mjs.map +1 -1
- package/dist/clis/args/{generate-Cs5okckF.mjs → generate-CQ78RxVS.mjs} +2 -2
- package/dist/clis/args/{generate-Cs5okckF.mjs.map → generate-CQ78RxVS.mjs.map} +1 -1
- package/dist/clis/args/generate.mjs +1 -1
- package/dist/clis/args/{help-BkF9Cxud.mjs → help-Dk-d57W1.mjs} +3 -3
- package/dist/clis/args/{help-BkF9Cxud.mjs.map → help-Dk-d57W1.mjs.map} +1 -1
- package/dist/clis/args/help.mjs +1 -1
- package/dist/clis/args/index.d.mts +2 -2
- package/dist/clis/args/index.mjs +4 -4
- package/dist/clis/args/init.mjs +1 -1
- package/dist/clis/args/inspect.mjs +1 -1
- package/dist/clis/args/install.mjs +1 -1
- package/dist/clis/args/list.mjs +1 -1
- package/dist/clis/args/login.mjs +1 -1
- package/dist/clis/args/migrate.mjs +1 -1
- package/dist/clis/args/publish.mjs +1 -1
- package/dist/clis/args/registry.mjs +1 -1
- package/dist/clis/args/service.mjs +1 -1
- package/dist/clis/args/switchboard.mjs +1 -1
- package/dist/clis/args/uninstall.mjs +1 -1
- package/dist/clis/args/unpublish.mjs +1 -1
- package/dist/clis/args/vetra.mjs +1 -1
- package/dist/clis/build-config.d.mts +1 -1
- package/dist/clis/build-config.d.mts.map +1 -1
- package/dist/clis/constants.d.mts +168 -49
- package/dist/clis/constants.d.mts.map +1 -1
- package/dist/clis/constants.mjs +6 -2
- package/dist/clis/constants.mjs.map +1 -1
- package/dist/clis/index.d.mts +476 -12
- package/dist/clis/index.d.mts.map +1 -1
- package/dist/clis/index.mjs +290 -3
- package/dist/clis/index.mjs.map +1 -1
- package/dist/{config-loader-19K8n4DO.js → config-loader-Dw3syw7r.js} +10 -14
- package/dist/config-loader-Dw3syw7r.js.map +1 -0
- package/dist/connect/config-loader.d.ts +2 -1
- package/dist/connect/config-loader.d.ts.map +1 -1
- package/dist/connect/config-loader.js +1 -1
- package/dist/connect/index.d.ts +435 -14
- package/dist/connect/index.d.ts.map +1 -1
- package/dist/connect/index.js +498 -2
- package/dist/connect/index.js.map +1 -1
- package/dist/connect/json-adapter.d.ts.map +1 -1
- package/dist/{constants-C_EQX4DY.d.ts → constants-BP_gjn8g.d.ts} +1 -1
- package/dist/constants-BP_gjn8g.d.ts.map +1 -0
- package/dist/constants.d.ts +1 -1
- package/dist/document-drive/index.d.ts +2 -2
- package/dist/document-drive/index.d.ts.map +1 -1
- package/dist/document-model/index.d.ts +5 -5
- package/dist/document-model/index.js +1202 -130
- package/dist/document-model/index.js.map +1 -1
- package/dist/document-model/mock.d.ts +1 -1
- package/dist/document-model/mock.js +32 -3
- package/dist/document-model/mock.js.map +1 -1
- package/dist/document-model/utils.d.ts +2 -2
- package/dist/document-model/utils.js +1 -1
- package/dist/index-B84oCK9j.d.ts +760 -0
- package/dist/index-B84oCK9j.d.ts.map +1 -0
- package/dist/{index-CQDIrK1k.d.ts → index-BJU79_T5.d.ts} +1 -1
- package/dist/index-BJU79_T5.d.ts.map +1 -0
- package/dist/index.d.ts +5 -6
- package/dist/mock-BcFWPQyK.d.ts +22 -0
- package/dist/mock-BcFWPQyK.d.ts.map +1 -0
- package/dist/processors/index.d.ts +1 -1
- package/dist/registry/index.d.ts +5 -4
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +5 -4
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/manifest-slim.d.ts +1 -1
- package/dist/registry/manifest-slim.d.ts.map +1 -1
- package/dist/registry/urls.d.ts +11 -0
- package/dist/registry/urls.d.ts.map +1 -0
- package/dist/registry/urls.js +15 -0
- package/dist/registry/urls.js.map +1 -0
- package/dist/{types-CFCA0OvL.d.ts → types-CTTYein-.d.ts} +29 -3
- package/dist/{types-CFCA0OvL.d.ts.map → types-CTTYein-.d.ts.map} +1 -1
- package/dist/{types-Bo4Rb8Yf.d.ts → types-DlBC9pV6.d.ts} +439 -12
- package/dist/types-DlBC9pV6.d.ts.map +1 -0
- package/dist/{utils-BEByNOHo.d.ts → utils-1EXU6Qr1.d.ts} +1 -1
- package/dist/utils-1EXU6Qr1.d.ts.map +1 -0
- package/dist/{utils-DKOFCM0Q.js → utils-CTt1XRn-.js} +36 -2
- package/dist/utils-CTt1XRn-.js.map +1 -0
- package/package.json +1 -1
- package/dist/clis/args/common-dbsvksvf.mjs.map +0 -1
- package/dist/config-loader-19K8n4DO.js.map +0 -1
- package/dist/constants-C_EQX4DY.d.ts.map +0 -1
- package/dist/index-CQDIrK1k.d.ts.map +0 -1
- package/dist/index-DLx4nU83.d.ts +0 -399
- package/dist/index-DLx4nU83.d.ts.map +0 -1
- package/dist/mock-CBcf78ra.d.ts +0 -7
- package/dist/mock-CBcf78ra.d.ts.map +0 -1
- package/dist/types-Bo4Rb8Yf.d.ts.map +0 -1
- package/dist/types-D0NNBVGA.d.ts +0 -167
- package/dist/types-D0NNBVGA.d.ts.map +0 -1
- package/dist/utils-BEByNOHo.d.ts.map +0 -1
- package/dist/utils-DKOFCM0Q.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as base64UrlToBytes, c as getUnixTimestamp, i as base58Decode, l as hashBrowser, n as generateId, o as buildOperationSignatureMessage, r as ab2hex, s as buildOperationSignatureParams, t as deriveOperationId, u as hex2ab } from "../utils-CTt1XRn-.js";
|
|
2
2
|
import { generateMock } from "./mock.js";
|
|
3
3
|
import { ZodError, z } from "zod";
|
|
4
4
|
import { stringify } from "safe-stable-stringify";
|
|
@@ -39,6 +39,75 @@ var DowngradeNotSupportedError = class extends Error {
|
|
|
39
39
|
this.toVersion = toVersion;
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Thrown when INITIALIZE_AUTH is applied to an already-initialized auth scope.
|
|
44
|
+
* The genesis action is valid only at auth revision zero.
|
|
45
|
+
*/
|
|
46
|
+
var AuthAlreadyInitializedError = class extends Error {
|
|
47
|
+
documentId;
|
|
48
|
+
constructor(documentId) {
|
|
49
|
+
super(`Auth scope already initialized for document ${documentId}: INITIALIZE_AUTH is valid only at auth revision zero`);
|
|
50
|
+
this.name = "AuthAlreadyInitializedError";
|
|
51
|
+
this.documentId = documentId;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when INITIALIZE_AUTH is not signed by the document creator (its signer
|
|
56
|
+
* does not match `header.sig.publicKey`), so it cannot set the auth policy.
|
|
57
|
+
*/
|
|
58
|
+
var AuthInitializerNotCreatorError = class extends Error {
|
|
59
|
+
documentId;
|
|
60
|
+
constructor(documentId) {
|
|
61
|
+
super(`INITIALIZE_AUTH for document ${documentId} must be signed by the document creator`);
|
|
62
|
+
this.name = "AuthInitializerNotCreatorError";
|
|
63
|
+
this.documentId = documentId;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Thrown when INITIALIZE_AUTH carries a version below 1. Version 0 is reserved
|
|
68
|
+
* for the uninitialized auth scope.
|
|
69
|
+
*/
|
|
70
|
+
var InvalidAuthVersionError = class extends Error {
|
|
71
|
+
documentId;
|
|
72
|
+
version;
|
|
73
|
+
constructor(documentId, version) {
|
|
74
|
+
super(`Invalid auth policy version ${version} for document ${documentId}: INITIALIZE_AUTH requires an integer version >= 1`);
|
|
75
|
+
this.name = "InvalidAuthVersionError";
|
|
76
|
+
this.documentId = documentId;
|
|
77
|
+
this.version = version;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** Thrown when a duplicate would lose the source policy's version or creator binding. */
|
|
81
|
+
var AuthPolicyNotPreservedError = class extends Error {
|
|
82
|
+
documentId;
|
|
83
|
+
constructor(documentId) {
|
|
84
|
+
super(`Duplicating document ${documentId} would not preserve its auth policy: the copy loses the policy version or its creator binding`);
|
|
85
|
+
this.name = "AuthPolicyNotPreservedError";
|
|
86
|
+
this.documentId = documentId;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Thrown when a grant referenced by REMOVE_GRANT or MOVE_GRANT does not exist.
|
|
91
|
+
*/
|
|
92
|
+
var GrantNotFoundError = class extends Error {
|
|
93
|
+
grantId;
|
|
94
|
+
constructor(grantId) {
|
|
95
|
+
super(`Grant not found in auth scope: ${grantId}`);
|
|
96
|
+
this.name = "GrantNotFoundError";
|
|
97
|
+
this.grantId = grantId;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Thrown when a disallowed action (UNDO, REDO, PRUNE) targets the auth scope.
|
|
102
|
+
*/
|
|
103
|
+
var AuthActionNotAllowedError = class extends Error {
|
|
104
|
+
actionType;
|
|
105
|
+
constructor(actionType) {
|
|
106
|
+
super(`${actionType} is not permitted on the auth scope`);
|
|
107
|
+
this.name = "AuthActionNotAllowedError";
|
|
108
|
+
this.actionType = actionType;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
42
111
|
var HashMismatchError = class extends Error {
|
|
43
112
|
_scope;
|
|
44
113
|
_document;
|
|
@@ -65,6 +134,25 @@ var HashMismatchError = class extends Error {
|
|
|
65
134
|
return this._operation;
|
|
66
135
|
}
|
|
67
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* Thrown when replay or import requires a document model version that is not
|
|
139
|
+
* registered. Carries the data the UI needs to explain the mismatch.
|
|
140
|
+
*/
|
|
141
|
+
var UnsupportedDocumentModelVersionError = class extends Error {
|
|
142
|
+
documentType;
|
|
143
|
+
requiredVersion;
|
|
144
|
+
availableVersions;
|
|
145
|
+
constructor(documentType, requiredVersion, availableVersions) {
|
|
146
|
+
super(`No reducer registered for document version ${requiredVersion}. Available versions: ${availableVersions.join(", ")}`);
|
|
147
|
+
this.name = "UnsupportedDocumentModelVersionError";
|
|
148
|
+
this.documentType = documentType;
|
|
149
|
+
this.requiredVersion = requiredVersion;
|
|
150
|
+
this.availableVersions = availableVersions;
|
|
151
|
+
}
|
|
152
|
+
static isError(error) {
|
|
153
|
+
return Error.isError(error) && error.name === "UnsupportedDocumentModelVersionError";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
68
156
|
//#endregion
|
|
69
157
|
//#region document-model/schemas.ts
|
|
70
158
|
const isDefinedNonNullAny = (v) => v !== void 0 && v !== null;
|
|
@@ -553,17 +641,102 @@ const ConfigEntrySchema = z.object({
|
|
|
553
641
|
required: z.boolean().optional(),
|
|
554
642
|
default: z.boolean().optional()
|
|
555
643
|
});
|
|
644
|
+
const PwaUrlPatternSchema = z.union([z.string(), z.strictObject({
|
|
645
|
+
source: z.string(),
|
|
646
|
+
flags: z.string().optional()
|
|
647
|
+
}).refine((p) => {
|
|
648
|
+
try {
|
|
649
|
+
new RegExp(p.source, p.flags);
|
|
650
|
+
return true;
|
|
651
|
+
} catch {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}, { message: "source/flags do not compile to a valid RegExp" })]);
|
|
655
|
+
const PwaRuntimeCachingSchema = z.strictObject({
|
|
656
|
+
urlPattern: PwaUrlPatternSchema,
|
|
657
|
+
handler: z.enum([
|
|
658
|
+
"CacheFirst",
|
|
659
|
+
"CacheOnly",
|
|
660
|
+
"NetworkFirst",
|
|
661
|
+
"NetworkOnly",
|
|
662
|
+
"StaleWhileRevalidate"
|
|
663
|
+
]),
|
|
664
|
+
method: z.enum([
|
|
665
|
+
"GET",
|
|
666
|
+
"POST",
|
|
667
|
+
"PUT",
|
|
668
|
+
"DELETE",
|
|
669
|
+
"HEAD",
|
|
670
|
+
"PATCH"
|
|
671
|
+
]).optional(),
|
|
672
|
+
options: z.strictObject({
|
|
673
|
+
cacheName: z.string().optional(),
|
|
674
|
+
networkTimeoutSeconds: z.number().optional(),
|
|
675
|
+
expiration: z.strictObject({
|
|
676
|
+
maxEntries: z.number().optional(),
|
|
677
|
+
maxAgeSeconds: z.number().optional()
|
|
678
|
+
}).optional(),
|
|
679
|
+
cacheableResponse: z.strictObject({
|
|
680
|
+
statuses: z.array(z.number()).optional(),
|
|
681
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
682
|
+
}).optional()
|
|
683
|
+
}).optional()
|
|
684
|
+
});
|
|
685
|
+
const PwaIconSchema = z.strictObject({
|
|
686
|
+
src: z.string(),
|
|
687
|
+
sizes: z.string().optional(),
|
|
688
|
+
type: z.string().optional(),
|
|
689
|
+
purpose: z.string().optional()
|
|
690
|
+
});
|
|
691
|
+
const PwaFileHandlerSchema = z.strictObject({
|
|
692
|
+
accept: z.record(z.string(), z.array(z.string().regex(/^\./, "file extensions must start with '.'"))),
|
|
693
|
+
icons: z.array(PwaIconSchema).optional(),
|
|
694
|
+
launch_type: z.enum(["single-client", "multiple-clients"]).optional()
|
|
695
|
+
});
|
|
696
|
+
const PwaManifestOverrideSchema = z.strictObject({
|
|
697
|
+
name: z.string().optional(),
|
|
698
|
+
short_name: z.string().optional(),
|
|
699
|
+
description: z.string().optional(),
|
|
700
|
+
theme_color: z.string().optional(),
|
|
701
|
+
background_color: z.string().optional(),
|
|
702
|
+
display: z.enum([
|
|
703
|
+
"fullscreen",
|
|
704
|
+
"standalone",
|
|
705
|
+
"minimal-ui",
|
|
706
|
+
"browser"
|
|
707
|
+
]).optional(),
|
|
708
|
+
start_url: z.string().optional(),
|
|
709
|
+
scope: z.string().optional(),
|
|
710
|
+
icons: z.array(PwaIconSchema).optional(),
|
|
711
|
+
file_handlers: z.array(PwaFileHandlerSchema).optional(),
|
|
712
|
+
launch_handler: z.strictObject({ client_mode: z.enum([
|
|
713
|
+
"auto",
|
|
714
|
+
"focus-existing",
|
|
715
|
+
"navigate-existing",
|
|
716
|
+
"navigate-new"
|
|
717
|
+
]) }).optional()
|
|
718
|
+
});
|
|
719
|
+
const PwaConfigSchema = z.strictObject({
|
|
720
|
+
manifest: PwaManifestOverrideSchema.optional(),
|
|
721
|
+
globPatterns: z.array(z.string()).optional(),
|
|
722
|
+
globIgnores: z.array(z.string()).optional(),
|
|
723
|
+
maximumFileSizeToCacheInBytes: z.number().optional(),
|
|
724
|
+
runtimeCaching: z.array(PwaRuntimeCachingSchema).optional(),
|
|
725
|
+
navigateFallbackDenylist: z.array(PwaUrlPatternSchema).optional()
|
|
726
|
+
});
|
|
556
727
|
const ManifestSchema = z.object({
|
|
557
728
|
name: z.string(),
|
|
558
729
|
description: z.string().optional(),
|
|
559
730
|
category: z.string().optional(),
|
|
731
|
+
image: z.string().optional(),
|
|
560
732
|
publisher: PublisherSchema.optional(),
|
|
561
733
|
documentModels: PowerhouseModulesSchema,
|
|
562
734
|
apps: PowerhouseModulesSchema,
|
|
563
735
|
editors: PowerhouseModulesSchema,
|
|
564
736
|
processors: PowerhouseModulesSchema,
|
|
565
737
|
subgraphs: PowerhouseModulesSchema,
|
|
566
|
-
config: z.array(ConfigEntrySchema).optional()
|
|
738
|
+
config: z.array(ConfigEntrySchema).optional(),
|
|
739
|
+
pwa: PwaConfigSchema.optional()
|
|
567
740
|
});
|
|
568
741
|
//#endregion
|
|
569
742
|
//#region document-model/actions.ts
|
|
@@ -849,6 +1022,542 @@ const actions = {
|
|
|
849
1022
|
...documentModelActions
|
|
850
1023
|
};
|
|
851
1024
|
//#endregion
|
|
1025
|
+
//#region document-model/document-type.ts
|
|
1026
|
+
const documentModelDocumentType = "powerhouse/document-model";
|
|
1027
|
+
const groupDocumentType = "powerhouse/reactor-group";
|
|
1028
|
+
/**
|
|
1029
|
+
* The group-model action types that change membership. The groups projection
|
|
1030
|
+
* filters its reads to these, so any other group operation is invisible to a
|
|
1031
|
+
* decision. Kept here so the reactor never depends on the group package; a
|
|
1032
|
+
* reactor-group test guards against drift.
|
|
1033
|
+
*/
|
|
1034
|
+
const groupMembershipActionTypes = ["ADD_MEMBER", "REMOVE_MEMBER"];
|
|
1035
|
+
//#endregion
|
|
1036
|
+
//#region document-model/auth-v1.ts
|
|
1037
|
+
/** Maximum number of grants in a policy. */
|
|
1038
|
+
const MAX_AUTH_GRANTS = 100;
|
|
1039
|
+
/** Maximum nesting depth of a condition tree. */
|
|
1040
|
+
const MAX_CONDITION_DEPTH = 10;
|
|
1041
|
+
/** Maximum node count (conditions plus operands) of a condition tree. */
|
|
1042
|
+
const MAX_CONDITION_NODES = 100;
|
|
1043
|
+
/** Maximum entries in an execute capability's operation list. */
|
|
1044
|
+
const MAX_CAPABILITY_OPERATIONS = 100;
|
|
1045
|
+
/**
|
|
1046
|
+
* Thrown when a grant violates the v1 validation rules. The message is stored
|
|
1047
|
+
* on error operations, so it must be a pure function of the input.
|
|
1048
|
+
*/
|
|
1049
|
+
var InvalidGrantError = class extends Error {
|
|
1050
|
+
grantId;
|
|
1051
|
+
constructor(grantId, problem) {
|
|
1052
|
+
super(`Invalid grant "${grantId}": ${problem}`);
|
|
1053
|
+
this.name = "InvalidGrantError";
|
|
1054
|
+
this.grantId = grantId;
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
/** Thrown for a `{ group }` principal on a group document: references never chain. */
|
|
1058
|
+
var GroupPrincipalNotAllowedError = class extends Error {
|
|
1059
|
+
grantId;
|
|
1060
|
+
constructor(grantId) {
|
|
1061
|
+
super(`Grant "${grantId}" uses a group principal on a group document: a group's auth scope cannot reference other groups`);
|
|
1062
|
+
this.name = "GroupPrincipalNotAllowedError";
|
|
1063
|
+
this.grantId = grantId;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
/**
|
|
1067
|
+
* Thrown when a change would leave a creator-less policy with no grant
|
|
1068
|
+
* permitting execute on the auth scope. Without the creator carve-out no
|
|
1069
|
+
* subject could ever administer such a policy again.
|
|
1070
|
+
*/
|
|
1071
|
+
var AuthAdministrationLockoutError = class extends Error {
|
|
1072
|
+
grantId;
|
|
1073
|
+
constructor(grantId) {
|
|
1074
|
+
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`);
|
|
1075
|
+
this.name = "AuthAdministrationLockoutError";
|
|
1076
|
+
this.grantId = grantId;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* Thrown when INITIALIZE_AUTH would create a creator-less policy with no grant
|
|
1081
|
+
* permitting execute on the auth scope. Such a policy would be born with no
|
|
1082
|
+
* subject able to administer it.
|
|
1083
|
+
*/
|
|
1084
|
+
var AuthAdministrationMissingError = class extends Error {
|
|
1085
|
+
constructor() {
|
|
1086
|
+
super("Initial grants include no reachable grant permitting execute on the auth scope: a policy with no creator must always include one");
|
|
1087
|
+
this.name = "AuthAdministrationMissingError";
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
const GRANT_KEYS = new Set([
|
|
1091
|
+
"id",
|
|
1092
|
+
"description",
|
|
1093
|
+
"effect",
|
|
1094
|
+
"principal",
|
|
1095
|
+
"capability",
|
|
1096
|
+
"where"
|
|
1097
|
+
]);
|
|
1098
|
+
const PRINCIPAL_KINDS = new Set([
|
|
1099
|
+
"anyone",
|
|
1100
|
+
"address",
|
|
1101
|
+
"group",
|
|
1102
|
+
"match"
|
|
1103
|
+
]);
|
|
1104
|
+
const COMPARISON_CONDITION_KINDS = new Set([
|
|
1105
|
+
"eq",
|
|
1106
|
+
"ne",
|
|
1107
|
+
"lt",
|
|
1108
|
+
"lte",
|
|
1109
|
+
"gt",
|
|
1110
|
+
"gte"
|
|
1111
|
+
]);
|
|
1112
|
+
function isPlainValue(value) {
|
|
1113
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1114
|
+
}
|
|
1115
|
+
function operandProblem(value, capabilityScope, budget) {
|
|
1116
|
+
budget.nodes -= 1;
|
|
1117
|
+
if (budget.nodes < 0) return `condition exceeds 100 nodes`;
|
|
1118
|
+
if (!isPlainValue(value)) return "operand must be an object";
|
|
1119
|
+
const keys = Object.keys(value);
|
|
1120
|
+
if (keys.length !== 1) return "operand must have exactly one of attr or lit";
|
|
1121
|
+
if (keys[0] === "attr") {
|
|
1122
|
+
const attr = value.attr;
|
|
1123
|
+
if (typeof attr !== "string" || attr.length === 0) return "attr must be a non-empty string";
|
|
1124
|
+
if (capabilityScope !== void 0 && capabilityScope !== "*" && attr.startsWith("doc.")) {
|
|
1125
|
+
const pathScope = attr.split(".")[1] ?? "";
|
|
1126
|
+
if (pathScope !== capabilityScope) return `condition path "${attr}" reads scope "${pathScope}" but the capability covers only scope "${capabilityScope}"`;
|
|
1127
|
+
}
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
if (keys[0] === "lit") {
|
|
1131
|
+
const lit = value.lit;
|
|
1132
|
+
if (lit !== null && typeof lit !== "string" && typeof lit !== "number" && typeof lit !== "boolean") return "lit must be a string, number, boolean, or null";
|
|
1133
|
+
if (typeof lit === "number" && !Number.isFinite(lit)) return "lit must be a finite number";
|
|
1134
|
+
if (typeof lit === "number" && Object.is(lit, -0)) return "lit must not be negative zero";
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
return `unknown operand kind "${keys[0]}"`;
|
|
1138
|
+
}
|
|
1139
|
+
function conditionProblem(value, capabilityScope, depth, budget) {
|
|
1140
|
+
if (depth > 10) return `condition exceeds depth 10`;
|
|
1141
|
+
budget.nodes -= 1;
|
|
1142
|
+
if (budget.nodes < 0) return `condition exceeds 100 nodes`;
|
|
1143
|
+
if (!isPlainValue(value)) return "condition must be an object";
|
|
1144
|
+
const keys = Object.keys(value);
|
|
1145
|
+
if (keys.length !== 1) return "condition must have exactly one operator";
|
|
1146
|
+
const kind = keys[0];
|
|
1147
|
+
const body = value[kind];
|
|
1148
|
+
if (COMPARISON_CONDITION_KINDS.has(kind)) {
|
|
1149
|
+
if (!Array.isArray(body) || body.length !== 2) return `${kind} requires a pair of operands`;
|
|
1150
|
+
for (const operand of body) {
|
|
1151
|
+
const problem = operandProblem(operand, capabilityScope, budget);
|
|
1152
|
+
if (problem !== null) return problem;
|
|
1153
|
+
}
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
if (kind === "in" || kind === "notIn") {
|
|
1157
|
+
if (!Array.isArray(body) || body.length !== 2 || !Array.isArray(body[1])) return `${kind} requires an operand and an operand list`;
|
|
1158
|
+
const first = operandProblem(body[0], capabilityScope, budget);
|
|
1159
|
+
if (first !== null) return first;
|
|
1160
|
+
for (const operand of body[1]) {
|
|
1161
|
+
const problem = operandProblem(operand, capabilityScope, budget);
|
|
1162
|
+
if (problem !== null) return problem;
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
if (kind === "exists") return operandProblem(body, capabilityScope, budget);
|
|
1167
|
+
if (kind === "and" || kind === "or") {
|
|
1168
|
+
if (!Array.isArray(body)) return `${kind} requires a condition list`;
|
|
1169
|
+
for (const child of body) {
|
|
1170
|
+
const problem = conditionProblem(child, capabilityScope, depth + 1, budget);
|
|
1171
|
+
if (problem !== null) return problem;
|
|
1172
|
+
}
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
if (kind === "not") return conditionProblem(body, capabilityScope, depth + 1, budget);
|
|
1176
|
+
return `unknown condition operator "${kind}"`;
|
|
1177
|
+
}
|
|
1178
|
+
function principalProblem(value, capabilityScope) {
|
|
1179
|
+
if (!isPlainValue(value)) return "principal must be an object";
|
|
1180
|
+
const keys = Object.keys(value);
|
|
1181
|
+
if (keys.length !== 1 || !PRINCIPAL_KINDS.has(keys[0])) return "principal must have exactly one of anyone, address, group, or match";
|
|
1182
|
+
const kind = keys[0];
|
|
1183
|
+
if (kind === "anyone" && value.anyone !== true) return "anyone must be true";
|
|
1184
|
+
if (kind === "address") {
|
|
1185
|
+
const address = value.address;
|
|
1186
|
+
if (typeof address !== "string" || address.length === 0) return "address must be a non-empty string";
|
|
1187
|
+
}
|
|
1188
|
+
if (kind === "group") {
|
|
1189
|
+
const group = value.group;
|
|
1190
|
+
if (typeof group !== "string" || group.length === 0) return "group must be a non-empty string";
|
|
1191
|
+
}
|
|
1192
|
+
if (kind === "match") return conditionProblem(value.match, capabilityScope, 1, { nodes: 100 });
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
function capabilityProblem(value) {
|
|
1196
|
+
if (!isPlainValue(value)) return "capability must be an object";
|
|
1197
|
+
const can = value.can;
|
|
1198
|
+
if (can !== "read" && can !== "execute") return "capability.can must be read or execute";
|
|
1199
|
+
const allowedKeys = can === "execute" ? [
|
|
1200
|
+
"can",
|
|
1201
|
+
"scope",
|
|
1202
|
+
"operation"
|
|
1203
|
+
] : ["can", "scope"];
|
|
1204
|
+
const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key)).sort();
|
|
1205
|
+
if (unknownKeys.length > 0) return `unknown capability key "${unknownKeys[0]}"`;
|
|
1206
|
+
if (value.scope !== void 0) {
|
|
1207
|
+
if (typeof value.scope !== "string" || value.scope.length === 0) return "capability.scope must be a non-empty string";
|
|
1208
|
+
}
|
|
1209
|
+
if (can === "execute" && value.operation !== void 0) {
|
|
1210
|
+
const operation = value.operation;
|
|
1211
|
+
if (!Array.isArray(operation)) return "capability.operation must be an array";
|
|
1212
|
+
if (operation.length > 100) return `capability.operation exceeds 100 entries`;
|
|
1213
|
+
for (const entry of operation) if (typeof entry !== "string" || entry.length === 0) return "capability.operation entries must be non-empty strings";
|
|
1214
|
+
}
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
/** Returns the first v1-rule violation, or null. Pure, total, deterministic. */
|
|
1218
|
+
function grantProblem(value) {
|
|
1219
|
+
if (!isPlainValue(value)) return "grant must be an object";
|
|
1220
|
+
const unknownKeys = Object.keys(value).filter((key) => !GRANT_KEYS.has(key)).sort();
|
|
1221
|
+
if (unknownKeys.length > 0) return `unknown grant key "${unknownKeys[0]}"`;
|
|
1222
|
+
if (typeof value.id !== "string" || value.id.length === 0) return "id must be a non-empty string";
|
|
1223
|
+
if (typeof value.description !== "string") return "description must be a string";
|
|
1224
|
+
if (value.effect !== "allow" && value.effect !== "deny") return "effect must be allow or deny";
|
|
1225
|
+
const capabilityValue = value.capability;
|
|
1226
|
+
const capability = capabilityProblem(capabilityValue);
|
|
1227
|
+
if (capability !== null) return capability;
|
|
1228
|
+
const capabilityScope = capabilityValue.scope;
|
|
1229
|
+
const principal = principalProblem(value.principal, capabilityScope);
|
|
1230
|
+
if (principal !== null) return principal;
|
|
1231
|
+
if (value.where !== void 0) return conditionProblem(value.where, capabilityScope, 1, { nodes: 100 });
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
const GrantSchema = () => z.custom((value) => grantProblem(value) === null);
|
|
1235
|
+
/** V1 shape rules plus the group-document group-principal ban. */
|
|
1236
|
+
function assertValidGrant(grant, documentType) {
|
|
1237
|
+
const grantId = isPlainValue(grant) && typeof grant.id === "string" ? grant.id : "";
|
|
1238
|
+
const problem = grantProblem(grant);
|
|
1239
|
+
if (problem !== null) throw new InvalidGrantError(grantId, problem);
|
|
1240
|
+
if (documentType === "powerhouse/reactor-group" && "group" in grant.principal) throw new GroupPrincipalNotAllowedError(grantId);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Validates an initial grant list: the count cap, every grant, and — on a
|
|
1244
|
+
* creator-less policy — that some grant keeps the auth scope administrable.
|
|
1245
|
+
*/
|
|
1246
|
+
function assertValidInitialGrants(grants, documentType, creator) {
|
|
1247
|
+
if (grants.length > 100) throw new InvalidGrantError("", `policy exceeds 100 grants`);
|
|
1248
|
+
for (const grant of grants) assertValidGrant(grant, documentType);
|
|
1249
|
+
if (creator === void 0 && !administrationReachable(grants)) throw new AuthAdministrationMissingError();
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Validates a grant upsert: the grant itself, the count cap on append, and
|
|
1253
|
+
* administration retention. Retention is checked on an append as well as an
|
|
1254
|
+
* in-place replace, because a grant appended after the administration grant can
|
|
1255
|
+
* shadow it (evaluation is last-applicable-grant-wins) and so take
|
|
1256
|
+
* administration away without removing anything.
|
|
1257
|
+
*/
|
|
1258
|
+
function assertValidGrantUpsert(grant, existing, documentType, creator) {
|
|
1259
|
+
assertValidGrant(grant, documentType);
|
|
1260
|
+
const exists = existing.some((g) => g.id === grant.id);
|
|
1261
|
+
if (!exists && existing.length >= 100) throw new InvalidGrantError(grant.id, `policy exceeds 100 grants`);
|
|
1262
|
+
assertAuthAdministrationRetained(creator, existing, exists ? existing.map((g) => g.id === grant.id ? grant : g) : [...existing, grant], grant.id);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* The request whose coverage keeps a policy administrable: a subject who may
|
|
1266
|
+
* SET_GRANT can upsert any grant, so every other repair stays reachable.
|
|
1267
|
+
*/
|
|
1268
|
+
const AUTH_ADMINISTRATION_REQUEST = {
|
|
1269
|
+
verb: "execute",
|
|
1270
|
+
scope: "auth",
|
|
1271
|
+
operation: "SET_GRANT"
|
|
1272
|
+
};
|
|
1273
|
+
/**
|
|
1274
|
+
* Whether some subject can still administer the auth scope under this grant list.
|
|
1275
|
+
*
|
|
1276
|
+
* Asks the evaluator rather than pattern-matching a single grant: evaluation is
|
|
1277
|
+
* last-applicable-grant-wins, so an allow that some later deny shadows keeps
|
|
1278
|
+
* nothing reachable even though it is still present in the list. Only anyone and
|
|
1279
|
+
* address principals are candidates, because those are the ones v1 can match at
|
|
1280
|
+
* all; a `where` condition or a group or match principal never applies.
|
|
1281
|
+
*/
|
|
1282
|
+
function administrationReachable(grants) {
|
|
1283
|
+
return grants.some((grant) => {
|
|
1284
|
+
const subject = administrationCandidate(grant);
|
|
1285
|
+
if (subject === void 0) return false;
|
|
1286
|
+
return evaluateGrantStack(grants, subject, AUTH_ADMINISTRATION_REQUEST).decision === "allow";
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* The subject to test this grant with, or undefined when the grant could never
|
|
1291
|
+
* carry administration for anyone.
|
|
1292
|
+
*/
|
|
1293
|
+
function administrationCandidate(grant) {
|
|
1294
|
+
if (grant.effect !== "allow" || grant.where !== void 0 || !capabilityCovers(grant.capability, AUTH_ADMINISTRATION_REQUEST)) return;
|
|
1295
|
+
if ("address" in grant.principal) return {
|
|
1296
|
+
address: grant.principal.address,
|
|
1297
|
+
key: void 0
|
|
1298
|
+
};
|
|
1299
|
+
if ("anyone" in grant.principal) return {
|
|
1300
|
+
address: void 0,
|
|
1301
|
+
key: void 0
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* A creator-less policy must always retain a grant permitting execute on the
|
|
1306
|
+
* auth scope; on a signed document the creator carve-out keeps administration
|
|
1307
|
+
* reachable instead. Rejects a change that takes the last such grant away. A
|
|
1308
|
+
* policy already without one is left alone: the change is not what locks it.
|
|
1309
|
+
*/
|
|
1310
|
+
function assertAuthAdministrationRetained(creator, previous, next, grantId) {
|
|
1311
|
+
if (creator !== void 0) return;
|
|
1312
|
+
if (administrationReachable(previous) && !administrationReachable(next)) throw new AuthAdministrationLockoutError(grantId);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* An operand whose shape validation would have rejected. Distinguished from
|
|
1316
|
+
* an unresolved path so a structurally malformed operand poisons its whole
|
|
1317
|
+
* condition to false rather than reading as "absent", which `not` would
|
|
1318
|
+
* otherwise widen to true.
|
|
1319
|
+
*/
|
|
1320
|
+
const INVALID_OPERAND = Symbol("invalid-operand");
|
|
1321
|
+
/**
|
|
1322
|
+
* Narrows to the values conditions compare. An object, array, or non-finite
|
|
1323
|
+
* number resolves to undefined, and every comparison involving undefined is
|
|
1324
|
+
* false.
|
|
1325
|
+
*/
|
|
1326
|
+
function asConditionValue(value) {
|
|
1327
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
1328
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Resolves one operand. Attr roots: `subject.*`, `doc.<scope>.*` where the
|
|
1332
|
+
* scope must be the executing scope (validation already rejects any other,
|
|
1333
|
+
* but resolution stays total), and `action.input.*`. Path steps read own
|
|
1334
|
+
* properties only, so prototype members can never influence a verdict.
|
|
1335
|
+
*/
|
|
1336
|
+
function resolveOperand(operand, subject, request, conditions) {
|
|
1337
|
+
if (!isPlainValue(operand)) return INVALID_OPERAND;
|
|
1338
|
+
const keys = Object.keys(operand);
|
|
1339
|
+
if (keys.length !== 1) return INVALID_OPERAND;
|
|
1340
|
+
if (keys[0] === "lit") {
|
|
1341
|
+
const value = asConditionValue(operand.lit);
|
|
1342
|
+
return value === void 0 ? INVALID_OPERAND : value;
|
|
1343
|
+
}
|
|
1344
|
+
if (keys[0] !== "attr") return INVALID_OPERAND;
|
|
1345
|
+
const attr = operand.attr;
|
|
1346
|
+
if (typeof attr !== "string" || attr.length === 0) return INVALID_OPERAND;
|
|
1347
|
+
const path = attr.split(".");
|
|
1348
|
+
let value;
|
|
1349
|
+
let rest;
|
|
1350
|
+
if (path[0] === "subject") {
|
|
1351
|
+
value = subject;
|
|
1352
|
+
rest = path.slice(1);
|
|
1353
|
+
} else if (path[0] === "doc") {
|
|
1354
|
+
if (path[1] !== request.scope) return;
|
|
1355
|
+
value = conditions.scopeState;
|
|
1356
|
+
rest = path.slice(2);
|
|
1357
|
+
} else if (path[0] === "action" && path[1] === "input") {
|
|
1358
|
+
value = conditions.actionInput;
|
|
1359
|
+
rest = path.slice(2);
|
|
1360
|
+
} else return;
|
|
1361
|
+
for (const segment of rest) {
|
|
1362
|
+
if (!isPlainValue(value) || !Object.hasOwn(value, segment)) return;
|
|
1363
|
+
value = value[segment];
|
|
1364
|
+
}
|
|
1365
|
+
return asConditionValue(value);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Total order within one type: numbers numerically, strings by code point.
|
|
1369
|
+
* Everything else, including mixed types, does not order.
|
|
1370
|
+
*/
|
|
1371
|
+
function compareValues(left, right) {
|
|
1372
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
|
|
1373
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
1374
|
+
const leftPoints = Array.from(left);
|
|
1375
|
+
const rightPoints = Array.from(right);
|
|
1376
|
+
const shared = Math.min(leftPoints.length, rightPoints.length);
|
|
1377
|
+
for (let i = 0; i < shared; i++) {
|
|
1378
|
+
const a = leftPoints[i].codePointAt(0) ?? 0;
|
|
1379
|
+
const b = rightPoints[i].codePointAt(0) ?? 0;
|
|
1380
|
+
if (a !== b) return a < b ? -1 : 1;
|
|
1381
|
+
}
|
|
1382
|
+
return leftPoints.length === rightPoints.length ? 0 : leftPoints.length < rightPoints.length ? -1 : 1;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Tri-state evaluation: undefined marks a structurally invalid node, which
|
|
1387
|
+
* poisons the whole tree to false at the top — and a structurally malformed
|
|
1388
|
+
* operand poisons its condition the same way. Both are distinct from an
|
|
1389
|
+
* operand whose path fails to resolve, which is a valid comparison that is
|
|
1390
|
+
* false. The distinction keeps `not` from widening over malformed input.
|
|
1391
|
+
*/
|
|
1392
|
+
function evaluateNode(node, subject, request, conditions) {
|
|
1393
|
+
if (!isPlainValue(node)) return;
|
|
1394
|
+
const keys = Object.keys(node);
|
|
1395
|
+
if (keys.length !== 1) return;
|
|
1396
|
+
const kind = keys[0];
|
|
1397
|
+
const body = node[kind];
|
|
1398
|
+
switch (kind) {
|
|
1399
|
+
case "eq":
|
|
1400
|
+
case "ne":
|
|
1401
|
+
case "lt":
|
|
1402
|
+
case "lte":
|
|
1403
|
+
case "gt":
|
|
1404
|
+
case "gte": {
|
|
1405
|
+
if (!Array.isArray(body) || body.length !== 2) return;
|
|
1406
|
+
const left = resolveOperand(body[0], subject, request, conditions);
|
|
1407
|
+
const right = resolveOperand(body[1], subject, request, conditions);
|
|
1408
|
+
if (left === INVALID_OPERAND || right === INVALID_OPERAND) return;
|
|
1409
|
+
if (left === void 0 || right === void 0) return false;
|
|
1410
|
+
if (kind === "eq") return left === right;
|
|
1411
|
+
if (kind === "ne") return left !== right;
|
|
1412
|
+
const order = compareValues(left, right);
|
|
1413
|
+
if (order === void 0) return false;
|
|
1414
|
+
switch (kind) {
|
|
1415
|
+
case "lt": return order < 0;
|
|
1416
|
+
case "lte": return order <= 0;
|
|
1417
|
+
case "gt": return order > 0;
|
|
1418
|
+
case "gte": return order >= 0;
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
case "in":
|
|
1423
|
+
case "notIn": {
|
|
1424
|
+
if (!Array.isArray(body) || body.length !== 2 || !Array.isArray(body[1])) return;
|
|
1425
|
+
const left = resolveOperand(body[0], subject, request, conditions);
|
|
1426
|
+
if (left === INVALID_OPERAND) return;
|
|
1427
|
+
const elements = body[1].map((element) => resolveOperand(element, subject, request, conditions));
|
|
1428
|
+
if (elements.some((value) => value === INVALID_OPERAND)) return;
|
|
1429
|
+
if (left === void 0) return false;
|
|
1430
|
+
const found = elements.some((value) => value !== void 0 && value === left);
|
|
1431
|
+
return kind === "in" ? found : !found;
|
|
1432
|
+
}
|
|
1433
|
+
case "exists": {
|
|
1434
|
+
const value = resolveOperand(body, subject, request, conditions);
|
|
1435
|
+
if (value === INVALID_OPERAND) return;
|
|
1436
|
+
return value !== void 0;
|
|
1437
|
+
}
|
|
1438
|
+
case "and":
|
|
1439
|
+
case "or": {
|
|
1440
|
+
if (!Array.isArray(body)) return;
|
|
1441
|
+
let result = kind === "and";
|
|
1442
|
+
for (const child of body) {
|
|
1443
|
+
const value = evaluateNode(child, subject, request, conditions);
|
|
1444
|
+
if (value === void 0) return;
|
|
1445
|
+
if (kind === "and") result = result && value;
|
|
1446
|
+
else result = result || value;
|
|
1447
|
+
}
|
|
1448
|
+
return result;
|
|
1449
|
+
}
|
|
1450
|
+
case "not": {
|
|
1451
|
+
const value = evaluateNode(body, subject, request, conditions);
|
|
1452
|
+
return value === void 0 ? void 0 : !value;
|
|
1453
|
+
}
|
|
1454
|
+
default: return;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Evaluates a version-1 condition. Deterministic, total, and pure: any input
|
|
1459
|
+
* shape yields a boolean and never throws, and a malformed condition is
|
|
1460
|
+
* false. These are consensus semantics, versioned by `PHAuthState.version`;
|
|
1461
|
+
* changing them requires a new version.
|
|
1462
|
+
*/
|
|
1463
|
+
function evaluateCondition(condition, subject, request, conditions) {
|
|
1464
|
+
return evaluateNode(condition, subject, request, conditions) === true;
|
|
1465
|
+
}
|
|
1466
|
+
/** Whether a capability's scope reaches the requested one. */
|
|
1467
|
+
function scopeCovers(scope, requested) {
|
|
1468
|
+
return scope === void 0 || scope === "*" || scope === requested;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Whether one grant answers this request.
|
|
1472
|
+
*
|
|
1473
|
+
* A grant allowing execute on a scope also allows reading it: executing an
|
|
1474
|
+
* operation means reading the state it applies to, so permitting the write while
|
|
1475
|
+
* withholding the read would describe an access nobody could use. The converse
|
|
1476
|
+
* does not hold -- a read grant confers no write.
|
|
1477
|
+
*
|
|
1478
|
+
* Only an allow carries across. A deny on execute withholds the write and says
|
|
1479
|
+
* nothing about the read, so a policy locking writes down does not silently
|
|
1480
|
+
* revoke a read grant sitting before it. The operation list is not consulted
|
|
1481
|
+
* either: it restricts which operations may be executed, not whether the scope
|
|
1482
|
+
* is visible, and a read carries no operation to match against.
|
|
1483
|
+
*
|
|
1484
|
+
* Reads are not consensus -- no replica records a read, and the two read call
|
|
1485
|
+
* sites are both inside the read gate -- so this rule is not part of what makes
|
|
1486
|
+
* an operation valid, and does not need a policy version of its own.
|
|
1487
|
+
*/
|
|
1488
|
+
function grantAnswers(grant, request) {
|
|
1489
|
+
if (capabilityCovers(grant.capability, request)) return true;
|
|
1490
|
+
return request.verb === "read" && grant.effect === "allow" && grant.capability.can === "execute" && scopeCovers(grant.capability.scope, request.scope);
|
|
1491
|
+
}
|
|
1492
|
+
function capabilityCovers(capability, request) {
|
|
1493
|
+
if (capability.can !== request.verb) return false;
|
|
1494
|
+
if (!scopeCovers(capability.scope, request.scope)) return false;
|
|
1495
|
+
if (capability.can === "execute") {
|
|
1496
|
+
if (capability.operation === void 0) return true;
|
|
1497
|
+
return request.operation !== void 0 && capability.operation.includes(request.operation);
|
|
1498
|
+
}
|
|
1499
|
+
return true;
|
|
1500
|
+
}
|
|
1501
|
+
function principalMatches(principal, subject, request, groups, conditions) {
|
|
1502
|
+
if ("anyone" in principal) return true;
|
|
1503
|
+
if ("address" in principal) return subject.address !== void 0 && subject.address.toLowerCase() === principal.address.toLowerCase();
|
|
1504
|
+
if ("group" in principal) {
|
|
1505
|
+
if (groups === void 0 || subject.address === void 0) return false;
|
|
1506
|
+
const group = groups[principal.group];
|
|
1507
|
+
if (group === void 0 || !Array.isArray(group.members)) return false;
|
|
1508
|
+
const address = subject.address.toLowerCase();
|
|
1509
|
+
return group.members.some((member) => member.toLowerCase() === address);
|
|
1510
|
+
}
|
|
1511
|
+
if ("match" in principal) {
|
|
1512
|
+
if (conditions === void 0) return false;
|
|
1513
|
+
return evaluateCondition(principal.match, subject, request, conditions);
|
|
1514
|
+
}
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* The group document ids named by `{ group }` principals in a grant list, in
|
|
1519
|
+
* order of first appearance. These are the streams the groups projection reads.
|
|
1520
|
+
*/
|
|
1521
|
+
function referencedGroupIds(grants) {
|
|
1522
|
+
const ids = [];
|
|
1523
|
+
for (const grant of grants) if ("group" in grant.principal && !ids.includes(grant.principal.group)) ids.push(grant.principal.group);
|
|
1524
|
+
return ids;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Evaluates a v1 grant stack: default deny, last applicable grant wins, and
|
|
1528
|
+
* reports which grant decided it. Group principals match only against a
|
|
1529
|
+
* supplied groups map, and `where` clauses and { match } principals evaluate
|
|
1530
|
+
* only against a supplied condition context; a grant that uses an unsupplied
|
|
1531
|
+
* feature never applies.
|
|
1532
|
+
*/
|
|
1533
|
+
function evaluateGrantStack(grants, subject, request, groups, conditions) {
|
|
1534
|
+
let applicable;
|
|
1535
|
+
for (const grant of grants) {
|
|
1536
|
+
if (grant.where !== void 0) {
|
|
1537
|
+
if (conditions === void 0) continue;
|
|
1538
|
+
if (!evaluateCondition(grant.where, subject, request, conditions)) continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (grantAnswers(grant, request) && principalMatches(grant.principal, subject, request, groups, conditions)) applicable = grant;
|
|
1541
|
+
}
|
|
1542
|
+
if (applicable === void 0) return {
|
|
1543
|
+
decision: "deny",
|
|
1544
|
+
refusal: "no-applicable-grant"
|
|
1545
|
+
};
|
|
1546
|
+
if (applicable.effect === "deny") return {
|
|
1547
|
+
decision: "deny",
|
|
1548
|
+
refusal: "denied-by-grant",
|
|
1549
|
+
grantId: applicable.id
|
|
1550
|
+
};
|
|
1551
|
+
return { decision: "allow" };
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Evaluates a v1 grant stack: default deny, last applicable grant wins. This is
|
|
1555
|
+
* {@link evaluateGrantStack} with the reason dropped.
|
|
1556
|
+
*/
|
|
1557
|
+
function evaluateGrants(grants, subject, request, groups, conditions) {
|
|
1558
|
+
return evaluateGrantStack(grants, subject, request, groups, conditions).decision;
|
|
1559
|
+
}
|
|
1560
|
+
//#endregion
|
|
852
1561
|
//#region document-model/constants.ts
|
|
853
1562
|
const documentModelFileExtension = "phdm";
|
|
854
1563
|
const documentModelInitialLocalState = {};
|
|
@@ -1435,8 +2144,403 @@ const HASH_ALGORITHM_SHA512 = "sha512";
|
|
|
1435
2144
|
const HASH_ENCODING_BASE64 = "base64";
|
|
1436
2145
|
const HASH_ENCODING_HEX = "hex";
|
|
1437
2146
|
//#endregion
|
|
1438
|
-
//#region document-model/
|
|
1439
|
-
|
|
2147
|
+
//#region document-model/state.ts
|
|
2148
|
+
/**
|
|
2149
|
+
* Creates a default PHAuthState
|
|
2150
|
+
*/
|
|
2151
|
+
function defaultAuthState() {
|
|
2152
|
+
return {
|
|
2153
|
+
version: 0,
|
|
2154
|
+
grants: []
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Creates a default PHDocumentState
|
|
2159
|
+
*/
|
|
2160
|
+
function defaultDocumentState() {
|
|
2161
|
+
return {
|
|
2162
|
+
version: 0,
|
|
2163
|
+
hash: {
|
|
2164
|
+
algorithm: HASH_ALGORITHM_SHA1,
|
|
2165
|
+
encoding: HASH_ENCODING_BASE64
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Creates a default PHBaseState with auth and document properties
|
|
2171
|
+
*/
|
|
2172
|
+
function defaultBaseState() {
|
|
2173
|
+
return {
|
|
2174
|
+
auth: defaultAuthState(),
|
|
2175
|
+
document: defaultDocumentState()
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Creates a PHAuthState with the given properties
|
|
2180
|
+
*/
|
|
2181
|
+
function createAuthState(auth) {
|
|
2182
|
+
return {
|
|
2183
|
+
...defaultAuthState(),
|
|
2184
|
+
...auth
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Creates a PHDocumentState with the given properties
|
|
2189
|
+
*/
|
|
2190
|
+
function createDocumentState(document) {
|
|
2191
|
+
return {
|
|
2192
|
+
...defaultDocumentState(),
|
|
2193
|
+
...document
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Creates a PHBaseState with the given auth and document properties
|
|
2198
|
+
*/
|
|
2199
|
+
function createBaseState(auth, document) {
|
|
2200
|
+
return {
|
|
2201
|
+
auth: createAuthState(auth),
|
|
2202
|
+
document: createDocumentState(document)
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Backfills the auth scope to the default for legacy documents serialized with
|
|
2207
|
+
* an empty `auth`. Replaces only `state.auth`. Idempotent.
|
|
2208
|
+
*/
|
|
2209
|
+
function backfillAuthState(state) {
|
|
2210
|
+
return {
|
|
2211
|
+
...state,
|
|
2212
|
+
auth: createAuthState(state.auth)
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function defaultGlobalState() {
|
|
2216
|
+
return {
|
|
2217
|
+
...defaultBaseState(),
|
|
2218
|
+
author: {
|
|
2219
|
+
name: "",
|
|
2220
|
+
website: ""
|
|
2221
|
+
},
|
|
2222
|
+
description: "",
|
|
2223
|
+
extension: "",
|
|
2224
|
+
id: "",
|
|
2225
|
+
name: "",
|
|
2226
|
+
specifications: []
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function defaultLocalState() {
|
|
2230
|
+
return {};
|
|
2231
|
+
}
|
|
2232
|
+
function defaultPHState() {
|
|
2233
|
+
return {
|
|
2234
|
+
...defaultBaseState(),
|
|
2235
|
+
global: defaultGlobalState(),
|
|
2236
|
+
local: defaultLocalState()
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
function createGlobalState(state) {
|
|
2240
|
+
return {
|
|
2241
|
+
...defaultGlobalState(),
|
|
2242
|
+
...state || {}
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
function createLocalState(state) {
|
|
2246
|
+
return {
|
|
2247
|
+
...defaultLocalState(),
|
|
2248
|
+
...state || {}
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function createState(baseState, globalState, localState) {
|
|
2252
|
+
return {
|
|
2253
|
+
...createBaseState(baseState?.auth, baseState?.document),
|
|
2254
|
+
global: createGlobalState(globalState),
|
|
2255
|
+
local: createLocalState(localState)
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
//#endregion
|
|
2259
|
+
//#region document-model/auth.ts
|
|
2260
|
+
const AUTH_ACTION_TYPES = [
|
|
2261
|
+
"INITIALIZE_AUTH",
|
|
2262
|
+
"SET_GRANT",
|
|
2263
|
+
"REMOVE_GRANT",
|
|
2264
|
+
"MOVE_GRANT"
|
|
2265
|
+
];
|
|
2266
|
+
function isAuthAction(action) {
|
|
2267
|
+
return AUTH_ACTION_TYPES.includes(action.type);
|
|
2268
|
+
}
|
|
2269
|
+
/** Highest known policy version; decide() fails closed above it. */
|
|
2270
|
+
const MAX_SUPPORTED_AUTH_VERSION = 1;
|
|
2271
|
+
const InitializeAuthActionInputSchema = () => z.object({
|
|
2272
|
+
version: z.number().int().min(1),
|
|
2273
|
+
grants: z.array(GrantSchema()).max(100)
|
|
2274
|
+
});
|
|
2275
|
+
const SetGrantActionInputSchema = () => z.object({ grant: GrantSchema() });
|
|
2276
|
+
const RemoveGrantActionInputSchema = () => z.object({ id: z.string() });
|
|
2277
|
+
const MoveGrantActionInputSchema = () => z.object({
|
|
2278
|
+
id: z.string(),
|
|
2279
|
+
index: z.number()
|
|
2280
|
+
});
|
|
2281
|
+
const initializeAuth = (input) => createAction("INITIALIZE_AUTH", input, void 0, InitializeAuthActionInputSchema, "auth");
|
|
2282
|
+
const setGrant = (input) => createAction("SET_GRANT", input, void 0, SetGrantActionInputSchema, "auth");
|
|
2283
|
+
const removeGrant = (input) => createAction("REMOVE_GRANT", input, void 0, RemoveGrantActionInputSchema, "auth");
|
|
2284
|
+
const moveGrant = (input) => createAction("MOVE_GRANT", input, void 0, MoveGrantActionInputSchema, "auth");
|
|
2285
|
+
/**
|
|
2286
|
+
* Destructuring a null input (reachable via raw synced operations) would
|
|
2287
|
+
* store an engine-specific TypeError message on the error operation.
|
|
2288
|
+
*/
|
|
2289
|
+
function assertActionInputShape(input) {
|
|
2290
|
+
if (!isPlainValue(input)) throw new InvalidActionInputError({ input: "must be an object" });
|
|
2291
|
+
}
|
|
2292
|
+
function withGrants(document, grants) {
|
|
2293
|
+
return {
|
|
2294
|
+
...document,
|
|
2295
|
+
state: {
|
|
2296
|
+
...document.state,
|
|
2297
|
+
auth: {
|
|
2298
|
+
...document.state.auth,
|
|
2299
|
+
grants
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
const P256_PUBKEY_MULTICODEC = [128, 36];
|
|
2305
|
+
const DID_KEY_PREFIX = "did:key:z";
|
|
2306
|
+
function bytesEqual(a, b) {
|
|
2307
|
+
if (a.length !== b.length) return false;
|
|
2308
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2309
|
+
return true;
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* True when `signerKey` (an ActionSigner app key, a did:key) identifies the same
|
|
2313
|
+
* key recorded as the document creator. Returns false
|
|
2314
|
+
* when there is no creator (empty JWK) or no signer key.
|
|
2315
|
+
*/
|
|
2316
|
+
function isDocumentCreator(creatorKey, signerKey) {
|
|
2317
|
+
if (!creatorKey?.x || !creatorKey.y) return false;
|
|
2318
|
+
if (!signerKey || !signerKey.startsWith(DID_KEY_PREFIX)) return false;
|
|
2319
|
+
const decoded = base58Decode(signerKey.slice(9));
|
|
2320
|
+
if (!decoded || decoded.length !== 35) return false;
|
|
2321
|
+
if (decoded[0] !== P256_PUBKEY_MULTICODEC[0] || decoded[1] !== P256_PUBKEY_MULTICODEC[1]) return false;
|
|
2322
|
+
const parityPrefix = decoded[2];
|
|
2323
|
+
if (parityPrefix !== 2 && parityPrefix !== 3) return false;
|
|
2324
|
+
const didX = decoded.subarray(3, 35);
|
|
2325
|
+
const jwkX = base64UrlToBytes(creatorKey.x);
|
|
2326
|
+
const jwkY = base64UrlToBytes(creatorKey.y);
|
|
2327
|
+
if (jwkX.length !== 32 || jwkY.length !== 32) return false;
|
|
2328
|
+
if (!bytesEqual(didX, jwkX)) return false;
|
|
2329
|
+
return (jwkY[31] & 1) === 1 === (parityPrefix === 3);
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Sets the initial policy. Valid only while the auth scope is uninitialized
|
|
2333
|
+
* (version 0). The input version is the policy language version and must be an
|
|
2334
|
+
* integer >= 1; 0 is reserved for the uninitialized state. On a signed-header
|
|
2335
|
+
* document it must be signed by the document creator (`header.sig.publicKey`).
|
|
2336
|
+
* A creator-less policy must include a grant permitting execute on the auth
|
|
2337
|
+
* scope, or it would be born locked out.
|
|
2338
|
+
*/
|
|
2339
|
+
function applyInitializeAuthAction(document, action) {
|
|
2340
|
+
assertActionInputShape(action.input);
|
|
2341
|
+
const { version, grants } = action.input;
|
|
2342
|
+
if (!Number.isInteger(version) || version < 1) throw new InvalidAuthVersionError(document.header.id, version);
|
|
2343
|
+
if (document.state.auth.version !== 0) throw new AuthAlreadyInitializedError(document.header.id);
|
|
2344
|
+
if (!Array.isArray(grants)) throw new InvalidActionInputError({ grants: "must be an array" });
|
|
2345
|
+
const creatorKey = document.header.sig.publicKey;
|
|
2346
|
+
const signerKey = action.context?.signer?.app.key;
|
|
2347
|
+
const hasCreator = Boolean(creatorKey.kty || creatorKey.x || creatorKey.y);
|
|
2348
|
+
if (hasCreator && !isDocumentCreator(creatorKey, signerKey)) throw new AuthInitializerNotCreatorError(document.header.id);
|
|
2349
|
+
const creator = hasCreator ? signerKey : void 0;
|
|
2350
|
+
assertValidInitialGrants(grants, document.header.documentType, creator);
|
|
2351
|
+
return {
|
|
2352
|
+
...document,
|
|
2353
|
+
state: {
|
|
2354
|
+
...document.state,
|
|
2355
|
+
auth: createAuthState(creator ? {
|
|
2356
|
+
version,
|
|
2357
|
+
grants,
|
|
2358
|
+
creator
|
|
2359
|
+
} : {
|
|
2360
|
+
version,
|
|
2361
|
+
grants
|
|
2362
|
+
})
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
/** Upserts a grant by id: replaces in place if present, otherwise appends. */
|
|
2367
|
+
function applySetGrantAction(document, action) {
|
|
2368
|
+
assertActionInputShape(action.input);
|
|
2369
|
+
const { grant } = action.input;
|
|
2370
|
+
const grants = document.state.auth.grants;
|
|
2371
|
+
assertValidGrantUpsert(grant, grants, document.header.documentType, document.state.auth.creator);
|
|
2372
|
+
return withGrants(document, grants.some((g) => g.id === grant.id) ? grants.map((g) => g.id === grant.id ? grant : g) : [...grants, grant]);
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Removes a grant by id; throws if the id is not present or if the removal
|
|
2376
|
+
* would leave a creator-less policy with no auth-administration grant.
|
|
2377
|
+
*/
|
|
2378
|
+
function applyRemoveGrantAction(document, action) {
|
|
2379
|
+
assertActionInputShape(action.input);
|
|
2380
|
+
const { id } = action.input;
|
|
2381
|
+
const { grants, creator } = document.state.auth;
|
|
2382
|
+
if (!grants.some((g) => g.id === id)) throw new GrantNotFoundError(id);
|
|
2383
|
+
const next = grants.filter((g) => g.id !== id);
|
|
2384
|
+
assertAuthAdministrationRetained(creator, grants, next, id);
|
|
2385
|
+
return withGrants(document, next);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Moves a grant by id to a new index. Order is load-bearing (the last
|
|
2389
|
+
* applicable grant wins), so the relative order of the other grants is kept.
|
|
2390
|
+
* The target index is clamped to the valid range; an unknown id throws.
|
|
2391
|
+
*
|
|
2392
|
+
* Order alone decides which grant wins, so a move can take administration away
|
|
2393
|
+
* without changing the list's contents. It carries the same retention rule as
|
|
2394
|
+
* the two mutation paths.
|
|
2395
|
+
*/
|
|
2396
|
+
function applyMoveGrantAction(document, action) {
|
|
2397
|
+
assertActionInputShape(action.input);
|
|
2398
|
+
const { id, index } = action.input;
|
|
2399
|
+
const { grants, creator } = document.state.auth;
|
|
2400
|
+
const from = grants.findIndex((g) => g.id === id);
|
|
2401
|
+
if (from === -1) throw new GrantNotFoundError(id);
|
|
2402
|
+
const next = [...grants];
|
|
2403
|
+
const [moved] = next.splice(from, 1);
|
|
2404
|
+
const to = Math.max(0, Math.min(index, next.length));
|
|
2405
|
+
next.splice(to, 0, moved);
|
|
2406
|
+
assertAuthAdministrationRetained(creator, grants, next, id);
|
|
2407
|
+
return withGrants(document, next);
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Dispatches an auth-scope action to its handler. This is the auth scope's
|
|
2411
|
+
* dedicated reducer: it is applied by the base reducer instead of the model
|
|
2412
|
+
* reducer, mirroring the document-scope platform handlers. Unknown types are a
|
|
2413
|
+
* no-op, matching the model-reducer default.
|
|
2414
|
+
*/
|
|
2415
|
+
function applyAuthAction(document, action) {
|
|
2416
|
+
switch (action.type) {
|
|
2417
|
+
case "INITIALIZE_AUTH": return applyInitializeAuthAction(document, action);
|
|
2418
|
+
case "SET_GRANT": return applySetGrantAction(document, action);
|
|
2419
|
+
case "REMOVE_GRANT": return applyRemoveGrantAction(document, action);
|
|
2420
|
+
case "MOVE_GRANT": return applyMoveGrantAction(document, action);
|
|
2421
|
+
default: return document;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Because only creators can initialize auth scopes, we must verify that either
|
|
2426
|
+
* the document has no auth or the version and creator match.
|
|
2427
|
+
*/
|
|
2428
|
+
function assertAuthPreservedOnDuplicate(documentId, source, duplicated) {
|
|
2429
|
+
if (!source || source.version === 0) return;
|
|
2430
|
+
if (duplicated === void 0 || duplicated.version !== source.version || duplicated.creator !== source.creator) throw new AuthPolicyNotPreservedError(documentId);
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* The auth scope a state snapshot may install, given the policy already there.
|
|
2434
|
+
*
|
|
2435
|
+
* `applyAuthAction` is the validated door onto `state.auth`, but a whole-state
|
|
2436
|
+
* snapshot (UPGRADE_DOCUMENT's `initialState`, LOAD_STATE's `data`) replaces the
|
|
2437
|
+
* scope wholesale and is authorized as a `document`-scope write. Without this,
|
|
2438
|
+
* a subject holding `execute` on `document` and no auth grant at all can install
|
|
2439
|
+
* a policy of its choosing, name itself `creator` (which exempts the policy from
|
|
2440
|
+
* the retention rule for good), or wipe an existing policy by carrying the
|
|
2441
|
+
* default uninitialized one.
|
|
2442
|
+
*
|
|
2443
|
+
* Three cases:
|
|
2444
|
+
*
|
|
2445
|
+
* - the snapshot carries no policy, or an uninitialized one: the document
|
|
2446
|
+
* keeps the policy it has, so a default-state upgrade cannot reset it;
|
|
2447
|
+
* - the document is uninitialized and the snapshot carries a policy: the
|
|
2448
|
+
* policy is installed after the same validation genesis applies, so it
|
|
2449
|
+
* cannot be born locked out;
|
|
2450
|
+
* - both carry a policy: they must agree. A duplicate preserves its source's
|
|
2451
|
+
* policy (see {@link assertAuthPreservedOnDuplicate}), and anything else is
|
|
2452
|
+
* an attempt to replace one policy with another.
|
|
2453
|
+
*/
|
|
2454
|
+
function resolveSnapshotAuth(documentId, documentType, current, incoming) {
|
|
2455
|
+
const currentAuth = current ?? createAuthState({
|
|
2456
|
+
version: 0,
|
|
2457
|
+
grants: []
|
|
2458
|
+
});
|
|
2459
|
+
if (!incoming || !incoming.version) return currentAuth;
|
|
2460
|
+
if (currentAuth.version !== 0) {
|
|
2461
|
+
if (stringify(incoming) !== stringify(currentAuth)) throw new AuthPolicyNotPreservedError(documentId);
|
|
2462
|
+
return incoming;
|
|
2463
|
+
}
|
|
2464
|
+
if (!Array.isArray(incoming.grants)) throw new InvalidActionInputError({ grants: "must be an array" });
|
|
2465
|
+
assertValidInitialGrants(incoming.grants, documentType, incoming.creator);
|
|
2466
|
+
return incoming;
|
|
2467
|
+
}
|
|
2468
|
+
/** UNDO, REDO and PRUNE are rejected on the auth scope. */
|
|
2469
|
+
function assertAuthScopeActionAllowed(action) {
|
|
2470
|
+
if (action.scope === "auth" && [
|
|
2471
|
+
"UNDO",
|
|
2472
|
+
"REDO",
|
|
2473
|
+
"PRUNE"
|
|
2474
|
+
].includes(action.type)) throw new AuthActionNotAllowedError(action.type);
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Evaluates the auth policy for a single request and reports why it refused.
|
|
2478
|
+
* Pure and deterministic.
|
|
2479
|
+
*
|
|
2480
|
+
* An uninitialized policy (version 0, absent auth state, or a legacy `{}`
|
|
2481
|
+
* auth scope serialized before PHAuthState had a version) leaves the document
|
|
2482
|
+
* open. Once a policy exists the default is deny, and grants stack in order.
|
|
2483
|
+
*
|
|
2484
|
+
* Group principals match only against a supplied groups map (the groups
|
|
2485
|
+
* projection, present when authGroups is on); with no map they never apply.
|
|
2486
|
+
* `where` clauses and { match } principals likewise evaluate only against a
|
|
2487
|
+
* supplied condition context (present when authConditions is on).
|
|
2488
|
+
*/
|
|
2489
|
+
function evaluate(auth, subject, request, groups, conditions) {
|
|
2490
|
+
if (!auth || !auth.version) return { decision: "allow" };
|
|
2491
|
+
if (request.verb === "execute" && request.scope === "auth" && subject.key !== void 0 && subject.key === auth.creator) return { decision: "allow" };
|
|
2492
|
+
if (auth.version > 1) return {
|
|
2493
|
+
decision: "deny",
|
|
2494
|
+
refusal: "version-unsupported"
|
|
2495
|
+
};
|
|
2496
|
+
return evaluateGrantStack(auth.grants, subject, request, groups, conditions);
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Evaluates the auth policy for a single request. Pure and deterministic. This
|
|
2500
|
+
* is {@link evaluate} with the reason dropped.
|
|
2501
|
+
*/
|
|
2502
|
+
function decide(auth, subject, request, groups, conditions) {
|
|
2503
|
+
return evaluate(auth, subject, request, groups, conditions).decision;
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* The group document ids a single auth action's input names with `{ group }`
|
|
2507
|
+
* principals. INITIALIZE_AUTH contributes the groups named across its grants,
|
|
2508
|
+
* SET_GRANT the groups named by its one grant; REMOVE_GRANT and MOVE_GRANT
|
|
2509
|
+
* contribute nothing. Total over any input shape, because references are read
|
|
2510
|
+
* from the input as it arrived, including inputs later stored as errors.
|
|
2511
|
+
*/
|
|
2512
|
+
function mentionedGroupIds(action) {
|
|
2513
|
+
const input = action.input;
|
|
2514
|
+
const candidates = [];
|
|
2515
|
+
if (action.type === "INITIALIZE_AUTH" && Array.isArray(input?.grants)) candidates.push(...input.grants);
|
|
2516
|
+
if (action.type === "SET_GRANT" && input?.grant !== void 0) candidates.push(input.grant);
|
|
2517
|
+
const ids = [];
|
|
2518
|
+
for (const candidate of candidates) {
|
|
2519
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
2520
|
+
const principal = candidate.principal;
|
|
2521
|
+
if (typeof principal !== "object" || principal === null) continue;
|
|
2522
|
+
const group = principal.group;
|
|
2523
|
+
if (typeof group === "string" && group !== "" && !ids.includes(group)) ids.push(group);
|
|
2524
|
+
}
|
|
2525
|
+
return ids;
|
|
2526
|
+
}
|
|
2527
|
+
//#endregion
|
|
2528
|
+
//#region document-model/denied.ts
|
|
2529
|
+
/**
|
|
2530
|
+
* True iff authorization rejected the action.
|
|
2531
|
+
*/
|
|
2532
|
+
function isDenied(operation) {
|
|
2533
|
+
return operation.deniedReason !== void 0;
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* The closed set of strings persisted as `deniedReason`. Re-evaluation compares
|
|
2537
|
+
* them, so they are consensus data: exact strings that embed no grant id,
|
|
2538
|
+
* subject or timestamp. Changing one is history-visible.
|
|
2539
|
+
*/
|
|
2540
|
+
const DOCUMENT_DELETED_REASON = "document deleted";
|
|
2541
|
+
const AUTH_VERSION_UNSUPPORTED_REASON = "auth policy version unsupported";
|
|
2542
|
+
const AUTH_NO_GRANT_REASON = "no grant permits this operation";
|
|
2543
|
+
const AUTH_DENIED_BY_GRANT_REASON = "denied by grant";
|
|
1440
2544
|
//#endregion
|
|
1441
2545
|
//#region document-model/document-schema.ts
|
|
1442
2546
|
const BaseDocumentHeaderSchema = z.object({
|
|
@@ -1709,6 +2813,7 @@ function createDocumentScopeOperations(header, state) {
|
|
|
1709
2813
|
function baseCreateDocument(createState, initialState, documentType = "") {
|
|
1710
2814
|
const state = createState(initialState);
|
|
1711
2815
|
const header = createPresignedHeader(generateId(), documentType);
|
|
2816
|
+
header.protocolVersions = { "base-reducer": 2 };
|
|
1712
2817
|
return {
|
|
1713
2818
|
header,
|
|
1714
2819
|
state,
|
|
@@ -1804,6 +2909,18 @@ function mapSkippedOperationsV2(operations) {
|
|
|
1804
2909
|
* Uses the V2 model where consecutive NOOPs form chains.
|
|
1805
2910
|
* Unlike V1 garbageCollect, this preserves ALL operations but marks which to apply.
|
|
1806
2911
|
*/
|
|
2912
|
+
/**
|
|
2913
|
+
* The base-reducer protocol version a document is written against. Every
|
|
2914
|
+
* document carries one, set from the CREATE_DOCUMENT input. A header without
|
|
2915
|
+
* one was built outside that path, and choosing a version on its behalf would
|
|
2916
|
+
* replay the document through a reducer it was not written for, so it is an
|
|
2917
|
+
* error rather than a default.
|
|
2918
|
+
*/
|
|
2919
|
+
function baseReducerVersion(header) {
|
|
2920
|
+
const version = header.protocolVersions?.["base-reducer"];
|
|
2921
|
+
if (typeof version !== "number") throw new Error(`Document ${header.id} carries no base-reducer protocol version`);
|
|
2922
|
+
return version;
|
|
2923
|
+
}
|
|
1807
2924
|
function garbageCollectV2(sortedOperations) {
|
|
1808
2925
|
const result = [];
|
|
1809
2926
|
let noopChainLength = 0;
|
|
@@ -1823,9 +2940,26 @@ function sortMappedOperations(operations) {
|
|
|
1823
2940
|
const defaultCreateState = (state) => {
|
|
1824
2941
|
return state;
|
|
1825
2942
|
};
|
|
2943
|
+
/**
|
|
2944
|
+
* Records an operation in the history without applying it, which is what a
|
|
2945
|
+
* denied operation needs: it occupies its index and contributes no state.
|
|
2946
|
+
*
|
|
2947
|
+
* The scope defaults to the action's own, and is passed explicitly by a rebuild
|
|
2948
|
+
* that is walking one stream and does not want to trust the action's copy.
|
|
2949
|
+
*/
|
|
2950
|
+
function appendWithoutApplying(document, operation, scope = operation.action.scope) {
|
|
2951
|
+
return {
|
|
2952
|
+
...document,
|
|
2953
|
+
operations: {
|
|
2954
|
+
...document.operations,
|
|
2955
|
+
[scope]: [...document.operations[scope] ?? [], operation]
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
1826
2959
|
function replayDocument(initialState, operations, reducer, header, dispatch, skipHeaderOperations = {}, options) {
|
|
1827
2960
|
const { checkHashes = true, reuseOperationResultingState, operationResultingStateParser = parseResultingState, skipIndexValidation } = options || {};
|
|
1828
|
-
|
|
2961
|
+
const backfilledInitialState = backfillAuthState(initialState);
|
|
2962
|
+
let documentState = backfilledInitialState;
|
|
1829
2963
|
const operationsToReplay = [];
|
|
1830
2964
|
const allScopes = new Set([
|
|
1831
2965
|
...Object.keys(operations),
|
|
@@ -1860,12 +2994,13 @@ function replayDocument(initialState, operations, reducer, header, dispatch, ski
|
|
|
1860
2994
|
const document = {
|
|
1861
2995
|
header,
|
|
1862
2996
|
state: defaultCreateState(documentState),
|
|
1863
|
-
initialState,
|
|
2997
|
+
initialState: backfilledInitialState,
|
|
1864
2998
|
operations: initialOperations,
|
|
1865
2999
|
clipboard: []
|
|
1866
3000
|
};
|
|
1867
3001
|
let result = document;
|
|
1868
3002
|
if (operationsToReplay.length) result = operationsToReplay.reduce((document, operation) => {
|
|
3003
|
+
if (isDenied(operation)) return updateHeaderRevision(appendWithoutApplying(document, operation), operation.action.scope, operation.timestampUtcMs);
|
|
1869
3004
|
return reducer(document, operation.action, dispatch, {
|
|
1870
3005
|
ignoreSkipOperations: true,
|
|
1871
3006
|
checkHashes,
|
|
@@ -2389,14 +3524,31 @@ function redoOperation(document, action, skip) {
|
|
|
2389
3524
|
});
|
|
2390
3525
|
}
|
|
2391
3526
|
function loadStateOperation(document, action) {
|
|
3527
|
+
const loaded = backfillAuthState(action.state.data);
|
|
3528
|
+
loaded.auth = resolveSnapshotAuth(document.header.id, document.header.documentType, backfillAuthState({ ...document.state }).auth, loaded.auth);
|
|
2392
3529
|
return {
|
|
2393
3530
|
...document,
|
|
2394
3531
|
header: {
|
|
2395
3532
|
...document.header,
|
|
2396
3533
|
name: action.state.name
|
|
2397
3534
|
},
|
|
2398
|
-
state:
|
|
3535
|
+
state: loaded
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Reads an operation's outcome. A denial takes precedence over a reducer
|
|
3540
|
+
* error, since a denied operation never reaches its reducer.
|
|
3541
|
+
*/
|
|
3542
|
+
function operationOutcome(operation) {
|
|
3543
|
+
if (operation.deniedReason !== void 0) return {
|
|
3544
|
+
kind: "denied",
|
|
3545
|
+
reason: operation.deniedReason
|
|
3546
|
+
};
|
|
3547
|
+
if (operation.error !== void 0) return {
|
|
3548
|
+
kind: "reducer-error",
|
|
3549
|
+
message: operation.error
|
|
2399
3550
|
};
|
|
3551
|
+
return { kind: "applied" };
|
|
2400
3552
|
}
|
|
2401
3553
|
//#endregion
|
|
2402
3554
|
//#region document-model/reducer.ts
|
|
@@ -2571,12 +3723,13 @@ function processUndoOperation(document, scope, customReducer, reuseOperationResu
|
|
|
2571
3723
|
function baseReducer(document, action, customReducer, dispatch, options = {}) {
|
|
2572
3724
|
const { skip, ignoreSkipOperations = false, reuseOperationResultingState = false, operationResultingStateParser, pruneOnSkip = true, branch = "main" } = options;
|
|
2573
3725
|
let _action = actionFromAction(action);
|
|
3726
|
+
assertAuthScopeActionAllowed(_action);
|
|
2574
3727
|
let skipValue = skip ?? options.replayOptions?.operation.skip ?? 0;
|
|
2575
3728
|
let newDocument = { ...document };
|
|
2576
3729
|
let reuseLastOperationIndex = false;
|
|
2577
3730
|
const shouldProcessSkipOperation = !ignoreSkipOperations && skipValue > 0;
|
|
2578
3731
|
if (isUndoRedo(_action)) {
|
|
2579
|
-
const { skip: calculatedSkip, action: transformedAction, document: processedDocument, reuseLastOperationIndex: reuseIndex } = processUndoRedo(document, _action, skipValue, options.protocolVersion ??
|
|
3732
|
+
const { skip: calculatedSkip, action: transformedAction, document: processedDocument, reuseLastOperationIndex: reuseIndex } = processUndoRedo(document, _action, skipValue, options.protocolVersion ?? baseReducerVersion(document.header));
|
|
2580
3733
|
_action = transformedAction;
|
|
2581
3734
|
skipValue = calculatedSkip;
|
|
2582
3735
|
newDocument = processedDocument;
|
|
@@ -2592,7 +3745,7 @@ function baseReducer(document, action, customReducer, dispatch, options = {}) {
|
|
|
2592
3745
|
branch
|
|
2593
3746
|
};
|
|
2594
3747
|
newDocument = updateDocument(newDocument, _action, reuseLastOperationIndex, skipValue, operationContext, options.replayOptions?.operation, options.skipIndexValidation);
|
|
2595
|
-
const protocolVersion = options.protocolVersion ??
|
|
3748
|
+
const protocolVersion = options.protocolVersion ?? baseReducerVersion(document.header);
|
|
2596
3749
|
if (isUndo(action) && protocolVersion < 2) return processUndoOperation(newDocument, action.scope, customReducer);
|
|
2597
3750
|
const isNoopWithSkip = _action.type === "NOOP" && skipValue > 0;
|
|
2598
3751
|
if ((isUndo(action) || isNoopWithSkip) && protocolVersion >= 2) {
|
|
@@ -2618,6 +3771,13 @@ function baseReducer(document, action, customReducer, dispatch, options = {}) {
|
|
|
2618
3771
|
}
|
|
2619
3772
|
newDocument = create(newDocument, (draft) => {
|
|
2620
3773
|
try {
|
|
3774
|
+
if (_action.scope === "auth") {
|
|
3775
|
+
const authState = applyAuthAction(newDocument, _action).state;
|
|
3776
|
+
unsafe(() => {
|
|
3777
|
+
draft.state = castDraft(authState);
|
|
3778
|
+
});
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
2621
3781
|
const newState = customReducer(draft.state, _action, dispatch);
|
|
2622
3782
|
if (newState) unsafe(() => {
|
|
2623
3783
|
draft.state = castDraft(newState);
|
|
@@ -3413,14 +4573,27 @@ const documentModelStateReducer = (state, action) => {
|
|
|
3413
4573
|
const documentModelReducer = createReducer(documentModelStateReducer);
|
|
3414
4574
|
//#endregion
|
|
3415
4575
|
//#region document-model/upgrades.ts
|
|
4576
|
+
/**
|
|
4577
|
+
* Canonical document-model version normalization: documents stamped with 0
|
|
4578
|
+
* or nothing at all predate versioning and are treated as version 1, the
|
|
4579
|
+
* same version the registry assigns unversioned modules. Every consumer
|
|
4580
|
+
* that resolves a module or compares versions must use this rule; resolving
|
|
4581
|
+
* 0 to "latest" instead re-pins a legacy document's history to whichever
|
|
4582
|
+
* module happens to be newest.
|
|
4583
|
+
*/
|
|
4584
|
+
function normalizeDocumentModelVersion(version) {
|
|
4585
|
+
return version && version > 0 ? version : 1;
|
|
4586
|
+
}
|
|
3416
4587
|
function applyInitialState(document, action) {
|
|
3417
4588
|
const input = action.input;
|
|
3418
4589
|
const newState = input.initialState || input.state;
|
|
3419
4590
|
if (newState) {
|
|
3420
|
-
|
|
4591
|
+
const merged = backfillAuthState({
|
|
3421
4592
|
...document.state,
|
|
3422
4593
|
...newState
|
|
3423
|
-
};
|
|
4594
|
+
});
|
|
4595
|
+
merged.auth = resolveSnapshotAuth(document.header.id, document.header.documentType, backfillAuthState({ ...document.state }).auth, merged.auth);
|
|
4596
|
+
document.state = merged;
|
|
3424
4597
|
document.initialState = document.state;
|
|
3425
4598
|
}
|
|
3426
4599
|
}
|
|
@@ -3482,7 +4655,6 @@ function computeUpgradeTransitions(manifest, fromVersion, toVersion) {
|
|
|
3482
4655
|
}
|
|
3483
4656
|
//#endregion
|
|
3484
4657
|
//#region document-model/versioned-replay.ts
|
|
3485
|
-
const NON_DOMAIN_SCOPES$1 = new Set(["auth", "document"]);
|
|
3486
4658
|
function highestReducerVersion(reducers) {
|
|
3487
4659
|
const keys = Object.keys(reducers).map(Number);
|
|
3488
4660
|
if (keys.length === 0) throw new Error("VersionedReplayConfig.reducers must not be empty");
|
|
@@ -3510,15 +4682,15 @@ function highestReducerVersion(reducers) {
|
|
|
3510
4682
|
*/
|
|
3511
4683
|
function replayDocumentVersioned(initialState, operations, config, header, dispatch, options) {
|
|
3512
4684
|
const { checkHashes = true, skipIndexValidation } = options || {};
|
|
3513
|
-
const protocolVersion = header
|
|
4685
|
+
const protocolVersion = baseReducerVersion(header);
|
|
3514
4686
|
const spine = (operations["document"] ?? []).slice().sort((a, b) => a.index - b.index);
|
|
3515
4687
|
const upgrades = spine.filter((op) => op.action.type === "UPGRADE_DOCUMENT");
|
|
3516
4688
|
const legacyFallback = () => {
|
|
3517
|
-
const
|
|
4689
|
+
const replayOps = Object.fromEntries(Object.entries(operations).filter(([s]) => s !== "document"));
|
|
3518
4690
|
const latestVersion = highestReducerVersion(config.reducers);
|
|
3519
4691
|
const reducer = config.reducers[latestVersion];
|
|
3520
4692
|
return {
|
|
3521
|
-
...replayDocument(initialState,
|
|
4693
|
+
...replayDocument(initialState, replayOps, reducer, header, dispatch, {}, options),
|
|
3522
4694
|
operations
|
|
3523
4695
|
};
|
|
3524
4696
|
};
|
|
@@ -3542,14 +4714,14 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3542
4714
|
const a = op.action;
|
|
3543
4715
|
return a.input.fromVersion > 0 && a.input.fromVersion < a.input.toVersion;
|
|
3544
4716
|
});
|
|
3545
|
-
const
|
|
4717
|
+
const replayScopes = Object.keys(operations).filter((s) => s !== "document");
|
|
3546
4718
|
const scopeOps = {};
|
|
3547
|
-
for (const s of
|
|
4719
|
+
for (const s of replayScopes) scopeOps[s] = (operations[s] ?? []).slice().sort((a, b) => a.index - b.index);
|
|
3548
4720
|
const boundaries = validatedUpgrades.map((upgradeOp) => {
|
|
3549
4721
|
const revisionSnapshot = upgradeOp.action.input.revision;
|
|
3550
4722
|
const upgradeTimestamp = upgradeOp.timestampUtcMs;
|
|
3551
4723
|
const boundary = {};
|
|
3552
|
-
for (const s of
|
|
4724
|
+
for (const s of replayScopes) {
|
|
3553
4725
|
const ops = scopeOps[s] ?? [];
|
|
3554
4726
|
if (revisionSnapshot !== void 0) {
|
|
3555
4727
|
const rev = revisionSnapshot[s] ?? 0;
|
|
@@ -3567,7 +4739,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3567
4739
|
}
|
|
3568
4740
|
return boundary;
|
|
3569
4741
|
});
|
|
3570
|
-
for (let i = 1; i < boundaries.length; i++) for (const s of
|
|
4742
|
+
for (let i = 1; i < boundaries.length; i++) for (const s of replayScopes) {
|
|
3571
4743
|
const prev = boundaries[i - 1]?.[s] ?? 0;
|
|
3572
4744
|
const curr = boundaries[i]?.[s] ?? 0;
|
|
3573
4745
|
if (boundaries[i]) boundaries[i][s] = Math.max(prev, curr);
|
|
@@ -3579,10 +4751,11 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3579
4751
|
]);
|
|
3580
4752
|
const initialOperations = {};
|
|
3581
4753
|
for (const s of allScopes) initialOperations[s] = [];
|
|
4754
|
+
const backfilledSeed = backfillAuthState(seedState);
|
|
3582
4755
|
let document = {
|
|
3583
4756
|
header,
|
|
3584
|
-
state:
|
|
3585
|
-
initialState:
|
|
4757
|
+
state: backfilledSeed,
|
|
4758
|
+
initialState: backfilledSeed,
|
|
3586
4759
|
operations: initialOperations,
|
|
3587
4760
|
clipboard: []
|
|
3588
4761
|
};
|
|
@@ -3590,17 +4763,15 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3590
4763
|
const segmentEndHashPerScope = /* @__PURE__ */ new Map();
|
|
3591
4764
|
for (let k = 0; k <= validatedUpgrades.length; k++) {
|
|
3592
4765
|
const reducer = config.reducers[currentVersion];
|
|
3593
|
-
if (!reducer)
|
|
3594
|
-
|
|
3595
|
-
throw new Error(`No reducer registered for document version ${currentVersion}. Available versions: ${available}`);
|
|
3596
|
-
}
|
|
3597
|
-
for (const s of domainScopes) {
|
|
4766
|
+
if (!reducer) throw new UnsupportedDocumentModelVersionError(header.documentType, currentVersion, Object.keys(config.reducers).map(Number).sort((a, b) => a - b));
|
|
4767
|
+
for (const s of replayScopes) {
|
|
3598
4768
|
const ops = scopeOps[s] ?? [];
|
|
3599
4769
|
const segStart = k === 0 ? 0 : boundaries[k - 1]?.[s] ?? 0;
|
|
3600
4770
|
const segEnd = k < validatedUpgrades.length ? boundaries[k]?.[s] ?? ops.length : ops.length;
|
|
3601
4771
|
const segOps = ops.slice(segStart, segEnd);
|
|
3602
4772
|
for (const op of segOps) {
|
|
3603
|
-
document =
|
|
4773
|
+
if (isDenied(op)) document = updateHeaderRevision(appendWithoutApplying(document, op, s), s, op.timestampUtcMs);
|
|
4774
|
+
else document = reducer(document, op.action, dispatch, {
|
|
3604
4775
|
ignoreSkipOperations: true,
|
|
3605
4776
|
checkHashes,
|
|
3606
4777
|
skipIndexValidation,
|
|
@@ -3617,6 +4788,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3617
4788
|
if (!spineOp) continue;
|
|
3618
4789
|
const spineActionType = spineOp.action.type;
|
|
3619
4790
|
if (spineActionType === "CREATE_DOCUMENT" || spineActionType === "UPGRADE_DOCUMENT") continue;
|
|
4791
|
+
if (isDenied(spineOp)) continue;
|
|
3620
4792
|
if (spineActionType === "DELETE_DOCUMENT") document = applyDeleteDocumentAction(document, spineOp.action);
|
|
3621
4793
|
else document = reducer(document, spineOp.action, dispatch, {
|
|
3622
4794
|
ignoreSkipOperations: true,
|
|
@@ -3647,7 +4819,7 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3647
4819
|
}
|
|
3648
4820
|
};
|
|
3649
4821
|
if (!checkHashes) {
|
|
3650
|
-
const allReplayedOps =
|
|
4822
|
+
const allReplayedOps = replayScopes.flatMap((s) => scopeOps[s] ?? []);
|
|
3651
4823
|
for (const scope of Object.keys(document.state)) {
|
|
3652
4824
|
const capturedHash = segmentEndHashPerScope.get(scope);
|
|
3653
4825
|
const scopeHash = capturedHash !== void 0 ? capturedHash : hashDocumentStateForScope(document, scope);
|
|
@@ -3691,7 +4863,6 @@ function replayDocumentVersioned(initialState, operations, config, header, dispa
|
|
|
3691
4863
|
}
|
|
3692
4864
|
//#endregion
|
|
3693
4865
|
//#region document-model/files.ts
|
|
3694
|
-
const NON_DOMAIN_SCOPES = new Set(["auth", "document"]);
|
|
3695
4866
|
function zipAsync(data) {
|
|
3696
4867
|
return new Promise((resolve, reject) => {
|
|
3697
4868
|
zip(data, (err, out) => err ? reject(err) : resolve(out));
|
|
@@ -3798,7 +4969,7 @@ async function parseZipData(data) {
|
|
|
3798
4969
|
async function loadFromZipData(data, reducer, options) {
|
|
3799
4970
|
const { initialState, header, clearedOperations } = await parseZipData(data);
|
|
3800
4971
|
return {
|
|
3801
|
-
...replayDocument(initialState, Object.fromEntries(Object.entries(clearedOperations).filter(([scope]) =>
|
|
4972
|
+
...replayDocument(initialState, Object.fromEntries(Object.entries(clearedOperations).filter(([scope]) => scope !== "document")), reducer, header, void 0, {}, options),
|
|
3802
4973
|
operations: clearedOperations
|
|
3803
4974
|
};
|
|
3804
4975
|
}
|
|
@@ -3834,105 +5005,6 @@ const getFileBrowser = (file) => {
|
|
|
3834
5005
|
return Promise.resolve().then(() => readFileBrowser(file));
|
|
3835
5006
|
};
|
|
3836
5007
|
//#endregion
|
|
3837
|
-
|
|
3838
|
-
/**
|
|
3839
|
-
* Creates a default PHAuthState
|
|
3840
|
-
*/
|
|
3841
|
-
function defaultAuthState() {
|
|
3842
|
-
return {};
|
|
3843
|
-
}
|
|
3844
|
-
/**
|
|
3845
|
-
* Creates a default PHDocumentState
|
|
3846
|
-
*/
|
|
3847
|
-
function defaultDocumentState() {
|
|
3848
|
-
return {
|
|
3849
|
-
version: 0,
|
|
3850
|
-
hash: {
|
|
3851
|
-
algorithm: HASH_ALGORITHM_SHA1,
|
|
3852
|
-
encoding: HASH_ENCODING_BASE64
|
|
3853
|
-
}
|
|
3854
|
-
};
|
|
3855
|
-
}
|
|
3856
|
-
/**
|
|
3857
|
-
* Creates a default PHBaseState with auth and document properties
|
|
3858
|
-
*/
|
|
3859
|
-
function defaultBaseState() {
|
|
3860
|
-
return {
|
|
3861
|
-
auth: defaultAuthState(),
|
|
3862
|
-
document: defaultDocumentState()
|
|
3863
|
-
};
|
|
3864
|
-
}
|
|
3865
|
-
/**
|
|
3866
|
-
* Creates a PHAuthState with the given properties
|
|
3867
|
-
*/
|
|
3868
|
-
function createAuthState(auth) {
|
|
3869
|
-
return {
|
|
3870
|
-
...defaultAuthState(),
|
|
3871
|
-
...auth
|
|
3872
|
-
};
|
|
3873
|
-
}
|
|
3874
|
-
/**
|
|
3875
|
-
* Creates a PHDocumentState with the given properties
|
|
3876
|
-
*/
|
|
3877
|
-
function createDocumentState(document) {
|
|
3878
|
-
return {
|
|
3879
|
-
...defaultDocumentState(),
|
|
3880
|
-
...document
|
|
3881
|
-
};
|
|
3882
|
-
}
|
|
3883
|
-
/**
|
|
3884
|
-
* Creates a PHBaseState with the given auth and document properties
|
|
3885
|
-
*/
|
|
3886
|
-
function createBaseState(auth, document) {
|
|
3887
|
-
return {
|
|
3888
|
-
auth: createAuthState(auth),
|
|
3889
|
-
document: createDocumentState(document)
|
|
3890
|
-
};
|
|
3891
|
-
}
|
|
3892
|
-
function defaultGlobalState() {
|
|
3893
|
-
return {
|
|
3894
|
-
...defaultBaseState(),
|
|
3895
|
-
author: {
|
|
3896
|
-
name: "",
|
|
3897
|
-
website: ""
|
|
3898
|
-
},
|
|
3899
|
-
description: "",
|
|
3900
|
-
extension: "",
|
|
3901
|
-
id: "",
|
|
3902
|
-
name: "",
|
|
3903
|
-
specifications: []
|
|
3904
|
-
};
|
|
3905
|
-
}
|
|
3906
|
-
function defaultLocalState() {
|
|
3907
|
-
return {};
|
|
3908
|
-
}
|
|
3909
|
-
function defaultPHState() {
|
|
3910
|
-
return {
|
|
3911
|
-
...defaultBaseState(),
|
|
3912
|
-
global: defaultGlobalState(),
|
|
3913
|
-
local: defaultLocalState()
|
|
3914
|
-
};
|
|
3915
|
-
}
|
|
3916
|
-
function createGlobalState(state) {
|
|
3917
|
-
return {
|
|
3918
|
-
...defaultGlobalState(),
|
|
3919
|
-
...state || {}
|
|
3920
|
-
};
|
|
3921
|
-
}
|
|
3922
|
-
function createLocalState(state) {
|
|
3923
|
-
return {
|
|
3924
|
-
...defaultLocalState(),
|
|
3925
|
-
...state || {}
|
|
3926
|
-
};
|
|
3927
|
-
}
|
|
3928
|
-
function createState(baseState, globalState, localState) {
|
|
3929
|
-
return {
|
|
3930
|
-
...createBaseState(baseState?.auth, baseState?.document),
|
|
3931
|
-
global: createGlobalState(globalState),
|
|
3932
|
-
local: createLocalState(localState)
|
|
3933
|
-
};
|
|
3934
|
-
}
|
|
3935
|
-
//#endregion
|
|
3936
|
-
export { AddChangeLogItemInputSchema, AddModuleInputSchema, AddOperationErrorInputSchema, AddOperationExampleInputSchema, AddOperationInputSchema, AddStateExampleInputSchema, AuthorSchema, BaseDocumentHeaderSchema, BaseDocumentStateSchema, CodeExampleSchema, ConfigEntrySchema, ConfigEntryTypeSchema, DeleteChangeLogItemInputSchema, DeleteModuleInputSchema, DeleteOperationErrorInputSchema, DeleteOperationExampleInputSchema, DeleteOperationInputSchema, DeleteStateExampleInputSchema, DocumentActionSchema, DocumentModelGlobalStateSchema, DocumentModelHeaderSchema, DocumentModelInputSchema, DocumentModelPHStateSchema, DocumentModelSchema, DocumentSpecificationSchema, DowngradeNotSupportedError, FileSystemError, HASH_ALGORITHM_SHA1, HASH_ALGORITHM_SHA256, HASH_ALGORITHM_SHA512, HASH_ENCODING_BASE64, HASH_ENCODING_HEX, HashMismatchError, IntegrityIssueSubType, IntegrityIssueType, InvalidActionInputError, InvalidActionInputZodError, LoadStateActionInputSchema, LoadStateActionSchema, LoadStateActionStateInputSchema, Load_StateSchema, ManifestSchema, ModuleSchema, MoveOperationInputSchema, OPERATION_NAME_PATTERN, OperationErrorSchema, OperationScopeSchema, OperationSpecificationSchema, PowerhouseModuleSchema, PowerhouseModulesSchema, PruneActionInputSchema, PruneActionSchema, PruneSchema, PublisherSchema, RESERVED_OPERATION_NAMES, RedoActionInputSchema, RedoActionSchema, RedoSchema, ReorderChangeLogItemsInputSchema, ReorderModuleOperationsInputSchema, ReorderModulesInputSchema, ReorderOperationErrorsInputSchema, ReorderOperationExamplesInputSchema, ReorderStateExamplesInputSchema, ScopeStateSchema, SetAuthorNameInputSchema, SetAuthorWebsiteInputSchema, SetInitialStateInputSchema, SetModelDescriptionInputSchema, SetModelExtensionInputSchema, SetModelIdInputSchema, SetModelNameInputSchema, SetModuleDescriptionInputSchema, SetModuleNameInputSchema, SetNameActionInputSchema, SetNameActionSchema, SetOperationDescriptionInputSchema, SetOperationErrorCodeInputSchema, SetOperationErrorDescriptionInputSchema, SetOperationErrorNameInputSchema, SetOperationErrorTemplateInputSchema, SetOperationNameInputSchema, SetOperationReducerInputSchema, SetOperationSchemaInputSchema, SetOperationScopeInputSchema, SetOperationTemplateInputSchema, SetPreferredEditorActionInputSchema, SetPreferredEditorActionSchema, SetStateSchemaInputSchema, Set_NameSchema, Set_PreferredEditorSchema, StateSchema, UndoActionInputSchema, UndoActionSchema, UndoSchema, UpdateChangeLogItemInputSchema, UpdateOperationExampleInputSchema, UpdateStateExampleInputSchema, ab2hex, actionContext, actionFromAction, actionSigner, actions, addChangeLogItem, addModule, addOperation, addOperationError, addOperationExample, addStateExample, addUndo, applyDeleteDocumentAction, applyUpgradeDocumentAction, assertIsDocumentModelDocument, assertIsDocumentModelState, assertModuleIdUnique, assertOperationErrorIdUnique, assertOperationExampleIdUnique, assertOperationIdUnique, attachBranch, baseActions, baseCreateDocument, baseLoadFromInput, baseLoadFromInputVersioned, baseReducer, baseSaveToFileHandle, buildOperationSignature, buildOperationSignatureMessage, buildOperationSignatureParams, buildSignedAction, checkCleanedOperationsIntegrity, checkOperationsIntegrity, computeUpgradeTransitions, createAction, createAuthState, createBaseState, createDocumentState, createGlobalState, createLocalState, createMinimalZip, createPresignedHeader, createReducer, createSignedHeader, createSignedHeaderForSigner, createState, createVerificationSigner, createZip, defaultAuthState, defaultBaseState, defaultDocumentState, defaultGlobalState, defaultLocalState, defaultPHState, definedNonNullAnySchema, deleteChangeLogItem, deleteModule, deleteOperation, deleteOperationError, deleteOperationExample, deleteStateExample, deriveOperationId, diffOperations, documentModelActions, documentModelDocumentType, documentModelFileExtension, documentModelGlobalState, documentModelHeaderReducer, documentModelInitialGlobalState, documentModelInitialLocalState, documentModelLoadFromInput, documentModelModuleReducer, documentModelOperationErrorReducer, documentModelOperationExampleReducer, documentModelOperationReducer, documentModelReducer, documentModelSaveToFileHandle, documentModelStateReducer, documentModelStateSchemaReducer, documentModelVersioningReducer, fetchFileBrowser, filterDocumentOperationsResultingState, filterDuplicatedOperations, findModuleOrThrow, findOperationErrorOrThrow, findOperationExampleOrThrow, findOperationOrThrow, garbageCollect, garbageCollectDocumentOperations, garbageCollectV2, generateId, generateMock, getAllOperationNames, getDocumentLastModified, getFileBrowser, getUnixTimestamp, groupOperationsByScope, hashBrowser, hashDocumentStateForScope, hex2ab, isDefinedNonNullAny, isDocumentAction, isDocumentModelDocument, isDocumentModelState, isNoopOperation, isReservedOperationName, isUndo, isUndoRedo, isValidOperationNameFormat, loadState, loadStateOperation, mapSkippedOperations, mapSkippedOperationsV2, merge, moveOperation, nextSkipNumber, noop, operationExampleCreators, operationFromAction, operationFromOperation, operationWithContext, operationsAreEqual, parseResultingState, precedes, prepareOperations, processUndoRedo, prune, pruneOperation, readFileBrowser, readOnly, redo, redoOperation, releaseNewVersion, removeExistingOperations, reorderChangeLogItems, reorderModuleOperations, reorderModules, reorderOperationErrors, reorderOperationExamples, reorderStateExamples, replayDocument, replayDocumentVersioned, replayOperations, reshuffleByTimestamp, reshuffleByTimestampAndIndex, setAuthorName, setAuthorWebsite, setInitialState, setModelDescription, setModelExtension, setModelId, setModelName, setModuleDescription, setModuleName, setName, setNameOperation, setOperationDescription, setOperationErrorCode, setOperationErrorDescription, setOperationErrorName, setOperationErrorTemplate, setOperationName, setOperationReducer, setOperationSchema, setOperationScope, setOperationTemplate, setPreferredEditor, setPreferredEditorOperation, setStateSchema, sign, skipHeaderOperations, sortMappedOperations, sortOperations, split, undo, undoOperation, undoOperationV2, updateChangeLogItem, updateDocument, updateHeaderRevision, updateOperationExample, updateStateExample, validateHeader, validateInitialState, validateModule, validateModuleOperation, validateModules, validateOperationName, validateOperations, validateStateSchemaName, verify, verifyOperationSignature, writeFileBrowser };
|
|
5008
|
+
export { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, AddChangeLogItemInputSchema, AddModuleInputSchema, AddOperationErrorInputSchema, AddOperationExampleInputSchema, AddOperationInputSchema, AddStateExampleInputSchema, AuthActionNotAllowedError, AuthAdministrationLockoutError, AuthAdministrationMissingError, AuthAlreadyInitializedError, AuthInitializerNotCreatorError, AuthPolicyNotPreservedError, AuthorSchema, BaseDocumentHeaderSchema, BaseDocumentStateSchema, CodeExampleSchema, ConfigEntrySchema, ConfigEntryTypeSchema, DOCUMENT_DELETED_REASON, DeleteChangeLogItemInputSchema, DeleteModuleInputSchema, DeleteOperationErrorInputSchema, DeleteOperationExampleInputSchema, DeleteOperationInputSchema, DeleteStateExampleInputSchema, DocumentActionSchema, DocumentModelGlobalStateSchema, DocumentModelHeaderSchema, DocumentModelInputSchema, DocumentModelPHStateSchema, DocumentModelSchema, DocumentSpecificationSchema, DowngradeNotSupportedError, FileSystemError, GrantNotFoundError, GrantSchema, GroupPrincipalNotAllowedError, HASH_ALGORITHM_SHA1, HASH_ALGORITHM_SHA256, HASH_ALGORITHM_SHA512, HASH_ENCODING_BASE64, HASH_ENCODING_HEX, HashMismatchError, InitializeAuthActionInputSchema, IntegrityIssueSubType, IntegrityIssueType, InvalidActionInputError, InvalidActionInputZodError, InvalidAuthVersionError, InvalidGrantError, LoadStateActionInputSchema, LoadStateActionSchema, LoadStateActionStateInputSchema, Load_StateSchema, MAX_AUTH_GRANTS, MAX_CAPABILITY_OPERATIONS, MAX_CONDITION_DEPTH, MAX_CONDITION_NODES, MAX_SUPPORTED_AUTH_VERSION, ManifestSchema, ModuleSchema, MoveGrantActionInputSchema, MoveOperationInputSchema, OPERATION_NAME_PATTERN, OperationErrorSchema, OperationScopeSchema, OperationSpecificationSchema, PowerhouseModuleSchema, PowerhouseModulesSchema, PruneActionInputSchema, PruneActionSchema, PruneSchema, PublisherSchema, PwaConfigSchema, RESERVED_OPERATION_NAMES, RedoActionInputSchema, RedoActionSchema, RedoSchema, RemoveGrantActionInputSchema, ReorderChangeLogItemsInputSchema, ReorderModuleOperationsInputSchema, ReorderModulesInputSchema, ReorderOperationErrorsInputSchema, ReorderOperationExamplesInputSchema, ReorderStateExamplesInputSchema, ScopeStateSchema, SetAuthorNameInputSchema, SetAuthorWebsiteInputSchema, SetGrantActionInputSchema, SetInitialStateInputSchema, SetModelDescriptionInputSchema, SetModelExtensionInputSchema, SetModelIdInputSchema, SetModelNameInputSchema, SetModuleDescriptionInputSchema, SetModuleNameInputSchema, SetNameActionInputSchema, SetNameActionSchema, SetOperationDescriptionInputSchema, SetOperationErrorCodeInputSchema, SetOperationErrorDescriptionInputSchema, SetOperationErrorNameInputSchema, SetOperationErrorTemplateInputSchema, SetOperationNameInputSchema, SetOperationReducerInputSchema, SetOperationSchemaInputSchema, SetOperationScopeInputSchema, SetOperationTemplateInputSchema, SetPreferredEditorActionInputSchema, SetPreferredEditorActionSchema, SetStateSchemaInputSchema, Set_NameSchema, Set_PreferredEditorSchema, StateSchema, UndoActionInputSchema, UndoActionSchema, UndoSchema, UnsupportedDocumentModelVersionError, UpdateChangeLogItemInputSchema, UpdateOperationExampleInputSchema, UpdateStateExampleInputSchema, ab2hex, actionContext, actionFromAction, actionSigner, actions, addChangeLogItem, addModule, addOperation, addOperationError, addOperationExample, addStateExample, addUndo, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyInitializeAuthAction, applyMoveGrantAction, applyRemoveGrantAction, applySetGrantAction, applyUpgradeDocumentAction, assertAuthAdministrationRetained, assertAuthPreservedOnDuplicate, assertAuthScopeActionAllowed, assertIsDocumentModelDocument, assertIsDocumentModelState, assertModuleIdUnique, assertOperationErrorIdUnique, assertOperationExampleIdUnique, assertOperationIdUnique, assertValidGrant, assertValidGrantUpsert, assertValidInitialGrants, attachBranch, backfillAuthState, base58Decode, base64UrlToBytes, baseActions, baseCreateDocument, baseLoadFromInput, baseLoadFromInputVersioned, baseReducer, baseReducerVersion, baseSaveToFileHandle, buildOperationSignature, buildOperationSignatureMessage, buildOperationSignatureParams, buildSignedAction, checkCleanedOperationsIntegrity, checkOperationsIntegrity, computeUpgradeTransitions, createAction, createAuthState, createBaseState, createDocumentState, createGlobalState, createLocalState, createMinimalZip, createPresignedHeader, createReducer, createSignedHeader, createSignedHeaderForSigner, createState, createVerificationSigner, createZip, decide, defaultAuthState, defaultBaseState, defaultDocumentState, defaultGlobalState, defaultLocalState, defaultPHState, definedNonNullAnySchema, deleteChangeLogItem, deleteModule, deleteOperation, deleteOperationError, deleteOperationExample, deleteStateExample, deriveOperationId, diffOperations, documentModelActions, documentModelDocumentType, documentModelFileExtension, documentModelGlobalState, documentModelHeaderReducer, documentModelInitialGlobalState, documentModelInitialLocalState, documentModelLoadFromInput, documentModelModuleReducer, documentModelOperationErrorReducer, documentModelOperationExampleReducer, documentModelOperationReducer, documentModelReducer, documentModelSaveToFileHandle, documentModelStateReducer, documentModelStateSchemaReducer, documentModelVersioningReducer, evaluate, evaluateCondition, evaluateGrantStack, evaluateGrants, fetchFileBrowser, filterDocumentOperationsResultingState, filterDuplicatedOperations, findModuleOrThrow, findOperationErrorOrThrow, findOperationExampleOrThrow, findOperationOrThrow, garbageCollect, garbageCollectDocumentOperations, garbageCollectV2, generateId, generateMock, getAllOperationNames, getDocumentLastModified, getFileBrowser, getUnixTimestamp, grantProblem, groupDocumentType, groupMembershipActionTypes, groupOperationsByScope, hashBrowser, hashDocumentStateForScope, hex2ab, initializeAuth, isAuthAction, isDefinedNonNullAny, isDenied, isDocumentAction, isDocumentCreator, isDocumentModelDocument, isDocumentModelState, isNoopOperation, isPlainValue, isReservedOperationName, isUndo, isUndoRedo, isValidOperationNameFormat, loadState, loadStateOperation, mapSkippedOperations, mapSkippedOperationsV2, mentionedGroupIds, merge, moveGrant, moveOperation, nextSkipNumber, noop, normalizeDocumentModelVersion, operationExampleCreators, operationFromAction, operationFromOperation, operationOutcome, operationWithContext, operationsAreEqual, parseResultingState, precedes, prepareOperations, processUndoRedo, prune, pruneOperation, readFileBrowser, readOnly, redo, redoOperation, referencedGroupIds, releaseNewVersion, removeExistingOperations, removeGrant, reorderChangeLogItems, reorderModuleOperations, reorderModules, reorderOperationErrors, reorderOperationExamples, reorderStateExamples, replayDocument, replayDocumentVersioned, replayOperations, reshuffleByTimestamp, reshuffleByTimestampAndIndex, resolveSnapshotAuth, setAuthorName, setAuthorWebsite, setGrant, setInitialState, setModelDescription, setModelExtension, setModelId, setModelName, setModuleDescription, setModuleName, setName, setNameOperation, setOperationDescription, setOperationErrorCode, setOperationErrorDescription, setOperationErrorName, setOperationErrorTemplate, setOperationName, setOperationReducer, setOperationSchema, setOperationScope, setOperationTemplate, setPreferredEditor, setPreferredEditorOperation, setStateSchema, sign, skipHeaderOperations, sortMappedOperations, sortOperations, split, undo, undoOperation, undoOperationV2, updateChangeLogItem, updateDocument, updateHeaderRevision, updateOperationExample, updateStateExample, validateHeader, validateInitialState, validateModule, validateModuleOperation, validateModules, validateOperationName, validateOperations, validateStateSchemaName, verify, verifyOperationSignature, writeFileBrowser };
|
|
3937
5009
|
|
|
3938
5010
|
//# sourceMappingURL=index.js.map
|