@powerhousedao/reactor-api 6.2.3-dev.4 → 6.2.3-dev.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +7 -2
  2. package/dist/{adapter-gateway-apollo-CxJNBVyD.mjs → adapter-gateway-apollo-B54G_cXT.mjs} +15 -13
  3. package/dist/{adapter-gateway-apollo-CxJNBVyD.mjs.map → adapter-gateway-apollo-B54G_cXT.mjs.map} +1 -1
  4. package/dist/{adapter-gateway-apollo-DowrX6E_.mjs → adapter-gateway-apollo-D2DNlPY0.mjs} +3 -3
  5. package/dist/{adapter-gateway-mercurius-CZVy88Za.mjs → adapter-gateway-mercurius-rbKlwF9m.mjs} +4 -4
  6. package/dist/{adapter-gateway-mercurius-CZVy88Za.mjs.map → adapter-gateway-mercurius-rbKlwF9m.mjs.map} +1 -1
  7. package/dist/{adapter-gateway-stitching-CaZjvRzx.mjs → adapter-gateway-stitching-Dzv10u8D.mjs} +15 -10
  8. package/dist/{adapter-gateway-stitching-CaZjvRzx.mjs.map → adapter-gateway-stitching-Dzv10u8D.mjs.map} +1 -1
  9. package/dist/{adapter-http-express-B39rPhqH.mjs → adapter-http-express-CWqziB1p.mjs} +7 -7
  10. package/dist/adapter-http-express-CWqziB1p.mjs.map +1 -0
  11. package/dist/{adapter-http-fastify-BwXO6rqE.mjs → adapter-http-fastify-CYZQ6FFv.mjs} +10 -9
  12. package/dist/adapter-http-fastify-CYZQ6FFv.mjs.map +1 -0
  13. package/dist/index.d.mts +1315 -602
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +2309 -517
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/{utils-XWVecaNn.mjs → pieces-DRY6haAO.mjs} +177 -9
  18. package/dist/pieces-DRY6haAO.mjs.map +1 -0
  19. package/dist/rolldown-runtime-8H4AJuhK.mjs +16 -0
  20. package/dist/run-node-handler-CQjyjOuN.mjs +52 -0
  21. package/dist/run-node-handler-CQjyjOuN.mjs.map +1 -0
  22. package/dist/src/packages/https-hooks.d.mts +4 -4
  23. package/dist/src/packages/https-hooks.d.mts.map +1 -1
  24. package/dist/src/packages/https-hooks.mjs +2 -2
  25. package/dist/src/packages/https-hooks.mjs.map +1 -1
  26. package/dist/src/packages/vite-loader.d.mts +19 -9
  27. package/dist/src/packages/vite-loader.d.mts.map +1 -1
  28. package/dist/src/packages/vite-loader.mjs +114 -46
  29. package/dist/src/packages/vite-loader.mjs.map +1 -1
  30. package/dist/{types-DcYyNcHJ.d.mts → types-DlTiz6AA.d.mts} +27 -4
  31. package/dist/types-DlTiz6AA.d.mts.map +1 -0
  32. package/dist/{websocket-DncAB9XK.mjs → websocket-_SH5aBcy.mjs} +3 -3
  33. package/dist/{websocket-DncAB9XK.mjs.map → websocket-_SH5aBcy.mjs.map} +1 -1
  34. package/package.json +19 -18
  35. package/dist/adapter-http-express-B39rPhqH.mjs.map +0 -1
  36. package/dist/adapter-http-fastify-BwXO6rqE.mjs.map +0 -1
  37. package/dist/path-normalize-BOQwY9wR.mjs +0 -23
  38. package/dist/path-normalize-BOQwY9wR.mjs.map +0 -1
  39. package/dist/types-DcYyNcHJ.d.mts.map +0 -1
  40. package/dist/utils-XWVecaNn.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,24 +1,27 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a15aa3df-1a6d-517d-93c4-3e8a84cefd09")}catch(e){}}();
3
- import { _ as createAuthorizationService, a as isSubgraphClass, b as MalformedStoredOperationError, d as loadSubgraphs, f as BaseSubgraph, g as AuthorizedDocumentHandle, h as AuthorizationPolicy, i as buildGraphqlOperations, l as loadDocumentModels, m as createCanonicalDocumentIdResolver, n as buildGraphQlDriveDocument, o as debounce, p as CanonicalDocumentIdResolutionError, r as buildGraphqlOperation, s as extractUpgradeManifests, t as buildGraphQlDocument, u as loadProcessors, v as AuthEvaluationUnsupportedError, y as ForbiddenError } from "./utils-XWVecaNn.mjs";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="65e47e18-15f4-5a67-b94b-f6e795c1eb9b")}catch(e){}}();
3
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
4
+ import { A as RefusalNotRecordedError, C as AuthorizationPolicy, D as AuthenticationRequiredError, E as AuthEvaluationUnsupportedError, O as ForbiddenError, S as createCanonicalDocumentIdResolver, T as createAuthorizationService, _ as loadProcessors, a as pieceListLocation, b as callerSubject, c as buildGraphQlDocument, d as buildGraphqlOperations, f as isSubgraphClass, g as loadDocumentModels, k as MalformedStoredOperationError, l as buildGraphQlDriveDocument, m as extractUpgradeManifests, o as piecesFromCdnList, p as debounce, r as PIECE_SOURCE_LIST, s as piecesFromListModule, u as buildGraphqlOperation, v as loadSubgraphs, w as AuthorizedDocumentHandle, x as CanonicalDocumentIdResolutionError, y as BaseSubgraph } from "./pieces-DRY6haAO.mjs";
5
+ import { findPackageJSON, isBuiltin } from "node:module";
4
6
  import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
5
7
  import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
6
8
  import { gql } from "graphql-tag";
7
- import { GraphQLError, Kind, parse, print } from "graphql";
8
- import { AuthEnforcementDisabledError, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, ModelReadGate, PropagationMode as PropagationMode$1, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
9
+ import { GraphQLError, Kind, Lexer, Source, TokenKind, parse, print, visit } from "graphql";
10
+ import { AuthEnforcementDisabledError, BareReadGate, ChannelError, ChannelErrorSource, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, JobStatus, ModelReadGate, PropagationMode as PropagationMode$1, SyncOperation, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, supportsPurgeRefusals, syncOperationErrorType } from "@powerhousedao/reactor";
11
+ import { deserializeSignature, readPeerManifest, serializeSignature, setName, withSignaturePolicy } from "@powerhousedao/shared/document-model";
9
12
  import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
10
- import path, { join } from "node:path";
13
+ import path, { isAbsolute, join } from "node:path";
11
14
  import { match } from "path-to-regexp";
12
15
  import posix from "node:path/posix";
13
16
  import { Readable } from "node:stream";
14
- import fs, { readFileSync, watchFile } from "node:fs";
17
+ import fs, { existsSync, readFileSync, watchFile } from "node:fs";
15
18
  import { pipeline } from "node:stream/promises";
16
- import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
19
+ import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
20
+ import { pathToFileURL } from "node:url";
17
21
  import { buildSubgraphSchema } from "@apollo/subgraph";
18
22
  import { typeDefs } from "@powerhousedao/document-engineering/graphql";
19
23
  import { camelCase, kebabCase, pascalCase } from "change-case";
20
24
  import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
21
- import { deserializeSignature, serializeSignature, setName } from "@powerhousedao/shared/document-model";
22
25
  import * as z$1 from "zod";
23
26
  import { z } from "zod";
24
27
  import { createHandler } from "graphql-sse/lib/use/fetch";
@@ -26,16 +29,17 @@ import lzString from "lz-string";
26
29
  import { PubSub, withFilter } from "graphql-subscriptions";
27
30
  import dotenv from "dotenv";
28
31
  import { buildTreeUrl } from "@powerhousedao/shared";
32
+ import { init, parse as parse$1 } from "es-module-lexer";
33
+ import { chmod, lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
29
34
  import { getConfig } from "@powerhousedao/config/node";
30
35
  import { reactorDriveDocumentModelModule } from "@powerhousedao/reactor-drive";
31
36
  import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
32
37
  import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
33
38
  import EventEmitter from "node:events";
34
39
  import { PostgresAnalyticsStore } from "@powerhousedao/analytics-engine-pg";
35
- import { AttachmentBuilder, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef } from "@powerhousedao/reactor-attachments";
40
+ import { AttachmentBuilder, AttachmentNotFound, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef, validateReserveMetadata } from "@powerhousedao/reactor-attachments";
36
41
  import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
37
42
  import { setupMcpServer } from "@powerhousedao/reactor-mcp";
38
- import { mkdir } from "node:fs/promises";
39
43
  import { tmpdir } from "node:os";
40
44
  import { WebSocketServer } from "ws";
41
45
  import { createRelationalDb } from "@powerhousedao/shared/processors";
@@ -46,19 +50,8 @@ import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
46
50
  import knex from "knex";
47
51
  import ClientPgLite from "knex-pglite";
48
52
  import { KyselyKnexDialect, PGColdDialect } from "kysely-knex";
49
- //#region \0rolldown/runtime.js
50
- var __defProp = Object.defineProperty;
51
- var __exportAll = (all, no_symbols) => {
52
- let target = {};
53
- for (var name in all) __defProp(target, name, {
54
- get: all[name],
55
- enumerable: true
56
- });
57
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
58
- return target;
59
- };
60
- //#endregion
61
53
  //#region src/graphql/analytics-subgraph.ts
54
+ const analyticsResolvers = AnalyticsResolvers;
62
55
  var AnalyticsSubgraph = class extends BaseSubgraph {
63
56
  analyticsStore;
64
57
  analyticsModel;
@@ -66,17 +59,60 @@ var AnalyticsSubgraph = class extends BaseSubgraph {
66
59
  typeDefs = gql`
67
60
  ${typedefs}
68
61
  `;
69
- resolvers = AnalyticsResolvers;
62
+ /**
63
+ * Every analytics field hangs off `Query.analytics`, so gating it gates them
64
+ * all. Series are aggregates a processor built from many documents and carry
65
+ * no document to decide on, so only a caller with policy-wide read (anyone
66
+ * under OPEN, an admin otherwise) may read them.
67
+ */
68
+ resolvers = {
69
+ ...analyticsResolvers,
70
+ Query: {
71
+ ...analyticsResolvers.Query,
72
+ analytics: (parent, args, ctx) => {
73
+ this.#assertCanReadAnalytics(ctx);
74
+ return analyticsResolvers.Query.analytics(parent, args, ctx);
75
+ }
76
+ }
77
+ };
70
78
  constructor(args) {
71
79
  super(args);
72
80
  this.analyticsStore = args.analyticsStore;
73
81
  this.analyticsModel = new AnalyticsModel(new AnalyticsQueryEngine(this.analyticsStore));
74
82
  args.graphqlManager.setAdditionalContextFields({ dataSources: { db: { Analytics: this.analyticsModel } } });
75
83
  }
84
+ #assertCanReadAnalytics(ctx) {
85
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return;
86
+ if (ctx.user?.address) throw new ForbiddenError("to read analytics");
87
+ throw new AuthenticationRequiredError("to read analytics");
88
+ }
76
89
  };
77
90
  //#endregion
78
91
  //#region src/graphql/auth/schema.graphql
79
- var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n# Operation user permission entry\ntype OperationUserPermission {\n documentId: String!\n operationType: String!\n userAddress: String!\n grantedBy: String!\n createdAt: DateTime!\n}\n\n# Combined document access info\ntype DocumentAccessInfo {\n documentId: String!\n permissions: [DocumentPermissionEntry!]!\n}\n\n# Operation permissions info\ntype OperationPermissionsInfo {\n documentId: String!\n operationType: String!\n userPermissions: [OperationUserPermission!]!\n}\n\n# Document protection info\ntype DocumentProtectionInfo {\n documentId: String!\n protected: Boolean!\n ownerAddress: String\n}\n\ntype Query {\n # Get permissions for a document (requires ADMIN permission or global admin)\n documentAccess(documentId: String!): DocumentAccessInfo!\n\n # Get protection status for a document (requires ADMIN permission or global admin)\n documentProtection(documentId: String!): DocumentProtectionInfo!\n\n # Get all documents the current user has explicit access to\n userDocumentPermissions: [DocumentPermissionEntry!]!\n\n # Get operation permissions for a document and operation type\n # (requires ADMIN permission or global admin)\n operationPermissions(\n documentId: String!\n operationType: String!\n ): OperationPermissionsInfo!\n\n # Check if the current user can execute a specific operation on a document\n canExecuteOperation(documentId: String!, operationType: String!): Boolean!\n}\n\ntype Mutation {\n # Set document protection status (requires ADMIN permission or global admin)\n setDocumentProtection(\n documentId: String!\n protected: Boolean!\n ): DocumentProtectionInfo!\n\n # Transfer document ownership (requires ADMIN permission or global admin)\n transferDocumentOwnership(\n documentId: String!\n newOwnerAddress: String!\n ): DocumentProtectionInfo!\n\n # Grant a user permission on a document (requires ADMIN permission or global admin)\n grantDocumentPermission(\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n ): DocumentPermissionEntry!\n\n # Revoke a user's permission on a document (requires ADMIN permission or global admin)\n revokeDocumentPermission(documentId: String!, userAddress: String!): Boolean!\n\n # --- Operation Permissions ---\n\n # Grant a user permission to execute an operation on a document\n grantOperationPermission(\n documentId: String!\n operationType: String!\n userAddress: String!\n ): OperationUserPermission!\n\n # Revoke a user's permission to execute an operation\n revokeOperationPermission(\n documentId: String!\n operationType: String!\n userAddress: String!\n ): Boolean!\n}\n";
92
+ var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n# Operation user permission entry\ntype OperationUserPermission {\n documentId: String!\n operationType: String!\n userAddress: String!\n grantedBy: String!\n createdAt: DateTime!\n}\n\n# Combined document access info\ntype DocumentAccessInfo {\n documentId: String!\n permissions: [DocumentPermissionEntry!]!\n}\n\n# Operation permissions info\ntype OperationPermissionsInfo {\n documentId: String!\n operationType: String!\n userPermissions: [OperationUserPermission!]!\n}\n\n# Document protection info\ntype DocumentProtectionInfo {\n documentId: String!\n protected: Boolean!\n ownerAddress: String\n}\n\ntype Query {\n # Get permissions for a document (requires ADMIN permission or global admin)\n documentAccess(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n ): DocumentAccessInfo!\n\n # Get protection status for a document (requires ADMIN permission or global admin)\n documentProtection(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n ): DocumentProtectionInfo!\n\n # Get all documents the current user has explicit access to\n userDocumentPermissions: [DocumentPermissionEntry!]!\n\n # Get operation permissions for a document and operation type\n # (requires ADMIN permission or global admin)\n operationPermissions(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n ): OperationPermissionsInfo!\n\n # Check if the current user can execute a specific operation on a document\n canExecuteOperation(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n ): Boolean!\n}\n\ntype Mutation {\n # Set document protection status (requires ADMIN permission or global admin)\n setDocumentProtection(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n protected: Boolean!\n ): DocumentProtectionInfo!\n\n # Transfer document ownership (requires ADMIN permission or global admin)\n transferDocumentOwnership(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n newOwnerAddress: String!\n ): DocumentProtectionInfo!\n\n # Grant a user permission on a document (requires ADMIN permission or global admin)\n grantDocumentPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n userAddress: String!\n permission: DocumentPermissionLevel!\n ): DocumentPermissionEntry!\n\n # Revoke a user's permission on a document (requires ADMIN permission or global admin)\n revokeDocumentPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n userAddress: String!\n ): Boolean!\n\n # --- Operation Permissions ---\n\n # Grant a user permission to execute an operation on a document\n grantOperationPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n userAddress: String!\n ): OperationUserPermission!\n\n # Revoke a user's permission to execute an operation\n revokeOperationPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n userAddress: String!\n ): Boolean!\n}\n";
93
+ //#endregion
94
+ //#region src/graphql/argument-aliases.ts
95
+ /** A renamed argument given under both names, or a required one under neither. */
96
+ var ArgumentAliasError = class extends GraphQLError {
97
+ constructor(message) {
98
+ super(message, { extensions: { code: "BAD_USER_INPUT" } });
99
+ }
100
+ };
101
+ /** The value of a required argument that may still arrive under its old name. */
102
+ function requireOneOf(args, name, deprecatedName) {
103
+ const current = args[name] ?? void 0;
104
+ const deprecated = args[deprecatedName] ?? void 0;
105
+ if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
106
+ if (current === void 0 && deprecated === void 0) throw new ArgumentAliasError(`${name} is required.`);
107
+ return current ?? deprecated;
108
+ }
109
+ /** The value of an optional argument that may still arrive under its old name. */
110
+ function optionalOneOf(args, name, deprecatedName) {
111
+ const current = args[name] ?? void 0;
112
+ const deprecated = args[deprecatedName] ?? void 0;
113
+ if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
114
+ return current ?? deprecated;
115
+ }
80
116
  //#endregion
81
117
  //#region src/graphql/auth/resolvers.ts
82
118
  async function documentAccess(service, authorizationService, args, userAddress) {
@@ -172,7 +208,7 @@ var AuthSubgraph = class extends BaseSubgraph {
172
208
  this.logger.debug("documentAccess(@args)", args);
173
209
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
174
210
  try {
175
- return await documentAccess(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
211
+ return await documentAccess(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
176
212
  } catch (error) {
177
213
  this.logger.error("Error in documentAccess: @error", error);
178
214
  throw error;
@@ -193,7 +229,7 @@ var AuthSubgraph = class extends BaseSubgraph {
193
229
  this.logger.debug("operationPermissions(@args)", args);
194
230
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
195
231
  try {
196
- return await operationPermissions(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
232
+ return await operationPermissions(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
197
233
  } catch (error) {
198
234
  this.logger.error("Error in operationPermissions: @error", error);
199
235
  throw error;
@@ -202,7 +238,7 @@ var AuthSubgraph = class extends BaseSubgraph {
202
238
  canExecuteOperation: async (_parent, args, ctx) => {
203
239
  this.logger.debug("canExecuteOperation(@args)", args);
204
240
  try {
205
- return await canExecuteOperation(this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
241
+ return await canExecuteOperation(this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
206
242
  } catch (error) {
207
243
  this.logger.error("Error in canExecuteOperation: @error", error);
208
244
  throw error;
@@ -212,7 +248,7 @@ var AuthSubgraph = class extends BaseSubgraph {
212
248
  this.logger.debug("documentProtection(@args)", args);
213
249
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
214
250
  try {
215
- return await documentProtection(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
251
+ return await documentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
216
252
  } catch (error) {
217
253
  this.logger.error("Error in documentProtection: @error", error);
218
254
  throw error;
@@ -224,7 +260,7 @@ var AuthSubgraph = class extends BaseSubgraph {
224
260
  this.logger.debug("setDocumentProtection(@args)", args);
225
261
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
226
262
  try {
227
- return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
263
+ return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
228
264
  } catch (error) {
229
265
  this.logger.error("Error in setDocumentProtection: @error", error);
230
266
  throw error;
@@ -234,7 +270,7 @@ var AuthSubgraph = class extends BaseSubgraph {
234
270
  this.logger.debug("transferDocumentOwnership(@args)", args);
235
271
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
236
272
  try {
237
- return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
273
+ return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
238
274
  } catch (error) {
239
275
  this.logger.error("Error in transferDocumentOwnership: @error", error);
240
276
  throw error;
@@ -244,7 +280,7 @@ var AuthSubgraph = class extends BaseSubgraph {
244
280
  this.logger.debug("grantDocumentPermission(@args)", args);
245
281
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
246
282
  try {
247
- const resolved = await this.withCanonicalDocumentId(args, ctx);
283
+ const resolved = await this.#withCanonicalDocumentArgs(args, ctx);
248
284
  return await grantDocumentPermission(this.documentPermissionService, this.authorizationService, {
249
285
  ...resolved,
250
286
  permission: resolved.permission
@@ -258,7 +294,7 @@ var AuthSubgraph = class extends BaseSubgraph {
258
294
  this.logger.debug("revokeDocumentPermission(@args)", args);
259
295
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
260
296
  try {
261
- return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
297
+ return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
262
298
  } catch (error) {
263
299
  this.logger.error("Error in revokeDocumentPermission: @error", error);
264
300
  throw error;
@@ -268,7 +304,7 @@ var AuthSubgraph = class extends BaseSubgraph {
268
304
  this.logger.debug("grantOperationPermission(@args)", args);
269
305
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
270
306
  try {
271
- return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
307
+ return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
272
308
  } catch (error) {
273
309
  this.logger.error("Error in grantOperationPermission: @error", error);
274
310
  throw error;
@@ -278,7 +314,7 @@ var AuthSubgraph = class extends BaseSubgraph {
278
314
  this.logger.debug("revokeOperationPermission(@args)", args);
279
315
  if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
280
316
  try {
281
- return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
317
+ return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
282
318
  } catch (error) {
283
319
  this.logger.error("Error in revokeOperationPermission: @error", error);
284
320
  throw error;
@@ -286,6 +322,16 @@ var AuthSubgraph = class extends BaseSubgraph {
286
322
  }
287
323
  }
288
324
  };
325
+ #withCanonicalDocumentArgs(args, ctx) {
326
+ const { documentIdOrSlug, documentId, ...rest } = args;
327
+ return this.withCanonicalDocumentId({
328
+ ...rest,
329
+ documentId: requireOneOf({
330
+ documentIdOrSlug,
331
+ documentId
332
+ }, "documentIdOrSlug", "documentId")
333
+ }, ctx);
334
+ }
289
335
  onSetup() {
290
336
  this.logger.debug("Setting up AuthSubgraph");
291
337
  return Promise.resolve();
@@ -311,15 +357,15 @@ function createAuthFetchMiddleware(authService) {
311
357
  async function createGatewayAdapter(type, logger) {
312
358
  switch (type) {
313
359
  case "apollo": {
314
- const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-DowrX6E_.mjs");
360
+ const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-D2DNlPY0.mjs");
315
361
  return new ApolloGatewayAdapter(logger);
316
362
  }
317
363
  case "mercurius": {
318
- const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-CZVy88Za.mjs");
364
+ const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-rbKlwF9m.mjs");
319
365
  return new MercuriusGatewayAdapter(logger);
320
366
  }
321
367
  case "stitching": {
322
- const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-CaZjvRzx.mjs");
368
+ const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-Dzv10u8D.mjs");
323
369
  return new StitchingGatewayAdapter(logger);
324
370
  }
325
371
  }
@@ -327,11 +373,11 @@ async function createGatewayAdapter(type, logger) {
327
373
  async function createHttpAdapter(type) {
328
374
  switch (type) {
329
375
  case "express": {
330
- const { createExpressHttpAdapter } = await import("./adapter-http-express-B39rPhqH.mjs");
376
+ const { createExpressHttpAdapter } = await import("./adapter-http-express-CWqziB1p.mjs");
331
377
  return createExpressHttpAdapter();
332
378
  }
333
379
  case "fastify": {
334
- const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-BwXO6rqE.mjs");
380
+ const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-CYZQ6FFv.mjs");
335
381
  return createFastifyHttpAdapter();
336
382
  }
337
383
  }
@@ -359,7 +405,7 @@ const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE
359
405
  */
360
406
  const SEGMENT = /^[a-z0-9][-a-z0-9._]*$/i;
361
407
  /** Segments that would read as path traversal rather than as a name. */
362
- const TRAVERSAL = new Set([".", ".."]);
408
+ const TRAVERSAL = /* @__PURE__ */ new Set([".", ".."]);
363
409
  var InvalidNamespaceError = class extends Error {};
364
410
  /**
365
411
  * Turns a package name into the path segments it occupies.
@@ -845,27 +891,29 @@ var HttpScope = class {
845
891
  headers: fetchHeaders(req),
846
892
  signal: controller.signal
847
893
  };
848
- if (method !== "GET" && method !== "HEAD" && body !== "none") if (body === "stream") init = {
849
- ...init,
850
- body: Readable.toWeb(req),
851
- duplex: "half"
852
- };
853
- else {
854
- try {
855
- raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
856
- } catch (error) {
857
- if (error instanceof BodyTooLargeError) {
858
- res.setHeader("connection", "close");
859
- res.once("finish", () => req.destroy());
860
- writeError(res, 413, "Payload too large");
861
- return;
862
- }
863
- throw error;
864
- }
865
- if (raw.length) init = {
894
+ if (method !== "GET" && method !== "HEAD" && body !== "none") {
895
+ if (body === "stream") init = {
866
896
  ...init,
867
- body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
897
+ body: Readable.toWeb(req),
898
+ duplex: "half"
868
899
  };
900
+ else {
901
+ try {
902
+ raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
903
+ } catch (error) {
904
+ if (error instanceof BodyTooLargeError) {
905
+ res.setHeader("connection", "close");
906
+ res.once("finish", () => req.destroy());
907
+ writeError(res, 413, "Payload too large");
908
+ return;
909
+ }
910
+ throw error;
911
+ }
912
+ if (raw.length) init = {
913
+ ...init,
914
+ body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
915
+ };
916
+ }
869
917
  }
870
918
  await writeResponse(res, await spec.handler(new Request(url, init), {
871
919
  ...ctx,
@@ -1042,7 +1090,7 @@ function verifyWebhook(options) {
1042
1090
  }
1043
1091
  /** Headers that would put a credential in a log or run journal. The configured signature
1044
1092
  * header joins them: it is an HMAC of the body under a live secret. */
1045
- const REDACTED_HEADERS = new Set([
1093
+ const REDACTED_HEADERS = /* @__PURE__ */ new Set([
1046
1094
  "authorization",
1047
1095
  "proxy-authorization",
1048
1096
  "cookie",
@@ -1315,6 +1363,7 @@ function newWebhookToken() {
1315
1363
  const NAMESPACE = "reactor_webhooks";
1316
1364
  /** Relational implementation, over the reactor's own database. */
1317
1365
  var RelationalWebhookStore = class {
1366
+ relationalDb;
1318
1367
  #db;
1319
1368
  constructor(relationalDb) {
1320
1369
  this.relationalDb = relationalDb;
@@ -1329,7 +1378,11 @@ var RelationalWebhookStore = class {
1329
1378
  "owner_key"
1330
1379
  ]).unique().execute();
1331
1380
  await db.schema.createTable("webhook_deliveries").ifNotExists().addColumn("token", "text", (col) => col.notNull()).addColumn("dedupe_key", "text", (col) => col.notNull()).addColumn("seen_at", "text", (col) => col.notNull()).addColumn("claim_id", "text", (col) => col.notNull()).execute();
1381
+ try {
1382
+ await db.schema.alterTable("webhook_deliveries").addColumn("expires_at", "text").execute();
1383
+ } catch {}
1332
1384
  await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
1385
+ await db.schema.createIndex("webhook_deliveries_expires_at").ifNotExists().on("webhook_deliveries").columns(["expires_at"]).execute();
1333
1386
  await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
1334
1387
  this.#db = db;
1335
1388
  }
@@ -1370,13 +1423,16 @@ var RelationalWebhookStore = class {
1370
1423
  }
1371
1424
  async seen(token, key, ttlSeconds) {
1372
1425
  const now = Date.now();
1426
+ const nowIso = new Date(now).toISOString();
1373
1427
  const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
1374
- await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
1428
+ await this.#handle.deleteFrom("webhook_deliveries").where((eb) => eb.or([eb("expires_at", "<", nowIso), eb.and([eb("expires_at", "is", null), eb("seen_at", "<", cutoff)])])).execute();
1429
+ await this.#handle.deleteFrom("webhook_deliveries").where("token", "=", token).where("dedupe_key", "=", key).where("seen_at", "<", cutoff).execute();
1375
1430
  const claimId = randomBytes(16).toString("hex");
1376
1431
  await this.#handle.insertInto("webhook_deliveries").values({
1377
1432
  token,
1378
1433
  dedupe_key: key,
1379
- seen_at: new Date(now).toISOString(),
1434
+ seen_at: nowIso,
1435
+ expires_at: new Date(now + ttlSeconds * 1e3).toISOString(),
1380
1436
  claim_id: claimId
1381
1437
  }).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
1382
1438
  return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
@@ -1422,10 +1478,14 @@ var MemoryWebhookStore = class {
1422
1478
  }
1423
1479
  seen(token, key, ttlSeconds) {
1424
1480
  const now = Date.now();
1425
- for (const [k, at] of this.#deliveries) if (at < now - ttlSeconds * 1e3) this.#deliveries.delete(k);
1481
+ for (const [k, entry] of this.#deliveries) if (entry.expiresAt < now) this.#deliveries.delete(k);
1426
1482
  const composite = `${token}|${key}`;
1427
- if (this.#deliveries.has(composite)) return Promise.resolve(true);
1428
- this.#deliveries.set(composite, now);
1483
+ const existing = this.#deliveries.get(composite);
1484
+ if (existing && existing.seenAt >= now - ttlSeconds * 1e3) return Promise.resolve(true);
1485
+ this.#deliveries.set(composite, {
1486
+ seenAt: now,
1487
+ expiresAt: now + ttlSeconds * 1e3
1488
+ });
1429
1489
  return Promise.resolve(false);
1430
1490
  }
1431
1491
  };
@@ -1460,7 +1520,7 @@ const stripScalarDefinitions = (doc) => {
1460
1520
  * whole gateway. We dedupe these by name; field/operation/extension nodes are
1461
1521
  * left untouched.
1462
1522
  */
1463
- const TYPE_DEFINITION_KINDS = new Set([
1523
+ const TYPE_DEFINITION_KINDS = /* @__PURE__ */ new Set([
1464
1524
  Kind.OBJECT_TYPE_DEFINITION,
1465
1525
  Kind.ENUM_TYPE_DEFINITION,
1466
1526
  Kind.INPUT_OBJECT_TYPE_DEFINITION,
@@ -1496,6 +1556,98 @@ const dedupeTypeDefinitions = (doc) => {
1496
1556
  definitions
1497
1557
  };
1498
1558
  };
1559
+ const EMPTY_DOCUMENT = {
1560
+ kind: Kind.DOCUMENT,
1561
+ definitions: []
1562
+ };
1563
+ const OUTPUT_TYPE_KINDS = [
1564
+ Kind.OBJECT_TYPE_DEFINITION,
1565
+ Kind.OBJECT_TYPE_EXTENSION,
1566
+ Kind.ENUM_TYPE_DEFINITION,
1567
+ Kind.ENUM_TYPE_EXTENSION,
1568
+ Kind.UNION_TYPE_DEFINITION,
1569
+ Kind.UNION_TYPE_EXTENSION,
1570
+ Kind.INTERFACE_TYPE_DEFINITION,
1571
+ Kind.INTERFACE_TYPE_EXTENSION
1572
+ ];
1573
+ const STATE_PREFIX_KINDS = new Set(OUTPUT_TYPE_KINDS);
1574
+ const ALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
1575
+ ...OUTPUT_TYPE_KINDS,
1576
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
1577
+ Kind.INPUT_OBJECT_TYPE_EXTENSION
1578
+ ]);
1579
+ const TYPE_NAME_KINDS = /* @__PURE__ */ new Set([
1580
+ ...ALL_PREFIX_KINDS,
1581
+ Kind.SCALAR_TYPE_DEFINITION,
1582
+ Kind.SCALAR_TYPE_EXTENSION,
1583
+ Kind.NAMED_TYPE
1584
+ ]);
1585
+ const LOCAL_STATE_STUBS = /* @__PURE__ */ new Set([
1586
+ "AccountSnapshotLocalState",
1587
+ "BudgetStatementLocalState",
1588
+ "ScopeFrameworkLocalState"
1589
+ ]);
1590
+ /** Parse SDL; blank or comment-only input yields an empty document. */
1591
+ function parseSdl(sdl) {
1592
+ const source = new Source(sdl);
1593
+ if (new Lexer(source).advance().kind === TokenKind.EOF) return EMPTY_DOCUMENT;
1594
+ try {
1595
+ return parse(source, { noLocation: true });
1596
+ } catch (error) {
1597
+ const withoutSemicolons = sdl.replaceAll(";", "");
1598
+ if (withoutSemicolons === sdl) throw error;
1599
+ return parse(withoutSemicolons, { noLocation: true });
1600
+ }
1601
+ }
1602
+ function definedTypeNames(doc, kinds) {
1603
+ return doc.definitions.filter((def) => kinds.has(def.kind)).map((def) => def.name.value);
1604
+ }
1605
+ /** Prefix type definition names and type references found in `names`. */
1606
+ function prefixTypeNames(doc, prefix, names) {
1607
+ return visit(doc, { enter(node) {
1608
+ if (!TYPE_NAME_KINDS.has(node.kind)) return void 0;
1609
+ const { name } = node;
1610
+ if (!names.has(name.value)) return void 0;
1611
+ return {
1612
+ ...node,
1613
+ name: {
1614
+ ...name,
1615
+ value: `${prefix}_${name.value}`
1616
+ }
1617
+ };
1618
+ } });
1619
+ }
1620
+ /** State schema definitions without inputs, `DateTime` or the given object types. */
1621
+ function stateTypeDefinitions(sdl, droppedObjectTypes = /* @__PURE__ */ new Set()) {
1622
+ return parseSdl(sdl).definitions.filter((def) => def.kind !== Kind.INPUT_OBJECT_TYPE_DEFINITION && def.kind !== Kind.INPUT_OBJECT_TYPE_EXTENSION && !(def.kind === Kind.SCALAR_TYPE_DEFINITION && def.name.value === "DateTime") && !(def.kind === Kind.OBJECT_TYPE_DEFINITION && droppedObjectTypes.has(def.name.value)));
1623
+ }
1624
+ /**
1625
+ * Prefixed operation schemas per module. The first definition of each module
1626
+ * carries a "Module: <Name>" description unless it has its own.
1627
+ */
1628
+ function buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema) {
1629
+ const names = new Set(allTypeNames);
1630
+ return specification?.modules.map((module) => {
1631
+ const definitions = module.operations.filter((op) => hasValidSchema(op.schema)).flatMap((op) => {
1632
+ const doc = parseSdl(op.schema ?? "");
1633
+ return prefixTypeNames(doc, documentName, /* @__PURE__ */ new Set([...names, ...definedTypeNames(doc, ALL_PREFIX_KINDS)])).definitions;
1634
+ });
1635
+ if (definitions.length === 0) return "";
1636
+ const [first, ...rest] = definitions;
1637
+ const header = "description" in first && !first.description ? {
1638
+ ...first,
1639
+ description: {
1640
+ kind: Kind.STRING,
1641
+ value: `Module: ${pascalCase(module.name)}`,
1642
+ block: true
1643
+ }
1644
+ } : first;
1645
+ return print({
1646
+ kind: Kind.DOCUMENT,
1647
+ definitions: [header, ...rest]
1648
+ });
1649
+ }).filter(Boolean).join("\n") ?? "";
1650
+ }
1499
1651
  const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
1500
1652
  const newResolvers = {
1501
1653
  ...resolvers,
@@ -1534,20 +1686,13 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
1534
1686
  const latestSpec = documentModel.global.specifications.at(-1);
1535
1687
  const globalSchema = latestSpec?.state.global.schema ?? "";
1536
1688
  const localSchema = latestSpec?.state.local.schema ?? "";
1537
- let tmpDmSchema = `
1538
- ${globalSchema.replaceAll("scalar DateTime", "").replaceAll(/input (.*?) {[\s\S]*?}/g, "")};
1539
-
1540
- ${localSchema.replaceAll("scalar DateTime", "").replaceAll(/input (.*?) {[\s\S]*?}/g, "").replaceAll("type AccountSnapshotLocalState", "").replaceAll("type BudgetStatementLocalState", "").replaceAll("type ScopeFrameworkLocalState", "")};
1541
-
1542
- \n`;
1543
- (tmpDmSchema.match(/(type|enum|union|interface)\s+(\w+)[\s{]/g)?.map((f) => f.replaceAll("type ", "").replaceAll("enum ", "").replaceAll("union ", "").replaceAll("interface ", "").replaceAll("{", "").trim()))?.forEach((f) => {
1544
- const typeRegex = new RegExp(`(?<![_A-Za-z0-9])(${f})(?![_A-Za-z0-9])|\\[(${f})\\]|\\[(${f})!\\]|\\[(${f})\\]!|\\[(${f})!\\]!`, "g");
1545
- tmpDmSchema = tmpDmSchema.replace(typeRegex, (match, p1, p2, p3, p4, p5) => {
1546
- if (match.startsWith("[")) return match.replace(p2 || p3 || p4 || p5, `${dmSchemaName}_${p2 || p3 || p4 || p5}`);
1547
- return `${dmSchemaName}_${p1}`;
1548
- });
1549
- });
1550
- dmSchema += tmpDmSchema;
1689
+ const definitions = [...stateTypeDefinitions(globalSchema), ...stateTypeDefinitions(localSchema, LOCAL_STATE_STUBS)];
1690
+ const stateDoc = {
1691
+ kind: Kind.DOCUMENT,
1692
+ definitions
1693
+ };
1694
+ const prefixed = prefixTypeNames(stateDoc, dmSchemaName, new Set(definedTypeNames(stateDoc, STATE_PREFIX_KINDS)));
1695
+ if (definitions.length > 0) dmSchema += `${print(prefixed)}\n`;
1551
1696
  dmSchema += `
1552
1697
  type ${dmSchemaName} implements IDocument {
1553
1698
  id: String!
@@ -1562,7 +1707,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
1562
1707
  stateJSON: JSONObject
1563
1708
  }\n`;
1564
1709
  });
1565
- return dedupeTypeDefinitions(gql`
1710
+ const schema = gql`
1566
1711
  scalar JSONObject
1567
1712
  scalar AttachmentRef
1568
1713
  # Codegen scalars not in the document-engineering set.
@@ -1613,7 +1758,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
1613
1758
  operations(first: Int, skip: Int): [Operation!]!
1614
1759
  stateJSON: JSONObject
1615
1760
  }
1616
- ${dmSchema.replaceAll(";", "")}
1761
+ ${dmSchema}
1617
1762
 
1618
1763
  type GqlDocument implements IDocument {
1619
1764
  id: String!
@@ -1638,34 +1783,32 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
1638
1783
  }
1639
1784
 
1640
1785
  ${stripScalarDefinitions(typeDefs$1)}
1641
- `);
1786
+ `;
1787
+ return dedupeTypeDefinitions(schema);
1642
1788
  };
1643
1789
  /**
1644
- * Extract type names from a GraphQL schema.
1645
- * @param {string} schema - GraphQL schema string
1646
- * @returns {string[]} Array of type names
1790
+ * Extract the names of types defined in a GraphQL schema.
1791
+ * Unparseable schemas yield no names.
1647
1792
  */
1648
1793
  function extractTypeNames(schema) {
1649
- const found = schema.match(/(type|enum|union|interface|input)\s+(\w+)[\s{]/g);
1650
- if (!found) return [];
1651
- return found.map((f) => f.replaceAll("type ", "").replaceAll("enum ", "").replaceAll("union ", "").replaceAll("interface ", "").replaceAll("input ", "").replaceAll("{", "").trim());
1794
+ try {
1795
+ return definedTypeNames(parseSdl(schema), ALL_PREFIX_KINDS);
1796
+ } catch (error) {
1797
+ logger.debug(`Skipping unparseable schema: ${String(error)}`);
1798
+ return [];
1799
+ }
1652
1800
  }
1653
1801
  /**
1654
- * Extract input type definitions from a GraphQL schema.
1655
- * @param {string} schema - GraphQL schema string
1656
- * @param {Set<string>} excludeTypeNames - Type names to exclude from extraction
1657
- * @returns {string} All input type definitions as a string
1802
+ * Print the input type definitions of a GraphQL schema.
1803
+ * @param excludeTypeNames - Input type names to leave out
1658
1804
  */
1659
1805
  function extractInputTypeDefinitions(schema, excludeTypeNames = /* @__PURE__ */ new Set()) {
1660
- const inputTypeRegex = /input\s+(\w+)\s*\{[^}]*\}/g;
1661
- const matches = [];
1662
- let match;
1663
- while ((match = inputTypeRegex.exec(schema)) !== null) {
1664
- const typeName = match[1];
1665
- if (!excludeTypeNames.has(typeName)) matches.push(match[0]);
1666
- }
1667
- if (matches.length === 0) return "";
1668
- return matches.join("\n\n");
1806
+ const definitions = parseSdl(schema).definitions.filter((def) => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && !excludeTypeNames.has(def.name.value));
1807
+ if (definitions.length === 0) return "";
1808
+ return print({
1809
+ kind: Kind.DOCUMENT,
1810
+ definitions
1811
+ });
1669
1812
  }
1670
1813
  /**
1671
1814
  * Extract the root state type name from a scope's GraphQL schema.
@@ -1734,9 +1877,7 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
1734
1877
  case Kind.OBJECT_TYPE_DEFINITION:
1735
1878
  objectNames.add(def.name.value);
1736
1879
  break;
1737
- case Kind.INPUT_OBJECT_TYPE_DEFINITION:
1738
- existingInputNames.add(def.name.value);
1739
- break;
1880
+ case Kind.INPUT_OBJECT_TYPE_DEFINITION: existingInputNames.add(def.name.value);
1740
1881
  }
1741
1882
  const inputTypes = [];
1742
1883
  for (const def of ast.definitions) {
@@ -1763,18 +1904,10 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
1763
1904
  */
1764
1905
  function applyGraphQLTypePrefixes(schema, prefix, externalTypeNames = []) {
1765
1906
  if (!schema || !schema.trim()) return schema;
1766
- let processedSchema = schema;
1767
- const localTypeNames = extractTypeNames(schema);
1768
- const allTypeNames = [...new Set([...localTypeNames, ...externalTypeNames])];
1769
- if (allTypeNames.length === 0) return schema;
1770
- allTypeNames.forEach((typeName) => {
1771
- const typeRegex = new RegExp(`(?<![_A-Za-z0-9])(${typeName})(?![_A-Za-z0-9])|\\[(${typeName})\\]|\\[(${typeName})!\\]|\\[(${typeName})\\]!|\\[(${typeName})!\\]!`, "g");
1772
- processedSchema = processedSchema.replace(typeRegex, (match, p1, p2, p3, p4, p5) => {
1773
- if (match.startsWith("[")) return match.replace(p2 || p3 || p4 || p5, `${prefix}_${p2 || p3 || p4 || p5}`);
1774
- return `${prefix}_${p1}`;
1775
- });
1776
- });
1777
- return processedSchema;
1907
+ const doc = parseSdl(schema);
1908
+ const names = /* @__PURE__ */ new Set([...definedTypeNames(doc, ALL_PREFIX_KINDS), ...externalTypeNames]);
1909
+ if (names.size === 0) return schema;
1910
+ return print(prefixTypeNames(doc, prefix, names));
1778
1911
  }
1779
1912
  /**
1780
1913
  * Generate a GraphQL schema for a document model.
@@ -1793,11 +1926,11 @@ function generateDocumentModelSchema(documentModel, options = {}) {
1793
1926
  const localStateTypeNames = extractTypeNames(localStateSchema ?? "");
1794
1927
  const stateTypeNames = [...globalStateTypeNames, ...localStateTypeNames];
1795
1928
  const allOperationTypeNames = specification?.modules.flatMap((module) => module.operations.flatMap((op) => extractTypeNames(op.schema ?? ""))) ?? [];
1796
- const allTypeNames = [...new Set([...stateTypeNames, ...allOperationTypeNames])];
1797
- const prefixedStateInputTypes = applyGraphQLTypePrefixes(extractInputTypeDefinitions(globalStateSchema ?? "", new Set(allOperationTypeNames)), documentName, allTypeNames);
1929
+ const allTypeNames = [.../* @__PURE__ */ new Set([...stateTypeNames, ...allOperationTypeNames])];
1930
+ const operationInputTypeNames = new Set(allOperationTypeNames);
1931
+ const prefixedStateInputTypes = applyGraphQLTypePrefixes(extractInputTypeDefinitions(globalStateSchema ?? "", operationInputTypeNames), documentName, allTypeNames);
1798
1932
  const hasValidSchema = (schema) => !!(schema && /\b(input|type|enum|union|interface)\s+\w+/.test(schema));
1799
- const stateSchemaTypes = globalStateSchema ? applyGraphQLTypePrefixes(globalStateSchema.replaceAll("scalar DateTime", "").replaceAll(/input (.*?) {[\s\S]*?}/g, ""), documentName, allTypeNames) : "";
1800
- if (useNewApi) return generateNewApiSchema(documentName, specification, stateSchemaTypes, prefixedStateInputTypes, allTypeNames, hasValidSchema);
1933
+ if (useNewApi) return generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
1801
1934
  return generateLegacyApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
1802
1935
  }
1803
1936
  /**
@@ -1807,10 +1940,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
1807
1940
  const createDocumentMutation = `${documentName}_createDocument(name:String!, driveId:String): String`;
1808
1941
  const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).map((op) => `${documentName}_${camelCase(op.name)}(
1809
1942
  driveId: String, docId: PHID, input: ${documentName}_${pascalCase(op.name)}Input): Int`)).join("\n ") ?? "";
1810
- const moduleSchemas = specification?.modules.filter((module) => module.operations.some((op) => hasValidSchema(op.schema))).map((module) => `"""
1811
- Module: ${pascalCase(module.name)}
1812
- """
1813
- ${module.operations.filter((op) => hasValidSchema(op.schema)).map((op) => applyGraphQLTypePrefixes(op.schema ?? "", documentName, allTypeNames)).join("\n ")}`).join("\n") ?? "";
1943
+ const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
1814
1944
  return gql`
1815
1945
  """
1816
1946
  Queries: ${documentName} Document
@@ -1847,7 +1977,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
1847
1977
  * in getDocumentModelTypeDefs() which is used during schema composition.
1848
1978
  * Including them here would cause duplicate type definitions.
1849
1979
  */
1850
- function generateNewApiSchema(documentName, specification, _stateSchemaTypes, prefixedStateInputTypes, allTypeNames, hasValidSchema) {
1980
+ function generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema) {
1851
1981
  const stateType = `${documentName}_FullState!`;
1852
1982
  const sharedBaseTypes = `
1853
1983
  """Hash configuration for document state"""
@@ -1890,7 +2020,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1890
2020
 
1891
2021
  input ${documentName}_SearchFilterInput {
1892
2022
  parentId: String
1893
- identifiers: [String!]
2023
+ identifiers: [String!] @deprecated(reason: "Ignored. Filter by parentId.")
1894
2024
  }
1895
2025
  `;
1896
2026
  const revisionType = RevisionType;
@@ -1924,7 +2054,6 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1924
2054
  """
1925
2055
  type ${documentName}_DocumentResultPage {
1926
2056
  items: [${documentName}MutationResult!]!
1927
- totalCount: Int!
1928
2057
  hasNextPage: Boolean!
1929
2058
  hasPreviousPage: Boolean!
1930
2059
  cursor: String
@@ -1933,7 +2062,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1933
2062
  const queries = `
1934
2063
  type ${documentName}Queries {
1935
2064
  """Get a specific ${documentName} document by identifier"""
1936
- document(identifier: String!, view: ${documentName}_ViewFilterInput): ${documentName}_DocumentWithChildren
2065
+ document(idOrSlug: String, identifier: String @deprecated(reason: "Use idOrSlug."), view: ${documentName}_ViewFilterInput): ${documentName}_DocumentWithChildren
1937
2066
 
1938
2067
  """Get all ${documentName} documents (paged)"""
1939
2068
  documents(paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
@@ -1942,10 +2071,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1942
2071
  findDocuments(search: ${documentName}_SearchFilterInput, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
1943
2072
 
1944
2073
  """Get outgoing relationships of a ${documentName} document"""
1945
- documentOutgoingRelationships(sourceIdentifier: String!, relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
2074
+ documentOutgoingRelationships(sourceIdOrSlug: String, sourceIdentifier: String @deprecated(reason: "Use sourceIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
1946
2075
 
1947
2076
  """Get incoming relationships to a ${documentName} document"""
1948
- documentIncomingRelationships(targetIdentifier: String!, relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
2077
+ documentIncomingRelationships(targetIdOrSlug: String, targetIdentifier: String @deprecated(reason: "Use targetIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
1949
2078
  }
1950
2079
  `;
1951
2080
  let initialStateInputSchema = "";
@@ -1976,13 +2105,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1976
2105
  initialStateInputSchema = inputTypeDefs ? `${inputTypeDefs}\n\n${wrapper}` : wrapper;
1977
2106
  }
1978
2107
  }
1979
- const createDocumentMutation = initialStateInputSchema ? `createDocument(name: String!, parentIdentifier: String, slug: String, preferredEditor: String, initialState: ${documentName}_InitialStateInput): ${documentName}MutationResult!` : `createDocument(name: String!, parentIdentifier: String, preferredEditor: String): ${documentName}MutationResult!`;
1980
- const createEmptyDocumentMutation = `createEmptyDocument(parentIdentifier: String): ${documentName}MutationResult!`;
1981
- const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).flatMap((op) => [`${camelCase(op.name)}(docId: PHID!, input: ${documentName}_${pascalCase(op.name)}Input!): ${documentName}MutationResult!`, `${camelCase(op.name)}Async(docId: PHID!, input: ${documentName}_${pascalCase(op.name)}Input!): String!`])).join("\n ") ?? "";
1982
- const moduleSchemas = specification?.modules.filter((module) => module.operations.some((op) => hasValidSchema(op.schema))).map((module) => `"""
1983
- Module: ${pascalCase(module.name)}
1984
- """
1985
- ${module.operations.filter((op) => hasValidSchema(op.schema)).map((op) => applyGraphQLTypePrefixes(op.schema ?? "", documentName, allTypeNames)).join("\n ")}`).join("\n") ?? "";
2108
+ const createDocumentMutation = initialStateInputSchema ? `createDocument(name: String!, parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug."), slug: String, preferredEditor: String, initialState: ${documentName}_InitialStateInput): ${documentName}MutationResult!` : `createDocument(name: String!, parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug."), preferredEditor: String): ${documentName}MutationResult!`;
2109
+ const createEmptyDocumentMutation = `createEmptyDocument(parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug.")): ${documentName}MutationResult!`;
2110
+ const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).flatMap((op) => [`${camelCase(op.name)}(documentIdOrSlug: String, docId: PHID @deprecated(reason: "Use documentIdOrSlug."), input: ${documentName}_${pascalCase(op.name)}Input!): ${documentName}MutationResult!`, `${camelCase(op.name)}Async(documentIdOrSlug: String, docId: PHID @deprecated(reason: "Use documentIdOrSlug."), input: ${documentName}_${pascalCase(op.name)}Input!): String!`])).join("\n ") ?? "";
2111
+ const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
1986
2112
  return gql`
1987
2113
  scalar DateTime
1988
2114
  scalar JSONObject
@@ -2135,7 +2261,8 @@ function OperationsFilterInputSchema() {
2135
2261
  return z$1.object({
2136
2262
  actionTypes: z$1.array(z$1.string()).nullish(),
2137
2263
  branch: z$1.string().nullish(),
2138
- documentId: z$1.string(),
2264
+ documentId: z$1.string().nullish(),
2265
+ documentIdOrSlug: z$1.string().nullish(),
2139
2266
  scopes: z$1.array(z$1.string()).nullish(),
2140
2267
  sinceRevision: z$1.number().nullish(),
2141
2268
  timestampFrom: z$1.string().nullish(),
@@ -2200,11 +2327,19 @@ function SyncEnvelopeInputSchema() {
2200
2327
  type: SyncEnvelopeTypeSchema
2201
2328
  });
2202
2329
  }
2330
+ function SyncRefusalInputSchema() {
2331
+ return z$1.object({
2332
+ branch: z$1.string(),
2333
+ documentId: z$1.string(),
2334
+ kind: z$1.string().nullish()
2335
+ });
2336
+ }
2203
2337
  function TouchChannelInputSchema() {
2204
2338
  return z$1.object({
2205
2339
  collectionId: z$1.string(),
2206
2340
  filter: z$1.lazy(() => RemoteFilterInputSchema()),
2207
2341
  id: z$1.string(),
2342
+ manifest: z$1.custom((v) => v != null).nullish(),
2208
2343
  name: z$1.string(),
2209
2344
  sinceTimestampUtcMs: z$1.string()
2210
2345
  });
@@ -2230,6 +2365,16 @@ const PhDocumentFieldsFragmentDoc = gql`
2230
2365
  lastModifiedAtUtcIso
2231
2366
  }
2232
2367
  `;
2368
+ const DocumentRelationshipFieldsFragmentDoc = gql`
2369
+ fragment DocumentRelationshipFields on DocumentRelationship {
2370
+ sourceId
2371
+ targetId
2372
+ relationshipType
2373
+ metadata
2374
+ createdAt
2375
+ updatedAt
2376
+ }
2377
+ `;
2233
2378
  const GetDocumentModelsDocument = gql`
2234
2379
  query GetDocumentModels($namespace: String, $paging: PagingInput) {
2235
2380
  documentModels(namespace: $namespace, paging: $paging) {
@@ -2240,7 +2385,6 @@ const GetDocumentModelsDocument = gql`
2240
2385
  version
2241
2386
  specification
2242
2387
  }
2243
- totalCount
2244
2388
  hasNextPage
2245
2389
  hasPreviousPage
2246
2390
  cursor
@@ -2299,7 +2443,6 @@ const GetDocumentWithOperationsDocument = gql`
2299
2443
  }
2300
2444
  }
2301
2445
  }
2302
- totalCount
2303
2446
  hasNextPage
2304
2447
  hasPreviousPage
2305
2448
  cursor
@@ -2326,7 +2469,6 @@ const GetDocumentOutgoingRelationshipsDocument = gql`
2326
2469
  items {
2327
2470
  ...PHDocumentFields
2328
2471
  }
2329
- totalCount
2330
2472
  hasNextPage
2331
2473
  hasPreviousPage
2332
2474
  cursor
@@ -2350,7 +2492,6 @@ const GetDocumentIncomingRelationshipsDocument = gql`
2350
2492
  items {
2351
2493
  ...PHDocumentFields
2352
2494
  }
2353
- totalCount
2354
2495
  hasNextPage
2355
2496
  hasPreviousPage
2356
2497
  cursor
@@ -2358,6 +2499,52 @@ const GetDocumentIncomingRelationshipsDocument = gql`
2358
2499
  }
2359
2500
  ${PhDocumentFieldsFragmentDoc}
2360
2501
  `;
2502
+ const GetDocumentOutgoingRelationshipEdgesDocument = gql`
2503
+ query GetDocumentOutgoingRelationshipEdges(
2504
+ $sourceIdentifier: String!
2505
+ $relationshipType: String
2506
+ $view: ViewFilterInput
2507
+ $paging: PagingInput
2508
+ ) {
2509
+ documentOutgoingRelationshipEdges(
2510
+ sourceIdentifier: $sourceIdentifier
2511
+ relationshipType: $relationshipType
2512
+ view: $view
2513
+ paging: $paging
2514
+ ) {
2515
+ items {
2516
+ ...DocumentRelationshipFields
2517
+ }
2518
+ hasNextPage
2519
+ hasPreviousPage
2520
+ cursor
2521
+ }
2522
+ }
2523
+ ${DocumentRelationshipFieldsFragmentDoc}
2524
+ `;
2525
+ const GetDocumentIncomingRelationshipEdgesDocument = gql`
2526
+ query GetDocumentIncomingRelationshipEdges(
2527
+ $targetIdentifier: String!
2528
+ $relationshipType: String
2529
+ $view: ViewFilterInput
2530
+ $paging: PagingInput
2531
+ ) {
2532
+ documentIncomingRelationshipEdges(
2533
+ targetIdentifier: $targetIdentifier
2534
+ relationshipType: $relationshipType
2535
+ view: $view
2536
+ paging: $paging
2537
+ ) {
2538
+ items {
2539
+ ...DocumentRelationshipFields
2540
+ }
2541
+ hasNextPage
2542
+ hasPreviousPage
2543
+ cursor
2544
+ }
2545
+ }
2546
+ ${DocumentRelationshipFieldsFragmentDoc}
2547
+ `;
2361
2548
  const FindDocumentsDocument = gql`
2362
2549
  query FindDocuments(
2363
2550
  $search: SearchFilterInput
@@ -2368,7 +2555,6 @@ const FindDocumentsDocument = gql`
2368
2555
  items {
2369
2556
  ...PHDocumentFields
2370
2557
  }
2371
- totalCount
2372
2558
  hasNextPage
2373
2559
  hasPreviousPage
2374
2560
  cursor
@@ -2412,7 +2598,6 @@ const GetDocumentOperationsDocument = gql`
2412
2598
  }
2413
2599
  }
2414
2600
  }
2415
- totalCount
2416
2601
  hasNextPage
2417
2602
  hasPreviousPage
2418
2603
  cursor
@@ -2548,12 +2733,34 @@ const AddRelationshipDocument = gql`
2548
2733
  $sourceIdentifier: String!
2549
2734
  $targetIdentifier: String!
2550
2735
  $relationshipType: String!
2736
+ $metadata: JSONObject
2551
2737
  $branch: String
2552
2738
  ) {
2553
2739
  addRelationship(
2554
2740
  sourceIdentifier: $sourceIdentifier
2555
2741
  targetIdentifier: $targetIdentifier
2556
2742
  relationshipType: $relationshipType
2743
+ metadata: $metadata
2744
+ branch: $branch
2745
+ ) {
2746
+ ...PHDocumentFields
2747
+ }
2748
+ }
2749
+ ${PhDocumentFieldsFragmentDoc}
2750
+ `;
2751
+ const UpdateRelationshipDocument = gql`
2752
+ mutation UpdateRelationship(
2753
+ $sourceIdentifier: String!
2754
+ $targetIdentifier: String!
2755
+ $relationshipType: String!
2756
+ $metadata: JSONObject
2757
+ $branch: String
2758
+ ) {
2759
+ updateRelationship(
2760
+ sourceIdentifier: $sourceIdentifier
2761
+ targetIdentifier: $targetIdentifier
2762
+ relationshipType: $relationshipType
2763
+ metadata: $metadata
2557
2764
  branch: $branch
2558
2765
  ) {
2559
2766
  ...PHDocumentFields
@@ -2650,11 +2857,15 @@ const PollSyncEnvelopesDocument = gql`
2650
2857
  $channelId: String!
2651
2858
  $outboxAck: Int!
2652
2859
  $outboxLatest: Int!
2860
+ $manifestRevision: String
2861
+ $refusals: [SyncRefusalInput!]
2653
2862
  ) {
2654
2863
  pollSyncEnvelopes(
2655
2864
  channelId: $channelId
2656
2865
  outboxAck: $outboxAck
2657
2866
  outboxLatest: $outboxLatest
2867
+ manifestRevision: $manifestRevision
2868
+ refusals: $refusals
2658
2869
  ) {
2659
2870
  envelopes {
2660
2871
  type
@@ -2714,6 +2925,8 @@ const PollSyncEnvelopesDocument = gql`
2714
2925
  errorType
2715
2926
  }
2716
2927
  hasMore
2928
+ manifestRevision
2929
+ peerManifestRevision
2717
2930
  }
2718
2931
  }
2719
2932
  `;
@@ -2722,12 +2935,19 @@ const TouchChannelDocument = gql`
2722
2935
  touchChannel(input: $input) {
2723
2936
  success
2724
2937
  ackOrdinal
2938
+ manifest
2725
2939
  }
2726
2940
  }
2727
2941
  `;
2728
2942
  const PushSyncEnvelopesDocument = gql`
2729
- mutation PushSyncEnvelopes($envelopes: [SyncEnvelopeInput!]!) {
2730
- pushSyncEnvelopes(envelopes: $envelopes)
2943
+ mutation PushSyncEnvelopes(
2944
+ $envelopes: [SyncEnvelopeInput!]!
2945
+ $peerManifestRevision: String
2946
+ ) {
2947
+ pushSyncEnvelopes(
2948
+ envelopes: $envelopes
2949
+ peerManifestRevision: $peerManifestRevision
2950
+ )
2731
2951
  }
2732
2952
  `;
2733
2953
  function getSdk(requester) {
@@ -2747,6 +2967,12 @@ function getSdk(requester) {
2747
2967
  GetDocumentIncomingRelationships(variables, options) {
2748
2968
  return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
2749
2969
  },
2970
+ GetDocumentOutgoingRelationshipEdges(variables, options) {
2971
+ return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
2972
+ },
2973
+ GetDocumentIncomingRelationshipEdges(variables, options) {
2974
+ return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
2975
+ },
2750
2976
  FindDocuments(variables, options) {
2751
2977
  return requester(FindDocumentsDocument, variables, options);
2752
2978
  },
@@ -2780,6 +3006,9 @@ function getSdk(requester) {
2780
3006
  AddRelationship(variables, options) {
2781
3007
  return requester(AddRelationshipDocument, variables, options);
2782
3008
  },
3009
+ UpdateRelationship(variables, options) {
3010
+ return requester(UpdateRelationshipDocument, variables, options);
3011
+ },
2783
3012
  RemoveRelationship(variables, options) {
2784
3013
  return requester(RemoveRelationshipDocument, variables, options);
2785
3014
  },
@@ -2820,8 +3049,7 @@ function toDocumentModelResultPage(result) {
2820
3049
  cursor: result.nextCursor ?? null,
2821
3050
  hasNextPage: !!result.nextCursor,
2822
3051
  hasPreviousPage: !!result.options.cursor,
2823
- items: models.map(toGqlDocumentModelState),
2824
- totalCount: result.results.length
3052
+ items: models.map(toGqlDocumentModelState)
2825
3053
  };
2826
3054
  }
2827
3055
  /**
@@ -2853,8 +3081,32 @@ function toPhDocumentResultPage(result) {
2853
3081
  cursor: result.nextCursor ?? null,
2854
3082
  hasNextPage: !!result.nextCursor,
2855
3083
  hasPreviousPage: !!result.options.cursor,
2856
- items: result.results.map(toGqlPhDocument),
2857
- totalCount: result.totalCount ?? result.results.length
3084
+ items: result.results.map(toGqlPhDocument)
3085
+ };
3086
+ }
3087
+ /**
3088
+ * Converts a relationship edge from the indexer to GraphQL format.
3089
+ */
3090
+ function toGqlDocumentRelationship(relationship) {
3091
+ return {
3092
+ sourceId: relationship.sourceId,
3093
+ targetId: relationship.targetId,
3094
+ relationshipType: relationship.relationshipType,
3095
+ metadata: relationship.metadata ?? null,
3096
+ createdAt: relationship.createdAt,
3097
+ updatedAt: relationship.updatedAt
3098
+ };
3099
+ }
3100
+ /**
3101
+ * Converts a PagedResults of relationship edges to GraphQL
3102
+ * DocumentRelationshipResultPage format
3103
+ */
3104
+ function toDocumentRelationshipResultPage(result) {
3105
+ return {
3106
+ cursor: result.nextCursor ?? null,
3107
+ hasNextPage: !!result.nextCursor,
3108
+ hasPreviousPage: !!result.options.cursor,
3109
+ items: result.results.map(toGqlDocumentRelationship)
2858
3110
  };
2859
3111
  }
2860
3112
  /**
@@ -2904,8 +3156,8 @@ function fromInputMaybe(value) {
2904
3156
  function toReactorPropagationMode(gqlMode) {
2905
3157
  if (gqlMode == null) return;
2906
3158
  switch (gqlMode) {
2907
- case PropagationMode.Cascade: return PropagationMode$1.Cascade;
2908
- case PropagationMode.Orphan: return PropagationMode$1.None;
3159
+ case "CASCADE": return PropagationMode$1.Cascade;
3160
+ case "ORPHAN": return PropagationMode$1.None;
2909
3161
  }
2910
3162
  }
2911
3163
  /**
@@ -2915,10 +3167,10 @@ function toReactorPropagationMode(gqlMode) {
2915
3167
  */
2916
3168
  function toGqlActionEvaluation(evaluation) {
2917
3169
  return evaluation.decision === "allow" ? {
2918
- decision: AuthDecision.Allow,
3170
+ decision: "ALLOW",
2919
3171
  reason: null
2920
3172
  } : {
2921
- decision: AuthDecision.Deny,
3173
+ decision: "DENY",
2922
3174
  reason: evaluation.reason
2923
3175
  };
2924
3176
  }
@@ -3123,8 +3375,7 @@ function toOperationResultPage(result) {
3123
3375
  cursor: result.nextCursor ?? null,
3124
3376
  hasNextPage: !!result.nextCursor,
3125
3377
  hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
3126
- items: result.results.map(serializeOperationForGraphQL),
3127
- totalCount: result.results.length
3378
+ items: result.results.map(serializeOperationForGraphQL)
3128
3379
  };
3129
3380
  }
3130
3381
  function toGqlDocumentChangeEvent(event) {
@@ -3196,6 +3447,21 @@ function pickDriveClient(reactorClient, driveContainerType, reactorDriveClient)
3196
3447
  }
3197
3448
  return reactorClient.drives;
3198
3449
  }
3450
+ /**
3451
+ * A mutation's document re-read as the caller, since the client reads it back
3452
+ * as its signer. A caller who may write but not read gets it stripped.
3453
+ */
3454
+ async function readAsCaller(reactorClient, document, subject, branch) {
3455
+ if (!subject) return document;
3456
+ try {
3457
+ return await reactorClient.get(document.header.id, {
3458
+ subject,
3459
+ branch
3460
+ });
3461
+ } catch (error) {
3462
+ throw new GraphQLError(`Failed to read the document back: ${error instanceof Error ? error.message : "Unknown error"}`);
3463
+ }
3464
+ }
3199
3465
  async function documentModels(reactorClient, args) {
3200
3466
  const namespace = fromInputMaybe(args.namespace);
3201
3467
  let paging;
@@ -3228,13 +3494,13 @@ async function document(reactorClient, args, subject) {
3228
3494
  };
3229
3495
  let result;
3230
3496
  try {
3231
- result = await reactorClient.get(args.identifier, view);
3497
+ result = await reactorClient.get(args.idOrSlug, view);
3232
3498
  } catch (error) {
3233
3499
  throw new GraphQLError(`Failed to fetch document: ${error instanceof Error ? error.message : "Unknown error"}`);
3234
3500
  }
3235
3501
  let children;
3236
3502
  try {
3237
- children = await reactorClient.getOutgoingRelationships(args.identifier, "child", view);
3503
+ children = await reactorClient.getOutgoingRelationships(args.idOrSlug, "child", view);
3238
3504
  } catch (error) {
3239
3505
  throw new GraphQLError(`Failed to fetch children: ${error instanceof Error ? error.message : "Unknown error"}`);
3240
3506
  }
@@ -3265,7 +3531,7 @@ async function documentOutgoingRelationships(reactorClient, args, subject) {
3265
3531
  }
3266
3532
  let result;
3267
3533
  try {
3268
- result = await reactorClient.getOutgoingRelationships(args.sourceIdentifier, args.relationshipType, view, paging);
3534
+ result = await reactorClient.getOutgoingRelationships(args.sourceIdOrSlug, args.relationshipType, view, paging);
3269
3535
  } catch (error) {
3270
3536
  throw new GraphQLError(`Failed to fetch outgoing relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
3271
3537
  }
@@ -3293,7 +3559,7 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
3293
3559
  }
3294
3560
  let result;
3295
3561
  try {
3296
- result = await reactorClient.getIncomingRelationships(args.targetIdentifier, args.relationshipType, view, paging);
3562
+ result = await reactorClient.getIncomingRelationships(args.targetIdOrSlug, args.relationshipType, view, paging);
3297
3563
  } catch (error) {
3298
3564
  throw new GraphQLError(`Failed to fetch incoming relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
3299
3565
  }
@@ -3303,6 +3569,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
3303
3569
  throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3304
3570
  }
3305
3571
  }
3572
+ async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
3573
+ const view = toRelationshipViewFilter(args.view, subject);
3574
+ const paging = toRelationshipPagingOptions(args.paging);
3575
+ let result;
3576
+ try {
3577
+ result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
3578
+ } catch (error) {
3579
+ throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
3580
+ }
3581
+ try {
3582
+ return toDocumentRelationshipResultPage(result);
3583
+ } catch (error) {
3584
+ throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3585
+ }
3586
+ }
3587
+ async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
3588
+ const view = toRelationshipViewFilter(args.view, subject);
3589
+ const paging = toRelationshipPagingOptions(args.paging);
3590
+ let result;
3591
+ try {
3592
+ result = await reactorClient.getIncomingRelationshipEdges(args.targetIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
3593
+ } catch (error) {
3594
+ throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
3595
+ }
3596
+ try {
3597
+ return toDocumentRelationshipResultPage(result);
3598
+ } catch (error) {
3599
+ throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3600
+ }
3601
+ }
3602
+ function toRelationshipViewFilter(input, subject) {
3603
+ if (!input) return subject ? { subject } : void 0;
3604
+ return {
3605
+ subject,
3606
+ branch: fromInputMaybe(input.branch),
3607
+ scopes: toMutableArray(fromInputMaybe(input.scopes))
3608
+ };
3609
+ }
3610
+ function toRelationshipPagingOptions(input) {
3611
+ if (!input) return;
3612
+ const cursor = fromInputMaybe(input.cursor);
3613
+ const limit = fromInputMaybe(input.limit);
3614
+ if (!cursor && !limit) return;
3615
+ return {
3616
+ cursor: cursor || "",
3617
+ limit: limit || 10
3618
+ };
3619
+ }
3306
3620
  async function findDocuments(reactorClient, args, subject) {
3307
3621
  let view = subject ? { subject } : void 0;
3308
3622
  if (args.view) view = {
@@ -3335,13 +3649,42 @@ async function findDocuments(reactorClient, args, subject) {
3335
3649
  throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3336
3650
  }
3337
3651
  }
3338
- async function jobStatus(reactorClient, args) {
3652
+ /**
3653
+ * The answer for a job the caller may not see, shaped as the reactor answers
3654
+ * an unknown one so that the two cannot be told apart.
3655
+ */
3656
+ function unknownJob(jobId) {
3657
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3658
+ return {
3659
+ id: jobId,
3660
+ documentId: "",
3661
+ status: JobStatus.FAILED,
3662
+ createdAtUtcIso: now,
3663
+ completedAtUtcIso: now,
3664
+ error: {
3665
+ name: "Error",
3666
+ message: "Job not found",
3667
+ stack: ""
3668
+ },
3669
+ consistencyToken: {
3670
+ version: 1,
3671
+ createdAtUtcIso: now,
3672
+ coordinates: []
3673
+ },
3674
+ meta: {
3675
+ batchId: jobId,
3676
+ batchJobIds: [jobId]
3677
+ }
3678
+ };
3679
+ }
3680
+ async function jobStatus(reactorClient, args, serves) {
3339
3681
  let result;
3340
3682
  try {
3341
3683
  result = await reactorClient.getJobStatus(args.jobId);
3342
3684
  } catch (error) {
3343
3685
  throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
3344
3686
  }
3687
+ if (!await serves(result.documentId)) result = unknownJob(args.jobId);
3345
3688
  try {
3346
3689
  return toGqlJobInfo(result);
3347
3690
  } catch (error) {
@@ -3379,7 +3722,7 @@ async function documentOperations(reactorClient, args, subject) {
3379
3722
  }
3380
3723
  let result;
3381
3724
  try {
3382
- result = await reactorClient.getOperations(args.filter.documentId, view, operationFilter, paging);
3725
+ result = await reactorClient.getOperations(args.filter.documentIdOrSlug, view, operationFilter, paging);
3383
3726
  } catch (error) {
3384
3727
  throw new GraphQLError(`Failed to fetch document operations: ${error instanceof Error ? error.message : "Unknown error"}`);
3385
3728
  }
@@ -3411,7 +3754,7 @@ async function evaluateActions(reactorClient, args, subject) {
3411
3754
  }));
3412
3755
  let result;
3413
3756
  try {
3414
- result = await reactorClient.evaluateActions(args.documentIdentifier, branch, candidates, subject);
3757
+ result = await reactorClient.evaluateActions(args.documentIdOrSlug, branch, candidates, subject);
3415
3758
  } catch (error) {
3416
3759
  if (AuthEnforcementDisabledError.isError(error)) throw new AuthEvaluationUnsupportedError();
3417
3760
  throw new GraphQLError(`Failed to evaluate actions: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -3424,51 +3767,54 @@ async function evaluateActions(reactorClient, args, subject) {
3424
3767
  anyDenied: result.anyDenied
3425
3768
  };
3426
3769
  }
3427
- async function createDocument(reactorClient, args, reactorDriveClient) {
3770
+ async function createDocument(reactorClient, args, reactorDriveClient, subject) {
3428
3771
  if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
3429
3772
  const document = args.document;
3430
3773
  if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
3431
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
3774
+ const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
3432
3775
  let result;
3433
3776
  try {
3434
- if (parentIdentifier) {
3435
- const parent = await reactorClient.get(parentIdentifier);
3436
- if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parentIdentifier, document);
3437
- else result = await reactorClient.create(document, parentIdentifier);
3777
+ if (parentIdOrSlug) {
3778
+ const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
3779
+ if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
3780
+ else result = await reactorClient.create(document, parentIdOrSlug);
3438
3781
  } else result = await reactorClient.create(document);
3439
3782
  } catch (error) {
3440
3783
  throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
3441
3784
  }
3785
+ result = await readAsCaller(reactorClient, result, subject);
3442
3786
  try {
3443
3787
  return toGqlPhDocument(result);
3444
3788
  } catch (error) {
3445
3789
  throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3446
3790
  }
3447
3791
  }
3448
- async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
3449
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
3792
+ async function createEmptyDocument(reactorClient, args, reactorDriveClient, subject) {
3793
+ const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
3450
3794
  const name = fromInputMaybe(args.name);
3451
3795
  let result;
3452
3796
  try {
3453
- if (parentIdentifier) {
3454
- const parent = await reactorClient.get(parentIdentifier);
3797
+ if (parentIdOrSlug) {
3798
+ const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
3455
3799
  if (isDriveContainerType(parent.header.documentType)) {
3456
- const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
3800
+ const module = await reactorClient.getDocumentModelModule(args.documentType);
3801
+ const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
3457
3802
  if (name) document.header.name = name;
3458
- result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parentIdentifier, document);
3459
- } else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier });
3803
+ result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
3804
+ } else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier: parentIdOrSlug });
3460
3805
  } else result = await reactorClient.createEmpty(args.documentType, {});
3461
3806
  } catch (error) {
3462
3807
  throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
3463
3808
  }
3809
+ result = await readAsCaller(reactorClient, result, subject);
3464
3810
  try {
3465
3811
  return toGqlPhDocument(result);
3466
3812
  } catch (error) {
3467
3813
  throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3468
3814
  }
3469
3815
  }
3470
- async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient) {
3471
- const parentIdentifier = fromInputMaybe(args.parentIdentifier);
3816
+ async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient, subject) {
3817
+ const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
3472
3818
  const name = fromInputMaybe(args.name);
3473
3819
  const slug = fromInputMaybe(args.slug);
3474
3820
  const preferredEditor = fromInputMaybe(args.preferredEditor);
@@ -3478,7 +3824,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
3478
3824
  } catch (error) {
3479
3825
  throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
3480
3826
  }
3481
- const document = module.utils.createDocument();
3827
+ const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
3482
3828
  const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
3483
3829
  const state = document.state;
3484
3830
  for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
@@ -3492,22 +3838,22 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
3492
3838
  preferredEditor
3493
3839
  };
3494
3840
  let result;
3495
- if (parentIdentifier) {
3841
+ if (parentIdOrSlug) {
3496
3842
  let parent;
3497
3843
  try {
3498
- parent = await reactorClient.get(parentIdentifier);
3844
+ parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
3499
3845
  } catch (error) {
3500
3846
  throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3501
3847
  }
3502
3848
  if (isDriveContainerType(parent.header.documentType)) {
3503
3849
  const driveClient = pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient);
3504
3850
  try {
3505
- result = await driveClient.addFile(parentIdentifier, document);
3851
+ result = await driveClient.addFile(parent.header.id, document);
3506
3852
  } catch (error) {
3507
3853
  throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
3508
3854
  }
3509
3855
  } else try {
3510
- result = await reactorClient.create(document, parentIdentifier);
3856
+ result = await reactorClient.create(document, parentIdOrSlug);
3511
3857
  } catch (error) {
3512
3858
  throw new GraphQLError(`Failed to create document with parent: ${error instanceof Error ? error.message : "Unknown error"}`);
3513
3859
  }
@@ -3516,6 +3862,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
3516
3862
  } catch (error) {
3517
3863
  throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
3518
3864
  }
3865
+ result = await readAsCaller(reactorClient, result, subject);
3519
3866
  try {
3520
3867
  return toGqlPhDocument(result);
3521
3868
  } catch (error) {
@@ -3532,12 +3879,12 @@ const DEFAULT_BRANCH = "main";
3532
3879
  * the schema cannot express: a signature travels as one string, and verification
3533
3880
  * reads it as the tuple it was.
3534
3881
  */
3535
- async function execute(reactorClient, args) {
3882
+ async function execute(reactorClient, args, subject) {
3536
3883
  const actions = toSubmittableActions(args.actions);
3537
3884
  const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
3538
3885
  let result;
3539
3886
  try {
3540
- result = await reactorClient.execute(args.documentIdentifier, branch, actions);
3887
+ result = await reactorClient.execute(args.documentIdOrSlug, branch, actions, void 0, subject);
3541
3888
  } catch (error) {
3542
3889
  throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
3543
3890
  }
@@ -3559,13 +3906,13 @@ async function executeAsync(reactorClient, args) {
3559
3906
  const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
3560
3907
  let job;
3561
3908
  try {
3562
- job = await reactorClient.executeAsync(args.documentIdentifier, branch, actions);
3909
+ job = await reactorClient.executeAsync(args.documentIdOrSlug, branch, actions);
3563
3910
  } catch (error) {
3564
3911
  throw new GraphQLError(`Failed to submit actions: ${error instanceof Error ? error.message : "Unknown error"}`);
3565
3912
  }
3566
3913
  return toGqlJobInfo(job);
3567
3914
  }
3568
- async function mutateDocument(reactorClient, args) {
3915
+ async function mutateDocument(reactorClient, args, subject) {
3569
3916
  let validatedActions;
3570
3917
  try {
3571
3918
  validatedActions = validateActions(args.actions);
@@ -3576,7 +3923,7 @@ async function mutateDocument(reactorClient, args) {
3576
3923
  const branch = args.view?.branch ?? "main";
3577
3924
  let result;
3578
3925
  try {
3579
- result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
3926
+ result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions, void 0, subject);
3580
3927
  } catch (error) {
3581
3928
  throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
3582
3929
  }
@@ -3603,71 +3950,96 @@ async function mutateDocumentAsync(reactorClient, args) {
3603
3950
  }
3604
3951
  return result.id;
3605
3952
  }
3606
- async function renameDocument(reactorClient, args, signal) {
3953
+ async function renameDocument(reactorClient, args, signal, subject) {
3607
3954
  const branch = fromInputMaybe(args.branch);
3608
3955
  let result;
3609
3956
  try {
3610
- result = await reactorClient.rename(args.documentIdentifier, args.name, branch, signal);
3957
+ result = await reactorClient.rename(args.documentIdOrSlug, args.name, branch, signal);
3611
3958
  } catch (error) {
3612
3959
  throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
3613
3960
  }
3961
+ result = await readAsCaller(reactorClient, result, subject, branch);
3614
3962
  try {
3615
3963
  return toGqlPhDocument(result);
3616
3964
  } catch (error) {
3617
3965
  throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3618
3966
  }
3619
3967
  }
3620
- async function setPreferredEditor(reactorClient, args, signal) {
3968
+ async function setPreferredEditor(reactorClient, args, signal, subject) {
3621
3969
  const branch = fromInputMaybe(args.branch);
3622
3970
  const preferredEditor = fromInputMaybe(args.preferredEditor) ?? null;
3623
3971
  let result;
3624
3972
  try {
3625
- result = await reactorClient.setPreferredEditor(args.documentIdentifier, preferredEditor, branch, signal);
3973
+ result = await reactorClient.setPreferredEditor(args.documentIdOrSlug, preferredEditor, branch, signal);
3626
3974
  } catch (error) {
3627
3975
  throw new GraphQLError(`Failed to set preferred editor: ${error instanceof Error ? error.message : "Unknown error"}`);
3628
3976
  }
3977
+ result = await readAsCaller(reactorClient, result, subject, branch);
3629
3978
  try {
3630
3979
  return toGqlPhDocument(result);
3631
3980
  } catch (error) {
3632
3981
  throw new GraphQLError(`Failed to convert updated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3633
3982
  }
3634
3983
  }
3635
- async function addRelationship(reactorClient, args) {
3984
+ async function addRelationship(reactorClient, args, subject) {
3636
3985
  const branch = fromInputMaybe(args.branch);
3986
+ const metadata = fromInputMaybe(args.metadata);
3637
3987
  let result;
3638
3988
  try {
3639
- result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, branch);
3989
+ result = await reactorClient.addRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
3640
3990
  } catch (error) {
3641
3991
  throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3642
3992
  }
3993
+ result = await readAsCaller(reactorClient, result, subject, branch);
3994
+ try {
3995
+ return toGqlPhDocument(result);
3996
+ } catch (error) {
3997
+ throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3998
+ }
3999
+ }
4000
+ async function updateRelationship(reactorClient, args, subject) {
4001
+ const branch = fromInputMaybe(args.branch);
4002
+ const metadata = fromInputMaybe(args.metadata) ?? null;
4003
+ let result;
4004
+ try {
4005
+ result = await reactorClient.updateRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
4006
+ } catch (error) {
4007
+ throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
4008
+ }
4009
+ result = await readAsCaller(reactorClient, result, subject, branch);
3643
4010
  try {
3644
4011
  return toGqlPhDocument(result);
3645
4012
  } catch (error) {
3646
4013
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3647
4014
  }
3648
4015
  }
3649
- async function removeRelationship(reactorClient, args) {
4016
+ async function removeRelationship(reactorClient, args, subject) {
3650
4017
  const branch = fromInputMaybe(args.branch);
3651
4018
  let result;
3652
4019
  try {
3653
- result = await reactorClient.removeRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, branch);
4020
+ result = await reactorClient.removeRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
3654
4021
  } catch (error) {
3655
4022
  throw new GraphQLError(`Failed to remove relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3656
4023
  }
4024
+ result = await readAsCaller(reactorClient, result, subject, branch);
3657
4025
  try {
3658
4026
  return toGqlPhDocument(result);
3659
4027
  } catch (error) {
3660
4028
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3661
4029
  }
3662
4030
  }
3663
- async function moveRelationship(reactorClient, args) {
4031
+ async function moveRelationship(reactorClient, args, subject) {
3664
4032
  const branch = fromInputMaybe(args.branch);
3665
4033
  let result;
3666
4034
  try {
3667
- result = await reactorClient.moveRelationship(args.sourceParentIdentifier, args.targetParentIdentifier, args.targetIdentifier, args.relationshipType, branch);
4035
+ result = await reactorClient.moveRelationship(args.sourceParentIdOrSlug, args.targetParentIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
3668
4036
  } catch (error) {
3669
4037
  throw new GraphQLError(`Failed to move relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3670
4038
  }
4039
+ result = {
4040
+ source: await readAsCaller(reactorClient, result.source, subject, branch),
4041
+ target: await readAsCaller(reactorClient, result.target, subject, branch)
4042
+ };
3671
4043
  try {
3672
4044
  return {
3673
4045
  source: toGqlPhDocument(result.source),
@@ -3680,9 +4052,12 @@ async function moveRelationship(reactorClient, args) {
3680
4052
  async function deleteDocument(reactorClient, args, reactorDriveClient) {
3681
4053
  const propagate = toReactorPropagationMode(args.propagate);
3682
4054
  try {
3683
- const driveParent = (await reactorClient.getIncomingRelationships(args.identifier, "child")).results.find((p) => isDriveContainerType(p.header.documentType));
3684
- if (driveParent) await pickDriveClient(reactorClient, driveParent.header.documentType, reactorDriveClient).removeNode(driveParent.header.id, args.identifier);
3685
- else await reactorClient.deleteDocument(args.identifier, propagate);
4055
+ const driveParent = (await reactorClient.getIncomingRelationships(args.idOrSlug, "child")).results.find((p) => isDriveContainerType(p.header.documentType));
4056
+ if (driveParent) {
4057
+ const driveClient = pickDriveClient(reactorClient, driveParent.header.documentType, reactorDriveClient);
4058
+ const documentId = await reactorClient.resolveIdOrSlug(args.idOrSlug);
4059
+ await driveClient.removeNode(driveParent.header.id, documentId);
4060
+ } else await reactorClient.deleteDocument(args.idOrSlug, propagate);
3686
4061
  return true;
3687
4062
  } catch (error) {
3688
4063
  throw new GraphQLError(`Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -3690,9 +4065,9 @@ async function deleteDocument(reactorClient, args, reactorDriveClient) {
3690
4065
  }
3691
4066
  async function deleteDocuments(reactorClient, args) {
3692
4067
  const propagate = toReactorPropagationMode(args.propagate);
3693
- const identifiers = [...args.identifiers];
4068
+ const idsOrSlugs = [...args.idsOrSlugs];
3694
4069
  try {
3695
- await reactorClient.deleteDocuments(identifiers, propagate);
4070
+ await reactorClient.deleteDocuments(idsOrSlugs, propagate);
3696
4071
  return true;
3697
4072
  } catch (error) {
3698
4073
  throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -3706,12 +4081,19 @@ async function deleteDocuments(reactorClient, args) {
3706
4081
  * an unanswered question: the first authenticated subject to use it adopts it.
3707
4082
  */
3708
4083
  async function touchChannel(syncManager, args, boundAddress) {
4084
+ const peer = readPeerManifest(args.input.manifest);
4085
+ let existing;
3709
4086
  try {
4087
+ existing = syncManager.getById(args.input.id);
4088
+ } catch {}
4089
+ if (existing) {
4090
+ await syncManager.setPeerManifest(args.input.id, peer);
3710
4091
  return {
3711
4092
  success: true,
3712
- ackOrdinal: syncManager.getById(args.input.id).channel.inbox.ackOrdinal
4093
+ ackOrdinal: existing.channel.inbox.ackOrdinal,
4094
+ manifest: syncManager.localManifest()
3713
4095
  };
3714
- } catch {}
4096
+ }
3715
4097
  const filter = {
3716
4098
  documentId: [...args.input.filter.documentId],
3717
4099
  scope: [...args.input.filter.scope],
@@ -3725,13 +4107,45 @@ async function touchChannel(syncManager, args, boundAddress) {
3725
4107
  await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
3726
4108
  type: "polling",
3727
4109
  parameters: {}
3728
- }, filter, options, args.input.id);
4110
+ }, filter, options, args.input.id, peer);
3729
4111
  } catch (error) {
3730
4112
  throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
3731
4113
  }
3732
4114
  return {
3733
4115
  success: true,
3734
- ackOrdinal: 0
4116
+ ackOrdinal: 0,
4117
+ manifest: syncManager.localManifest()
4118
+ };
4119
+ }
4120
+ async function syncHolds(syncManager, args) {
4121
+ return (await syncManager.listHolds({
4122
+ remoteName: args.remoteName ?? void 0,
4123
+ documentId: args.documentId ?? void 0
4124
+ })).map((hold) => ({
4125
+ ...hold,
4126
+ reason: {
4127
+ ...hold.reason,
4128
+ peerSupports: [...hold.reason.peerSupports]
4129
+ },
4130
+ heldAtUtcMs: String(hold.heldAtUtcMs)
4131
+ }));
4132
+ }
4133
+ function peerAgreement(syncManager, args) {
4134
+ const agreement = syncManager.agreement();
4135
+ const members = [...agreement.members([args.collectionId])].map(([remoteName, supports]) => ({
4136
+ remoteName,
4137
+ announced: "revision" in supports,
4138
+ protocols: supports.protocols,
4139
+ features: supports.features
4140
+ }));
4141
+ return {
4142
+ collectionId: args.collectionId,
4143
+ local: agreement.local(),
4144
+ members,
4145
+ limitedBy: Object.keys(agreement.basis().wanted).map((protocol) => ({
4146
+ protocol,
4147
+ remoteNames: agreement.limitedBy(args.collectionId, protocol)
4148
+ }))
3735
4149
  };
3736
4150
  }
3737
4151
  /**
@@ -3786,7 +4200,8 @@ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
3786
4200
  * client ordinal the switchboard has successfully applied, so the client
3787
4201
  * can trim its own outbox)
3788
4202
  */
3789
- function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
4203
+ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set(), gatedOpIds) {
4204
+ const isHeld = (syncOp) => heldOpIds.has(syncOp.id) || gatedOpIds !== void 0 && !gatedOpIds.has(syncOp.id);
3790
4205
  let remote;
3791
4206
  try {
3792
4207
  remote = syncManager.getById(args.channelId);
@@ -3794,7 +4209,11 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3794
4209
  throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3795
4210
  }
3796
4211
  remote.channel.notePoll();
3797
- const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !heldOpIds.has(syncOp.id)).map((syncOp) => ({
4212
+ const revisions = {
4213
+ manifestRevision: syncManager.localManifest().revision,
4214
+ peerManifestRevision: remote.meta.peer?.manifest?.revision ?? null
4215
+ };
4216
+ const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !isHeld(syncOp)).map((syncOp) => ({
3798
4217
  documentId: syncOp.documentId,
3799
4218
  error: syncOp.error?.message ?? "Unknown error",
3800
4219
  errorType: syncOperationErrorType(syncOp.error),
@@ -3829,7 +4248,8 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3829
4248
  envelopes: [],
3830
4249
  ackOrdinal: remote.channel.inbox.ackOrdinal,
3831
4250
  deadLetters,
3832
- hasMore: false
4251
+ hasMore: false,
4252
+ ...revisions
3833
4253
  };
3834
4254
  const sorted = [...operations].sort((a, b) => {
3835
4255
  return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
@@ -3843,9 +4263,10 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3843
4263
  hasMore = true;
3844
4264
  break;
3845
4265
  }
3846
- if (heldOpIds.has(syncOp.id)) continue;
4266
+ if (isHeld(syncOp)) continue;
3847
4267
  syncOp.deliveredCount ??= 0;
3848
4268
  syncOp.emittedCount ??= 0;
4269
+ if (!forbiddenIds.has(syncOp.documentId)) while (syncOp.deliveredCount > 0 && syncOp.operations[syncOp.deliveredCount - 1].context.ordinal > args.outboxLatest) syncOp.deliveredCount -= 1;
3849
4270
  while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
3850
4271
  const remaining = syncOp.operations.slice(syncOp.deliveredCount);
3851
4272
  if (remaining.length === 0) continue;
@@ -3897,9 +4318,71 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3897
4318
  envelopes,
3898
4319
  ackOrdinal: remote.channel.inbox.ackOrdinal,
3899
4320
  deadLetters,
3900
- hasMore
4321
+ hasMore,
4322
+ ...revisions
3901
4323
  };
3902
4324
  }
4325
+ const MARKER_REFUSAL = "marker";
4326
+ /**
4327
+ * The client's UNSUPPORTED_PROTOCOL refusals of polled rows. Each becomes a hold
4328
+ * for that client, as a pushed refusal does, rather than counting as delivered.
4329
+ * A refused marker is not a hold: {@link recordPollMarkerRefusals} takes it.
4330
+ * A kind this server does not know is ignored.
4331
+ */
4332
+ function holdPollRefusals(syncManager, channelId, all) {
4333
+ const refusals = all?.filter((refusal) => refusal.kind === void 0 || refusal.kind === null);
4334
+ if (!refusals?.length) return;
4335
+ let remote;
4336
+ try {
4337
+ remote = syncManager.getById(channelId);
4338
+ } catch {
4339
+ return;
4340
+ }
4341
+ const refused = refusals.map((refusal) => {
4342
+ const syncOp = new SyncOperation(crypto.randomUUID(), "", [], remote.meta.name, refusal.documentId, [], refusal.branch, []);
4343
+ syncOp.failed(new ChannelError(ChannelErrorSource.Outbox, /* @__PURE__ */ new Error(`Refused by ${remote.meta.name}`), "UNSUPPORTED_PROTOCOL"));
4344
+ return syncOp;
4345
+ });
4346
+ remote.channel.deadLetter.add(...refused);
4347
+ }
4348
+ /**
4349
+ * The client's refusals of purge markers it polled. Persisted before the poll is
4350
+ * served; a failure fails the poll, and the client reports them again. The sync
4351
+ * manager keeps only refusals of markers this remote was owed.
4352
+ */
4353
+ async function recordPollMarkerRefusals(syncManager, channelId, all) {
4354
+ const refusals = all?.filter((refusal) => refusal.kind === MARKER_REFUSAL);
4355
+ if (!refusals?.length) return;
4356
+ if (!supportsPurgeRefusals(syncManager)) return;
4357
+ let remote;
4358
+ try {
4359
+ remote = syncManager.getById(channelId);
4360
+ } catch {
4361
+ return;
4362
+ }
4363
+ try {
4364
+ await syncManager.recordPolledMarkerRefusals(remote.meta.name, refusals.map(({ documentId, branch }) => ({
4365
+ documentId,
4366
+ branch
4367
+ })));
4368
+ } catch (error) {
4369
+ throw new RefusalNotRecordedError(error);
4370
+ }
4371
+ }
4372
+ /**
4373
+ * A poll naming no revision is from a client without peer agreement: it is
4374
+ * recorded silent, and what it can no longer run is held, before it is served.
4375
+ */
4376
+ async function silenceUnversionedPoll(syncManager, channelId, manifestRevision) {
4377
+ if (typeof manifestRevision === "string") return;
4378
+ let remote;
4379
+ try {
4380
+ remote = syncManager.getById(channelId);
4381
+ } catch {
4382
+ return;
4383
+ }
4384
+ if (remote.meta.peer?.manifest) await syncManager.setPeerManifest(channelId, null);
4385
+ }
3903
4386
  /**
3904
4387
  * Receives sync envelopes pushed by a client and adds them to the
3905
4388
  * appropriate channel inboxes.
@@ -3908,7 +4391,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3908
4391
  * It must be preserved because the inbox mailbox tracks applied ordinals
3909
4392
  * and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
3910
4393
  */
3911
- function pushSyncEnvelopes(syncManager, args) {
4394
+ async function pushSyncEnvelopes(syncManager, args) {
3912
4395
  const remoteSyncOps = /* @__PURE__ */ new Map();
3913
4396
  for (const envelope of args.envelopes) {
3914
4397
  let remote;
@@ -3929,7 +4412,12 @@ function pushSyncEnvelopes(syncManager, args) {
3929
4412
  for (const syncOp of consolidated) syncOp.jobDependencies = syncOp.jobDependencies.filter((dep) => validKeys.has(dep));
3930
4413
  remote.channel.inbox.add(...consolidated);
3931
4414
  }
3932
- return Promise.resolve(true);
4415
+ try {
4416
+ await syncManager.receiptsStored?.([...remoteSyncOps.keys()].map((remote) => remote.meta.name));
4417
+ } catch (error) {
4418
+ throw new GraphQLError(`Failed to store received markers: ${error instanceof Error ? error.message : String(error)}`);
4419
+ }
4420
+ return true;
3933
4421
  }
3934
4422
  //#endregion
3935
4423
  //#region src/graphql/document-model-subgraph.ts
@@ -3956,6 +4444,16 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
3956
4444
  const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
3957
4445
  return this.resolvers[`${documentName}Mutations`];
3958
4446
  }
4447
+ /** Drops the items the host's own ACL refuses the caller. */
4448
+ async #readableItems(page, ctx) {
4449
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
4450
+ const items = [];
4451
+ for (const item of page.items) if (await this.canReadDocument(item.id, ctx)) items.push(item);
4452
+ return {
4453
+ ...page,
4454
+ items
4455
+ };
4456
+ }
3959
4457
  /**
3960
4458
  * Generate __resolveType functions for union types found in the document model schema.
3961
4459
  * Parses the state schema to find union definitions and their member types,
@@ -4011,13 +4509,14 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4011
4509
  Query: { [documentName]: () => ({}) },
4012
4510
  [`${documentName}Queries`]: {
4013
4511
  document: async (_, args, ctx) => {
4014
- const { identifier, view } = args;
4015
- if (!identifier) throw new GraphQLError("Document identifier is required");
4512
+ const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
4513
+ const { view } = args;
4514
+ if (!idOrSlug) throw new GraphQLError("Document identifier is required");
4016
4515
  const result = await document(this.reactorClient, {
4017
- identifier,
4516
+ idOrSlug,
4018
4517
  view
4019
- });
4020
- if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
4518
+ }, this.viewSubject(ctx));
4519
+ if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${idOrSlug} is not of type ${documentType}`);
4021
4520
  await this.assertCanReadCanonical(result.document.id, ctx);
4022
4521
  return result;
4023
4522
  },
@@ -4026,17 +4525,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4026
4525
  const result = await findDocuments(this.reactorClient, {
4027
4526
  search: { type: documentType },
4028
4527
  paging
4029
- });
4030
- if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
4031
- const filteredItems = [];
4032
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
4033
- return {
4034
- ...result,
4035
- items: filteredItems,
4036
- totalCount: filteredItems.length
4037
- };
4038
- }
4039
- return result;
4528
+ }, this.viewSubject(ctx));
4529
+ return this.#readableItems(result, ctx);
4040
4530
  },
4041
4531
  findDocuments: async (_, args, ctx) => {
4042
4532
  const { search, view, paging } = args;
@@ -4047,98 +4537,94 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4047
4537
  },
4048
4538
  view,
4049
4539
  paging
4050
- });
4051
- if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
4052
- const filteredItems = [];
4053
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
4054
- return {
4055
- ...result,
4056
- items: filteredItems,
4057
- totalCount: filteredItems.length
4058
- };
4059
- }
4060
- return result;
4540
+ }, this.viewSubject(ctx));
4541
+ return this.#readableItems(result, ctx);
4061
4542
  },
4062
4543
  documentOutgoingRelationships: async (_, args, ctx) => {
4544
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
4063
4545
  const { relationshipType, view, paging } = args;
4064
- const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
4546
+ const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
4065
4547
  const result = await documentOutgoingRelationships(this.reactorClient, {
4066
- sourceIdentifier: handle.fetchIdentifier,
4548
+ sourceIdOrSlug: handle.fetchIdentifier,
4067
4549
  relationshipType,
4068
4550
  view,
4069
4551
  paging
4070
- });
4071
- const filteredItems = result.items.filter((item) => item.documentType === documentType);
4552
+ }, this.viewSubject(ctx));
4553
+ const readable = await this.#readableItems(result, ctx);
4554
+ const filteredItems = readable.items.filter((item) => item.documentType === documentType);
4072
4555
  return {
4073
- ...result,
4074
- items: filteredItems,
4075
- totalCount: filteredItems.length
4556
+ ...readable,
4557
+ items: filteredItems
4076
4558
  };
4077
4559
  },
4078
4560
  documentIncomingRelationships: async (_, args, ctx) => {
4561
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
4079
4562
  const { relationshipType, view, paging } = args;
4080
- const handle = await this.assertCanRead(args.targetIdentifier, ctx);
4081
- return documentIncomingRelationships(this.reactorClient, {
4082
- targetIdentifier: handle.fetchIdentifier,
4563
+ const handle = await this.assertCanRead(targetIdOrSlug, ctx);
4564
+ const result = await documentIncomingRelationships(this.reactorClient, {
4565
+ targetIdOrSlug: handle.fetchIdentifier,
4083
4566
  relationshipType,
4084
4567
  view,
4085
4568
  paging
4086
- });
4569
+ }, this.viewSubject(ctx));
4570
+ return this.#readableItems(result, ctx);
4087
4571
  }
4088
4572
  },
4089
4573
  Mutation: { [documentName]: () => ({}) },
4090
4574
  [`${documentName}Mutations`]: {
4091
4575
  createDocument: async (_, args, ctx) => {
4092
4576
  const { name, slug, preferredEditor, initialState } = args;
4093
- let parentIdentifier = args.parentIdentifier;
4094
- if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
4577
+ let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
4578
+ if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
4095
4579
  else this.assertCanCreate(ctx);
4096
4580
  let createdDoc;
4097
4581
  if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
4098
4582
  documentType,
4099
- parentIdentifier,
4583
+ parentIdOrSlug,
4100
4584
  name,
4101
4585
  slug,
4102
4586
  preferredEditor,
4103
4587
  initialState: initialState ?? {}
4104
- }, this.graphqlManager.reactorDriveClient);
4588
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4105
4589
  else createdDoc = await createEmptyDocument(this.reactorClient, {
4106
4590
  documentType,
4107
- parentIdentifier,
4591
+ parentIdOrSlug,
4108
4592
  name
4109
- }, this.graphqlManager.reactorDriveClient);
4593
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4110
4594
  if (ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4111
- if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
4595
+ if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)], void 0, this.viewSubject(ctx)));
4112
4596
  return createdDoc;
4113
4597
  },
4114
4598
  createEmptyDocument: async (_, args, ctx) => {
4115
- let parentIdentifier = args.parentIdentifier;
4116
- if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
4599
+ let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
4600
+ if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
4117
4601
  else this.assertCanCreate(ctx);
4118
4602
  const result = await createEmptyDocument(this.reactorClient, {
4119
4603
  documentType,
4120
- parentIdentifier
4121
- }, this.graphqlManager.reactorDriveClient);
4604
+ parentIdOrSlug
4605
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4122
4606
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4123
4607
  return result;
4124
4608
  },
4125
4609
  ...operations.reduce((mutations, op) => {
4126
4610
  mutations[camelCase(op.name)] = async (_, args, ctx) => {
4127
- const { docId, input } = args;
4128
- const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
4129
- if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4611
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
4612
+ const { input } = args;
4613
+ const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
4614
+ if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${documentIdOrSlug} is not of type ${documentType}`);
4130
4615
  const action = this.documentModel.actions[camelCase(op.name)];
4131
4616
  if (!action) throw new GraphQLError(`Action ${op.name} not found`);
4132
4617
  try {
4133
- return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
4618
+ return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)], void 0, this.viewSubject(ctx)));
4134
4619
  } catch (error) {
4135
4620
  throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
4136
4621
  }
4137
4622
  };
4138
4623
  mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
4139
- const { docId, input } = args;
4140
- const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
4141
- if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4624
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
4625
+ const { input } = args;
4626
+ const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
4627
+ if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${documentIdOrSlug} is not of type ${documentType}`);
4142
4628
  const action = this.documentModel.actions[camelCase(op.name)];
4143
4629
  if (!action) throw new GraphQLError(`Action ${op.name} not found`);
4144
4630
  try {
@@ -4162,7 +4648,7 @@ const DRIVE_ID_HEADER = "drive-id";
4162
4648
  * in the ownership set yet); other create-shaped operations that may
4163
4649
  * synthesize a drive must be added here too.
4164
4650
  */
4165
- const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
4651
+ const CACHE_BYPASS_OPERATIONS = /* @__PURE__ */ new Set(["createDocument", "createEmptyDocument"]);
4166
4652
  const driveIdMap = /* @__PURE__ */ new WeakMap();
4167
4653
  /** Internal — only `graphql-manager.ts` should call this. */
4168
4654
  function getRequestDriveId(request) {
@@ -4232,6 +4718,7 @@ function wrongShardResponse(driveId) {
4232
4718
  * 421 response.
4233
4719
  */
4234
4720
  var DriveOwnershipCache = class {
4721
+ reactorClient;
4235
4722
  drives = /* @__PURE__ */ new Set();
4236
4723
  constructor(reactorClient) {
4237
4724
  this.reactorClient = reactorClient;
@@ -4363,8 +4850,8 @@ var GraphQLManager = class {
4363
4850
  subgraphHandlerCache = /* @__PURE__ */ new Map();
4364
4851
  /** subgraphPath → the http adapter handle of its mounted route. */
4365
4852
  subgraphRouteHandles = /* @__PURE__ */ new Map();
4366
- /** Handle of the currently mounted supergraph SSE route, if any. */
4367
- sseRouteHandle;
4853
+ /** Mounted SSE routes, keyed by path: the supergraph's and each subgraph's. */
4854
+ sseRouteHandles = /* @__PURE__ */ new Map();
4368
4855
  /**
4369
4856
  * Package name → the subgraph instances registered from it, keyed by
4370
4857
  * subgraph name. Tracks the package association so that removed packages
@@ -4383,26 +4870,43 @@ var GraphQLManager = class {
4383
4870
  authorizationService;
4384
4871
  /** Route service in use: the injected one, or one built on first need. */
4385
4872
  #fallbackRoutes;
4386
- constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient, syncServingGate, httpRoutes) {
4387
- this.path = path;
4388
- this.httpServer = httpServer;
4389
- this.wsServer = wsServer;
4390
- this.reactorClient = reactorClient;
4391
- this.relationalDb = relationalDb;
4392
- this.analyticsStore = analyticsStore;
4393
- this.syncManager = syncManager;
4394
- this.logger = logger;
4395
- this.httpAdapter = httpAdapter;
4396
- this.gatewayAdapter = gatewayAdapter;
4397
- this.authService = authService;
4398
- this.documentPermissionService = documentPermissionService;
4399
- this.featureFlags = featureFlags;
4400
- this.port = port;
4401
- this.syncServingGate = syncServingGate;
4402
- this.httpRoutes = httpRoutes;
4403
- if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
4404
- this.authorizationService = authorizationService;
4405
- this.reactorDriveClient = reactorDriveClient;
4873
+ path;
4874
+ httpServer;
4875
+ wsServer;
4876
+ reactorClient;
4877
+ relationalDb;
4878
+ analyticsStore;
4879
+ syncManager;
4880
+ logger;
4881
+ httpAdapter;
4882
+ gatewayAdapter;
4883
+ authService;
4884
+ documentPermissionService;
4885
+ featureFlags;
4886
+ port;
4887
+ syncServingGate;
4888
+ httpRoutes;
4889
+ attachments;
4890
+ constructor(options) {
4891
+ this.path = options.path;
4892
+ this.httpServer = options.httpServer;
4893
+ this.wsServer = options.wsServer;
4894
+ this.reactorClient = options.reactorClient;
4895
+ this.relationalDb = options.relationalDb;
4896
+ this.analyticsStore = options.analyticsStore;
4897
+ this.syncManager = options.syncManager;
4898
+ this.logger = options.logger;
4899
+ this.httpAdapter = options.httpAdapter;
4900
+ this.gatewayAdapter = options.gatewayAdapter;
4901
+ this.authService = options.authService;
4902
+ this.documentPermissionService = options.documentPermissionService;
4903
+ this.featureFlags = options.featureFlags ?? DefaultFeatureFlags;
4904
+ this.port = options.port ?? 4001;
4905
+ this.authorizationService = options.authorizationService;
4906
+ this.reactorDriveClient = options.reactorDriveClient;
4907
+ this.syncServingGate = options.syncServingGate;
4908
+ this.httpRoutes = options.httpRoutes;
4909
+ this.attachments = options.attachments;
4406
4910
  this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
4407
4911
  this.wsServer.setMaxListeners(0);
4408
4912
  }
@@ -4425,23 +4929,25 @@ var GraphQLManager = class {
4425
4929
  const matched = driveMatcher(url.pathname);
4426
4930
  const driveIdOrSlug = matched ? matched.params.drive : void 0;
4427
4931
  if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
4932
+ const user = getAuthContext(request)?.user;
4428
4933
  try {
4429
- const driveDoc = await this.reactorClient.get(driveIdOrSlug);
4430
- if (!await this.authorizationService.canRead(driveDoc.header.id, getAuthContext(request)?.user?.address)) {
4934
+ const driveDoc = await this.reactorClient.get(driveIdOrSlug, { subject: callerSubject(user) });
4935
+ if (!await this.authorizationService.canRead(driveDoc.header.id, user?.address)) {
4431
4936
  this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
4432
4937
  return Response.json({ error: "Drive not found" }, { status: 404 });
4433
4938
  }
4434
4939
  const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto")?.split(",")[0].trim() ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("x-forwarded-host")?.split(",")[0].trim() ?? request.headers.get("host") ?? ""}${(request.headers.get("x-forwarded-prefix")?.split(",")[0].trim().replace(/\/$/, "") ?? "") + (this.path === "/" ? "" : this.path)}/graphql/r`;
4940
+ const global = driveDoc.state.global;
4435
4941
  return Response.json({
4436
4942
  id: driveDoc.header.id,
4437
4943
  slug: driveDoc.header.slug,
4438
4944
  meta: driveDoc.header.meta,
4439
- name: driveDoc.state.global.name || driveDoc.header.name,
4440
- icon: driveDoc.state.global.icon ?? void 0,
4945
+ name: global?.name || driveDoc.header.name,
4946
+ icon: global?.icon ?? void 0,
4441
4947
  ...graphqlEndpoint && { graphqlEndpoint }
4442
4948
  });
4443
4949
  } catch (error) {
4444
- this.logger.debug(`Drive not found: ${driveIdOrSlug}`, error);
4950
+ this.logger.debug("Drive not found: @drive (@error)", driveIdOrSlug, error);
4445
4951
  return Response.json({ error: "Drive not found" }, { status: 404 });
4446
4952
  }
4447
4953
  };
@@ -4474,7 +4980,7 @@ var GraphQLManager = class {
4474
4980
  for (const subgraph of coreSubgraphs) try {
4475
4981
  await this.registerSubgraph(subgraph, supergraph, true);
4476
4982
  } catch (error) {
4477
- this.logger.error(`Failed to setup core subgraph ${subgraph.name}`, error);
4983
+ this.logger.error("Failed to setup core subgraph @subgraph: @error", subgraph.name, error);
4478
4984
  }
4479
4985
  return this.#setupSubgraphs(this.coreSubgraphsMap);
4480
4986
  }
@@ -4495,12 +5001,13 @@ var GraphQLManager = class {
4495
5001
  path: this.path,
4496
5002
  documentPermissionService: this.documentPermissionService,
4497
5003
  authorizationService: this.authorizationService,
4498
- syncServingGate: this.syncServingGate
5004
+ syncServingGate: this.syncServingGate,
5005
+ attachments: this.attachments
4499
5006
  });
4500
5007
  await this.#addSubgraphInstance(subgraphInstance, supergraph, false, DOCUMENT_MODEL_SUBGRAPH_SOURCE);
4501
5008
  registeredNames.add(subgraphInstance.name);
4502
5009
  } catch (error) {
4503
- this.logger.error(`Failed to setup document model subgraph for ${documentModel.documentModel.global.id}`, error instanceof Error ? error.message : error);
5010
+ this.logger.error("Failed to setup document model subgraph for @documentType: @error", documentModel.documentModel.global.id, error instanceof Error ? error.message : error);
4504
5011
  this.logger.debug("@error", error);
4505
5012
  }
4506
5013
  }
@@ -4562,6 +5069,7 @@ var GraphQLManager = class {
4562
5069
  routeHandle.dispose();
4563
5070
  this.subgraphRouteHandles.delete(subgraphPath);
4564
5071
  }
5072
+ this.#disposeSSERoute(subgraphPath);
4565
5073
  const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
4566
5074
  if (wsDisposer) {
4567
5075
  this.subgraphWsDisposers.delete(subgraphPath);
@@ -4631,7 +5139,8 @@ var GraphQLManager = class {
4631
5139
  path: this.path,
4632
5140
  documentPermissionService: this.documentPermissionService,
4633
5141
  authorizationService: this.authorizationService,
4634
- syncServingGate: this.syncServingGate
5142
+ syncServingGate: this.syncServingGate,
5143
+ attachments: this.attachments
4635
5144
  });
4636
5145
  return this.#addSubgraphInstance(subgraphInstance, supergraph, core, packageName);
4637
5146
  }
@@ -4658,7 +5167,7 @@ var GraphQLManager = class {
4658
5167
  try {
4659
5168
  routes?.disposeScope(packageName);
4660
5169
  } catch (error) {
4661
- this.logger.warn(`Failed to release HTTP routes for "${packageName}": @error`, error);
5170
+ this.logger.warn(`Failed to release HTTP routes for "@package": @error`, packageName, error);
4662
5171
  }
4663
5172
  }
4664
5173
  /**
@@ -4758,15 +5267,12 @@ var GraphQLManager = class {
4758
5267
  authService: this.authService,
4759
5268
  requireAuthenticatedCaller: () => this.#requireAuthMiddleware !== void 0,
4760
5269
  logger: this.logger,
4761
- buildContext: (connectionParams, user) => {
4762
- const context = {
4763
- headers: connectionParams,
4764
- db: this.relationalDb,
4765
- ...this.getAdditionalContextFields()
4766
- };
4767
- if (user) context.user = user;
4768
- return context;
4769
- }
5270
+ buildContext: (connectionParams, user) => ({
5271
+ headers: connectionParams,
5272
+ db: this.relationalDb,
5273
+ ...this.getAdditionalContextFields(),
5274
+ user
5275
+ })
4770
5276
  });
4771
5277
  }
4772
5278
  setSupergraph(supergraph, subgraphs) {
@@ -4901,10 +5407,7 @@ var GraphQLManager = class {
4901
5407
  const allSubgraphs = this.#getAllSubgraphs();
4902
5408
  const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
4903
5409
  if (modules.length === 0) {
4904
- if (this.sseRouteHandle !== void 0) {
4905
- this.sseRouteHandle.dispose();
4906
- this.sseRouteHandle = void 0;
4907
- }
5410
+ this.#disposeSSERoute(superGraphPath);
4908
5411
  return;
4909
5412
  }
4910
5413
  try {
@@ -4930,8 +5433,16 @@ var GraphQLManager = class {
4930
5433
  contextFactory: this.#makeContextFactory()
4931
5434
  });
4932
5435
  const handler = this.#composeFetchMiddleware(rawHandler);
4933
- if (this.sseRouteHandle !== void 0) this.sseRouteHandle.dispose();
4934
- this.sseRouteHandle = this.httpAdapter.mount(ssePath, handler, { exact: true });
5436
+ this.#disposeSSERoute(basePath);
5437
+ this.sseRouteHandles.set(ssePath, this.httpAdapter.mount(ssePath, handler, { exact: true }));
5438
+ }
5439
+ #disposeSSERoute(basePath) {
5440
+ const ssePath = basePath + "/stream";
5441
+ const handle = this.sseRouteHandles.get(ssePath);
5442
+ if (handle !== void 0) {
5443
+ handle.dispose();
5444
+ this.sseRouteHandles.delete(ssePath);
5445
+ }
4935
5446
  }
4936
5447
  /**
4937
5448
  * Compose the request-level fetch middleware chain. Auth runs first
@@ -5215,7 +5726,6 @@ const DocumentModelStateDTO = z.object({
5215
5726
  }).strip();
5216
5727
  const DocumentModelResultPageDTO = z.object({
5217
5728
  items: z.array(DocumentModelStateDTO),
5218
- totalCount: z.number().int(),
5219
5729
  hasNextPage: z.boolean(),
5220
5730
  hasPreviousPage: z.boolean(),
5221
5731
  cursor: z.string().nullable().optional()
@@ -5236,7 +5746,20 @@ const PHDocumentDTO = z.object({
5236
5746
  }).strip();
5237
5747
  const PHDocumentResultPageDTO = z.object({
5238
5748
  items: z.array(PHDocumentDTO),
5239
- totalCount: z.number().int(),
5749
+ hasNextPage: z.boolean(),
5750
+ hasPreviousPage: z.boolean(),
5751
+ cursor: z.string().nullable().optional()
5752
+ }).strip();
5753
+ const DocumentRelationshipDTO = z.object({
5754
+ sourceId: z.string(),
5755
+ targetId: z.string(),
5756
+ relationshipType: z.string(),
5757
+ metadata: JSONObjectDTO.nullable().optional(),
5758
+ createdAt: DateTimeDTO,
5759
+ updatedAt: DateTimeDTO
5760
+ }).strip();
5761
+ const DocumentRelationshipResultPageDTO = z.object({
5762
+ items: z.array(DocumentRelationshipDTO),
5240
5763
  hasNextPage: z.boolean(),
5241
5764
  hasPreviousPage: z.boolean(),
5242
5765
  cursor: z.string().nullable().optional()
@@ -5291,7 +5814,6 @@ const OperationDTO = z.object({
5291
5814
  }).strip();
5292
5815
  const OperationResultPageDTO = z.object({
5293
5816
  items: z.array(OperationDTO),
5294
- totalCount: z.number().int(),
5295
5817
  hasNextPage: z.boolean(),
5296
5818
  hasPreviousPage: z.boolean(),
5297
5819
  cursor: z.string().nullable().optional()
@@ -5344,6 +5866,12 @@ const operationValidators = {
5344
5866
  GetDocumentIncomingRelationships: (data) => {
5345
5867
  if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
5346
5868
  },
5869
+ GetDocumentOutgoingRelationshipEdges: (data) => {
5870
+ if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
5871
+ },
5872
+ GetDocumentIncomingRelationshipEdges: (data) => {
5873
+ if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
5874
+ },
5347
5875
  FindDocuments: (data) => {
5348
5876
  if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
5349
5877
  },
@@ -5377,6 +5905,9 @@ const operationValidators = {
5377
5905
  AddRelationship: (data) => {
5378
5906
  if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
5379
5907
  },
5908
+ UpdateRelationship: (data) => {
5909
+ if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
5910
+ },
5380
5911
  RemoveRelationship: (data) => {
5381
5912
  if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
5382
5913
  },
@@ -5418,7 +5949,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
5418
5949
  }
5419
5950
  //#endregion
5420
5951
  //#region src/graphql/reactor/schema.graphql
5421
- var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n}\n\ninput OperationsFilterInput {\n documentId: String!\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n \"\"\"\n How the origin classified the failure. A peer needs this to mirror the origin's\n quarantine decision; without it a held auth operation would freeze the document\n on the peer side while the origin kept syncing.\n \"\"\"\n errorType: String\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Null until then, which\n is the state every job is in when it is handed back from a submission.\n \"\"\"\n result: JSONObject\n\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n result: JSONObject!\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"The index of the last operation in the scope the action applies to.\"\"\"\n prevOpIndex: Int\n\n \"\"\"The hash of the state the action applies to.\"\"\"\n prevOpHash: String\n\n \"\"\"Covers replay of an operation that would otherwise be a no-op.\"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n}\n\ntype Query {\n # Get document models for a namespace\n documentModels(\n namespace: String\n paging: PagingInput\n ): DocumentModelResultPage!\n\n # Get a specific document by ID or slug\n document(identifier: String!, view: ViewFilterInput): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get incoming relationships of a given type to a target document\n documentIncomingRelationships(\n targetIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n documentIdentifier: String!\n branch: String\n candidates: [ActionCandidateInput!]!\n ): ActionEvaluations!\n\n \"\"\"\n Polls for sync envelopes from a channel.\n\n An operation stored before the API required an action id cannot be\n represented by this schema, and rather than serving a partial response beside\n a bare non-null violation, this fails with extensions.code\n MALFORMED_STORED_OPERATION naming the operation. That code means the failure\n is worth polling through: the document holding the operation needs repairing,\n but the channel serves every other document, and a peer that stopped polling\n would stop receiving those too.\n \"\"\"\n pollSyncEnvelopes(\n channelId: String!\n outboxAck: Int!\n outboxLatest: Int!\n ): PollSyncEnvelopesResult!\n}\n\ntype Mutation {\n # Create a new document\n createDocument(document: JSONObject!, parentIdentifier: String): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdentifier: String\n ): PHDocument!\n\n \"\"\"\n Applies actions to a document and waits for the result.\n\n Each action is coerced against `ActionInput`, so one missing a field the wire\n declares is refused before any work starts. The id in particular: it is hashed\n into the operation id and replay dedupes by it, so an action without one is\n stored under an operation id shared by every id-less operation on the same\n document, scope and branch.\n\n Named for `IReactorClient.execute`, which this is the wire form of. `branch`\n defaults to `main`.\n \"\"\"\n execute(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): PHDocument!\n\n \"\"\"\n Submits actions to a document and returns the job that will apply them.\n\n Coerced exactly as `execute` is, so a malformed action is refused here rather\n than surfacing later as a failed job. Returns the job rather than only its id,\n so a caller has the status and the creation time it would otherwise have to\n ask for; `result` is null until the job produces one.\n \"\"\"\n executeAsync(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): JobInfo!\n\n # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n @deprecated(\n reason: \"Use execute. Actions here are untyped, so a malformed one is refused by a hand-written check rather than by the schema, and `view.scopes` is accepted but ignored.\"\n )\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\n @deprecated(\n reason: \"Use executeAsync, which is typed and returns the job rather than only its id.\"\n )\n\n # Rename a document\n renameDocument(\n documentIdentifier: String!\n name: String!\n branch: String\n ): PHDocument!\n\n # Update the preferred editor recorded in the document header meta.\n # Pass null/omit to clear it.\n setPreferredEditor(\n documentIdentifier: String!\n preferredEditor: String\n branch: String\n ): PHDocument!\n\n # Add a relationship between two documents\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdentifier: String!\n targetParentIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(identifier: String!, propagate: PropagationMode): Boolean!\n\n # Delete multiple documents\n deleteDocuments(identifiers: [String!]!, propagate: PropagationMode): Boolean!\n\n # Touch (create or update) a channel for sync\n touchChannel(input: TouchChannelInput!): TouchChannelResult!\n\n # Push sync envelopes to a channel\n pushSyncEnvelopes(envelopes: [SyncEnvelopeInput!]!): Boolean!\n}\n\ntype Subscription {\n # Subscribe to document changes\n documentChanges(\n search: SearchFilterInput\n view: ViewFilterInput\n ): DocumentChangeEvent!\n\n # Subscribe to job changes\n jobChanges(jobId: String!): JobChangeEvent!\n}\n";
5952
+ var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n @deprecated(reason: \"Ignored. Filter by type or parentId.\")\n}\n\ninput OperationsFilterInput {\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\n# A directed edge between two documents, with the metadata recorded against it.\ntype DocumentRelationship {\n sourceId: String!\n targetId: String!\n relationshipType: String!\n metadata: JSONObject\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\ntype DocumentRelationshipResultPage {\n items: [DocumentRelationship!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n \"\"\"\n How the origin classified the failure. A peer needs this to mirror the origin's\n quarantine decision; without it a held auth operation would freeze the document\n on the peer side while the origin kept syncing.\n \"\"\"\n errorType: String\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n # The server's current manifest revision\n manifestRevision: String\n # The client's manifest revision as the server holds it\n peerManifestRevision: String\n}\n\ntype SyncHoldReason {\n protocol: String!\n version: Int!\n peerSupports: [Int!]!\n}\n\n# A document held back from a remote whose peer cannot run its versions\ntype SyncHold {\n remoteName: String!\n documentId: String!\n branch: String!\n reason: SyncHoldReason!\n heldAtUtcMs: String!\n}\n\ntype PeerAgreementMember {\n remoteName: String!\n # False for a peer that announced nothing: it gets the baselines\n announced: Boolean!\n protocols: JSONObject!\n features: JSONObject!\n}\n\ntype PeerAgreementLimit {\n protocol: String!\n # The remotes whose peers lack the version new documents would take\n remoteNames: [String!]!\n}\n\ntype PeerAgreement {\n collectionId: String!\n local: JSONObject!\n members: [PeerAgreementMember!]!\n limitedBy: [PeerAgreementLimit!]!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Null until then, which\n is the state every job is in when it is handed back from a submission.\n \"\"\"\n result: JSONObject\n\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Nullable for the same\n reason as `JobInfo.result`: a job that has not written yet has produced\n nothing, and an empty object would claim otherwise.\n \"\"\"\n result: JSONObject\n\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"\n The index of the last operation in the scope the action applies to.\n \"\"\"\n prevOpIndex: Int\n\n \"\"\"\n The hash of the state the action applies to.\n \"\"\"\n prevOpHash: String\n\n \"\"\"\n Covers replay of an operation that would otherwise be a no-op.\n \"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput SyncRefusalInput {\n documentId: String!\n branch: String!\n # \"marker\" for a refused purge marker; absent for UNSUPPORTED_PROTOCOL\n kind: String\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n # The client's peer manifest; absent from a client without peer agreement\n manifest: JSONObject\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n # The server's peer manifest\n manifest: JSONObject\n}\n\ntype Query {\n # Get document models for a namespace\n documentModels(\n namespace: String\n paging: PagingInput\n ): DocumentModelResultPage!\n\n # Get a specific document by ID or slug\n document(\n idOrSlug: String\n identifier: String @deprecated(reason: \"Use idOrSlug.\")\n view: ViewFilterInput\n ): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get incoming relationships of a given type to a target document\n documentIncomingRelationships(\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get the outgoing relationship edges of a source document. Unlike\n # documentOutgoingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentOutgoingRelationshipEdges(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Get the incoming relationship edges of a target document. Unlike\n # documentIncomingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentIncomingRelationshipEdges(\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\n branch: String\n candidates: [ActionCandidateInput!]!\n ): ActionEvaluations!\n\n \"\"\"\n Polls for sync envelopes from a channel.\n\n An operation stored before the API required an action id cannot be\n represented by this schema, and rather than serving a partial response beside\n a bare non-null violation, this fails with extensions.code\n MALFORMED_STORED_OPERATION naming the operation. That code means the failure\n is worth polling through: the document holding the operation needs repairing,\n but the channel serves every other document, and a peer that stopped polling\n would stop receiving those too.\n \"\"\"\n pollSyncEnvelopes(\n channelId: String!\n outboxAck: Int!\n outboxLatest: Int!\n # The client's manifest revision; absent from a client without peer agreement\n manifestRevision: String\n # Documents the client refused from earlier polls: held, or a refused marker\n refusals: [SyncRefusalInput!]\n ): PollSyncEnvelopesResult!\n\n # Supreme admin, or the address the named remote's channel is bound to\n syncHolds(remoteName: String, documentId: String): [SyncHold!]!\n\n # Supreme admin, or an address bound to a channel in the collection\n peerAgreement(collectionId: String!): PeerAgreement!\n}\n\ntype Mutation {\n # Create a new document\n createDocument(\n document: JSONObject!\n parentIdOrSlug: String\n parentIdentifier: String @deprecated(reason: \"Use parentIdOrSlug.\")\n ): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdOrSlug: String\n parentIdentifier: String @deprecated(reason: \"Use parentIdOrSlug.\")\n ): PHDocument!\n\n \"\"\"\n Applies actions to a document and waits for the result.\n\n Each action is coerced against `ActionInput`, so one missing a field the wire\n declares is refused before any work starts. The id in particular: it is hashed\n into the operation id and replay dedupes by it, so an action without one is\n stored under an operation id shared by every id-less operation on the same\n document, scope and branch.\n\n Named for `IReactorClient.execute`, which this is the wire form of. `branch`\n defaults to `main`.\n \"\"\"\n execute(\n documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\n actions: [ActionInput!]!\n branch: String\n ): PHDocument!\n\n \"\"\"\n Submits actions to a document and returns the job that will apply them.\n\n Coerced exactly as `execute` is, so a malformed action is refused here rather\n than surfacing later as a failed job. Returns the job rather than only its id,\n so a caller has the status and the creation time it would otherwise have to\n ask for; `result` is null until the job produces one.\n \"\"\"\n executeAsync(\n documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\n actions: [ActionInput!]!\n branch: String\n ): JobInfo!\n\n # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n @deprecated(\n reason: \"Use execute. Actions here are untyped, so a malformed one is refused by a hand-written check rather than by the schema, and `view.scopes` is accepted but ignored.\"\n )\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\n @deprecated(\n reason: \"Use executeAsync, which is typed and returns the job rather than only its id.\"\n )\n\n # Rename a document\n renameDocument(\n documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\n name: String!\n branch: String\n ): PHDocument!\n\n # Update the preferred editor recorded in the document header meta.\n # Pass null/omit to clear it.\n setPreferredEditor(\n documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\n preferredEditor: String\n branch: String\n ): PHDocument!\n\n # Add a relationship between two documents. Adding one that already exists is\n # a no-op, metadata included -- use updateRelationship to change an edge.\n addRelationship(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Replace the metadata of an existing relationship, preserving its createdAt.\n # Omitting metadata, or passing null, clears it.\n updateRelationship(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdOrSlug: String\n sourceParentIdentifier: String\n @deprecated(reason: \"Use sourceParentIdOrSlug.\")\n targetParentIdOrSlug: String\n targetParentIdentifier: String\n @deprecated(reason: \"Use targetParentIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(\n idOrSlug: String\n identifier: String @deprecated(reason: \"Use idOrSlug.\")\n propagate: PropagationMode\n ): Boolean!\n\n # Delete multiple documents\n deleteDocuments(\n idsOrSlugs: [String!]\n identifiers: [String!] @deprecated(reason: \"Use idsOrSlugs.\")\n propagate: PropagationMode\n ): Boolean!\n\n # Touch (create or update) a channel for sync\n touchChannel(input: TouchChannelInput!): TouchChannelResult!\n\n # Push sync envelopes to a channel\n pushSyncEnvelopes(\n envelopes: [SyncEnvelopeInput!]!\n # The server's manifest revision the client gated these envelopes under\n peerManifestRevision: String\n ): Boolean!\n}\n\ntype Subscription {\n # Subscribe to document changes\n documentChanges(\n search: SearchFilterInput\n view: ViewFilterInput\n ): DocumentChangeEvent!\n\n # Subscribe to job changes\n jobChanges(jobId: String!): JobChangeEvent!\n}\n";
5422
5953
  //#endregion
5423
5954
  //#region src/graphql/reactor/pubsub.ts
5424
5955
  const pubSub = new PubSub();
@@ -5429,26 +5960,79 @@ const SUBSCRIPTION_TRIGGERS = {
5429
5960
  DOCUMENT_CHANGES: "DOCUMENT_CHANGES",
5430
5961
  JOB_CHANGES: "JOB_CHANGES"
5431
5962
  };
5432
- let globalDocumentUnsubscribe = null;
5433
- let documentSubscriberCount = 0;
5434
- function ensureGlobalDocumentSubscription(reactorClient) {
5435
- if (documentSubscriberCount === 0) globalDocumentUnsubscribe = reactorClient.subscribe({}, (event) => {
5436
- const payload = {
5437
- documentChanges: event,
5438
- search: {},
5439
- view: void 0
5963
+ function subjectKey(subject) {
5964
+ return `${subject.address ?? ""}|${subject.key ?? ""}`;
5965
+ }
5966
+ /**
5967
+ * The reactor's change feed, read once per distinct subject and shared by that
5968
+ * subject's GraphQL subscriptions. Each feed is read as its subject, so the
5969
+ * client withholds what that subject may not read; one feed read with no view
5970
+ * would gate every subscriber as the host.
5971
+ */
5972
+ var DocumentChangeFeed = class {
5973
+ reactorClient;
5974
+ #pubSub = new PubSub();
5975
+ #feeds = /* @__PURE__ */ new Map();
5976
+ constructor(reactorClient) {
5977
+ this.reactorClient = reactorClient;
5978
+ }
5979
+ subscribe(subject) {
5980
+ const key = subjectKey(subject);
5981
+ const topic = `${SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES}:${key}`;
5982
+ const iterator = this.#pubSub.asyncIterableIterator(topic);
5983
+ this.#acquire(key, topic, subject);
5984
+ let released = false;
5985
+ const release = () => {
5986
+ if (!released) {
5987
+ released = true;
5988
+ this.#release(key);
5989
+ }
5440
5990
  };
5441
- pubSub.publish(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES, payload);
5442
- });
5443
- documentSubscriberCount++;
5444
- return () => {
5445
- documentSubscriberCount--;
5446
- if (documentSubscriberCount === 0 && globalDocumentUnsubscribe) {
5447
- globalDocumentUnsubscribe();
5448
- globalDocumentUnsubscribe = null;
5991
+ return {
5992
+ next: () => iterator.next(),
5993
+ return: () => {
5994
+ release();
5995
+ return iterator.return();
5996
+ },
5997
+ throw: (error) => {
5998
+ release();
5999
+ return iterator.throw(error);
6000
+ },
6001
+ [Symbol.asyncIterator]() {
6002
+ return this;
6003
+ }
6004
+ };
6005
+ }
6006
+ #acquire(key, topic, subject) {
6007
+ const feed = this.#feeds.get(key);
6008
+ if (feed) {
6009
+ feed.subscribers++;
6010
+ return;
5449
6011
  }
5450
- };
5451
- }
6012
+ const view = { subject };
6013
+ const unsubscribe = this.reactorClient.subscribe({}, (event) => {
6014
+ const payload = {
6015
+ documentChanges: event,
6016
+ search: {},
6017
+ view
6018
+ };
6019
+ this.#pubSub.publish(topic, payload);
6020
+ }, view);
6021
+ this.#feeds.set(key, {
6022
+ unsubscribe,
6023
+ subscribers: 1
6024
+ });
6025
+ }
6026
+ #release(key) {
6027
+ const feed = this.#feeds.get(key);
6028
+ if (!feed) return;
6029
+ feed.subscribers--;
6030
+ if (feed.subscribers === 0) {
6031
+ this.#feeds.delete(key);
6032
+ feed.unsubscribe();
6033
+ }
6034
+ }
6035
+ };
5452
6036
  const activeJobSubscriptions = /* @__PURE__ */ new Map();
5453
6037
  function ensureJobSubscription(reactorClient, jobId) {
5454
6038
  let subscription = activeJobSubscriptions.get(jobId);
@@ -5465,7 +6049,7 @@ function ensureJobSubscription(reactorClient, jobId) {
5465
6049
  createdAt: jobInfo.createdAtUtcIso,
5466
6050
  completedAt: jobInfo.completedAtUtcIso ?? null,
5467
6051
  error: jobInfo.error?.message ?? null,
5468
- result: jobInfo.result ?? {}
6052
+ result: jobInfo.result ?? null
5469
6053
  },
5470
6054
  jobId,
5471
6055
  documentId: jobInfo.documentId
@@ -5513,6 +6097,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
5513
6097
  }
5514
6098
  name = "r";
5515
6099
  hasSubscriptions = true;
6100
+ #documentChanges;
6101
+ get documentChanges() {
6102
+ this.#documentChanges ??= new DocumentChangeFeed(this.reactorClient);
6103
+ return this.#documentChanges;
6104
+ }
5516
6105
  /**
5517
6106
  * Check operation-level permissions for an array of actions.
5518
6107
  * Delegates to base assertCanExecuteOperation for each action.
@@ -5543,6 +6132,38 @@ var ReactorSubgraph = class extends BaseSubgraph {
5543
6132
  }
5544
6133
  }
5545
6134
  /**
6135
+ * Drops the relationship edges whose far-end document the caller cannot read.
6136
+ *
6137
+ * An edge names two documents, so returning it discloses the far end's id. The
6138
+ * document-shaped relationship queries already withhold far-end documents the
6139
+ * caller cannot read; the edge-shaped queries have to withhold the same ones,
6140
+ * or they become a way to enumerate around that check. Supreme admins read
6141
+ * everything, so the per-edge check is skipped for them.
6142
+ *
6143
+ * The cursor describes where to resume in the underlying unfiltered stream,
6144
+ * so it is left as it came. A filtered page is therefore often shorter than
6145
+ * the requested limit, and that is the stream continuing rather than a sign
6146
+ * it ended.
6147
+ */
6148
+ async filterRelationshipEdges(page, farEnd, ctx) {
6149
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
6150
+ const decisions = /* @__PURE__ */ new Map();
6151
+ const items = [];
6152
+ for (const edge of page.items) {
6153
+ const documentId = edge[farEnd];
6154
+ let canRead = decisions.get(documentId);
6155
+ if (canRead === void 0) {
6156
+ canRead = await this.canReadDocument(documentId, ctx);
6157
+ decisions.set(documentId, canRead);
6158
+ }
6159
+ if (canRead) items.push(edge);
6160
+ }
6161
+ return {
6162
+ ...page,
6163
+ items
6164
+ };
6165
+ }
6166
+ /**
5546
6167
  * Adds to `forbidden` the canonical document ids in `syncOps` that the caller
5547
6168
  * cannot read, checking each distinct id once. Sync operation document ids are
5548
6169
  * canonical (never slugs), so no resolution is needed.
@@ -5575,9 +6196,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
5575
6196
  * reports the liveness that keeps it from being reclaimed -- but they never
5576
6197
  * adopt, since the push path has no drive check to clear the claimant.
5577
6198
  *
5578
- * Nothing is enforced or adopted without a serving gate. Below
5579
- * `authEnforcement` there is no policy being enforced for the channel to
5580
- * belong to, and refusing a poll there would break sync for no gain.
6199
+ * Nothing is enforced or adopted without a serving gate: with no gate the
6200
+ * channel serves every subject alike, so there is no subject for it to
6201
+ * belong to.
5581
6202
  */
5582
6203
  async #bindOrRefuseChannel(channelId, ctx, options = {}) {
5583
6204
  if (!this.syncServingGate) return;
@@ -5607,7 +6228,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
5607
6228
  await this.assertCanReadCanonical(parent.id, ctx);
5608
6229
  try {
5609
6230
  const filter = {
5610
- documentId: parent.id,
6231
+ documentIdOrSlug: parent.id,
5611
6232
  branch: args.filter?.branch,
5612
6233
  scopes: args.filter?.scopes,
5613
6234
  actionTypes: args.filter?.actionTypes,
@@ -5637,10 +6258,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
5637
6258
  document: async (_parent, args, ctx) => {
5638
6259
  this.logger.debug("document(@args)", args);
5639
6260
  try {
5640
- const handle = await this.assertCanRead(args.identifier, ctx);
6261
+ const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
6262
+ const handle = await this.assertCanRead(idOrSlug, ctx);
5641
6263
  return await document(this.reactorClient, {
5642
- ...args,
5643
- identifier: handle.fetchIdentifier
6264
+ idOrSlug: handle.fetchIdentifier,
6265
+ view: args.view
5644
6266
  }, this.viewSubject(ctx));
5645
6267
  } catch (error) {
5646
6268
  this.logger.error("Error in document: @Error", error);
@@ -5650,10 +6272,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
5650
6272
  documentOutgoingRelationships: async (_parent, args, ctx) => {
5651
6273
  this.logger.debug("documentOutgoingRelationships(@args)", args);
5652
6274
  try {
5653
- const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
6275
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
6276
+ const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
5654
6277
  return await documentOutgoingRelationships(this.reactorClient, {
5655
- ...args,
5656
- sourceIdentifier: handle.fetchIdentifier
6278
+ sourceIdOrSlug: handle.fetchIdentifier,
6279
+ relationshipType: args.relationshipType,
6280
+ view: args.view,
6281
+ paging: args.paging
5657
6282
  }, this.viewSubject(ctx));
5658
6283
  } catch (error) {
5659
6284
  this.logger.error("Error in documentOutgoingRelationships: @Error", error);
@@ -5663,10 +6288,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
5663
6288
  documentIncomingRelationships: async (_parent, args, ctx) => {
5664
6289
  this.logger.debug("documentIncomingRelationships(@args)", args);
5665
6290
  try {
5666
- const handle = await this.assertCanRead(args.targetIdentifier, ctx);
6291
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6292
+ const handle = await this.assertCanRead(targetIdOrSlug, ctx);
5667
6293
  const result = await documentIncomingRelationships(this.reactorClient, {
5668
- ...args,
5669
- targetIdentifier: handle.fetchIdentifier
6294
+ targetIdOrSlug: handle.fetchIdentifier,
6295
+ relationshipType: args.relationshipType,
6296
+ view: args.view,
6297
+ paging: args.paging
5670
6298
  }, this.viewSubject(ctx));
5671
6299
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
5672
6300
  const filteredItems = [];
@@ -5682,6 +6310,40 @@ var ReactorSubgraph = class extends BaseSubgraph {
5682
6310
  throw error;
5683
6311
  }
5684
6312
  },
6313
+ documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
6314
+ this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
6315
+ try {
6316
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
6317
+ const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
6318
+ const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
6319
+ sourceIdOrSlug: handle.fetchIdentifier,
6320
+ relationshipType: args.relationshipType,
6321
+ view: args.view,
6322
+ paging: args.paging
6323
+ }, this.viewSubject(ctx));
6324
+ return await this.filterRelationshipEdges(result, "targetId", ctx);
6325
+ } catch (error) {
6326
+ this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
6327
+ throw error;
6328
+ }
6329
+ },
6330
+ documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
6331
+ this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
6332
+ try {
6333
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6334
+ const handle = await this.assertCanRead(targetIdOrSlug, ctx);
6335
+ const result = await documentIncomingRelationshipEdges(this.reactorClient, {
6336
+ targetIdOrSlug: handle.fetchIdentifier,
6337
+ relationshipType: args.relationshipType,
6338
+ view: args.view,
6339
+ paging: args.paging
6340
+ }, this.viewSubject(ctx));
6341
+ return await this.filterRelationshipEdges(result, "sourceId", ctx);
6342
+ } catch (error) {
6343
+ this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
6344
+ throw error;
6345
+ }
6346
+ },
5685
6347
  findDocuments: async (_parent, args, ctx) => {
5686
6348
  this.logger.debug("findDocuments(@args)", args);
5687
6349
  try {
@@ -5703,10 +6365,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5703
6365
  throw error;
5704
6366
  }
5705
6367
  },
5706
- jobStatus: async (_parent, args) => {
6368
+ jobStatus: async (_parent, args, ctx) => {
5707
6369
  this.logger.debug("jobStatus(@args)", args);
5708
6370
  try {
5709
- return await jobStatus(this.reactorClient, args);
6371
+ return await jobStatus(this.reactorClient, args, (id) => this.servesDocument(id, ctx));
5710
6372
  } catch (error) {
5711
6373
  this.logger.error("Error in jobStatus: @Error", error);
5712
6374
  throw error;
@@ -5715,32 +6377,63 @@ var ReactorSubgraph = class extends BaseSubgraph {
5715
6377
  documentOperations: async (_parent, args, ctx) => {
5716
6378
  this.logger.debug("documentOperations(@args)", args);
5717
6379
  try {
5718
- const handle = await this.assertCanRead(args.filter.documentId, ctx);
6380
+ const { documentIdOrSlug, documentId, ...filter } = args.filter;
6381
+ const handle = await this.assertCanRead(requireOneOf({
6382
+ documentIdOrSlug,
6383
+ documentId
6384
+ }, "documentIdOrSlug", "documentId"), ctx);
5719
6385
  return await documentOperations(this.reactorClient, {
5720
- ...args,
5721
6386
  filter: {
5722
- ...args.filter,
5723
- documentId: handle.fetchIdentifier
5724
- }
6387
+ ...filter,
6388
+ documentIdOrSlug: handle.fetchIdentifier
6389
+ },
6390
+ paging: args.paging
5725
6391
  }, this.viewSubject(ctx));
5726
6392
  } catch (error) {
5727
6393
  this.logger.error("Error in documentOperations: @Error", error);
5728
6394
  throw error;
5729
6395
  }
5730
6396
  },
6397
+ /**
6398
+ * Gated on reading the document, the same gate every other read here
6399
+ * carries: a verdict is derived from the document's policy, so a caller
6400
+ * who may not read the document may not preflight against it either.
6401
+ * That gate decides whether the query runs; the answer comes entirely
6402
+ * from the reactor's decision model.
6403
+ */
5731
6404
  evaluateActions: async (_parent, args, ctx) => {
5732
6405
  this.logger.debug("evaluateActions(@args)", args);
5733
6406
  try {
5734
- const handle = await this.assertCanRead(args.documentIdentifier, ctx);
6407
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
6408
+ const handle = await this.assertCanRead(documentIdOrSlug, ctx);
5735
6409
  return await evaluateActions(this.reactorClient, {
5736
- ...args,
5737
- documentIdentifier: handle.fetchIdentifier
6410
+ documentIdOrSlug: handle.fetchIdentifier,
6411
+ branch: args.branch,
6412
+ candidates: args.candidates
5738
6413
  }, this.viewSubject(ctx));
5739
6414
  } catch (error) {
5740
6415
  this.logger.error("Error in evaluateActions: @Error", error);
5741
6416
  throw error;
5742
6417
  }
5743
6418
  },
6419
+ syncHolds: async (_parent, args, ctx) => {
6420
+ const address = ctx.user?.address;
6421
+ if (!this.authorizationService.isSupremeAdmin(address)) {
6422
+ let bound;
6423
+ try {
6424
+ bound = args.remoteName ? this.syncManager.getByName(args.remoteName).meta.options.boundAddress : void 0;
6425
+ } catch {
6426
+ bound = void 0;
6427
+ }
6428
+ if (address === void 0 || bound !== address) throw new ForbiddenError("to list sync holds");
6429
+ }
6430
+ return syncHolds(this.syncManager, args);
6431
+ },
6432
+ peerAgreement: (_parent, args, ctx) => {
6433
+ const address = ctx.user?.address;
6434
+ if (!this.authorizationService.isSupremeAdmin(address) && (address === void 0 || !this.syncManager.list().some((remote) => remote.meta.collectionId.key === args.collectionId && remote.meta.options.boundAddress === address))) throw new ForbiddenError("to read peer agreement");
6435
+ return peerAgreement(this.syncManager, args);
6436
+ },
5744
6437
  pollSyncEnvelopes: async (_parent, args, ctx) => {
5745
6438
  this.logger.debug("pollSyncEnvelopes(@args)", args);
5746
6439
  try {
@@ -5754,19 +6447,18 @@ var ReactorSubgraph = class extends BaseSubgraph {
5754
6447
  const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
5755
6448
  if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
5756
6449
  await this.#bindOrRefuseChannel(args.channelId, ctx);
6450
+ holdPollRefusals(this.syncManager, args.channelId, args.refusals);
6451
+ await recordPollMarkerRefusals(this.syncManager, args.channelId, args.refusals);
6452
+ await silenceUnversionedPoll(this.syncManager, args.channelId, args.manifestRevision);
5757
6453
  const forbiddenIds = /* @__PURE__ */ new Set();
5758
6454
  if (!isAdmin) {
5759
6455
  if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
5760
6456
  await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
5761
6457
  }
5762
- const heldOpIds = this.syncServingGate ? await collectHeldSyncOperations([...remote.channel.outbox.items, ...remote.channel.deadLetter.items], this.syncServingGate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
5763
- const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds);
5764
- return {
5765
- envelopes,
5766
- ackOrdinal,
5767
- deadLetters,
5768
- hasMore
5769
- };
6458
+ const gate = this.syncServingGate;
6459
+ const gated = gate ? [...remote.channel.outbox.items, ...remote.channel.deadLetter.items] : void 0;
6460
+ const heldOpIds = gate && gated ? await collectHeldSyncOperations(gated, gate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
6461
+ return pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds, gated && new Set(gated.map((syncOp) => syncOp.id)));
5770
6462
  } catch (error) {
5771
6463
  this.logger.error("Error in pollSyncEnvelopes(@args): @Error", args, error);
5772
6464
  throw error;
@@ -5777,11 +6469,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
5777
6469
  createDocument: async (_parent, args, ctx) => {
5778
6470
  this.logger.debug("createDocument(@args)", args);
5779
6471
  try {
5780
- if (args.parentIdentifier) {
5781
- const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
6472
+ const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
6473
+ if (parentIdOrSlug) {
6474
+ const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
5782
6475
  await this.assertCanWriteCanonical(parent.document.id, ctx);
5783
6476
  } else this.assertCanCreate(ctx);
5784
- const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
6477
+ const result = await createDocument(this.reactorClient, {
6478
+ document: args.document,
6479
+ parentIdOrSlug
6480
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
5785
6481
  if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
5786
6482
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
5787
6483
  return result;
@@ -5793,11 +6489,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
5793
6489
  createEmptyDocument: async (_parent, args, ctx) => {
5794
6490
  this.logger.debug("createEmptyDocument(@args)", args);
5795
6491
  try {
5796
- if (args.parentIdentifier) {
5797
- const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
6492
+ const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
6493
+ if (parentIdOrSlug) {
6494
+ const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
5798
6495
  await this.assertCanWriteCanonical(parent.document.id, ctx);
5799
6496
  } else this.assertCanCreate(ctx);
5800
- const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
6497
+ const result = await createEmptyDocument(this.reactorClient, {
6498
+ documentType: args.documentType,
6499
+ parentIdOrSlug
6500
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
5801
6501
  if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
5802
6502
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
5803
6503
  return result;
@@ -5809,11 +6509,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
5809
6509
  execute: async (_parent, args, ctx) => {
5810
6510
  this.logger.debug("execute(@args)", args);
5811
6511
  try {
5812
- const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
6512
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
6513
+ const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
5813
6514
  return await execute(this.reactorClient, {
5814
- ...args,
5815
- documentIdentifier: handle.fetchIdentifier
5816
- });
6515
+ documentIdOrSlug: handle.fetchIdentifier,
6516
+ actions: args.actions,
6517
+ branch: args.branch
6518
+ }, this.viewSubject(ctx));
5817
6519
  } catch (error) {
5818
6520
  this.logger.error("Error in execute(@args): @Error", args, error);
5819
6521
  throw error;
@@ -5822,10 +6524,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
5822
6524
  executeAsync: async (_parent, args, ctx) => {
5823
6525
  this.logger.debug("executeAsync(@args)", args);
5824
6526
  try {
5825
- const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
6527
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
6528
+ const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
5826
6529
  return await executeAsync(this.reactorClient, {
5827
- ...args,
5828
- documentIdentifier: handle.fetchIdentifier
6530
+ documentIdOrSlug: handle.fetchIdentifier,
6531
+ actions: args.actions,
6532
+ branch: args.branch
5829
6533
  });
5830
6534
  } catch (error) {
5831
6535
  this.logger.error("Error in executeAsync(@args): @Error", args, error);
@@ -5839,7 +6543,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
5839
6543
  return await mutateDocument(this.reactorClient, {
5840
6544
  ...args,
5841
6545
  documentIdentifier: handle.fetchIdentifier
5842
- });
6546
+ }, this.viewSubject(ctx));
5843
6547
  } catch (error) {
5844
6548
  this.logger.error("Error in mutateDocument(@args): @Error", args, error);
5845
6549
  throw error;
@@ -5861,11 +6565,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
5861
6565
  renameDocument: async (_parent, args, ctx) => {
5862
6566
  this.logger.debug("renameDocument(@args)", args);
5863
6567
  try {
5864
- const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
6568
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
6569
+ const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
5865
6570
  return await renameDocument(this.reactorClient, {
5866
- ...args,
5867
- documentIdentifier: handle.fetchIdentifier
5868
- });
6571
+ documentIdOrSlug: handle.fetchIdentifier,
6572
+ name: args.name,
6573
+ branch: args.branch
6574
+ }, void 0, this.viewSubject(ctx));
5869
6575
  } catch (error) {
5870
6576
  this.logger.error("Error in renameDocument(@args): @Error", args, error);
5871
6577
  throw error;
@@ -5874,11 +6580,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
5874
6580
  setPreferredEditor: async (_parent, args, ctx) => {
5875
6581
  this.logger.debug("setPreferredEditor(@args)", args);
5876
6582
  try {
5877
- const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
6583
+ const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
6584
+ const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
5878
6585
  return await setPreferredEditor(this.reactorClient, {
5879
- ...args,
5880
- documentIdentifier: handle.fetchIdentifier
5881
- });
6586
+ documentIdOrSlug: handle.fetchIdentifier,
6587
+ preferredEditor: args.preferredEditor,
6588
+ branch: args.branch
6589
+ }, void 0, this.viewSubject(ctx));
5882
6590
  } catch (error) {
5883
6591
  this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
5884
6592
  throw error;
@@ -5887,24 +6595,51 @@ var ReactorSubgraph = class extends BaseSubgraph {
5887
6595
  addRelationship: async (_parent, args, ctx) => {
5888
6596
  this.logger.debug("addRelationship(@args)", args);
5889
6597
  try {
5890
- const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
6598
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
6599
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6600
+ const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
5891
6601
  return await addRelationship(this.reactorClient, {
5892
- ...args,
5893
- sourceIdentifier: handle.fetchIdentifier
5894
- });
6602
+ sourceIdOrSlug: handle.fetchIdentifier,
6603
+ targetIdOrSlug,
6604
+ relationshipType: args.relationshipType,
6605
+ metadata: args.metadata,
6606
+ branch: args.branch
6607
+ }, this.viewSubject(ctx));
5895
6608
  } catch (error) {
5896
6609
  this.logger.error("Error in addRelationship(@args): @Error", args, error);
5897
6610
  throw error;
5898
6611
  }
5899
6612
  },
6613
+ updateRelationship: async (_parent, args, ctx) => {
6614
+ this.logger.debug("updateRelationship(@args)", args);
6615
+ try {
6616
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
6617
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6618
+ const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
6619
+ return await updateRelationship(this.reactorClient, {
6620
+ sourceIdOrSlug: handle.fetchIdentifier,
6621
+ targetIdOrSlug,
6622
+ relationshipType: args.relationshipType,
6623
+ metadata: args.metadata,
6624
+ branch: args.branch
6625
+ }, this.viewSubject(ctx));
6626
+ } catch (error) {
6627
+ this.logger.error("Error in updateRelationship(@args): @Error", args, error);
6628
+ throw error;
6629
+ }
6630
+ },
5900
6631
  removeRelationship: async (_parent, args, ctx) => {
5901
6632
  this.logger.debug("removeRelationship(@args)", args);
5902
6633
  try {
5903
- const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
6634
+ const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
6635
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6636
+ const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
5904
6637
  return await removeRelationship(this.reactorClient, {
5905
- ...args,
5906
- sourceIdentifier: handle.fetchIdentifier
5907
- });
6638
+ sourceIdOrSlug: handle.fetchIdentifier,
6639
+ targetIdOrSlug,
6640
+ relationshipType: args.relationshipType,
6641
+ branch: args.branch
6642
+ }, this.viewSubject(ctx));
5908
6643
  } catch (error) {
5909
6644
  this.logger.error("Error in removeRelationship(@args): @Error", args, error);
5910
6645
  throw error;
@@ -5913,26 +6648,31 @@ var ReactorSubgraph = class extends BaseSubgraph {
5913
6648
  moveRelationship: async (_parent, args, ctx) => {
5914
6649
  this.logger.debug("moveRelationship(@args)", args);
5915
6650
  try {
5916
- const sourceHandle = await this.assertCanWrite(args.sourceParentIdentifier, ctx);
5917
- const targetHandle = await this.assertCanWrite(args.targetParentIdentifier, ctx);
6651
+ const sourceParentIdOrSlug = requireOneOf(args, "sourceParentIdOrSlug", "sourceParentIdentifier");
6652
+ const targetParentIdOrSlug = requireOneOf(args, "targetParentIdOrSlug", "targetParentIdentifier");
6653
+ const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
6654
+ const sourceHandle = await this.assertCanWrite(sourceParentIdOrSlug, ctx);
6655
+ const targetHandle = await this.assertCanWrite(targetParentIdOrSlug, ctx);
5918
6656
  return await moveRelationship(this.reactorClient, {
5919
- ...args,
5920
- sourceParentIdentifier: sourceHandle.fetchIdentifier,
5921
- targetParentIdentifier: targetHandle.fetchIdentifier
5922
- });
6657
+ sourceParentIdOrSlug: sourceHandle.fetchIdentifier,
6658
+ targetParentIdOrSlug: targetHandle.fetchIdentifier,
6659
+ targetIdOrSlug,
6660
+ relationshipType: args.relationshipType,
6661
+ branch: args.branch
6662
+ }, this.viewSubject(ctx));
5923
6663
  } catch (error) {
5924
- this.logger.error("Error in moveRelationship(@args): @Error @args", error, args);
6664
+ this.logger.error("Error in moveRelationship(@args): @Error", args, error);
5925
6665
  throw error;
5926
6666
  }
5927
6667
  },
5928
6668
  deleteDocument: async (_parent, args, ctx) => {
5929
6669
  this.logger.debug("deleteDocument(@args)", args);
5930
6670
  try {
5931
- const identifier = (await this.assertCanWrite(args.identifier, ctx)).fetchIdentifier;
5932
- const driveIdToInvalidate = await this.#resolveDriveId(identifier);
6671
+ const idOrSlug = (await this.assertCanWrite(requireOneOf(args, "idOrSlug", "identifier"), ctx)).fetchIdentifier;
6672
+ const driveIdToInvalidate = await this.#resolveDriveId(idOrSlug);
5933
6673
  const result = await deleteDocument(this.reactorClient, {
5934
- ...args,
5935
- identifier
6674
+ idOrSlug,
6675
+ propagate: args.propagate
5936
6676
  }, this.graphqlManager.reactorDriveClient);
5937
6677
  if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
5938
6678
  return result;
@@ -5944,14 +6684,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
5944
6684
  deleteDocuments: async (_parent, args, ctx) => {
5945
6685
  this.logger.debug("deleteDocuments(@args)", args);
5946
6686
  try {
5947
- const identifiers = [];
5948
- for (const identifier of args.identifiers) {
5949
- const handle = await this.assertCanWrite(identifier, ctx);
5950
- identifiers.push(handle.fetchIdentifier);
6687
+ const idsOrSlugs = [];
6688
+ for (const idOrSlug of requireOneOf(args, "idsOrSlugs", "identifiers")) {
6689
+ const handle = await this.assertCanWrite(idOrSlug, ctx);
6690
+ idsOrSlugs.push(handle.fetchIdentifier);
5951
6691
  }
5952
6692
  return await deleteDocuments(this.reactorClient, {
5953
- ...args,
5954
- identifiers
6693
+ idsOrSlugs,
6694
+ propagate: args.propagate
5955
6695
  });
5956
6696
  } catch (error) {
5957
6697
  this.logger.error("Error in deleteDocuments(@args): @Error", args, error);
@@ -6030,10 +6770,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6030
6770
  documentChanges: {
6031
6771
  subscribe: (rootValue, args, ctx, info) => {
6032
6772
  this.logger.debug("documentChanges subscription started");
6033
- return withFilter(() => {
6034
- ensureGlobalDocumentSubscription(this.reactorClient);
6035
- return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES);
6036
- }, async (payload, filterArgs, filterCtx) => {
6773
+ return withFilter(() => this.documentChanges.subscribe(this.viewSubject(ctx)), async (payload, filterArgs, filterCtx) => {
6037
6774
  if (!payload) return false;
6038
6775
  const search = {
6039
6776
  type: filterArgs?.search?.type ?? void 0,
@@ -6060,6 +6797,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6060
6797
  }, async (payload, filterArgs, filterCtx) => {
6061
6798
  if (!payload || !filterArgs) return false;
6062
6799
  if (!matchesJobFilter(payload, filterArgs)) return false;
6800
+ if (!await this.servesDocument(payload.documentId, filterCtx)) return false;
6063
6801
  if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
6064
6802
  if (!payload.documentId) return false;
6065
6803
  return this.canReadDocument(payload.documentId, filterCtx);
@@ -6088,10 +6826,10 @@ const ADMIN_USERS = getAdminUsers();
6088
6826
  //#endregion
6089
6827
  //#region src/graphql/system/version.ts
6090
6828
  function getVersion() {
6091
- return "6.2.3-dev.4";
6829
+ return "6.2.3-dev.41";
6092
6830
  }
6093
6831
  function getGitHash() {
6094
- return "e30f072e1e13237f4a9ce0f7c5ca18700cab7845";
6832
+ return "84f56c54bfdb09f7789448e0c77f6c57454a47a6";
6095
6833
  }
6096
6834
  function getGitUrl() {
6097
6835
  return buildTreeUrl(getGitHash());
@@ -6119,7 +6857,446 @@ var SystemSubgraph = class extends BaseSubgraph {
6119
6857
  }) } };
6120
6858
  };
6121
6859
  //#endregion
6860
+ //#region src/packages/registry-cache.ts
6861
+ /** Entry files a registry package's node build is served from, CDN-relative. */
6862
+ const PACKAGE_ENTRIES = {
6863
+ documentModels: "node/document-models/index.mjs",
6864
+ subgraphs: "node/subgraphs/index.mjs",
6865
+ processors: "node/processors/index.mjs"
6866
+ };
6867
+ const REGISTRY_ENTRY_ABSENT = "ERR_REGISTRY_ENTRY_ABSENT";
6868
+ const MANIFEST_FILE = "manifest.json";
6869
+ function defaultRegistryCacheDir(cwd = process.cwd()) {
6870
+ return path.join(cwd, ".ph", "registry-packages");
6871
+ }
6872
+ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
6873
+ const PACKAGE_NAME = /^(@[a-z0-9][-a-z0-9._]*\/)?[a-z0-9][-a-z0-9._]*$/i;
6874
+ function isValidPackageName(name) {
6875
+ return PACKAGE_NAME.test(name) && !name.includes("..") && name.length <= 214;
6876
+ }
6877
+ const MAX_FILE_BYTES = 16777216;
6878
+ const MAX_TOTAL_BYTES = 134217728;
6879
+ const MAX_FILES = 2e3;
6880
+ const MAX_IN_FLIGHT = 4;
6881
+ const FETCH_TIMEOUT_MS = 3e4;
6882
+ const DEAD_PID_GRACE_MS = 6e4;
6883
+ const ABANDONED_AFTER_MS = 36e5;
6884
+ const LEFTOVER = /\.(?:tmp|stale)-(\d+)-[0-9a-f]{8}$/;
6885
+ const STATIC_IMPORT = 1;
6886
+ const DYNAMIC_IMPORT = 2;
6887
+ var RegistryPackageCache = class {
6888
+ cacheDir;
6889
+ registryUrl;
6890
+ logger;
6891
+ fetchTimeoutMs;
6892
+ inFlight = /* @__PURE__ */ new Map();
6893
+ verified = /* @__PURE__ */ new Map();
6894
+ constructor(options) {
6895
+ this.registryUrl = options.registryUrl.endsWith("/") ? options.registryUrl : `${options.registryUrl}/`;
6896
+ this.cacheDir = path.resolve(options.cacheDir ?? defaultRegistryCacheDir());
6897
+ this.logger = options.logger ?? childLogger(["reactor-api", "registry-cache"]);
6898
+ this.fetchTimeoutMs = options.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;
6899
+ }
6900
+ /** Base URL every file of `name@version` is fetched under. */
6901
+ baseUrl(name, version) {
6902
+ return `${this.registryUrl}-/cdn/${name}@${version}/`;
6903
+ }
6904
+ entryDir(name, version) {
6905
+ assertCoordinate(name, version);
6906
+ const dir = path.join(this.cacheDir, `${name}@${version}`);
6907
+ assertWithin(dir, this.cacheDir);
6908
+ return dir;
6909
+ }
6910
+ /** Complete cached versions of `name`, newest-agnostic. */
6911
+ async cachedVersions(name) {
6912
+ if (!isValidPackageName(name)) return [];
6913
+ const parent = path.dirname(path.join(this.cacheDir, `${name}@0.0.0`));
6914
+ const leaf = path.basename(name);
6915
+ let names;
6916
+ try {
6917
+ names = await readdir(parent);
6918
+ } catch {
6919
+ return [];
6920
+ }
6921
+ const versions = [];
6922
+ for (const entry of names) {
6923
+ if (!entry.startsWith(`${leaf}@`)) continue;
6924
+ const version = entry.slice(leaf.length + 1);
6925
+ if (!EXACT_VERSION.test(version)) continue;
6926
+ try {
6927
+ await stat(path.join(parent, entry, MANIFEST_FILE));
6928
+ versions.push(version);
6929
+ } catch {}
6930
+ }
6931
+ return versions;
6932
+ }
6933
+ async ensurePackage(name, version) {
6934
+ const dir = this.entryDir(name, version);
6935
+ const done = this.verified.get(dir);
6936
+ if (done) return {
6937
+ ...done,
6938
+ source: "cache"
6939
+ };
6940
+ const pending = this.inFlight.get(dir);
6941
+ if (pending) return pending;
6942
+ const started = this.resolveEntry(name, version, dir).finally(() => this.inFlight.delete(dir));
6943
+ this.inFlight.set(dir, started);
6944
+ return started;
6945
+ }
6946
+ async resolveEntry(name, version, dir) {
6947
+ let manifest = await this.verify(dir, name, version);
6948
+ let source = "cache";
6949
+ if (!manifest) {
6950
+ manifest = await this.fill(name, version, dir);
6951
+ source = "download";
6952
+ }
6953
+ await this.linkBareImports(manifest.bareImports);
6954
+ const entries = {};
6955
+ for (const [kind, rel] of Object.entries(manifest.entries)) entries[kind] = path.join(dir, rel);
6956
+ const result = {
6957
+ name,
6958
+ version,
6959
+ dir,
6960
+ entries,
6961
+ source
6962
+ };
6963
+ this.verified.set(dir, result);
6964
+ return result;
6965
+ }
6966
+ /** The manifest when every listed file is present and hashes to it. */
6967
+ async verify(dir, name, version) {
6968
+ let manifest;
6969
+ try {
6970
+ manifest = parseManifest(await readFile(path.join(dir, MANIFEST_FILE), "utf8"));
6971
+ } catch (error) {
6972
+ if (error.code === "ENOENT") return null;
6973
+ this.logger.warn("Unreadable cache manifest in @dir: @error", dir, error);
6974
+ await this.discard(dir);
6975
+ return null;
6976
+ }
6977
+ if (manifest.name !== name || manifest.version !== version || Object.values(manifest.entries).some((rel) => !(rel in manifest.files))) {
6978
+ this.logger.warn("Cache manifest in @dir names another package", dir);
6979
+ await this.discard(dir);
6980
+ return null;
6981
+ }
6982
+ for (const [rel, expected] of Object.entries(manifest.files)) {
6983
+ let actual;
6984
+ try {
6985
+ const file = path.join(dir, rel);
6986
+ assertWithin(file, dir);
6987
+ actual = sha256(await readFile(file));
6988
+ } catch {
6989
+ actual = null;
6990
+ }
6991
+ if (actual !== expected) {
6992
+ this.logger.warn("Cached @file of @package failed verification; fetching it again", rel, `${name}@${version}`);
6993
+ await this.discard(dir);
6994
+ return null;
6995
+ }
6996
+ }
6997
+ return manifest;
6998
+ }
6999
+ async discard(dir) {
7000
+ const aside = `${dir}.stale-${process.pid}-${randomUUID().slice(0, 8)}`;
7001
+ try {
7002
+ await rename(dir, aside);
7003
+ } catch {
7004
+ return;
7005
+ }
7006
+ await rm(aside, {
7007
+ recursive: true,
7008
+ force: true
7009
+ });
7010
+ }
7011
+ async fill(name, version, dir) {
7012
+ const staging = `${dir}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
7013
+ assertWithin(staging, this.cacheDir);
7014
+ await sweepLeftovers(path.dirname(dir));
7015
+ await mkdir(staging, { recursive: true });
7016
+ try {
7017
+ const manifest = await this.download(name, version, staging);
7018
+ await writeFile(path.join(staging, MANIFEST_FILE), JSON.stringify(manifest, null, 2));
7019
+ await chmod(staging, 493);
7020
+ try {
7021
+ await rename(staging, dir);
7022
+ } catch {
7023
+ const theirs = await this.verify(dir, name, version);
7024
+ if (theirs) return theirs;
7025
+ await this.discard(dir);
7026
+ await rename(staging, dir);
7027
+ }
7028
+ return manifest;
7029
+ } finally {
7030
+ await rm(staging, {
7031
+ recursive: true,
7032
+ force: true
7033
+ });
7034
+ }
7035
+ }
7036
+ async download(name, version, staging) {
7037
+ await init;
7038
+ const base = this.baseUrl(name, version);
7039
+ const files = {};
7040
+ const bare = /* @__PURE__ */ new Set();
7041
+ const entryRels = new Set(Object.values(PACKAGE_ENTRIES));
7042
+ const queue = [...entryRels];
7043
+ const seen = new Set(queue);
7044
+ let totalBytes = 0;
7045
+ const processOne = async (rel) => {
7046
+ const url = new URL(rel, base).href;
7047
+ const body = await this.fetchBounded(url, entryRels.has(rel));
7048
+ if (!body) return;
7049
+ totalBytes += body.byteLength;
7050
+ if (totalBytes > MAX_TOTAL_BYTES) throw new Error(`${name}@${version} exceeds the ${MAX_TOTAL_BYTES}-byte cache cap`);
7051
+ const target = path.join(staging, rel);
7052
+ assertWithin(target, staging);
7053
+ for (const dep of this.dependenciesOf(body.toString("utf8"), url, base)) {
7054
+ if (dep.kind === "bare") {
7055
+ bare.add(dep.specifier);
7056
+ continue;
7057
+ }
7058
+ if (seen.has(dep.rel)) continue;
7059
+ if (seen.size >= MAX_FILES) throw new Error(`${name}@${version} imports more than ${MAX_FILES} files`);
7060
+ seen.add(dep.rel);
7061
+ queue.push(dep.rel);
7062
+ }
7063
+ files[rel] = sha256(body);
7064
+ await mkdir(path.dirname(target), { recursive: true });
7065
+ await writeFile(target, body);
7066
+ };
7067
+ const active = /* @__PURE__ */ new Set();
7068
+ let failure;
7069
+ while ((queue.length > 0 || active.size > 0) && failure === void 0) {
7070
+ while (queue.length > 0 && active.size < MAX_IN_FLIGHT) {
7071
+ const task = processOne(queue.shift()).then(() => {
7072
+ active.delete(task);
7073
+ }, (error) => {
7074
+ active.delete(task);
7075
+ failure ??= error instanceof Error ? error : new Error(String(error));
7076
+ });
7077
+ active.add(task);
7078
+ }
7079
+ if (active.size > 0) await Promise.race(active);
7080
+ }
7081
+ if (failure !== void 0) {
7082
+ await Promise.allSettled(active);
7083
+ throw failure;
7084
+ }
7085
+ const entries = {};
7086
+ for (const [kind, rel] of Object.entries(PACKAGE_ENTRIES)) if (rel in files) entries[kind] = rel;
7087
+ if (Object.keys(entries).length === 0) throw new Error(`${name}@${version} serves no document models, subgraphs or processors`);
7088
+ return {
7089
+ name,
7090
+ version,
7091
+ entries,
7092
+ files: Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b))),
7093
+ bareImports: [...bare].sort()
7094
+ };
7095
+ }
7096
+ dependenciesOf(source, url, base) {
7097
+ const [imports] = parse$1(source, url);
7098
+ const deps = [];
7099
+ for (const imp of imports) {
7100
+ const kind = imp.t;
7101
+ if (kind !== STATIC_IMPORT && kind !== DYNAMIC_IMPORT) continue;
7102
+ const specifier = imp.n;
7103
+ if (specifier === void 0) {
7104
+ if (kind === DYNAMIC_IMPORT) this.logger.warn("Non-literal dynamic import in @url is not cached: @expr", url, source.slice(imp.s, imp.e));
7105
+ continue;
7106
+ }
7107
+ if (isBuiltin(specifier)) continue;
7108
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || /^https?:\/\//i.test(specifier)) {
7109
+ deps.push({
7110
+ kind: "file",
7111
+ rel: relativeTo(base, specifier, url)
7112
+ });
7113
+ continue;
7114
+ }
7115
+ if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) throw new Error(`${url} imports an unsupported URL: ${specifier}`);
7116
+ deps.push({
7117
+ kind: "bare",
7118
+ specifier
7119
+ });
7120
+ }
7121
+ return deps;
7122
+ }
7123
+ async fetchBounded(url, optional = false) {
7124
+ const response = await fetch(url, { signal: AbortSignal.timeout(this.fetchTimeoutMs) });
7125
+ if (optional && response.status === 404) {
7126
+ await response.body?.cancel();
7127
+ return null;
7128
+ }
7129
+ if (!response.ok) {
7130
+ await response.body?.cancel();
7131
+ throw new Error(`${url} responded ${response.status}`);
7132
+ }
7133
+ const declared = response.headers.get("content-length");
7134
+ if (declared && Number(declared) > MAX_FILE_BYTES) {
7135
+ await response.body?.cancel();
7136
+ throw new Error(`${url} exceeds the ${MAX_FILE_BYTES}-byte file cap`);
7137
+ }
7138
+ if (!response.body) return Buffer.from(await response.arrayBuffer());
7139
+ const reader = response.body.getReader();
7140
+ const chunks = [];
7141
+ let total = 0;
7142
+ for (;;) {
7143
+ const { done, value } = await reader.read();
7144
+ if (done) break;
7145
+ total += value.byteLength;
7146
+ if (total > MAX_FILE_BYTES) {
7147
+ await reader.cancel();
7148
+ throw new Error(`${url} exceeds the ${MAX_FILE_BYTES}-byte file cap`);
7149
+ }
7150
+ chunks.push(value);
7151
+ }
7152
+ return Buffer.concat(chunks);
7153
+ }
7154
+ async linkBareImports(specifiers) {
7155
+ const outsideUrl = pathToFileURL(path.join(path.dirname(this.cacheDir), "/")).href;
7156
+ const linkRoot = path.join(this.cacheDir, "node_modules");
7157
+ for (const specifier of specifiers) {
7158
+ const pkg = packageNameOf(specifier);
7159
+ if (!pkg || !isValidPackageName(pkg)) continue;
7160
+ const link = path.join(linkRoot, pkg);
7161
+ assertWithin(link, linkRoot);
7162
+ if (packageRoot(specifier, pkg, outsideUrl)) {
7163
+ await removeLink(link);
7164
+ continue;
7165
+ }
7166
+ const fallback = packageRoot(specifier, pkg, import.meta.url);
7167
+ if (!fallback) {
7168
+ this.logger.warn("Registry package import @specifier resolves from neither the project nor reactor-api", specifier);
7169
+ continue;
7170
+ }
7171
+ try {
7172
+ await ensureLink(link, await realpath(fallback));
7173
+ } catch (error) {
7174
+ this.logger.warn("Could not link @pkg for cached registry packages: @error", pkg, error);
7175
+ }
7176
+ }
7177
+ }
7178
+ };
7179
+ function sha256(data) {
7180
+ return createHash("sha256").update(data).digest("hex");
7181
+ }
7182
+ function assertCoordinate(name, version) {
7183
+ if (!isValidPackageName(name)) throw new Error(`Invalid registry package name: ${name}`);
7184
+ if (!EXACT_VERSION.test(version)) throw new Error(`Invalid registry package version: ${version}`);
7185
+ }
7186
+ function assertWithin(target, root) {
7187
+ const resolvedRoot = path.resolve(root);
7188
+ const resolved = path.resolve(target);
7189
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) throw new Error(`Path escapes the registry cache: ${target}`);
7190
+ }
7191
+ function relativeTo(base, specifier, fromUrl) {
7192
+ const resolved = new URL(specifier, fromUrl);
7193
+ resolved.search = "";
7194
+ resolved.hash = "";
7195
+ if (!resolved.href.startsWith(base)) throw new Error(`${fromUrl} imports ${specifier}, which is outside the package`);
7196
+ const rel = decodeURIComponent(resolved.href.slice(base.length));
7197
+ if (rel === "" || rel === "manifest.json" || rel.includes("\\") || rel.includes("\0") || rel.split("/").some((s) => s === "" || s === "." || s === "..")) throw new Error(`${fromUrl} imports an unusable path: ${specifier}`);
7198
+ return rel;
7199
+ }
7200
+ function parseManifest(raw) {
7201
+ const value = JSON.parse(raw);
7202
+ if (typeof value.name !== "string" || typeof value.version !== "string" || typeof value.entries !== "object" || value.entries === null || typeof value.files !== "object" || value.files === null || !Array.isArray(value.bareImports)) throw new Error("malformed manifest");
7203
+ return value;
7204
+ }
7205
+ function packageNameOf(specifier) {
7206
+ const parts = specifier.split("/");
7207
+ if (specifier.startsWith("@")) return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
7208
+ return parts[0] || null;
7209
+ }
7210
+ function packageRoot(specifier, pkg, base) {
7211
+ let found;
7212
+ try {
7213
+ found = findPackageJSON(specifier, base);
7214
+ } catch {
7215
+ return null;
7216
+ }
7217
+ if (!found) return null;
7218
+ let dir = path.dirname(found);
7219
+ for (let i = 0; i < 16; i++) {
7220
+ if (dir.endsWith(`${path.sep}node_modules${path.sep}${pkg}`)) return dir;
7221
+ if (readPackageName(path.join(dir, "package.json")) === pkg) return dir;
7222
+ const parent = path.dirname(dir);
7223
+ if (parent === dir) break;
7224
+ dir = parent;
7225
+ }
7226
+ return null;
7227
+ }
7228
+ function readPackageName(file) {
7229
+ try {
7230
+ return JSON.parse(readFileSync(file, "utf8")).name;
7231
+ } catch {
7232
+ return;
7233
+ }
7234
+ }
7235
+ async function sweepLeftovers(parent) {
7236
+ let names;
7237
+ try {
7238
+ names = await readdir(parent);
7239
+ } catch {
7240
+ return;
7241
+ }
7242
+ const now = Date.now();
7243
+ for (const name of names) {
7244
+ const match = LEFTOVER.exec(name);
7245
+ if (!match) continue;
7246
+ const pid = Number(match[1]);
7247
+ if (pid === process.pid) continue;
7248
+ const full = path.join(parent, name);
7249
+ let age;
7250
+ try {
7251
+ age = now - (await lstat(full)).mtimeMs;
7252
+ } catch {
7253
+ continue;
7254
+ }
7255
+ if (age > ABANDONED_AFTER_MS || age > DEAD_PID_GRACE_MS && !isAlive(pid)) await rm(full, {
7256
+ recursive: true,
7257
+ force: true
7258
+ });
7259
+ }
7260
+ }
7261
+ function isAlive(pid) {
7262
+ try {
7263
+ process.kill(pid, 0);
7264
+ return true;
7265
+ } catch (error) {
7266
+ return error.code === "EPERM";
7267
+ }
7268
+ }
7269
+ async function ensureLink(link, target) {
7270
+ try {
7271
+ if (await readlink(link) === target) return;
7272
+ } catch {}
7273
+ await mkdir(path.dirname(link), { recursive: true });
7274
+ await sweepLeftovers(path.dirname(link));
7275
+ const temp = `${link}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
7276
+ await symlink(target, temp, "junction");
7277
+ try {
7278
+ await rename(temp, link);
7279
+ } catch (error) {
7280
+ await rm(temp, { force: true });
7281
+ throw error;
7282
+ }
7283
+ }
7284
+ async function removeLink(link) {
7285
+ try {
7286
+ await readlink(link);
7287
+ } catch {
7288
+ return;
7289
+ }
7290
+ await rm(link, { force: true });
7291
+ }
7292
+ //#endregion
6122
7293
  //#region src/packages/http-loader.ts
7294
+ function piecesBaseUrl(registryUrl, packageName, version) {
7295
+ return `${registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`}-/cdn/${packageName}@${version}/node/pieces/`;
7296
+ }
7297
+ function documentModelsOf(module) {
7298
+ return Object.values(module).filter((m) => m !== null && typeof m === "object" && "documentModel" in m && m.documentModel !== null);
7299
+ }
6123
7300
  /**
6124
7301
  * Extract subgraph classes from the imported subgraphs/index.mjs module shape.
6125
7302
  *
@@ -6146,8 +7323,14 @@ var HttpPackageLoader = class {
6146
7323
  logger = childLogger(["reactor-api", "http-loader"]);
6147
7324
  name = "HttpPackageLoader";
6148
7325
  documentModelLoader;
7326
+ cache;
7327
+ versions = /* @__PURE__ */ new Map();
6149
7328
  constructor(options) {
6150
7329
  this.registryUrl = options.registryUrl.endsWith("/") ? options.registryUrl : `${options.registryUrl}/`;
7330
+ this.cache = new RegistryPackageCache({
7331
+ registryUrl: this.registryUrl,
7332
+ cacheDir: options.cacheDir
7333
+ });
6151
7334
  this.documentModelLoader = new HttpDocumentModelLoader(this);
6152
7335
  }
6153
7336
  /**
@@ -6179,38 +7362,95 @@ var HttpPackageLoader = class {
6179
7362
  tag: void 0
6180
7363
  };
6181
7364
  }
7365
+ resolveVersion(packageSpec) {
7366
+ const known = this.versions.get(packageSpec);
7367
+ if (known) return known;
7368
+ const lookup = this.lookUpVersion(packageSpec).then((version) => {
7369
+ if (!version) this.versions.delete(packageSpec);
7370
+ return version;
7371
+ });
7372
+ this.versions.set(packageSpec, lookup);
7373
+ return lookup;
7374
+ }
7375
+ async lookUpVersion(packageSpec) {
7376
+ const { name, tag } = this.parsePackageSpec(packageSpec);
7377
+ if (tag && EXACT_VERSION.test(tag)) return tag;
7378
+ const version = await this.packageVersion(packageSpec);
7379
+ if (version) return version;
7380
+ const cached = await this.cache.cachedVersions(name);
7381
+ if (cached.length === 1) {
7382
+ this.logger.warn("Could not resolve a version for @package; using cached @version", packageSpec, cached[0]);
7383
+ return cached[0];
7384
+ }
7385
+ }
7386
+ /** Caches the pinned version of `packageSpec` on disk. */
7387
+ async cachePackage(packageSpec) {
7388
+ const { name: packageName } = this.parsePackageSpec(packageSpec);
7389
+ if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
7390
+ const version = await this.resolveVersion(packageSpec);
7391
+ if (!version) throw new Error(`No exact version found for ${packageSpec}`);
7392
+ const cached = await this.cache.ensurePackage(packageName, version);
7393
+ this.logger.verbose(`Package ${packageName}@${version} at ${cached.dir} (${cached.source})`);
7394
+ return cached;
7395
+ }
7396
+ /** One entry module of the package, plus the cached file it came from. */
7397
+ async importEntry(packageSpec, kind) {
7398
+ const { name: packageName } = this.parsePackageSpec(packageSpec);
7399
+ if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
7400
+ const version = await this.resolveVersion(packageSpec);
7401
+ let cached;
7402
+ if (version) try {
7403
+ cached = await this.cache.ensurePackage(packageName, version);
7404
+ } catch (error) {
7405
+ this.logger.warn("Could not cache @package, importing it over HTTP: @error", packageSpec, error);
7406
+ }
7407
+ if (cached) {
7408
+ const filePath = cached.entries[kind];
7409
+ if (!filePath) throw Object.assign(/* @__PURE__ */ new Error(`${packageName}@${cached.version} serves no ${kind}`), { code: REGISTRY_ENTRY_ABSENT });
7410
+ return {
7411
+ module: await import(
7412
+ /* @vite-ignore */
7413
+ pathToFileURL(filePath).href
7414
+ ),
7415
+ filePath
7416
+ };
7417
+ }
7418
+ const pinned = version ? `${packageName}@${version}` : packageSpec;
7419
+ const url = `${this.registryUrl}-/cdn/${pinned}/${PACKAGE_ENTRIES[kind]}`;
7420
+ this.logger.verbose(`Importing ${kind} from: ${url}`);
7421
+ return { module: await import(
7422
+ /* @vite-ignore */
7423
+ url
7424
+ ) };
7425
+ }
7426
+ importDocumentModels(packageSpec) {
7427
+ return this.importEntry(packageSpec, "documentModels");
7428
+ }
6182
7429
  async loadDocumentModels(packageSpec) {
6183
7430
  const { name: packageName } = this.parsePackageSpec(packageSpec);
6184
- if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
6185
- const url = `${this.registryUrl}-/cdn/${packageSpec}/node/document-models/index.mjs`;
6186
- this.logger.verbose(`Importing document-models from: ${url}`);
6187
- const module = await import(url);
6188
- const models = Object.values(module).filter((m) => m !== null && typeof m === "object" && "documentModel" in m && m.documentModel !== null);
7431
+ const { module } = await this.importDocumentModels(packageSpec);
7432
+ const models = documentModelsOf(module);
6189
7433
  this.logger.verbose(`Loaded ${models.length} document models from ${packageName}`);
6190
7434
  return models;
6191
7435
  }
6192
7436
  async loadUpgradeManifests(packageSpec) {
6193
7437
  const { name: packageName } = this.parsePackageSpec(packageSpec);
6194
- if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
6195
- const manifests = extractUpgradeManifests(await import(`${this.registryUrl}-/cdn/${packageSpec}/node/document-models/index.mjs`));
7438
+ const { module } = await this.importDocumentModels(packageSpec);
7439
+ const manifests = extractUpgradeManifests(module);
6196
7440
  if (manifests.length > 0) this.logger.verbose(`Loaded ${manifests.length} upgrade manifests from ${packageName}`);
6197
7441
  return manifests;
6198
7442
  }
6199
7443
  async loadSubgraphs(packageSpec) {
6200
7444
  const { name: packageName } = this.parsePackageSpec(packageSpec);
6201
- if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
6202
- const url = `${this.registryUrl}-/cdn/${packageSpec}/node/subgraphs/index.mjs`;
6203
- this.logger.verbose(`Importing subgraphs from: ${url}`);
6204
- const subgraphs = extractSubgraphsFromModule(await import(url));
7445
+ const { module } = await this.importEntry(packageSpec, "subgraphs");
7446
+ const subgraphs = extractSubgraphsFromModule(module);
6205
7447
  this.logger.verbose(`Loaded ${subgraphs.length} subgraphs from ${packageName}`);
6206
7448
  return subgraphs;
6207
7449
  }
6208
7450
  async loadProcessors(packageSpec) {
6209
7451
  const { name: packageName } = this.parsePackageSpec(packageSpec);
6210
- if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
6211
- const url = `${this.registryUrl}-/cdn/${packageSpec}/node/processors/index.mjs`;
6212
- this.logger.verbose(`Importing processors from: ${url}`);
6213
- const factory = (await import(url)).processorFactory;
7452
+ const { module } = await this.importEntry(packageSpec, "processors");
7453
+ const factory = module.processorFactory;
6214
7454
  if (factory && typeof factory === "function") {
6215
7455
  this.logger.verbose(`Loaded processor factory from ${packageName}`);
6216
7456
  return factory;
@@ -6218,6 +7458,27 @@ var HttpPackageLoader = class {
6218
7458
  this.logger.verbose(`No processor factory found in ${packageName}`);
6219
7459
  return null;
6220
7460
  }
7461
+ async loadPieces(packageSpec) {
7462
+ const { name: packageName } = this.parsePackageSpec(packageSpec);
7463
+ if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
7464
+ const version = await this.resolveVersion(packageSpec);
7465
+ if (!version) {
7466
+ this.logger.verbose(`No package version found for: ${packageSpec}`);
7467
+ return [];
7468
+ }
7469
+ const base = piecesBaseUrl(this.registryUrl, packageName, version);
7470
+ this.logger.verbose(`Importing pieces from: ${base}index.mjs`);
7471
+ let module;
7472
+ try {
7473
+ module = await import(`${base}index.mjs`);
7474
+ } catch (error) {
7475
+ this.logger.verbose("No pieces found for @package: @error", packageName, error);
7476
+ return [];
7477
+ }
7478
+ const pieces = piecesFromCdnList(module, base, packageName, this.logger, version);
7479
+ this.logger.verbose(`Loaded ${pieces.length} pieces from ${packageName}`);
7480
+ return pieces;
7481
+ }
6221
7482
  /**
6222
7483
  * Load document models from multiple packages.
6223
7484
  * Continues loading even if some packages fail.
@@ -6234,28 +7495,30 @@ var HttpPackageLoader = class {
6234
7495
  logger?.info(logMsg);
6235
7496
  this.logger.info(logMsg);
6236
7497
  } catch (error) {
6237
- const errMsg = `Failed to load package ${trimmedName}`;
6238
- logger?.error(errMsg, error);
6239
- this.logger.error(errMsg, error);
7498
+ logger?.error(`Failed to load package ${trimmedName}`, error);
7499
+ this.logger.error("Failed to load package @package: @error", trimmedName, error);
6240
7500
  }
6241
7501
  }
6242
7502
  return allModels;
6243
7503
  }
6244
- isValidPackageName(name) {
6245
- return /^(@[a-z0-9][-a-z0-9._]*\/)?[a-z0-9][-a-z0-9._]*$/i.test(name) && !name.includes("..") && name.length <= 214;
7504
+ async packageVersion(packageSpec) {
7505
+ try {
7506
+ const response = await fetch(`${this.registryUrl}-/cdn/${packageSpec}/package.json`);
7507
+ if (!response.ok) return void 0;
7508
+ const pkg = await response.json();
7509
+ return typeof pkg.version === "string" && EXACT_VERSION.test(pkg.version) ? pkg.version : void 0;
7510
+ } catch (error) {
7511
+ this.logger.verbose("Could not read the version of @package: @error", packageSpec, error);
7512
+ return;
7513
+ }
6246
7514
  }
6247
7515
  };
6248
- /**
6249
- * Returns live modules (host-only sources): dynamically loaded models are
6250
- * registered on the host registry but never reach executor worker threads.
6251
- * Making them worker-executable means returning an importable source here
6252
- * (the CDN URL as a package specifier, given process-wide https hooks).
6253
- */
6254
7516
  var HttpDocumentModelLoader = class {
6255
7517
  loader;
6256
7518
  logger = childLogger(["reactor-api", "http-document-model-loader"]);
6257
7519
  documentTypeCache = /* @__PURE__ */ new Map();
6258
7520
  packageModulesCache = /* @__PURE__ */ new Map();
7521
+ packageFileCache = /* @__PURE__ */ new Map();
6259
7522
  onModelLoaded;
6260
7523
  constructor(loader) {
6261
7524
  this.loader = loader;
@@ -6266,15 +7529,16 @@ var HttpDocumentModelLoader = class {
6266
7529
  clearCache() {
6267
7530
  this.documentTypeCache.clear();
6268
7531
  this.packageModulesCache.clear();
7532
+ this.packageFileCache.clear();
6269
7533
  }
6270
7534
  async load(documentType) {
6271
7535
  const packageName = await this.findPackageByDocumentType(documentType);
6272
- let models;
6273
- const cachedModels = this.packageModulesCache.get(packageName);
6274
- if (cachedModels) models = cachedModels;
6275
- else {
6276
- models = await this.loader.loadDocumentModels(packageName);
7536
+ let models = this.packageModulesCache.get(packageName);
7537
+ if (!models) {
7538
+ const { module, filePath } = await this.loader.importDocumentModels(packageName);
7539
+ models = documentModelsOf(module);
6277
7540
  this.packageModulesCache.set(packageName, models);
7541
+ if (filePath) this.packageFileCache.set(packageName, filePath);
6278
7542
  }
6279
7543
  const model = models.find((m) => m.documentModel.global.id === documentType);
6280
7544
  if (!model) {
@@ -6283,7 +7547,8 @@ var HttpDocumentModelLoader = class {
6283
7547
  }
6284
7548
  this.logger.info(`Loaded document model "${documentType}" from package "${packageName}"`);
6285
7549
  if (this.onModelLoaded) this.onModelLoaded(model);
6286
- return model;
7550
+ const filePath = this.packageFileCache.get(packageName);
7551
+ return filePath ? { filePath } : model;
6287
7552
  }
6288
7553
  async findPackageByDocumentType(documentType) {
6289
7554
  const cached = this.documentTypeCache.get(documentType);
@@ -6306,6 +7571,7 @@ var HttpDocumentModelLoader = class {
6306
7571
  }
6307
7572
  removeFromCache(packageName) {
6308
7573
  this.packageModulesCache.delete(packageName);
7574
+ this.packageFileCache.delete(packageName);
6309
7575
  for (const [docType, pkg] of this.documentTypeCache) if (pkg === packageName) this.documentTypeCache.delete(docType);
6310
7576
  }
6311
7577
  };
@@ -6315,8 +7581,13 @@ var HttpDocumentModelLoader = class {
6315
7581
  * This class is used to load packages using the import keyword.
6316
7582
  */
6317
7583
  var ImportPackageLoader = class {
7584
+ hostDir;
6318
7585
  logger = childLogger(["reactor-api", "import-loader"]);
6319
7586
  name = "ImportPackageLoader";
7587
+ constructor(hostDir = process.cwd()) {
7588
+ this.hostDir = hostDir;
7589
+ }
7590
+ reportedUnbuilt = /* @__PURE__ */ new Set();
6320
7591
  async loadDocumentModels(identifier) {
6321
7592
  this.logger.verbose(`Loading document models from package: ${identifier}`);
6322
7593
  const pkgModule = await loadDocumentModels(identifier);
@@ -6359,6 +7630,31 @@ var ImportPackageLoader = class {
6359
7630
  this.logger.verbose(` ➜ No Processor Factory found: ${identifier}`);
6360
7631
  return null;
6361
7632
  }
7633
+ async loadPieces(identifier) {
7634
+ this.logger.verbose(`Loading pieces from package: ${identifier}`);
7635
+ const { root, listPath } = pieceListLocation(identifier, this.hostDir);
7636
+ if (!existsSync(listPath)) {
7637
+ this.reportUnbuilt(identifier, root, listPath);
7638
+ return [];
7639
+ }
7640
+ const module = await import(
7641
+ /* @vite-ignore */
7642
+ pathToFileURL(listPath).href
7643
+ );
7644
+ const pieces = piecesFromListModule(module, {
7645
+ root,
7646
+ identifier,
7647
+ logger: this.logger
7648
+ });
7649
+ this.logger.verbose(` ➜ Loaded ${pieces.length} Pieces from: ${identifier}`);
7650
+ return pieces;
7651
+ }
7652
+ reportUnbuilt(identifier, root, listPath) {
7653
+ if (!isAbsolute(identifier) || this.reportedUnbuilt.has(identifier)) return;
7654
+ if (!existsSync(join(root, PIECE_SOURCE_LIST))) return;
7655
+ this.reportedUnbuilt.add(identifier);
7656
+ this.logger.warn("@pkg declares pieces but @list was never built; run `ph build`", identifier, listPath);
7657
+ }
6362
7658
  };
6363
7659
  //#endregion
6364
7660
  //#region src/packages/package-manager.ts
@@ -6374,10 +7670,13 @@ var ImportPackageLoader = class {
6374
7670
  * either bare (`'pkg'`) or as a subpath (`'pkg/subgraphs'`), since loaders
6375
7671
  * import sub-entries like `${pkg}/subgraphs` / `${pkg}/document-models`.
6376
7672
  */
6377
- function isExpectedLoaderMiss(error, pkg) {
7673
+ function isExpectedLoaderMiss(error, pkg, subPath) {
6378
7674
  if (!(error instanceof Error)) return false;
6379
7675
  const code = error.code;
6380
7676
  if (code === "ERR_UNSUPPORTED_DIR_IMPORT") return true;
7677
+ if (code === "ERR_REGISTRY_ENTRY_ABSENT") return true;
7678
+ if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && subPath !== void 0 && error.message.includes(`'./${subPath}'`)) return true;
7679
+ if (subPath !== void 0 && new RegExp(`^Failed to fetch \\S*/${subPath}/index\\.mjs: 404$`).test(error.message)) return true;
6381
7680
  if (error.message.startsWith("Invalid package name:")) return true;
6382
7681
  if (code === "ERR_MODULE_NOT_FOUND") return error.message.includes(`'${pkg}'`) || error.message.includes(`"${pkg}"`) || error.message.includes(`'${pkg}/`) || error.message.includes(`"${pkg}/`);
6383
7682
  return false;
@@ -6393,12 +7692,14 @@ function getUniqueUpgradeManifests(...upgradeManifests) {
6393
7692
  return Array.from(uniqueManifests.values());
6394
7693
  }
6395
7694
  var PackageManager = class {
7695
+ options;
6396
7696
  logger = childLogger(["reactor-api", "package-manager"]);
6397
7697
  loaders;
6398
7698
  docModelsMap = /* @__PURE__ */ new Map();
6399
7699
  upgradeManifestsMap = /* @__PURE__ */ new Map();
6400
7700
  subgraphsMap = /* @__PURE__ */ new Map();
6401
7701
  processorMap = /* @__PURE__ */ new Map();
7702
+ piecesMap = /* @__PURE__ */ new Map();
6402
7703
  configWatcher;
6403
7704
  debouncedUpdateCallbacks = /* @__PURE__ */ new Map();
6404
7705
  eventEmitter = new EventEmitter();
@@ -6417,10 +7718,12 @@ var PackageManager = class {
6417
7718
  const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
6418
7719
  const subgraphsMap = await this.loadSubgraphs(packages);
6419
7720
  const processorsMap = await this.loadProcessors(packages);
7721
+ const piecesMap = await this.loadPieces(packages);
6420
7722
  this.upgradeManifestsMap = upgradeManifestsMap;
6421
7723
  this.updatePackagesMap(documentModelsMap);
6422
7724
  this.updateSubgraphsMap(subgraphsMap);
6423
7725
  this.updateProcessorsMap(processorsMap);
7726
+ this.updatePiecesMap(piecesMap);
6424
7727
  try {
6425
7728
  this.subscribePackages(packages);
6426
7729
  } catch (error) {
@@ -6430,7 +7733,8 @@ var PackageManager = class {
6430
7733
  documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
6431
7734
  upgradeManifests: this.getUniqueUpgradeManifests(),
6432
7735
  subgraphs: subgraphsMap,
6433
- processors: processorsMap
7736
+ processors: processorsMap,
7737
+ pieces: piecesMap
6434
7738
  };
6435
7739
  }
6436
7740
  async loadDocumentModels(packages) {
@@ -6499,7 +7803,7 @@ var PackageManager = class {
6499
7803
  loader: loader.name,
6500
7804
  error
6501
7805
  });
6502
- this.logger.debug(`[${loader.name}] Failed to load subgraphs from package ${pkg}`, error);
7806
+ this.logger.debug("[@loader] Failed to load subgraphs from package @package: @error", loader.name, pkg, error);
6503
7807
  }
6504
7808
  this.maybeWarnAllLoadersFailed("subgraphs", pkg, succeeded, failures);
6505
7809
  subgraphsMap.set(pkg, allSubgraphs);
@@ -6523,16 +7827,41 @@ var PackageManager = class {
6523
7827
  loader: loader.name,
6524
7828
  error
6525
7829
  });
6526
- this.logger.debug(`[${loader.name}] Failed to load processors from package ${pkg}`, error);
7830
+ this.logger.debug("[@loader] Failed to load processors from package @package: @error", loader.name, pkg, error);
6527
7831
  }
6528
7832
  this.maybeWarnAllLoadersFailed("processors", pkg, succeeded, failures);
6529
7833
  processorsMap.set(pkg, allProcessors);
6530
7834
  }
6531
7835
  return processorsMap;
6532
7836
  }
7837
+ async loadPieces(packages) {
7838
+ this.logger.debug(`Loading pieces from packages: ${packages.join(", ")}`);
7839
+ const piecesMap = /* @__PURE__ */ new Map();
7840
+ for (const pkg of packages) {
7841
+ const allPieces = [];
7842
+ const failures = [];
7843
+ let succeeded = false;
7844
+ for (const loader of this.loaders) try {
7845
+ allPieces.push(...await loader.loadPieces(pkg));
7846
+ succeeded = true;
7847
+ break;
7848
+ } catch (error) {
7849
+ failures.push({
7850
+ loader: loader.name,
7851
+ error
7852
+ });
7853
+ this.logger.debug("[@loader] Failed to load pieces from package @package: @error", loader.name, pkg, error);
7854
+ }
7855
+ this.maybeWarnAllLoadersFailed("pieces", pkg, succeeded, failures);
7856
+ if (allPieces.length > 0) this.logger.info("Loaded @count piece(s) from package @pkg: @pieces", allPieces.length, pkg, allPieces.map((piece) => piece.name).join(", "));
7857
+ piecesMap.set(pkg, allPieces);
7858
+ }
7859
+ return piecesMap;
7860
+ }
6533
7861
  maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
6534
7862
  if (succeeded || failures.length === 0) return;
6535
- const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg));
7863
+ const subPath = kind === "document models" ? "document-models" : kind;
7864
+ const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg, subPath));
6536
7865
  if (realFailures.length === 0) return;
6537
7866
  const details = realFailures.map(({ loader, error }) => {
6538
7867
  return ` [${loader}] ${error instanceof Error ? error.message : String(error)}`;
@@ -6572,6 +7901,15 @@ var PackageManager = class {
6572
7901
  else processorsMap.set(pkg, pkgProcessors);
6573
7902
  this.updateProcessorsMap(processorsMap);
6574
7903
  }
7904
+ async updatePiecesForPackage(pkg) {
7905
+ this.logger.debug(`Updating pieces for package: ${pkg}`);
7906
+ const pieces = await this.loadPieces([pkg]);
7907
+ const piecesMap = new Map(this.piecesMap);
7908
+ const pkgPieces = pieces.get(pkg) ?? [];
7909
+ if (pkgPieces.length === 0) piecesMap.delete(pkg);
7910
+ else piecesMap.set(pkg, pkgPieces);
7911
+ this.updatePiecesMap(piecesMap);
7912
+ }
6575
7913
  /** Debounced per key so repeated subscribePackages calls reuse one timer. */
6576
7914
  getDebouncedUpdateCallback(key, update) {
6577
7915
  if (!this.debouncedUpdateCallbacks.has(key)) {
@@ -6590,10 +7928,12 @@ var PackageManager = class {
6590
7928
  const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
6591
7929
  const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
6592
7930
  const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
7931
+ const onPieces = this.getDebouncedUpdateCallback(`${pkg}:pieces`, () => this.updatePiecesForPackage(pkg));
6593
7932
  for (const loader of this.loaders) {
6594
7933
  if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
6595
7934
  if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
6596
7935
  if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
7936
+ if (loader.onPiecesChange) unsubs.push(loader.onPiecesChange(pkg, onPieces));
6597
7937
  }
6598
7938
  }
6599
7939
  return () => {
@@ -6641,6 +7981,19 @@ var PackageManager = class {
6641
7981
  this.processorMap = processorsMap;
6642
7982
  this.eventEmitter.emit("processorsChange", processorsMap);
6643
7983
  }
7984
+ updatePiecesMap(piecesMap) {
7985
+ const oldPackages = Array.from(this.piecesMap.keys());
7986
+ const newPackages = Array.from(piecesMap.keys());
7987
+ oldPackages.filter((pkg) => !newPackages.includes(pkg)).forEach((pkg) => {
7988
+ this.logger.info("Removed pieces from: @pkg", pkg);
7989
+ });
7990
+ this.piecesMap = piecesMap;
7991
+ this.eventEmitter.emit("piecesChange", piecesMap);
7992
+ }
7993
+ /** The pieces every loaded package ships, keyed by the package. */
7994
+ getPieces() {
7995
+ return new Map(this.piecesMap);
7996
+ }
6644
7997
  /**
6645
7998
  * Remove `pkg` from all package maps and emit the change events, tearing
6646
7999
  * down everything the package registered (subgraphs, processors, document
@@ -6659,9 +8012,11 @@ var PackageManager = class {
6659
8012
  this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
6660
8013
  this.subgraphsMap = dropKey(this.subgraphsMap);
6661
8014
  this.processorMap = dropKey(this.processorMap);
8015
+ this.piecesMap = dropKey(this.piecesMap);
6662
8016
  this.updatePackagesMap(this.docModelsMap);
6663
8017
  this.updateSubgraphsMap(this.subgraphsMap);
6664
8018
  this.updateProcessorsMap(this.processorMap);
8019
+ this.updatePiecesMap(this.piecesMap);
6665
8020
  }
6666
8021
  onDocumentModelsChange(handler) {
6667
8022
  this.eventEmitter.on("documentModelsChange", handler);
@@ -6672,6 +8027,9 @@ var PackageManager = class {
6672
8027
  onProcessorsChange(handler) {
6673
8028
  this.eventEmitter.on("processorsChange", handler);
6674
8029
  }
8030
+ onPiecesChange(handler) {
8031
+ this.eventEmitter.on("piecesChange", handler);
8032
+ }
6675
8033
  };
6676
8034
  //#endregion
6677
8035
  //#region src/config.ts
@@ -6689,20 +8047,55 @@ async function createStartupAttachmentBackend({ db, env = process.env, s3Depende
6689
8047
  }
6690
8048
  //#endregion
6691
8049
  //#region src/services/attachment-access.service.ts
8050
+ /** The scope an attachment's bytes belong to: the document's own domain state. */
8051
+ const ATTACHMENT_SCOPE = "global";
8052
+ /** The branch an attachment reference is resolved against. */
8053
+ const ATTACHMENT_BRANCH = "main";
6692
8054
  const HASH_PATTERN = /^[a-f0-9]{64}$/;
6693
8055
  /**
6694
8056
  * Composes document authorization with the projected document/ref
6695
8057
  * relationship. Order is fixed: validate ref, resolve the canonical document
6696
- * id, check `canRead`, then check the reference index. A denied document
6697
- * never reaches the reference reader, and the facade never touches
6698
- * attachment metadata, storage backends, or presigners.
8058
+ * id, decide the read, check the reactor's read gate serves the document, then
8059
+ * check the reference index and that the subject may read a scope whose
8060
+ * operations reference the attachment. A denied document never reaches the
8061
+ * reference reader, and the facade never touches attachment metadata, storage
8062
+ * backends, or presigners.
8063
+ *
8064
+ * The named document decides alone: an attachment several documents reference
8065
+ * is readable through each of them on that document's own terms.
8066
+ *
8067
+ * The read is decided by the document's own policy when this composition has a
8068
+ * model to enforce it with, and by the host's permission tables when it does
8069
+ * not. That is not a preference between two equivalent answers: a host running
8070
+ * document policies keeps no rows in the permission tables, so asking them
8071
+ * about such a document returns whatever the host-wide policy says — under
8072
+ * `OPEN`, `true`, for everyone. Serving bytes on that answer hands the file to
8073
+ * anyone who learns its hash, which the document's own state may well have told
8074
+ * them. Both are kept because they are the two halves of one rule: an
8075
+ * attachment is readable by whoever may read the document that references it,
8076
+ * and each composition can only express that in its own terms. The caller
8077
+ * decision mirrors the routes' `requireAuth`.
6699
8078
  */
6700
8079
  var AttachmentAccessService = class {
6701
- constructor(resolveCanonicalId, authorization, references, projection) {
8080
+ resolveCanonicalId;
8081
+ authorization;
8082
+ references;
8083
+ projection;
8084
+ readGate;
8085
+ scopeGate;
8086
+ options;
8087
+ constructor(resolveCanonicalId, authorization, references, projection, readGate, scopeGate, options = {}) {
6702
8088
  this.resolveCanonicalId = resolveCanonicalId;
6703
8089
  this.authorization = authorization;
6704
8090
  this.references = references;
6705
8091
  this.projection = projection;
8092
+ this.readGate = readGate;
8093
+ this.scopeGate = scopeGate;
8094
+ this.options = options;
8095
+ }
8096
+ admitCaller(request) {
8097
+ if ((request.intent === "read" ? this.options.refuseAnonymousReads ?? false : this.options.refuseAnonymousWrites ?? true) && !request.userAddress) return Promise.resolve({ kind: "unauthenticated" });
8098
+ return Promise.resolve({ kind: "admitted" });
6706
8099
  }
6707
8100
  async canReadAttachment(request) {
6708
8101
  if (this.projection.status !== "available") return { kind: "projection-unavailable" };
@@ -6714,14 +8107,51 @@ var AttachmentAccessService = class {
6714
8107
  } catch {
6715
8108
  return { kind: "denied" };
6716
8109
  }
6717
- if (!await this.authorization.canRead(documentId, request.userAddress)) return { kind: "denied" };
6718
- if (!await this.references.hasReference(documentId, ref)) return { kind: "denied" };
8110
+ if (!await this.canReadDocument(documentId, request)) return { kind: "denied" };
8111
+ const subject = {
8112
+ address: request.userAddress,
8113
+ key: request.appKey
8114
+ };
8115
+ let served;
8116
+ try {
8117
+ served = await this.readGate.isServed(documentId, { subject });
8118
+ } catch {
8119
+ return { kind: "denied" };
8120
+ }
8121
+ if (!served) return { kind: "denied" };
8122
+ const scopes = await this.references.referencingScopes(documentId, ref);
8123
+ if (scopes.length === 0) return { kind: "denied" };
8124
+ let held;
8125
+ try {
8126
+ held = (await this.readGate.get(documentId, {
8127
+ subject,
8128
+ scopes
8129
+ })).state;
8130
+ } catch {
8131
+ return { kind: "denied" };
8132
+ }
8133
+ if (!scopes.some((scope) => scope in held)) return { kind: "denied" };
6719
8134
  return {
6720
8135
  kind: "allowed",
6721
8136
  documentId,
6722
8137
  ref
6723
8138
  };
6724
8139
  }
8140
+ /**
8141
+ * Whether the caller may read the state the reference lives in.
8142
+ *
8143
+ * A failure here is rethrown rather than turned into a denial. A read side
8144
+ * that is down must not read as a policy: a denial nobody can distinguish
8145
+ * from a refusal is one nobody investigates, and the route answers 500 for a
8146
+ * reason.
8147
+ */
8148
+ async canReadDocument(documentId, request) {
8149
+ if (!this.scopeGate) return this.authorization.canRead(documentId, request.userAddress);
8150
+ return (await this.scopeGate.scopePredicateById(documentId, {
8151
+ address: request.userAddress,
8152
+ key: request.appKey
8153
+ }, ATTACHMENT_BRANCH))(ATTACHMENT_SCOPE);
8154
+ }
6725
8155
  };
6726
8156
  /**
6727
8157
  * Parses and canonicalizes a caller-supplied ref through the shared parser.
@@ -6742,6 +8172,140 @@ function normalizeAttachmentRef(value) {
6742
8172
  return createRef(hash, version);
6743
8173
  }
6744
8174
  //#endregion
8175
+ //#region src/services/authorized-attachment.service.ts
8176
+ /** Matches the attachment routes' `documentId` bound. */
8177
+ const MAX_DOCUMENT_ID_LEN = 512;
8178
+ /** The reference index is not maintained in this composition. */
8179
+ var AttachmentAccessUnavailable = class extends Error {
8180
+ constructor() {
8181
+ super("Attachment access is unavailable");
8182
+ this.name = "AttachmentAccessUnavailable";
8183
+ }
8184
+ };
8185
+ /** The access decision itself failed; the cause is logged, not surfaced. */
8186
+ var AttachmentAccessFailed = class extends Error {
8187
+ constructor(cause) {
8188
+ super("Attachment access decision failed", { cause });
8189
+ this.name = "AttachmentAccessFailed";
8190
+ }
8191
+ };
8192
+ /** An `IAttachmentService` whose reads and writes are decided for one subject. */
8193
+ var AuthorizedAttachmentService = class {
8194
+ inner;
8195
+ access;
8196
+ subject;
8197
+ logger;
8198
+ constructor(inner, access, subject, logger) {
8199
+ this.inner = inner;
8200
+ this.access = access;
8201
+ this.subject = subject;
8202
+ this.logger = logger;
8203
+ }
8204
+ /** Hash-first only: an upload-first handle can send after it expires. */
8205
+ async reserve(options) {
8206
+ const { mimeType, fileName, extension, clientHash, sizeBytes } = options;
8207
+ if (clientHash === void 0) throw new Error("Attachment reservations require a client hash");
8208
+ const copy = {
8209
+ mimeType,
8210
+ fileName,
8211
+ extension,
8212
+ clientHash,
8213
+ sizeBytes
8214
+ };
8215
+ await this.admit("write");
8216
+ validateReserveMetadata(copy);
8217
+ return this.inner.reserve(copy);
8218
+ }
8219
+ async stat(ref, options) {
8220
+ const decision = await this.decideRead(ref, options);
8221
+ return this.inner.stat(decision.ref, {
8222
+ ...options,
8223
+ documentId: decision.documentId
8224
+ });
8225
+ }
8226
+ async get(ref, options) {
8227
+ const decision = await this.decideRead(ref, options);
8228
+ return this.inner.get(decision.ref, {
8229
+ ...options,
8230
+ documentId: decision.documentId
8231
+ });
8232
+ }
8233
+ async getDownloadTarget(ref, options) {
8234
+ const decision = await this.decideRead(ref, options);
8235
+ return this.inner.getDownloadTarget(decision.ref, {
8236
+ ...options,
8237
+ documentId: decision.documentId
8238
+ });
8239
+ }
8240
+ async decideRead(ref, options) {
8241
+ const documentId = readDocumentId(options);
8242
+ if (documentId === null) throw new AttachmentNotFound(ref);
8243
+ await this.admit("read");
8244
+ let decision;
8245
+ try {
8246
+ decision = await this.access.canReadAttachment({
8247
+ documentId,
8248
+ attachmentRef: ref,
8249
+ userAddress: this.subject.address,
8250
+ appKey: this.subject.key
8251
+ });
8252
+ } catch (err) {
8253
+ throw this.accessFailed(err);
8254
+ }
8255
+ if (decision.kind === "allowed") return {
8256
+ documentId: decision.documentId,
8257
+ ref: decision.ref
8258
+ };
8259
+ if (decision.kind === "projection-unavailable") throw new AttachmentAccessUnavailable();
8260
+ throw new AttachmentNotFound(ref);
8261
+ }
8262
+ async admit(intent) {
8263
+ let result;
8264
+ try {
8265
+ result = await this.access.admitCaller({
8266
+ intent,
8267
+ userAddress: this.subject.address,
8268
+ appKey: this.subject.key
8269
+ });
8270
+ } catch (err) {
8271
+ throw this.accessFailed(err);
8272
+ }
8273
+ if (result.kind !== "admitted") throw new AuthenticationRequiredError();
8274
+ }
8275
+ accessFailed(err) {
8276
+ this.logger.error("Attachment access decision failed: @error", err);
8277
+ return new AttachmentAccessFailed(err);
8278
+ }
8279
+ };
8280
+ /** The options' `documentId` when it is one the routes would accept, else null. */
8281
+ function readDocumentId(options) {
8282
+ if (typeof options !== "object" || options === null) return null;
8283
+ if (isAbortSignal(options)) return null;
8284
+ const value = options.documentId;
8285
+ if (typeof value !== "string") return null;
8286
+ if (value.trim().length === 0 || value.length > MAX_DOCUMENT_ID_LEN) return null;
8287
+ return value;
8288
+ }
8289
+ /** Duck-checked too, so a signal from another realm is still a signal. */
8290
+ function isAbortSignal(value) {
8291
+ if (value instanceof AbortSignal) return true;
8292
+ const candidate = value;
8293
+ return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function";
8294
+ }
8295
+ var AttachmentClientProvider = class {
8296
+ service;
8297
+ access;
8298
+ logger;
8299
+ constructor(service, access, logger) {
8300
+ this.service = service;
8301
+ this.access = access;
8302
+ this.logger = logger;
8303
+ }
8304
+ forSubject(subject) {
8305
+ return createAttachmentClient(new AuthorizedAttachmentService(this.service, this.access, subject, this.logger));
8306
+ }
8307
+ };
8308
+ //#endregion
6745
8309
  //#region src/graphql/gateway/require-auth-middleware.ts
6746
8310
  /**
6747
8311
  * Returns a fetch middleware that rejects anonymous callers with a 401.
@@ -6767,14 +8331,42 @@ function normalizeAttachmentRef(value) {
6767
8331
  * context has no caller, and is rejected. The server never composes this
6768
8332
  * middleware without the auth middleware in the chain (see
6769
8333
  * `assertRequireAuthenticatedCallerAllowed` in `server.ts`).
8334
+ *
8335
+ * `exemptPaths` names the mounted paths that stay reachable anonymously — a
8336
+ * deployment whose product has a flow that runs before sign-in, such as
8337
+ * previewing an invitation from its code, needs somewhere to serve it from.
8338
+ * Each entry is a hole in the floor, so the match is deliberately the
8339
+ * narrowest one that works: the request's pathname, compared in full. A
8340
+ * prefix rule would exempt `/graphql/public-admin` along with
8341
+ * `/graphql/public`, and an exempt surface nobody can enumerate by reading
8342
+ * the configuration is not one anybody audits.
6770
8343
  */
6771
- function createRequireAuthFetchMiddleware() {
8344
+ function createRequireAuthFetchMiddleware(exemptPaths = []) {
8345
+ const exempt = new Set(exemptPaths.map(normalizeExemptPath));
6772
8346
  return (next) => async (request) => {
6773
8347
  if (request.method === "OPTIONS") return next(request);
8348
+ if (exempt.size > 0 && exempt.has(normalizeExemptPath(new URL(request.url).pathname))) return next(request);
6774
8349
  if (getAuthContext(request)?.user === void 0) return new globalThis.Response(JSON.stringify({ error: "Authentication required" }), { status: 401 });
6775
8350
  return next(request);
6776
8351
  };
6777
8352
  }
8353
+ /**
8354
+ * The form both sides of the comparison are reduced to: surrounding whitespace
8355
+ * gone, and one trailing slash gone, so `/graphql/public/` and
8356
+ * `/graphql/public` are the same path. A client that appends a slash is not
8357
+ * asking for a different route, and a configuration that carries one is not
8358
+ * asking for a different rule.
8359
+ *
8360
+ * Deliberately NOT normalized: case, percent-encoding, and a missing leading
8361
+ * slash. The first two would let two spellings of one path diverge from how
8362
+ * the router itself matches; the third is a configuration error, and
8363
+ * `assertRequireAuthenticatedCallerAllowed` refuses to boot on it rather than
8364
+ * leaving an exemption that silently never applies.
8365
+ */
8366
+ function normalizeExemptPath(path) {
8367
+ const trimmed = path.trim();
8368
+ return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
8369
+ }
6778
8370
  //#endregion
6779
8371
  //#region src/migrations/001_create_document_permissions.ts
6780
8372
  var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
@@ -7177,6 +8769,7 @@ async function createRenownCredentialVerifier(config = {}) {
7177
8769
  * - Users can be granted permission to execute specific operations
7178
8770
  */
7179
8771
  var DocumentPermissionService = class {
8772
+ db;
7180
8773
  config;
7181
8774
  constructor(db, config = { defaultProtection: false }) {
7182
8775
  this.db = db;
@@ -7252,8 +8845,38 @@ var DocumentPermissionService = class {
7252
8845
  * Delete all permissions for a document (used when deleting a document)
7253
8846
  */
7254
8847
  async deleteAllDocumentPermissions(documentId) {
7255
- await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).execute();
7256
- await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).execute();
8848
+ const permissions = await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).returning("id").execute();
8849
+ const operations = await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).returning("id").execute();
8850
+ return {
8851
+ DocumentPermission: permissions.length,
8852
+ OperationUserPermission: operations.length
8853
+ };
8854
+ }
8855
+ /** Delete a document's protection row, which names its owner. */
8856
+ async deleteDocumentProtection(documentId) {
8857
+ return (await this.db.deleteFrom("DocumentProtection").where("documentId", "=", documentId).returning("documentId").execute()).length;
8858
+ }
8859
+ /** Every row that names the address, for a subject access request. */
8860
+ async rowsForAddress(address) {
8861
+ const lower = address.toLowerCase();
8862
+ return {
8863
+ permissions: await this.db.selectFrom("DocumentPermission").select([
8864
+ "documentId",
8865
+ "userAddress",
8866
+ "permission",
8867
+ "grantedBy",
8868
+ "createdAt",
8869
+ "updatedAt"
8870
+ ]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
8871
+ operationPermissions: await this.db.selectFrom("OperationUserPermission").select([
8872
+ "documentId",
8873
+ "operationType",
8874
+ "userAddress",
8875
+ "grantedBy",
8876
+ "createdAt"
8877
+ ]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
8878
+ ownedDocumentIds: (await this.db.selectFrom("DocumentProtection").select("documentId").where("ownerAddress", "=", lower).orderBy("documentId").execute()).map((row) => row.documentId)
8879
+ };
7257
8880
  }
7258
8881
  /**
7259
8882
  * Grant a user permission to execute an operation on a document
@@ -7447,12 +9070,16 @@ function createGetParentIdsFn(reactorClient) {
7447
9070
  * Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
7448
9071
  * unrestricted reactor access, so access is limited to supreme admins when
7449
9072
  * auth is enabled. OPEN policy is public; any other policy without an
7450
- * AuthService fails closed.
9073
+ * AuthService fails closed. The tools read as the caller: anonymous under OPEN,
9074
+ * else the admin's address and the app key its token carries.
7451
9075
  */
7452
9076
  function createMcpRequestAuthorizer(authService, authorizationService) {
7453
9077
  return async (req) => {
7454
9078
  if (!authService) {
7455
- if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return { authorized: true };
9079
+ if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
9080
+ authorized: true,
9081
+ subject: {}
9082
+ };
7456
9083
  return {
7457
9084
  authorized: false,
7458
9085
  status: 401,
@@ -7475,10 +9102,39 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
7475
9102
  status: 403,
7476
9103
  message: "Forbidden: MCP access requires an administrator"
7477
9104
  };
7478
- return { authorized: true };
9105
+ return {
9106
+ authorized: true,
9107
+ subject: {
9108
+ address: context.user.address,
9109
+ key: context.user.appKey
9110
+ }
9111
+ };
7479
9112
  };
7480
9113
  }
7481
9114
  //#endregion
9115
+ //#region src/services/sync-serving-gate.ts
9116
+ /**
9117
+ * The gate sync serving evaluates a document's own policy through, deciding
9118
+ * what the reactor client's read gate decides at the same flag level.
9119
+ *
9120
+ * Under `authEnforcement` it is built here rather than taken off the client
9121
+ * because it also carries the host's closes-by-default setting, which withholds
9122
+ * the domain scopes of a document nobody has policied yet. That answer belongs
9123
+ * to serving alone -- replay must keep reading an uninitialized document in
9124
+ * full -- so the two gates are separate objects over the same model.
9125
+ *
9126
+ * Below it the registered model ignores the auth scope, so the client reads
9127
+ * through the policy alone, and so does serving: a policy written below the
9128
+ * flag still withholds, where serving ungated would sync to a peer what `get`
9129
+ * refuses it.
9130
+ */
9131
+ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
9132
+ if (!reactorModule) return;
9133
+ const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
9134
+ if (!model) return new SyncScopeGate(new BareReadGate(), reactorModule.documentView, logger);
9135
+ return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
9136
+ }
9137
+ //#endregion
7482
9138
  //#region src/services/renown-config.ts
7483
9139
  /** Reading credentials from this reactor's own read model needs a host-supplied
7484
9140
  * verifier: core has no idea which subgraph serves that read model. */
@@ -7660,10 +9316,36 @@ function assertSkipCredentialVerificationAllowed(resolvesCallerIdentity, skipCre
7660
9316
  * require-authenticated-caller middleware would reject every caller —
7661
9317
  * authenticated ones included. Refuse to boot rather than run broken.
7662
9318
  */
7663
- function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity) {
9319
+ function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity, exemptPaths = []) {
9320
+ /**
9321
+ * An exemption is only ever a hole in this floor, so configuring one while
9322
+ * the floor is off is not a harmless no-op: it reads, to anyone auditing the
9323
+ * configuration, as a surface that was deliberately opened — and therefore
9324
+ * as a floor that exists. Refuse rather than let the two drift.
9325
+ */
9326
+ if (!requireAuthenticatedCaller && exemptPaths.length > 0) throw new Error("REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS is set but refused: REQUIRE_AUTHENTICATED_CALLER is off, so nothing is being exempted from anything and the configuration claims a protection the server is not applying. Enable the floor, or drop the exemptions.");
9327
+ /**
9328
+ * A path that cannot match anything is worse than no path: the operator
9329
+ * believes the flow is reachable, and finds out when the first user cannot
9330
+ * claim an invitation. `URL.pathname` is always absolute, so an entry that
9331
+ * does not start with `/` never matches, whatever the router does.
9332
+ */
9333
+ const relative = exemptPaths.filter((path) => !path.trim().startsWith("/"));
9334
+ if (relative.length > 0) throw new Error(`REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS contains ${relative.map((path) => `"${path}"`).join(", ")}, which cannot match: an exempt path is compared against the request's pathname and must start with '/' (for example '/graphql/public').`);
7664
9335
  if (!requireAuthenticatedCaller || resolvesCallerIdentity) return;
7665
9336
  throw new Error("REQUIRE_AUTHENTICATED_CALLER is set but refused: it rejects every request without a resolved caller, and with neither AUTH_ENABLED nor RESOLVE_CALLER_IDENTITY the server never reads a bearer, so it would reject every caller, including authenticated ones. Enable identity resolution first (RESOLVE_CALLER_IDENTITY=true or AUTH_ENABLED=true).");
7666
9337
  }
9338
+ /**
9339
+ * Deciding an attachment read by the document's policy needs a policy model to
9340
+ * decide with, and that is what auth enforcement supplies. Without one there is
9341
+ * nothing to consult, so the setting would silently leave the permission tables
9342
+ * in charge — configuration that describes a protection the server is not
9343
+ * applying, which is worse than no configuration at all. Refuse instead.
9344
+ */
9345
+ function assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, hasDecisionModel) {
9346
+ if (!attachmentReadsFollowDocumentPolicy || hasDecisionModel) return;
9347
+ throw new Error("ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY is set but refused: deciding an attachment read by the referencing document's policy requires a policy model to evaluate, and this composition has none, so the host permission tables would keep deciding while the configuration says otherwise. Enable auth enforcement first (REACTOR_AUTH_ENFORCEMENT=true).");
9348
+ }
7667
9349
  function createReadinessGate() {
7668
9350
  let ready = false;
7669
9351
  return {
@@ -7718,27 +9400,6 @@ function makeDbClosers(knexInstance, pglite) {
7718
9400
  return closers;
7719
9401
  }
7720
9402
  /**
7721
- * The gate sync serving evaluates a document's own policy through, or undefined
7722
- * when there is none to evaluate.
7723
- *
7724
- * It is built here rather than taken off the reactor client because it is not
7725
- * the same gate reads use: it carries the host's closes-by-default setting,
7726
- * which withholds the domain scopes of a document nobody has policied yet. That
7727
- * answer belongs to serving alone -- replay must keep reading an uninitialized
7728
- * document in full -- so the two gates are deliberately separate objects over
7729
- * the same model.
7730
- *
7731
- * Undefined below `authEnforcement`, where the registered model ignores the auth
7732
- * scope: gating through it would serve every domain scope of a policied document
7733
- * to anyone, which is worse than not gating at all.
7734
- */
7735
- function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
7736
- if (!reactorModule) return;
7737
- const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
7738
- if (!model) return;
7739
- return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
7740
- }
7741
- /**
7742
9403
  * Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
7743
9404
  * Defaults to "apollo" (the federation gateway, production behavior).
7744
9405
  * "stitching" selects the in-process graphql-tools merge gateway (#1565
@@ -7754,8 +9415,28 @@ function resolveGatewayAdapterType(logger) {
7754
9415
  /**
7755
9416
  * Sets up the subgraph manager and registers subgraphs
7756
9417
  */
7757
- async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes) {
7758
- const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter(resolveGatewayAdapterType(logger), logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate, httpRoutes);
9418
+ async function setupGraphQLManager({ httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes, attachments }) {
9419
+ const graphqlManager = new GraphQLManager({
9420
+ path: config.basePath,
9421
+ httpServer,
9422
+ wsServer,
9423
+ reactorClient: client,
9424
+ relationalDb,
9425
+ analyticsStore,
9426
+ syncManager,
9427
+ logger,
9428
+ httpAdapter,
9429
+ gatewayAdapter: await createGatewayAdapter(resolveGatewayAdapterType(logger), logger),
9430
+ authService,
9431
+ documentPermissionService,
9432
+ featureFlags: { enableDocumentModelSubgraphs },
9433
+ port,
9434
+ authorizationService,
9435
+ reactorDriveClient,
9436
+ syncServingGate,
9437
+ httpRoutes,
9438
+ attachments
9439
+ });
7759
9440
  await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
7760
9441
  for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
7761
9442
  await graphqlManager.updateRouter();
@@ -7853,6 +9534,8 @@ async function _setupCommonInfrastructure(options) {
7853
9534
  let authEnabled = false;
7854
9535
  let configuredResolveIdentity;
7855
9536
  let configuredRequireAuth;
9537
+ let configuredExemptPaths;
9538
+ let configuredAttachmentPolicyReads;
7856
9539
  let configuredRenown;
7857
9540
  if (options.configFile) {
7858
9541
  const config = getConfig(options.configFile);
@@ -7864,8 +9547,10 @@ async function _setupCommonInfrastructure(options) {
7864
9547
  authEnabled = options.auth.enabled;
7865
9548
  configuredResolveIdentity = options.auth.resolveIdentity;
7866
9549
  configuredRequireAuth = options.auth.requireAuthenticatedCaller;
9550
+ configuredExemptPaths = options.auth.requireAuthenticatedCallerExemptPaths;
9551
+ configuredAttachmentPolicyReads = options.auth.attachmentReadsFollowDocumentPolicy;
7867
9552
  }
7868
- const { AUTH_ENABLED, RESOLVE_CALLER_IDENTITY, REQUIRE_AUTHENTICATED_CALLER, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
9553
+ const { AUTH_ENABLED, RESOLVE_CALLER_IDENTITY, REQUIRE_AUTHENTICATED_CALLER, REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS, ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
7869
9554
  if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
7870
9555
  /**
7871
9556
  * Whether the auth middleware reads the bearer and populates `ctx.user`,
@@ -7896,6 +9581,30 @@ async function _setupCommonInfrastructure(options) {
7896
9581
  */
7897
9582
  let requireAuthenticatedCaller = configuredRequireAuth ?? false;
7898
9583
  if (REQUIRE_AUTHENTICATED_CALLER !== void 0) requireAuthenticatedCaller = REQUIRE_AUTHENTICATED_CALLER === "true";
9584
+ /**
9585
+ * The paths the floor above does not apply to. Comma-separated, like
9586
+ * `ADMINS`, because a deployment configures this the same way it configures
9587
+ * everything else: one variable, one line, however many values it has.
9588
+ *
9589
+ * Empty entries are dropped rather than refused, so a trailing comma or a
9590
+ * value split across lines in a manifest is not a boot failure; a value that
9591
+ * cannot ever match is refused, in
9592
+ * {@link assertRequireAuthenticatedCallerAllowed}.
9593
+ */
9594
+ let requireAuthenticatedCallerExemptPaths = configuredExemptPaths ?? [];
9595
+ if (REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS !== void 0) requireAuthenticatedCallerExemptPaths = REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS.split(",").map((path) => path.trim()).filter((path) => path.length > 0);
9596
+ /**
9597
+ * Whether an attachment read is decided by the referencing document's own
9598
+ * policy rather than by the host's permission tables.
9599
+ *
9600
+ * Off by default, and a deployment's choice rather than something derived
9601
+ * from the flags around it. Which model governs those bytes is configuration
9602
+ * in the same sense the storage backend behind them is: a host that has said
9603
+ * nothing keeps exactly the behaviour it has, and one that wants the change
9604
+ * asks for it and can take it back without disturbing anything else.
9605
+ */
9606
+ let attachmentReadsFollowDocumentPolicy = configuredAttachmentPolicyReads ?? false;
9607
+ if (ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY !== void 0) attachmentReadsFollowDocumentPolicy = ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY === "true";
7899
9608
  if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
7900
9609
  let defaultProtection = false;
7901
9610
  if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
@@ -7910,7 +9619,7 @@ async function _setupCommonInfrastructure(options) {
7910
9619
  const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
7911
9620
  assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
7912
9621
  assertSkipCredentialVerificationAllowed(resolveCallerIdentity, skipCredentialVerification, process.env);
7913
- assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity);
9622
+ assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity, requireAuthenticatedCallerExemptPaths);
7914
9623
  if (authEnabled && skipCredentialVerification) logger.warn("SECURITY: SKIP_CREDENTIAL_VERIFICATION is enabled — Renown credential verification is disabled and a bearer token's claimed address is NOT cryptographically bound to its signing key. Identity is unverifiable; use only in development or test.");
7915
9624
  httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
7916
9625
  const readiness = createReadinessGate();
@@ -7946,8 +9655,8 @@ async function _setupCommonInfrastructure(options) {
7946
9655
  });
7947
9656
  authFetchMiddleware = createAuthFetchMiddleware(authService);
7948
9657
  if (requireAuthenticatedCaller) {
7949
- requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
7950
- logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
9658
+ requireAuthFetchMiddleware = createRequireAuthFetchMiddleware(requireAuthenticatedCallerExemptPaths);
9659
+ logger.info(requireAuthenticatedCallerExemptPaths.length > 0 ? `Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph, except on ${requireAuthenticatedCallerExemptPaths.join(", ")}` : "Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
7951
9660
  } else logger.warn("Anonymous callers are admitted on every transport, including WebSocket subscriptions: REQUIRE_AUTHENTICATED_CALLER is not set. The authorization policy gates the reactor's own documents, but a package-provided subgraph authorizes nothing on its own. Set REQUIRE_AUTHENTICATED_CALLER=true to refuse anonymous callers before any subgraph sees them.");
7952
9661
  }
7953
9662
  const dbClosers = [];
@@ -7972,7 +9681,7 @@ async function _setupCommonInfrastructure(options) {
7972
9681
  await mkdir(attachmentStoragePath, { recursive: true });
7973
9682
  const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
7974
9683
  dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
7975
- const ATTACHMENT_SWEEP_INTERVAL_MS = 3600 * 1e3;
9684
+ const ATTACHMENT_SWEEP_INTERVAL_MS = 36e5;
7976
9685
  const attachmentBackend = await createStartupAttachmentBackend({ db: attachmentDb });
7977
9686
  const attachmentBuilder = new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(ATTACHMENT_SWEEP_INTERVAL_MS);
7978
9687
  if (attachmentBackend) attachmentBuilder.withBackend(attachmentBackend);
@@ -8008,6 +9717,7 @@ async function _setupCommonInfrastructure(options) {
8008
9717
  httpRoutes,
8009
9718
  authFetchMiddleware,
8010
9719
  requireAuthFetchMiddleware,
9720
+ authEnabled,
8011
9721
  authService,
8012
9722
  relationalDb,
8013
9723
  analyticsStore,
@@ -8017,13 +9727,14 @@ async function _setupCommonInfrastructure(options) {
8017
9727
  attachmentReferenceIndex,
8018
9728
  packages,
8019
9729
  dbClosers,
8020
- readiness
9730
+ readiness,
9731
+ attachmentReadsFollowDocumentPolicy
8021
9732
  };
8022
9733
  }
8023
9734
  /**
8024
9735
  * Private helper function containing common setup logic for API initialization
8025
9736
  */
8026
- async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes) {
9737
+ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes, attachmentReadsFollowDocumentPolicy = false) {
8027
9738
  const hostModuleBase = {
8028
9739
  ...createReactorHostModuleBase({
8029
9740
  client: reactorClient,
@@ -8048,7 +9759,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
8048
9759
  try {
8049
9760
  return fn(moduleFor(packageName));
8050
9761
  } catch (e) {
8051
- logger.error(`Error initializing processor factory for package ${packageName}:`, e);
9762
+ logger.error("Error initializing processor factory for package @package: @error", packageName, e);
8052
9763
  return null;
8053
9764
  }
8054
9765
  }))).filter((factory) => factory.status === "fulfilled" && factory.value !== null && typeof factory.value === "function");
@@ -8065,17 +9776,44 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
8065
9776
  const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
8066
9777
  const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
8067
9778
  logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
8068
- const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection);
9779
+ assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, syncServingGate !== void 0);
9780
+ const floor = requireAuthFetchMiddleware !== void 0;
9781
+ const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, reactorClient, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0, {
9782
+ refuseAnonymousWrites: authEnabled || floor,
9783
+ refuseAnonymousReads: floor
9784
+ });
9785
+ const attachmentClientProvider = new AttachmentClientProvider(attachments.service, attachmentAccess, logger);
8069
9786
  const coreSubgraphs = DefaultCoreSubgraphs.slice();
8070
9787
  coreSubgraphs.push(ReactorSubgraph);
8071
9788
  if (documentPermissionService) {
8072
9789
  coreSubgraphs.push(AuthSubgraph);
8073
9790
  logger.info("Auth subgraph registered (document permissions enabled)");
8074
9791
  }
8075
- const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
8076
- extended: subgraphs,
8077
- core: coreSubgraphs
8078
- }, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes);
9792
+ const graphqlManager = await setupGraphQLManager({
9793
+ httpAdapter,
9794
+ authFetchMiddleware,
9795
+ requireAuthFetchMiddleware,
9796
+ httpServer,
9797
+ wsServer,
9798
+ client: reactorClient,
9799
+ relationalDb,
9800
+ analyticsStore,
9801
+ syncManager,
9802
+ subgraphs: {
9803
+ extended: subgraphs,
9804
+ core: coreSubgraphs
9805
+ },
9806
+ logger: logger.child(["graphql-manager"]),
9807
+ authorizationService,
9808
+ authService,
9809
+ documentPermissionService,
9810
+ enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,
9811
+ port,
9812
+ reactorDriveClient,
9813
+ syncServingGate,
9814
+ httpRoutes,
9815
+ attachments: attachmentClientProvider
9816
+ });
8079
9817
  setupEventListeners(packages, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry);
8080
9818
  if (mcpServerEnabled) {
8081
9819
  await setupMcpServer({
@@ -8101,7 +9839,11 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
8101
9839
  attachments,
8102
9840
  attachmentReferenceIndex,
8103
9841
  attachmentAccess,
9842
+ attachmentClientProvider,
8104
9843
  authService,
9844
+ requireAuthenticatedCaller: floor,
9845
+ authorizationService,
9846
+ relationalDb,
8105
9847
  dispose
8106
9848
  };
8107
9849
  }
@@ -8148,7 +9890,7 @@ function buildApiDispose(args) {
8148
9890
  };
8149
9891
  }
8150
9892
  async function initializeAndStartAPI(clientInitializer, options, processorApp) {
8151
- const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
9893
+ const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness, attachmentReadsFollowDocumentPolicy } = await _setupCommonInfrastructure(options);
8152
9894
  const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
8153
9895
  const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
8154
9896
  status: "unavailable",
@@ -8165,13 +9907,14 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
8165
9907
  const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
8166
9908
  if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
8167
9909
  return {
8168
- ...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger), httpRoutes),
9910
+ ...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger), httpRoutes, attachmentReadsFollowDocumentPolicy),
8169
9911
  client: reactorClient,
8170
9912
  syncManager,
8171
9913
  documentModelRegistry,
8172
9914
  readiness,
8173
9915
  attachmentReferenceProjection,
8174
- packageManager: packages
9916
+ packageManager: packages,
9917
+ documentPermissionService
8175
9918
  };
8176
9919
  }
8177
9920
  /**
@@ -8195,6 +9938,55 @@ function withScheme(origin) {
8195
9938
  return /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
8196
9939
  }
8197
9940
  //#endregion
9941
+ //#region src/services/privacy-permissions.ts
9942
+ /**
9943
+ * reactor-privacy's permission lookup and eraser, over this host's
9944
+ * DocumentPermissionService. Typed structurally on the privacy side.
9945
+ */
9946
+ var PrivacyPermissionAdapter = class {
9947
+ service;
9948
+ constructor(service) {
9949
+ this.service = service;
9950
+ }
9951
+ async rowsForAddress(address) {
9952
+ const lower = address.toLowerCase();
9953
+ const found = await this.service.rowsForAddress(address);
9954
+ const rows = [];
9955
+ for (const row of found.permissions) for (const column of ["userAddress", "grantedBy"]) {
9956
+ if (row[column] !== lower) continue;
9957
+ rows.push({
9958
+ table: "DocumentPermission",
9959
+ column,
9960
+ documentId: row.documentId,
9961
+ detail: { permission: row.permission }
9962
+ });
9963
+ }
9964
+ for (const row of found.operationPermissions) for (const column of ["userAddress", "grantedBy"]) {
9965
+ if (row[column] !== lower) continue;
9966
+ rows.push({
9967
+ table: "OperationUserPermission",
9968
+ column,
9969
+ documentId: row.documentId,
9970
+ detail: { operationType: row.operationType }
9971
+ });
9972
+ }
9973
+ for (const documentId of found.ownedDocumentIds) rows.push({
9974
+ table: "DocumentProtection",
9975
+ column: "ownerAddress",
9976
+ documentId
9977
+ });
9978
+ return rows;
9979
+ }
9980
+ async erasePermissions(documentId) {
9981
+ const permissions = await this.service.deleteAllDocumentPermissions(documentId);
9982
+ const protection = await this.service.deleteDocumentProtection(documentId);
9983
+ return {
9984
+ ...permissions,
9985
+ DocumentProtection: protection
9986
+ };
9987
+ }
9988
+ };
9989
+ //#endregion
8198
9990
  //#region src/services/package-storage.ts
8199
9991
  var InMemoryPackageStorage = class {
8200
9992
  packages = /* @__PURE__ */ new Map();
@@ -8298,7 +10090,7 @@ var PackageManagementService = class {
8298
10090
  }
8299
10091
  };
8300
10092
  //#endregion
8301
- export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, createWsAuthHandlers, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
10093
+ export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessFailed, AttachmentAccessService, AttachmentAccessUnavailable, AttachmentClientProvider, AuthDecision, AuthDecisionSchema, AuthEvaluationUnsupportedError, AuthService, AuthSubgraph, AuthenticationRequiredError, AuthorizationPolicy, AuthorizedAttachmentService, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, DocumentRelationshipFieldsFragmentDoc, EvaluateActionsDocument, FindDocumentsDocument, ForbiddenError, GetDocumentDocument, GetDocumentIncomingRelationshipEdgesDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipEdgesDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MalformedStoredOperationError, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PrivacyPermissionAdapter, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RefusalNotRecordedError, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SyncRefusalInputSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, UpdateRelationshipDocument, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAttachmentPolicyReadsAllowed, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, callerSubject, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, createWsAuthHandlers, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, piecesBaseUrl, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
8302
10094
 
8303
10095
  //# sourceMappingURL=index.mjs.map
8304
- //# debugId=a15aa3df-1a6d-517d-93c4-3e8a84cefd09
10096
+ //# debugId=65e47e18-15f4-5a67-b94b-f6e795c1eb9b