@powerhousedao/reactor-api 6.2.2-dev.9 → 6.2.2

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 (28) hide show
  1. package/dist/{adapter-gateway-apollo-CxV_hMTv.mjs → adapter-gateway-apollo-DdamNLPT.mjs} +10 -4
  2. package/dist/adapter-gateway-apollo-DdamNLPT.mjs.map +1 -0
  3. package/dist/adapter-http-express-CgVPgEMm.mjs +225 -0
  4. package/dist/adapter-http-express-CgVPgEMm.mjs.map +1 -0
  5. package/dist/{adapter-http-fastify-B248RJtm.mjs → adapter-http-fastify-D98iRljY.mjs} +38 -26
  6. package/dist/adapter-http-fastify-D98iRljY.mjs.map +1 -0
  7. package/dist/index.d.mts +584 -50
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +1258 -174
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/path-normalize-DWWFLiy6.mjs +23 -0
  12. package/dist/path-normalize-DWWFLiy6.mjs.map +1 -0
  13. package/dist/src/packages/https-hooks.mjs +1 -1
  14. package/dist/src/packages/vite-loader.d.mts +3 -2
  15. package/dist/src/packages/vite-loader.d.mts.map +1 -1
  16. package/dist/src/packages/vite-loader.mjs +33 -8
  17. package/dist/src/packages/vite-loader.mjs.map +1 -1
  18. package/dist/{types-DnHgkahK.d.mts → types-Bz2niABC.d.mts} +13 -2
  19. package/dist/types-Bz2niABC.d.mts.map +1 -0
  20. package/dist/{utils-iibOQ50e.mjs → utils-lHoPB-ft.mjs} +130 -23
  21. package/dist/utils-lHoPB-ft.mjs.map +1 -0
  22. package/package.json +22 -19
  23. package/dist/adapter-gateway-apollo-CxV_hMTv.mjs.map +0 -1
  24. package/dist/adapter-http-express-D5OpUIbV.mjs +0 -137
  25. package/dist/adapter-http-express-D5OpUIbV.mjs.map +0 -1
  26. package/dist/adapter-http-fastify-B248RJtm.mjs.map +0 -1
  27. package/dist/types-DnHgkahK.d.mts.map +0 -1
  28. package/dist/utils-iibOQ50e.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,32 +1,34 @@
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]="e58cea2c-2319-52a8-90cd-3e99d94ba98f")}catch(e){}}();
3
- import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, f as AuthorizationPolicy, i as buildGraphqlOperations, l as loadProcessors, m as createAuthorizationService, n as buildGraphQlDriveDocument, o as debounce, p as AuthorizedDocumentHandle, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-iibOQ50e.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]="f43e08dc-7398-57dd-8824-db5d97ccc6ca")}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-lHoPB-ft.mjs";
4
4
  import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
5
5
  import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
6
6
  import { gql } from "graphql-tag";
7
7
  import { GraphQLError, Kind, parse, print } from "graphql";
8
- import { DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
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
9
  import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
10
10
  import path from "node:path";
11
11
  import { match } from "path-to-regexp";
12
12
  import { buildSubgraphSchema } from "@apollo/subgraph";
13
13
  import { typeDefs } from "@powerhousedao/document-engineering/graphql";
14
14
  import { camelCase, kebabCase, pascalCase } from "change-case";
15
- import { GraphQLJSONObject } from "graphql-type-json";
16
- import { setName } from "@powerhousedao/shared/document-model";
15
+ import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
16
+ import { deserializeSignature, serializeSignature, setName } from "@powerhousedao/shared/document-model";
17
17
  import * as z$1 from "zod";
18
18
  import { z } from "zod";
19
19
  import { createHandler } from "graphql-sse/lib/use/fetch";
20
+ import lzString from "lz-string";
20
21
  import { PubSub, withFilter } from "graphql-subscriptions";
21
22
  import dotenv from "dotenv";
22
23
  import { buildTreeUrl } from "@powerhousedao/shared";
23
24
  import { getConfig } from "@powerhousedao/config/node";
24
25
  import { reactorDriveDocumentModelModule } from "@powerhousedao/reactor-drive";
26
+ import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
25
27
  import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
26
28
  import EventEmitter from "node:events";
27
29
  import fs, { watchFile } from "node:fs";
28
30
  import { PostgresAnalyticsStore } from "@powerhousedao/analytics-engine-pg";
29
- import { AttachmentBuilder } from "@powerhousedao/reactor-attachments";
31
+ import { AttachmentBuilder, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef } from "@powerhousedao/reactor-attachments";
30
32
  import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
31
33
  import { setupMcpServer } from "@powerhousedao/reactor-mcp";
32
34
  import { mkdir } from "node:fs/promises";
@@ -34,7 +36,7 @@ import { tmpdir } from "node:os";
34
36
  import { WebSocketServer } from "ws";
35
37
  import { createRelationalDb } from "@powerhousedao/shared/processors";
36
38
  import { Kysely, Migrator, sql } from "kysely";
37
- import { fetchDelegationCredential, verifyAuthBearerToken } from "@renown/sdk";
39
+ import { fetchDelegationCredential, resolveSwitchboardEndpoint, verifyAuthBearerToken } from "@renown/sdk";
38
40
  import { PGlite } from "@electric-sql/pglite";
39
41
  import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
40
42
  import knex from "knex";
@@ -305,7 +307,7 @@ function createAuthFetchMiddleware(authService) {
305
307
  async function createGatewayAdapter(type, logger) {
306
308
  switch (type) {
307
309
  case "apollo": {
308
- const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-CxV_hMTv.mjs");
310
+ const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-DdamNLPT.mjs");
309
311
  return new ApolloGatewayAdapter(logger);
310
312
  }
311
313
  case "mercurius": {
@@ -317,11 +319,11 @@ async function createGatewayAdapter(type, logger) {
317
319
  async function createHttpAdapter(type) {
318
320
  switch (type) {
319
321
  case "express": {
320
- const { createExpressHttpAdapter } = await import("./adapter-http-express-D5OpUIbV.mjs");
322
+ const { createExpressHttpAdapter } = await import("./adapter-http-express-CgVPgEMm.mjs");
321
323
  return createExpressHttpAdapter();
322
324
  }
323
325
  case "fastify": {
324
- const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-B248RJtm.mjs");
326
+ const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-D98iRljY.mjs");
325
327
  return createFastifyHttpAdapter();
326
328
  }
327
329
  }
@@ -396,7 +398,8 @@ const dedupeTypeDefinitions = (doc) => {
396
398
  const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
397
399
  const newResolvers = {
398
400
  ...resolvers,
399
- JSONObject: GraphQLJSONObject
401
+ JSONObject: GraphQLJSONObject,
402
+ Unknown: GraphQLJSON
400
403
  };
401
404
  return {
402
405
  typeDefs: getDocumentModelTypeDefs(documentModels, typeDefs),
@@ -404,7 +407,7 @@ const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
404
407
  };
405
408
  };
406
409
  const createSchema = (documentModels, resolvers, typeDefs) => {
407
- return buildSubgraphSchema(buildSubgraphSchemaModule(documentModels, resolvers, typeDefs));
410
+ return buildSubgraphSchema([buildSubgraphSchemaModule(documentModels, resolvers, typeDefs)]);
408
411
  };
409
412
  /**
410
413
  * Create a merged GraphQL schema from multiple subgraph modules.
@@ -460,6 +463,10 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
460
463
  });
461
464
  return dedupeTypeDefinitions(gql`
462
465
  scalar JSONObject
466
+ scalar AttachmentRef
467
+ # Codegen scalars not in the document-engineering set.
468
+ scalar Unknown
469
+ scalar Address
463
470
  ${typeDefs.join("\n").replaceAll(";", "")}
464
471
 
465
472
  type PHOperationContext {
@@ -878,6 +885,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
878
885
  return gql`
879
886
  scalar DateTime
880
887
  scalar JSONObject
888
+ scalar AttachmentRef
881
889
 
882
890
  ${revisionType}
883
891
 
@@ -926,6 +934,11 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
926
934
  }
927
935
  //#endregion
928
936
  //#region src/graphql/reactor/gen/graphql.ts
937
+ let AuthDecision = /* @__PURE__ */ function(AuthDecision) {
938
+ AuthDecision["Allow"] = "ALLOW";
939
+ AuthDecision["Deny"] = "DENY";
940
+ return AuthDecision;
941
+ }({});
929
942
  let DocumentChangeType = /* @__PURE__ */ function(DocumentChangeType) {
930
943
  DocumentChangeType["ChildAdded"] = "CHILD_ADDED";
931
944
  DocumentChangeType["ChildRemoved"] = "CHILD_REMOVED";
@@ -948,11 +961,24 @@ let SyncEnvelopeType = /* @__PURE__ */ function(SyncEnvelopeType) {
948
961
  }({});
949
962
  const isDefinedNonNullAny = (v) => v !== void 0 && v !== null;
950
963
  const definedNonNullAnySchema = z$1.any().refine((v) => isDefinedNonNullAny(v));
964
+ const AuthDecisionSchema = z$1.enum(AuthDecision);
951
965
  const DocumentChangeTypeSchema = z$1.enum(DocumentChangeType);
952
966
  const PropagationModeSchema = z$1.enum(PropagationMode);
953
967
  const SyncEnvelopeTypeSchema = z$1.enum(SyncEnvelopeType);
968
+ function ActionCandidateInputSchema() {
969
+ return z$1.object({
970
+ input: z$1.custom((v) => v != null).nullish(),
971
+ scope: z$1.string(),
972
+ type: z$1.string()
973
+ });
974
+ }
954
975
  function ActionContextInputSchema() {
955
- return z$1.object({ signer: z$1.lazy(() => ReactorSignerInputSchema().nullish()) });
976
+ return z$1.object({
977
+ nonce: z$1.string().nullish(),
978
+ prevOpHash: z$1.string().nullish(),
979
+ prevOpIndex: z$1.number().nullish(),
980
+ signer: z$1.lazy(() => ReactorSignerInputSchema().nullish())
981
+ });
956
982
  }
957
983
  function ActionInputSchema() {
958
984
  return z$1.object({
@@ -989,6 +1015,7 @@ function OperationContextInputSchema() {
989
1015
  function OperationInputSchema() {
990
1016
  return z$1.object({
991
1017
  action: z$1.lazy(() => ActionInputSchema()),
1018
+ deniedReason: z$1.string().nullish(),
992
1019
  error: z$1.string().nullish(),
993
1020
  hash: z$1.string(),
994
1021
  id: z$1.string().nullish(),
@@ -1147,6 +1174,7 @@ const GetDocumentWithOperationsDocument = gql`
1147
1174
  hash
1148
1175
  skip
1149
1176
  error
1177
+ deniedReason
1150
1178
  id
1151
1179
  action {
1152
1180
  id
@@ -1259,6 +1287,7 @@ const GetDocumentOperationsDocument = gql`
1259
1287
  hash
1260
1288
  skip
1261
1289
  error
1290
+ deniedReason
1262
1291
  id
1263
1292
  action {
1264
1293
  id
@@ -1301,6 +1330,28 @@ const GetJobStatusDocument = gql`
1301
1330
  }
1302
1331
  }
1303
1332
  `;
1333
+ const EvaluateActionsDocument = gql`
1334
+ query EvaluateActions(
1335
+ $documentIdentifier: String!
1336
+ $branch: String
1337
+ $candidates: [ActionCandidateInput!]!
1338
+ ) {
1339
+ evaluateActions(
1340
+ documentIdentifier: $documentIdentifier
1341
+ branch: $branch
1342
+ candidates: $candidates
1343
+ ) {
1344
+ evaluations {
1345
+ decision
1346
+ reason
1347
+ }
1348
+ allAllowed
1349
+ anyAllowed
1350
+ allDenied
1351
+ anyDenied
1352
+ }
1353
+ }
1354
+ `;
1304
1355
  const CreateDocumentDocument = gql`
1305
1356
  mutation CreateDocument($document: JSONObject!, $parentIdentifier: String) {
1306
1357
  createDocument(document: $document, parentIdentifier: $parentIdentifier) {
@@ -1326,13 +1377,13 @@ const CreateEmptyDocumentDocument = gql`
1326
1377
  const MutateDocumentDocument = gql`
1327
1378
  mutation MutateDocument(
1328
1379
  $documentIdentifier: String!
1329
- $actions: [JSONObject!]!
1330
- $view: ViewFilterInput
1380
+ $actions: [ActionInput!]!
1381
+ $branch: String
1331
1382
  ) {
1332
- mutateDocument(
1383
+ mutateDocument: execute(
1333
1384
  documentIdentifier: $documentIdentifier
1334
1385
  actions: $actions
1335
- view: $view
1386
+ branch: $branch
1336
1387
  ) {
1337
1388
  ...PHDocumentFields
1338
1389
  }
@@ -1342,14 +1393,21 @@ const MutateDocumentDocument = gql`
1342
1393
  const MutateDocumentAsyncDocument = gql`
1343
1394
  mutation MutateDocumentAsync(
1344
1395
  $documentIdentifier: String!
1345
- $actions: [JSONObject!]!
1346
- $view: ViewFilterInput
1396
+ $actions: [ActionInput!]!
1397
+ $branch: String
1347
1398
  ) {
1348
- mutateDocumentAsync(
1399
+ mutateDocumentAsync: executeAsync(
1349
1400
  documentIdentifier: $documentIdentifier
1350
1401
  actions: $actions
1351
- view: $view
1352
- )
1402
+ branch: $branch
1403
+ ) {
1404
+ id
1405
+ status
1406
+ result
1407
+ error
1408
+ createdAt
1409
+ completedAt
1410
+ }
1353
1411
  }
1354
1412
  `;
1355
1413
  const RenameDocumentDocument = gql`
@@ -1509,6 +1567,7 @@ const PollSyncEnvelopesDocument = gql`
1509
1567
  hash
1510
1568
  skip
1511
1569
  error
1570
+ deniedReason
1512
1571
  id
1513
1572
  action {
1514
1573
  id
@@ -1551,6 +1610,7 @@ const PollSyncEnvelopesDocument = gql`
1551
1610
  deadLetters {
1552
1611
  documentId
1553
1612
  error
1613
+ errorType
1554
1614
  }
1555
1615
  hasMore
1556
1616
  }
@@ -1595,6 +1655,9 @@ function getSdk(requester) {
1595
1655
  GetJobStatus(variables, options) {
1596
1656
  return requester(GetJobStatusDocument, variables, options);
1597
1657
  },
1658
+ EvaluateActions(variables, options) {
1659
+ return requester(EvaluateActionsDocument, variables, options);
1660
+ },
1598
1661
  CreateDocument(variables, options) {
1599
1662
  return requester(CreateDocumentDocument, variables, options);
1600
1663
  },
@@ -1745,28 +1808,132 @@ function toReactorPropagationMode(gqlMode) {
1745
1808
  }
1746
1809
  }
1747
1810
  /**
1811
+ * Maps one predicted verdict to its GraphQL shape. A refusal's reason is the
1812
+ * consensus string the reactor would record for it, carried through verbatim so
1813
+ * a client can tell a deletion refusal from a missing grant; an allow has none.
1814
+ */
1815
+ function toGqlActionEvaluation(evaluation) {
1816
+ return evaluation.decision === "allow" ? {
1817
+ decision: AuthDecision.Allow,
1818
+ reason: null
1819
+ } : {
1820
+ decision: AuthDecision.Deny,
1821
+ reason: evaluation.reason
1822
+ };
1823
+ }
1824
+ /**
1748
1825
  * Converts readonly arrays to mutable arrays for ReactorClient
1749
1826
  */
1750
1827
  function toMutableArray(arr) {
1751
1828
  return arr ? [...arr] : void 0;
1752
1829
  }
1753
1830
  /**
1754
- * Validates that a JSONObject represents a valid Action structure
1831
+ * Validates that an incoming value has the structure of an Action.
1832
+ *
1833
+ * This is the whole guard on the way in. `mutateDocument` takes untyped JSON, so
1834
+ * nothing has checked these fields before a resolver runs, and the id in
1835
+ * particular has to be checked here: it is hashed into the operation id and
1836
+ * replay dedupes by it, so an action without one is stored under an operation id
1837
+ * shared by every id-less operation on the same document, scope and branch.
1838
+ *
1839
+ * The storage constraint would refuse such a row anyway, but only after a job
1840
+ * has been enqueued and failed. Refusing here answers the caller instead.
1755
1841
  */
1756
1842
  function validateActionStructure(obj) {
1757
1843
  if (!obj || typeof obj !== "object") return false;
1758
1844
  const action = obj;
1845
+ if (typeof action.id !== "string" || !action.id) return false;
1759
1846
  if (typeof action.type !== "string" || !action.type) return false;
1760
1847
  if (typeof action.scope !== "string" || !action.scope) return false;
1848
+ if (typeof action.timestampUtcMs !== "string" || !action.timestampUtcMs) return false;
1761
1849
  if (!("input" in action)) return false;
1762
1850
  return true;
1763
1851
  }
1764
1852
  /**
1853
+ * Restores an action's signatures to the tuples verification reads.
1854
+ *
1855
+ * A signature is a five-param tuple, and GraphQL declares `signatures` as a
1856
+ * list of strings, so a client joins each one for transport. The sync path
1857
+ * already splits them on arrival; this is the same step for the mutation path,
1858
+ * which had none - an action submitted there kept whatever shape it arrived in,
1859
+ * so the same signature was persisted as a tuple or as one joined string
1860
+ * depending on which door it came through.
1861
+ *
1862
+ * A tuple passes through untouched, so an in-process caller handing over real
1863
+ * signatures is unaffected.
1864
+ */
1865
+ function withDeserializedSignatures(action) {
1866
+ const signer = action.context?.signer;
1867
+ if (!signer?.signatures || signer.signatures.length === 0) return action;
1868
+ return {
1869
+ ...action,
1870
+ context: {
1871
+ ...action.context,
1872
+ signer: {
1873
+ ...signer,
1874
+ signatures: signer.signatures.map(deserializeSignature)
1875
+ }
1876
+ }
1877
+ };
1878
+ }
1879
+ /**
1880
+ * Converts actions the schema has already coerced into the actions the reactor
1881
+ * applies.
1882
+ *
1883
+ * The one conversion the schema cannot express is the signature: GraphQL
1884
+ * declares `signatures` as a list of strings, not of lists, so each arrives
1885
+ * joined and has to be read back as the tuple verification indexes into.
1886
+ *
1887
+ * Nothing is validated here. `ActionInput` states every field the reactor needs,
1888
+ * so anything that reached this point has them - unlike the untyped path, which
1889
+ * has to check by hand.
1890
+ */
1891
+ function toSubmittableActions(actions) {
1892
+ return actions.map((action) => {
1893
+ const context = toSubmittableContext(action.context);
1894
+ const submittable = {
1895
+ id: action.id,
1896
+ type: action.type,
1897
+ timestampUtcMs: action.timestampUtcMs,
1898
+ input: action.input,
1899
+ scope: action.scope
1900
+ };
1901
+ return context ? {
1902
+ ...submittable,
1903
+ context
1904
+ } : submittable;
1905
+ });
1906
+ }
1907
+ /**
1908
+ * `ReactorSignerInput` leaves `user` and `app` optional where `ActionSigner`
1909
+ * requires them, so an incomplete signer cannot be represented. It is carried
1910
+ * through as it arrived rather than filled in: the signature verifier is what
1911
+ * should refuse it, and inventing either field here would be inventing the part
1912
+ * that says who signed.
1913
+ */
1914
+ function toSubmittableContext(context) {
1915
+ if (!context) return;
1916
+ const submittable = {};
1917
+ const prevOpIndex = fromInputMaybe(context.prevOpIndex);
1918
+ if (prevOpIndex !== void 0) submittable.prevOpIndex = prevOpIndex;
1919
+ const prevOpHash = fromInputMaybe(context.prevOpHash);
1920
+ if (prevOpHash !== void 0) submittable.prevOpHash = prevOpHash;
1921
+ const nonce = fromInputMaybe(context.nonce);
1922
+ if (nonce !== void 0) submittable.nonce = nonce;
1923
+ const signer = fromInputMaybe(context.signer);
1924
+ if (signer) submittable.signer = {
1925
+ user: fromInputMaybe(signer.user),
1926
+ app: fromInputMaybe(signer.app),
1927
+ signatures: signer.signatures.map(deserializeSignature)
1928
+ };
1929
+ return Object.keys(submittable).length > 0 ? submittable : void 0;
1930
+ }
1931
+ /**
1765
1932
  * Converts a JSONObject to an Action, validating basic structure
1766
1933
  */
1767
1934
  function jsonObjectToAction(obj) {
1768
- if (!validateActionStructure(obj)) throw new GraphQLError("Invalid action structure. Actions must have: type (string), scope (string), and input (any)");
1769
- return obj;
1935
+ if (!validateActionStructure(obj)) throw new GraphQLError("Invalid action structure. Actions must have: id (string), type (string), scope (string), timestampUtcMs (string), and input (any)");
1936
+ return withDeserializedSignatures(obj);
1770
1937
  }
1771
1938
  /**
1772
1939
  * Validates a list of actions by converting them from JSON and checking structure
@@ -1780,10 +1947,57 @@ function validateActions(actions) {
1780
1947
  }
1781
1948
  return convertedActions;
1782
1949
  }
1950
+ /** The operation fields the schema declares non-null. */
1951
+ const REQUIRED_OPERATION_FIELDS = [
1952
+ "index",
1953
+ "timestampUtcMs",
1954
+ "hash",
1955
+ "skip"
1956
+ ];
1957
+ /** The action fields the schema declares non-null. */
1958
+ const REQUIRED_ACTION_FIELDS = [
1959
+ "id",
1960
+ "type",
1961
+ "timestampUtcMs",
1962
+ "input",
1963
+ "scope"
1964
+ ];
1965
+ /**
1966
+ * Names an operation well enough to find the row it came from, without assuming
1967
+ * the fields that might be the missing ones.
1968
+ */
1969
+ function describeOperation(stored) {
1970
+ const parts = [typeof stored.id === "string" && stored.id ? `operation ${stored.id}` : "operation with no id"];
1971
+ if (typeof stored.index === "number") parts.push(`index ${stored.index}`);
1972
+ const scope = stored.action?.scope;
1973
+ if (typeof scope === "string" && scope) parts.push(`scope ${scope}`);
1974
+ return parts.join(", ");
1975
+ }
1976
+ /**
1977
+ * Refuses an operation the schema cannot represent, naming the field.
1978
+ *
1979
+ * The alternative is what execution does unaided: nullify the field, bubble that
1980
+ * up the non-null chain until it takes out the envelope's whole operation list,
1981
+ * and report a bare "Cannot return null for non-nullable field" with no way to
1982
+ * tell which of a hundred operations was at fault. Checked before the response
1983
+ * is built so the failure is typed and attributable.
1984
+ *
1985
+ * Presence is all that is checked. The schema's own serialization still rejects
1986
+ * a value of the wrong type, and inventing further rules here would put a second
1987
+ * contract beside the one the schema already states.
1988
+ */
1989
+ function assertServable(operation) {
1990
+ const stored = operation;
1991
+ for (const field of REQUIRED_OPERATION_FIELDS) if (stored[field] === void 0 || stored[field] === null) throw new MalformedStoredOperationError(`${describeOperation(stored)} has no ${field}`);
1992
+ const action = stored.action;
1993
+ if (action === void 0 || action === null) throw new MalformedStoredOperationError(`${describeOperation(stored)} has no action`);
1994
+ for (const field of REQUIRED_ACTION_FIELDS) if (action[field] === void 0 || action[field] === null) throw new MalformedStoredOperationError(`${describeOperation(stored)} has an action with no ${field}`);
1995
+ }
1783
1996
  /**
1784
1997
  * Transforms an operation to serialize signatures from tuples to strings for GraphQL compatibility.
1785
1998
  */
1786
1999
  function serializeOperationForGraphQL(operation) {
2000
+ assertServable(operation);
1787
2001
  const signer = operation.action.context?.signer;
1788
2002
  if (!signer?.signatures) return operation;
1789
2003
  return {
@@ -1794,7 +2008,7 @@ function serializeOperationForGraphQL(operation) {
1794
2008
  ...operation.action.context,
1795
2009
  signer: {
1796
2010
  ...signer,
1797
- signatures: signer.signatures.map((sig) => Array.isArray(sig) ? sig.join(", ") : sig)
2011
+ signatures: signer.signatures.map(serializeSignature)
1798
2012
  }
1799
2013
  }
1800
2014
  }
@@ -1904,9 +2118,10 @@ async function documentModels(reactorClient, args) {
1904
2118
  throw new GraphQLError(`Failed to convert document models to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
1905
2119
  }
1906
2120
  }
1907
- async function document(reactorClient, args) {
1908
- let view;
2121
+ async function document(reactorClient, args, subject) {
2122
+ let view = subject ? { subject } : void 0;
1909
2123
  if (args.view) view = {
2124
+ subject,
1910
2125
  branch: fromInputMaybe(args.view.branch),
1911
2126
  scopes: toMutableArray(fromInputMaybe(args.view.scopes))
1912
2127
  };
@@ -1931,9 +2146,10 @@ async function document(reactorClient, args) {
1931
2146
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
1932
2147
  }
1933
2148
  }
1934
- async function documentOutgoingRelationships(reactorClient, args) {
1935
- let view;
2149
+ async function documentOutgoingRelationships(reactorClient, args, subject) {
2150
+ let view = subject ? { subject } : void 0;
1936
2151
  if (args.view) view = {
2152
+ subject,
1937
2153
  branch: fromInputMaybe(args.view.branch),
1938
2154
  scopes: toMutableArray(fromInputMaybe(args.view.scopes))
1939
2155
  };
@@ -1958,9 +2174,10 @@ async function documentOutgoingRelationships(reactorClient, args) {
1958
2174
  throw new GraphQLError(`Failed to convert outgoing relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
1959
2175
  }
1960
2176
  }
1961
- async function documentIncomingRelationships(reactorClient, args) {
1962
- let view;
2177
+ async function documentIncomingRelationships(reactorClient, args, subject) {
2178
+ let view = subject ? { subject } : void 0;
1963
2179
  if (args.view) view = {
2180
+ subject,
1964
2181
  branch: fromInputMaybe(args.view.branch),
1965
2182
  scopes: toMutableArray(fromInputMaybe(args.view.scopes))
1966
2183
  };
@@ -1985,9 +2202,10 @@ async function documentIncomingRelationships(reactorClient, args) {
1985
2202
  throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
1986
2203
  }
1987
2204
  }
1988
- async function findDocuments(reactorClient, args) {
1989
- let view;
2205
+ async function findDocuments(reactorClient, args, subject) {
2206
+ let view = subject ? { subject } : void 0;
1990
2207
  if (args.view) view = {
2208
+ subject,
1991
2209
  branch: fromInputMaybe(args.view.branch),
1992
2210
  scopes: toMutableArray(fromInputMaybe(args.view.scopes))
1993
2211
  };
@@ -2029,11 +2247,12 @@ async function jobStatus(reactorClient, args) {
2029
2247
  throw new GraphQLError(`Failed to convert job status to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2030
2248
  }
2031
2249
  }
2032
- async function documentOperations(reactorClient, args) {
2033
- let view;
2250
+ async function documentOperations(reactorClient, args, subject) {
2251
+ let view = subject ? { subject } : void 0;
2034
2252
  const branch = fromInputMaybe(args.filter.branch);
2035
2253
  const scopes = toMutableArray(fromInputMaybe(args.filter.scopes));
2036
2254
  if (branch || scopes) view = {
2255
+ subject,
2037
2256
  branch,
2038
2257
  scopes
2039
2258
  };
@@ -2069,6 +2288,41 @@ async function documentOperations(reactorClient, args) {
2069
2288
  throw new GraphQLError(`Failed to convert operations to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2070
2289
  }
2071
2290
  }
2291
+ /**
2292
+ * Predicts the admission verdict for each candidate operation, deciding against
2293
+ * the document policy through the reactor's own decision model.
2294
+ *
2295
+ * The subject is supplied by the subgraph from the authenticated request, never
2296
+ * by the caller: answering for an arbitrary subject would disclose what a policy
2297
+ * grants somebody else. It carries the caller's address and the app key their
2298
+ * token authenticated with, which is the same pair a signed action presents, so
2299
+ * a grant naming either matches here exactly as it would at admission.
2300
+ *
2301
+ * A reactor without authEnforcement raises the named error, which becomes an
2302
+ * "unsupported" code rather than a denial.
2303
+ */
2304
+ async function evaluateActions(reactorClient, args, subject) {
2305
+ const branch = fromInputMaybe(args.branch) ?? "main";
2306
+ const candidates = args.candidates.map((candidate) => ({
2307
+ scope: candidate.scope,
2308
+ type: candidate.type,
2309
+ input: fromInputMaybe(candidate.input)
2310
+ }));
2311
+ let result;
2312
+ try {
2313
+ result = await reactorClient.evaluateActions(args.documentIdentifier, branch, candidates, subject);
2314
+ } catch (error) {
2315
+ if (AuthEnforcementDisabledError.isError(error)) throw new AuthEvaluationUnsupportedError();
2316
+ throw new GraphQLError(`Failed to evaluate actions: ${error instanceof Error ? error.message : "Unknown error"}`);
2317
+ }
2318
+ return {
2319
+ evaluations: result.evaluations.map(toGqlActionEvaluation),
2320
+ allAllowed: result.allAllowed,
2321
+ anyAllowed: result.anyAllowed,
2322
+ allDenied: result.allDenied,
2323
+ anyDenied: result.anyDenied
2324
+ };
2325
+ }
2072
2326
  async function createDocument(reactorClient, args, reactorDriveClient) {
2073
2327
  if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
2074
2328
  const document = args.document;
@@ -2167,6 +2421,49 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
2167
2421
  throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2168
2422
  }
2169
2423
  }
2424
+ /** The branch a mutation applies to when the caller names none. */
2425
+ const DEFAULT_BRANCH = "main";
2426
+ /**
2427
+ * Applies actions to a document and waits for the result.
2428
+ *
2429
+ * The wire form of `IReactorClient.execute`, and named for it. Takes the actions
2430
+ * already coerced against `ActionInput`, so the only conversion left is the one
2431
+ * the schema cannot express: a signature travels as one string, and verification
2432
+ * reads it as the tuple it was.
2433
+ */
2434
+ async function execute(reactorClient, args) {
2435
+ const actions = toSubmittableActions(args.actions);
2436
+ const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
2437
+ let result;
2438
+ try {
2439
+ result = await reactorClient.execute(args.documentIdentifier, branch, actions);
2440
+ } catch (error) {
2441
+ throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
2442
+ }
2443
+ try {
2444
+ return toGqlPhDocument(result);
2445
+ } catch (error) {
2446
+ throw new GraphQLError(`Failed to convert executed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
2447
+ }
2448
+ }
2449
+ /**
2450
+ * Submits actions to a document and returns the job that will apply them.
2451
+ *
2452
+ * Returns the job rather than only its id, matching
2453
+ * `IReactorClient.executeAsync`. A job handed back from a submission has no
2454
+ * result yet, which is why `JobInfo.result` is nullable.
2455
+ */
2456
+ async function executeAsync(reactorClient, args) {
2457
+ const actions = toSubmittableActions(args.actions);
2458
+ const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
2459
+ let job;
2460
+ try {
2461
+ job = await reactorClient.executeAsync(args.documentIdentifier, branch, actions);
2462
+ } catch (error) {
2463
+ throw new GraphQLError(`Failed to submit actions: ${error instanceof Error ? error.message : "Unknown error"}`);
2464
+ }
2465
+ return toGqlJobInfo(job);
2466
+ }
2170
2467
  async function mutateDocument(reactorClient, args) {
2171
2468
  let validatedActions;
2172
2469
  try {
@@ -2300,7 +2597,14 @@ async function deleteDocuments(reactorClient, args) {
2300
2597
  throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
2301
2598
  }
2302
2599
  }
2303
- async function touchChannel(syncManager, args) {
2600
+ /**
2601
+ * Registers a sync channel, or reports the state of one already registered.
2602
+ *
2603
+ * `boundAddress` claims a channel at creation for the subject that created it.
2604
+ * An anonymously created channel is left unbound, which is not a permission but
2605
+ * an unanswered question: the first authenticated subject to use it adopts it.
2606
+ */
2607
+ async function touchChannel(syncManager, args, boundAddress) {
2304
2608
  try {
2305
2609
  return {
2306
2610
  success: true,
@@ -2312,7 +2616,10 @@ async function touchChannel(syncManager, args) {
2312
2616
  scope: [...args.input.filter.scope],
2313
2617
  branch: args.input.filter.branch
2314
2618
  };
2315
- const options = { sinceTimestampUtcMs: args.input.sinceTimestampUtcMs };
2619
+ const options = {
2620
+ sinceTimestampUtcMs: args.input.sinceTimestampUtcMs,
2621
+ boundAddress
2622
+ };
2316
2623
  try {
2317
2624
  await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
2318
2625
  type: "polling",
@@ -2327,9 +2634,50 @@ async function touchChannel(syncManager, args) {
2327
2634
  };
2328
2635
  }
2329
2636
  /**
2637
+ * The sync operations in `syncOps` this subject may not be served.
2638
+ *
2639
+ * A sync operation is one document, one branch and (from the outbox) one scope,
2640
+ * so the answer is per entry rather than per operation: an entry is withheld
2641
+ * whole or served whole, which is what keeps a scope's operation sequence
2642
+ * unbroken for whoever does receive it. A dead letter can name several scopes,
2643
+ * and is withheld only when the policy serves none of them -- the metadata
2644
+ * scopes are readable by every holder, so in practice a dead letter naming one
2645
+ * is always served.
2646
+ *
2647
+ * The policy is read once per document and branch, and the memo lives no longer
2648
+ * than this call: a poll must not answer from a policy that changed between
2649
+ * polls, since the widening case is exactly the one holding exists to serve.
2650
+ *
2651
+ * A read that fails for any reason other than the document being absent is
2652
+ * allowed to propagate. The poll then fails, the puller retries, and nothing is
2653
+ * served in the meantime -- which is the safe direction, and a visible one.
2654
+ */
2655
+ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
2656
+ const held = /* @__PURE__ */ new Set();
2657
+ const predicates = /* @__PURE__ */ new Map();
2658
+ for (const syncOp of syncOps) {
2659
+ const key = `${syncOp.documentId}\u0000${syncOp.branch}`;
2660
+ let predicate = predicates.get(key);
2661
+ if (!predicate) {
2662
+ predicate = gate.scopePredicateById(syncOp.documentId, subject, syncOp.branch, signal);
2663
+ predicates.set(key, predicate);
2664
+ }
2665
+ const readable = await predicate;
2666
+ if (!syncOp.scopes.some((scope) => readable(scope))) held.add(syncOp.id);
2667
+ }
2668
+ return held;
2669
+ }
2670
+ /**
2330
2671
  * Polls the switchboard for new sync envelopes and acknowledges previously
2331
2672
  * received operations.
2332
2673
  *
2674
+ * `forbiddenIds` and `heldOpIds` are two different refusals. A forbidden
2675
+ * document is consumed: its delivery counters are advanced as though it had
2676
+ * been sent, so it is never reconsidered. A held sync operation is withheld:
2677
+ * nothing about it is touched, so a policy that later widens serves it whole on
2678
+ * a subsequent poll. Which one applies is the caller's decision, and the two
2679
+ * are intersected, so a document refused either way is not served.
2680
+ *
2333
2681
  * Ordinal frames of reference:
2334
2682
  * - `outboxAck` / `outboxLatest`: switchboard's ordinals (used to trim/filter
2335
2683
  * the switchboard's outbox)
@@ -2337,16 +2685,17 @@ async function touchChannel(syncManager, args) {
2337
2685
  * client ordinal the switchboard has successfully applied, so the client
2338
2686
  * can trim its own outbox)
2339
2687
  */
2340
- function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set()) {
2688
+ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
2341
2689
  let remote;
2342
2690
  try {
2343
2691
  remote = syncManager.getById(args.channelId);
2344
2692
  } catch (error) {
2345
2693
  throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2346
2694
  }
2347
- const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId)).map((syncOp) => ({
2695
+ const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !heldOpIds.has(syncOp.id)).map((syncOp) => ({
2348
2696
  documentId: syncOp.documentId,
2349
2697
  error: syncOp.error?.message ?? "Unknown error",
2698
+ errorType: syncOperationErrorType(syncOp.error),
2350
2699
  jobId: syncOp.jobId,
2351
2700
  branch: syncOp.branch,
2352
2701
  scopes: syncOp.scopes,
@@ -2392,6 +2741,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
2392
2741
  hasMore = true;
2393
2742
  break;
2394
2743
  }
2744
+ if (heldOpIds.has(syncOp.id)) continue;
2395
2745
  syncOp.deliveredCount ??= 0;
2396
2746
  syncOp.emittedCount ??= 0;
2397
2747
  while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
@@ -2863,6 +3213,12 @@ function filterLatestDocumentModelVersions(documentModels) {
2863
3213
  return Array.from(latestByName.values());
2864
3214
  }
2865
3215
  const DefaultFeatureFlags = { enableDocumentModelSubgraphs: true };
3216
+ /**
3217
+ * The registration-source label for document-model-generated subgraphs, so
3218
+ * they can be pruned like package-contributed ones (see
3219
+ * regenerateDocumentModelSubgraphs).
3220
+ */
3221
+ const DOCUMENT_MODEL_SUBGRAPH_SOURCE = "document-models";
2866
3222
  var GraphQLManager = class {
2867
3223
  initialized = false;
2868
3224
  coreSubgraphsMap = /* @__PURE__ */ new Map();
@@ -2870,11 +3226,25 @@ var GraphQLManager = class {
2870
3226
  subgraphs = /* @__PURE__ */ new Map();
2871
3227
  subgraphWsDisposers = /* @__PURE__ */ new Map();
2872
3228
  #authMiddleware;
3229
+ #requireAuthMiddleware;
2873
3230
  #driveMiddleware;
2874
3231
  driveOwnershipCache;
2875
3232
  /** Cached document models for schema generation - updated on init and regenerate */
2876
3233
  cachedDocumentModels = [];
2877
3234
  subgraphHandlerCache = /* @__PURE__ */ new Map();
3235
+ /** subgraphPath → the http adapter handle of its mounted route. */
3236
+ subgraphRouteHandles = /* @__PURE__ */ new Map();
3237
+ /** Handle of the currently mounted supergraph SSE route, if any. */
3238
+ sseRouteHandle;
3239
+ /**
3240
+ * Package name → the subgraph instances registered from it, keyed by
3241
+ * subgraph name. Tracks the package association so that removed packages
3242
+ * can be torn down (see unregisterPackage / prunePackageSubgraphs).
3243
+ */
3244
+ packageSubgraphs = /* @__PURE__ */ new Map();
3245
+ /** Per-subgraph handlers by name, before auth/drive middleware is applied,
3246
+ * for in-process queries by trusted internals. */
3247
+ #internalSubgraphHandlers = /* @__PURE__ */ new Map();
2878
3248
  /**
2879
3249
  * Optional reactor-drive client. When the switchboard is configured with a
2880
3250
  * reactor-drive container, this is provided and the resolvers dispatch to
@@ -2882,7 +3252,7 @@ var GraphQLManager = class {
2882
3252
  */
2883
3253
  reactorDriveClient;
2884
3254
  authorizationService;
2885
- constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient) {
3255
+ constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient, syncServingGate) {
2886
3256
  this.path = path;
2887
3257
  this.httpServer = httpServer;
2888
3258
  this.wsServer = wsServer;
@@ -2897,14 +3267,16 @@ var GraphQLManager = class {
2897
3267
  this.documentPermissionService = documentPermissionService;
2898
3268
  this.featureFlags = featureFlags;
2899
3269
  this.port = port;
3270
+ this.syncServingGate = syncServingGate;
2900
3271
  if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
2901
3272
  this.authorizationService = authorizationService;
2902
3273
  this.reactorDriveClient = reactorDriveClient;
2903
3274
  this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
2904
3275
  this.wsServer.setMaxListeners(0);
2905
3276
  }
2906
- async init(coreSubgraphs, authMiddleware) {
3277
+ async init(coreSubgraphs, authMiddleware, requireAuthMiddleware) {
2907
3278
  this.#authMiddleware = authMiddleware;
3279
+ this.#requireAuthMiddleware = requireAuthMiddleware;
2908
3280
  this.logger.debug(`Initializing Subgraph Manager...`);
2909
3281
  await this.driveOwnershipCache.init();
2910
3282
  this.#driveMiddleware = createDriveFetchMiddleware(this.driveOwnershipCache);
@@ -2914,7 +3286,7 @@ var GraphQLManager = class {
2914
3286
  if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
2915
3287
  await this.gatewayAdapter.start(this.httpServer);
2916
3288
  this.httpAdapter.setupMiddleware({ bodyLimit: "50mb" });
2917
- const driveRoutePath = path.join(this.path, "d/:drive");
3289
+ const driveRoutePath = path.posix.join(this.path, "d/:drive");
2918
3290
  const driveMatcher = match(driveRoutePath);
2919
3291
  this.httpAdapter.mount(driveRoutePath, async (request) => {
2920
3292
  const url = new URL(request.url);
@@ -2952,7 +3324,8 @@ var GraphQLManager = class {
2952
3324
  try {
2953
3325
  const models = (await this.reactorClient.getDocumentModelModules()).results;
2954
3326
  this.cachedDocumentModels = models;
2955
- await this.#setupDocumentModelSubgraphs("graphql", models);
3327
+ const registeredNames = await this.#setupDocumentModelSubgraphs("graphql", models);
3328
+ await this.prunePackageSubgraphs(DOCUMENT_MODEL_SUBGRAPH_SOURCE, registeredNames);
2956
3329
  await this.updateRouter();
2957
3330
  this.logger.info("Regenerated document model subgraphs with @count models", models.length);
2958
3331
  } catch (error) {
@@ -2970,6 +3343,7 @@ var GraphQLManager = class {
2970
3343
  }
2971
3344
  async #setupDocumentModelSubgraphs(supergraph, documentModels) {
2972
3345
  const latestDocumentModels = filterLatestDocumentModelVersions(documentModels);
3346
+ const registeredNames = /* @__PURE__ */ new Set();
2973
3347
  for (const documentModel of latestDocumentModels) {
2974
3348
  if (DOCUMENT_MODELS_TO_EXCLUDE.includes(documentModel.documentModel.global.id)) continue;
2975
3349
  if (!hasOperationSchemas(documentModel)) continue;
@@ -2982,31 +3356,92 @@ var GraphQLManager = class {
2982
3356
  syncManager: this.syncManager,
2983
3357
  path: this.path,
2984
3358
  documentPermissionService: this.documentPermissionService,
2985
- authorizationService: this.authorizationService
3359
+ authorizationService: this.authorizationService,
3360
+ syncServingGate: this.syncServingGate
2986
3361
  });
2987
- await this.#addSubgraphInstance(subgraphInstance, supergraph, false);
3362
+ await this.#addSubgraphInstance(subgraphInstance, supergraph, false, DOCUMENT_MODEL_SUBGRAPH_SOURCE);
3363
+ registeredNames.add(subgraphInstance.name);
2988
3364
  } catch (error) {
2989
3365
  this.logger.error(`Failed to setup document model subgraph for ${documentModel.documentModel.global.id}`, error instanceof Error ? error.message : error);
2990
3366
  this.logger.debug("@error", error);
2991
3367
  }
2992
3368
  }
3369
+ return registeredNames;
3370
+ }
3371
+ /** Core subgraph names are reserved: they register with core=true during
3372
+ * init() and no package subgraph may take one of their names. */
3373
+ #isCoreNameReserved(name) {
3374
+ for (const subgraphs of this.coreSubgraphsMap.values()) if (subgraphs.some((it) => it.name === name)) return true;
3375
+ return false;
2993
3376
  }
2994
- async #addSubgraphInstance(subgraphInstance, supergraph = "", core = false) {
3377
+ async #addSubgraphInstance(subgraphInstance, supergraph = "", core = false, packageName) {
3378
+ if (!core && this.#isCoreNameReserved(subgraphInstance.name)) {
3379
+ this.logger.error("Rejecting subgraph @name: name is reserved by a registered core subgraph", subgraphInstance.name);
3380
+ return;
3381
+ }
2995
3382
  const subgraphsMap = core ? this.coreSubgraphsMap : this.subgraphs;
2996
- const subgraphs = subgraphsMap.get(supergraph) ?? [];
2997
- const existingSubgraph = subgraphs.find((it) => it.name === subgraphInstance.name);
2998
- if (existingSubgraph) {
3383
+ const existingSubgraph = subgraphsMap.get(supergraph)?.find((it) => it.name === subgraphInstance.name);
3384
+ if (existingSubgraph === subgraphInstance) {
2999
3385
  this.logger.debug(`Skipping duplicate subgraph: ${subgraphInstance.name}`);
3000
3386
  return existingSubgraph;
3001
3387
  }
3388
+ if (existingSubgraph) await this.#removeSubgraphInstance(existingSubgraph);
3002
3389
  await subgraphInstance.onSetup?.();
3390
+ const subgraphs = subgraphsMap.get(supergraph) ?? [];
3003
3391
  subgraphs.push(subgraphInstance);
3004
3392
  subgraphsMap.set(supergraph, subgraphs);
3005
3393
  if (supergraph !== "" && supergraph !== "graphql") subgraphsMap.get("graphql")?.push(subgraphInstance);
3006
- this.logger.info(`Registered ${this.path.endsWith("/") ? this.path : this.path + "/"}${supergraph ? supergraph + "/" : ""}${subgraphInstance.name} subgraph.`);
3394
+ if (packageName !== void 0) {
3395
+ let byName = this.packageSubgraphs.get(packageName);
3396
+ if (!byName) {
3397
+ byName = /* @__PURE__ */ new Map();
3398
+ this.packageSubgraphs.set(packageName, byName);
3399
+ }
3400
+ byName.set(subgraphInstance.name, subgraphInstance);
3401
+ }
3402
+ this.logger.info(`${existingSubgraph ? "Replaced" : "Registered"} ${this.path.endsWith("/") ? this.path : this.path + "/"}${supergraph ? supergraph + "/" : ""}${subgraphInstance.name} subgraph.`);
3007
3403
  return subgraphInstance;
3008
3404
  }
3009
3405
  /**
3406
+ * Tear down a subgraph instance: disconnect it, drop it from every
3407
+ * bucket, unmount its route, dispose its WebSocket handler, and remove
3408
+ * its internal handler. Used when a subgraph is replaced or its package
3409
+ * is removed; #setupSubgraphs then never re-mounts it.
3410
+ */
3411
+ async #removeSubgraphInstance(instance) {
3412
+ try {
3413
+ await instance.onDisconnect?.();
3414
+ } catch (error) {
3415
+ this.logger.error("Error disconnecting subgraph @name: @error", instance.name, error);
3416
+ }
3417
+ for (const subgraphsMap of [this.coreSubgraphsMap, this.subgraphs]) for (const [bucket, subgraphs] of subgraphsMap.entries()) {
3418
+ if (!subgraphs.includes(instance)) continue;
3419
+ subgraphsMap.set(bucket, subgraphs.filter((it) => it !== instance));
3420
+ const subgraphPath = this.#getSubgraphPath(instance, bucket);
3421
+ this.subgraphHandlerCache.delete(subgraphPath);
3422
+ const routeHandle = this.subgraphRouteHandles.get(subgraphPath);
3423
+ if (routeHandle !== void 0) {
3424
+ this.httpAdapter.unmount(routeHandle);
3425
+ this.subgraphRouteHandles.delete(subgraphPath);
3426
+ }
3427
+ const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
3428
+ if (wsDisposer) {
3429
+ this.subgraphWsDisposers.delete(subgraphPath);
3430
+ try {
3431
+ await wsDisposer.dispose();
3432
+ } catch (error) {
3433
+ this.logger.error("Error disposing subgraph websocket @name: @error", instance.name, error);
3434
+ }
3435
+ }
3436
+ }
3437
+ if (this.#internalSubgraphHandlers.get(instance.name)?.subgraph === instance) this.#internalSubgraphHandlers.delete(instance.name);
3438
+ for (const [packageName, byName] of this.packageSubgraphs) {
3439
+ if (byName.get(instance.name) !== instance) continue;
3440
+ byName.delete(instance.name);
3441
+ if (byName.size === 0) this.packageSubgraphs.delete(packageName);
3442
+ }
3443
+ }
3444
+ /**
3010
3445
  * Register a pre-constructed subgraph instance.
3011
3446
  * Use this when you need to pass custom dependencies to a subgraph.
3012
3447
  */
@@ -3027,7 +3462,13 @@ var GraphQLManager = class {
3027
3462
  getAuthorizationService() {
3028
3463
  return this.authorizationService;
3029
3464
  }
3030
- async registerSubgraph(subgraph, supergraph = "", core = false) {
3465
+ /**
3466
+ * Register a subgraph class. `packageName` labels the resulting instance
3467
+ * with its contributing package so the instance can be torn down when the
3468
+ * package is removed or drops the subgraph (see unregisterPackage and
3469
+ * prunePackageSubgraphs).
3470
+ */
3471
+ async registerSubgraph(subgraph, supergraph = "", core = false, packageName) {
3031
3472
  const subgraphInstance = new subgraph({
3032
3473
  relationalDb: this.relationalDb,
3033
3474
  analyticsStore: this.analyticsStore,
@@ -3036,9 +3477,76 @@ var GraphQLManager = class {
3036
3477
  syncManager: this.syncManager,
3037
3478
  path: this.path,
3038
3479
  documentPermissionService: this.documentPermissionService,
3039
- authorizationService: this.authorizationService
3480
+ authorizationService: this.authorizationService,
3481
+ syncServingGate: this.syncServingGate
3040
3482
  });
3041
- return this.#addSubgraphInstance(subgraphInstance, supergraph, core);
3483
+ return this.#addSubgraphInstance(subgraphInstance, supergraph, core, packageName);
3484
+ }
3485
+ /**
3486
+ * Tear down every subgraph instance registered from `packageName`:
3487
+ * disconnects the instances, unmounts their routes, disposes their
3488
+ * WebSocket handlers, then refreshes the router so the supergraph SDL and
3489
+ * the supergraph SSE handler no longer reference them. No-op if the
3490
+ * package has no registered subgraphs.
3491
+ */
3492
+ async unregisterPackage(packageName) {
3493
+ const byName = this.packageSubgraphs.get(packageName);
3494
+ if (!byName || byName.size === 0) return;
3495
+ for (const instance of Array.from(byName.values())) await this.#removeSubgraphInstance(instance);
3496
+ await this.updateRouter();
3497
+ }
3498
+ /**
3499
+ * Remove this package's registered subgraphs whose names are not in
3500
+ * `keepNames` — the package is still loaded but dropped some of its
3501
+ * subgraphs. Refreshes the router if anything was removed.
3502
+ */
3503
+ async prunePackageSubgraphs(packageName, keepNames) {
3504
+ const byName = this.packageSubgraphs.get(packageName);
3505
+ if (!byName) return;
3506
+ let removed = 0;
3507
+ for (const [name, instance] of Array.from(byName.entries())) if (!keepNames.has(name)) {
3508
+ await this.#removeSubgraphInstance(instance);
3509
+ removed++;
3510
+ }
3511
+ if (removed > 0) await this.updateRouter();
3512
+ }
3513
+ /** Find a registered subgraph by name, core or package-contributed. */
3514
+ getSubgraphByName(name) {
3515
+ for (const subgraphs of [...this.coreSubgraphsMap.values(), ...this.subgraphs.values()]) {
3516
+ const found = subgraphs.find((subgraph) => subgraph.name === name);
3517
+ if (found) return found;
3518
+ }
3519
+ }
3520
+ /** Whether a registered subgraph has a live handler, i.e. whether
3521
+ * {@link executeSubgraphQuery} can reach it right now. */
3522
+ hasSubgraphHandler(name) {
3523
+ return this.#internalSubgraphHandlers.has(name);
3524
+ }
3525
+ /** Run a query against one registered subgraph in-process: the subgraph's own
3526
+ * handler, without a socket or the auth/drive middleware. Trusted callers. */
3527
+ async executeSubgraphQuery(subgraphName, query, variables) {
3528
+ const entry = this.#internalSubgraphHandlers.get(subgraphName);
3529
+ if (!entry) throw new Error(this.getSubgraphByName(subgraphName) ? `Subgraph "${subgraphName}" is registered but has no handler yet` : `Subgraph "${subgraphName}" is not registered`);
3530
+ const url = new URL(entry.path, `http://localhost:${this.port}/`);
3531
+ const response = await entry.handler(new Request(url, {
3532
+ method: "POST",
3533
+ headers: {
3534
+ "content-type": "application/json",
3535
+ accept: "application/json"
3536
+ },
3537
+ body: JSON.stringify({
3538
+ query,
3539
+ variables
3540
+ })
3541
+ }));
3542
+ if (!response.ok) {
3543
+ const detail = await response.text().catch(() => "");
3544
+ throw new Error(`Subgraph "${subgraphName}" query failed: ${response.status}${detail ? ` ${detail}` : ""}`);
3545
+ }
3546
+ const body = await response.json();
3547
+ if (body.errors?.length) throw new Error(body.errors.map((error) => error.message).join("; "));
3548
+ if (!body.data) throw new Error(`Subgraph "${subgraphName}" returned no data`);
3549
+ return body.data;
3042
3550
  }
3043
3551
  updateRouter = debounce(this._updateRouter.bind(this), 1e3);
3044
3552
  async _updateRouter() {
@@ -3050,7 +3558,7 @@ var GraphQLManager = class {
3050
3558
  } catch (error) {
3051
3559
  this.logger.error("Failed to update Apollo Gateway supergraph: @error", error);
3052
3560
  }
3053
- const superGraphPath = path.join(this.path, "graphql");
3561
+ const superGraphPath = path.posix.join(this.path, "graphql");
3054
3562
  this.#setupSupergraphSSE(superGraphPath);
3055
3563
  }
3056
3564
  getAdditionalContextFields = () => {
@@ -3094,10 +3602,18 @@ var GraphQLManager = class {
3094
3602
  return (connectionParams) => this.#createWebSocketContext(connectionParams);
3095
3603
  }
3096
3604
  setSupergraph(supergraph, subgraphs) {
3097
- this.subgraphs.set(supergraph, subgraphs);
3605
+ const accepted = [];
3606
+ for (const subgraph of subgraphs) {
3607
+ if (this.#isCoreNameReserved(subgraph.name)) {
3608
+ this.logger.error("Rejecting subgraph @name: name is reserved by a registered core subgraph", subgraph.name);
3609
+ continue;
3610
+ }
3611
+ accepted.push(subgraph);
3612
+ }
3613
+ this.subgraphs.set(supergraph, accepted);
3098
3614
  const globalSubgraphs = this.subgraphs.get("graphql");
3099
- if (globalSubgraphs) this.subgraphs.set("graphql", [...globalSubgraphs, ...subgraphs]);
3100
- else this.subgraphs.set("graphql", subgraphs);
3615
+ if (globalSubgraphs) this.subgraphs.set("graphql", [...globalSubgraphs, ...accepted]);
3616
+ else this.subgraphs.set("graphql", accepted);
3101
3617
  return this.updateRouter();
3102
3618
  }
3103
3619
  async shutdown() {
@@ -3113,19 +3629,41 @@ var GraphQLManager = class {
3113
3629
  });
3114
3630
  }
3115
3631
  #getSubgraphPath(subgraph, supergraph) {
3116
- return path.join(subgraph.path ?? "", supergraph, subgraph.name);
3632
+ return path.posix.join(this.path, supergraph, subgraph.name);
3633
+ }
3634
+ /** The in-process handler map is keyed by bare name, so two distinct
3635
+ * subgraphs sharing one name would shadow each other: keep the first. */
3636
+ #setInternalSubgraphHandler(subgraph, subgraphPath, handler) {
3637
+ const existing = this.#internalSubgraphHandlers.get(subgraph.name);
3638
+ if (existing && existing.subgraph !== subgraph) {
3639
+ this.logger.warn("Two subgraphs are named @name (@kept and @ignored); in-process queries keep the first", subgraph.name, existing.path, subgraphPath);
3640
+ return;
3641
+ }
3642
+ this.#internalSubgraphHandlers.set(subgraph.name, {
3643
+ subgraph,
3644
+ path: subgraphPath,
3645
+ handler
3646
+ });
3117
3647
  }
3118
3648
  async #setupSubgraphs(subgraphsMap) {
3119
3649
  for (const [supergraph, subgraphs] of subgraphsMap.entries()) for (const subgraph of subgraphs) {
3120
3650
  this.logger.debug(`Setting up subgraph ${subgraph.name}`);
3121
3651
  const subgraphPath = this.#getSubgraphPath(subgraph, supergraph);
3122
3652
  try {
3123
- if (this.subgraphHandlerCache.has(subgraphPath)) continue;
3653
+ const cached = this.subgraphHandlerCache.get(subgraphPath);
3654
+ if (cached) {
3655
+ if (cached.subgraph !== subgraph) this.logger.warn("Subgraph path @path already mounted by @kept; @name is shadowed and will not be mounted", subgraphPath, cached.subgraph.name, subgraph.name);
3656
+ continue;
3657
+ }
3124
3658
  const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3125
3659
  const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
3126
3660
  const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3127
- this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
3128
- this.httpAdapter.mount(subgraphPath, fetchHandler);
3661
+ this.subgraphHandlerCache.set(subgraphPath, {
3662
+ handler: fetchHandler,
3663
+ subgraph
3664
+ });
3665
+ this.#setInternalSubgraphHandler(subgraph, subgraphPath, rawHandler);
3666
+ this.subgraphRouteHandles.set(subgraphPath, this.httpAdapter.mount(subgraphPath, fetchHandler));
3129
3667
  if (subgraph.hasSubscriptions) {
3130
3668
  try {
3131
3669
  if (this.subgraphWsDisposers.get(subgraphPath)) this.logger.warn("Overwriting existing WebSocket disposer for subgraph at path @path", subgraphPath);
@@ -3171,7 +3709,7 @@ var GraphQLManager = class {
3171
3709
  }));
3172
3710
  }
3173
3711
  async #createSupergraphGateway() {
3174
- const superGraphPath = path.join(this.path, "graphql");
3712
+ const superGraphPath = path.posix.join(this.path, "graphql");
3175
3713
  const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
3176
3714
  const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3177
3715
  this.httpAdapter.mount(superGraphPath, fetchHandler);
@@ -3190,7 +3728,13 @@ var GraphQLManager = class {
3190
3728
  #setupSupergraphSSE(superGraphPath) {
3191
3729
  const allSubgraphs = this.#getAllSubgraphs();
3192
3730
  const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
3193
- if (modules.length === 0) return;
3731
+ if (modules.length === 0) {
3732
+ if (this.sseRouteHandle !== void 0) {
3733
+ this.httpAdapter.unmount(this.sseRouteHandle);
3734
+ this.sseRouteHandle = void 0;
3735
+ }
3736
+ return;
3737
+ }
3194
3738
  try {
3195
3739
  const mergedSchema = createMergedSchema(modules);
3196
3740
  this.#setupSSEHandler(mergedSchema, superGraphPath);
@@ -3214,16 +3758,20 @@ var GraphQLManager = class {
3214
3758
  contextFactory: this.#makeContextFactory()
3215
3759
  });
3216
3760
  const handler = this.#composeFetchMiddleware(rawHandler);
3217
- this.httpAdapter.mount(ssePath, handler, { exact: true });
3761
+ if (this.sseRouteHandle !== void 0) this.httpAdapter.unmount(this.sseRouteHandle);
3762
+ this.sseRouteHandle = this.httpAdapter.mount(ssePath, handler, { exact: true });
3218
3763
  }
3219
3764
  /**
3220
3765
  * Compose the request-level fetch middleware chain. Auth runs first
3221
3766
  * (so we don't validate shard before knowing the request is even
3222
- * authorized), drive-ownership validation runs after.
3767
+ * authorized); the require-authenticated-caller middleware runs next,
3768
+ * before shard validation, so an anonymous caller is rejected with a
3769
+ * 401 rather than a 421; drive-ownership validation runs last.
3223
3770
  */
3224
3771
  #composeFetchMiddleware(rawHandler) {
3225
3772
  let handler = rawHandler;
3226
3773
  if (this.#driveMiddleware) handler = this.#driveMiddleware(handler);
3774
+ if (this.#requireAuthMiddleware) handler = this.#requireAuthMiddleware(handler);
3227
3775
  if (this.#authMiddleware) handler = this.#authMiddleware(handler);
3228
3776
  return handler;
3229
3777
  }
@@ -3329,6 +3877,31 @@ var PackagesSubgraph = class extends BaseSubgraph {
3329
3877
  //#endregion
3330
3878
  //#region src/graphql/playground.ts
3331
3879
  /**
3880
+ * Decodes an `explorerURLState` URL parameter. Returns `null` for anything
3881
+ * that is not a valid lz-compressed payload with a non-empty `document`.
3882
+ */
3883
+ function decodeExplorerUrlState(encoded) {
3884
+ try {
3885
+ const decompressed = lzString.decompressFromEncodedURIComponent(encoded);
3886
+ if (!decompressed) return null;
3887
+ const payload = JSON.parse(decompressed);
3888
+ if (typeof payload.document !== "string" || payload.document.length === 0) return null;
3889
+ const variables = typeof payload.variables === "string" ? payload.variables : void 0;
3890
+ let headers;
3891
+ if (typeof payload.headers === "string") {
3892
+ const parsed = JSON.parse(payload.headers);
3893
+ if (parsed && typeof parsed === "object") headers = parsed;
3894
+ }
3895
+ return {
3896
+ query: payload.document,
3897
+ variables,
3898
+ headers
3899
+ };
3900
+ } catch {
3901
+ return null;
3902
+ }
3903
+ }
3904
+ /**
3332
3905
  * Pinned CDN versions for GraphiQL playground dependencies.
3333
3906
  * Using pinned versions avoids unpkg.com redirect issues that can
3334
3907
  * trigger CORS errors in the browser.
@@ -3339,7 +3912,29 @@ const CDN_VERSIONS = {
3339
3912
  graphiql: "3.8.3",
3340
3913
  pluginExplorer: "4.0.0"
3341
3914
  };
3342
- function renderGraphqlPlayground(url, query, headers = {}) {
3915
+ /**
3916
+ * Escape a string for embedding as a single-quoted JS string literal inside
3917
+ * an HTML `<script>` block. The payload reaches the browser through an
3918
+ * attacker-controllable URL parameter, so the embedding must survive two
3919
+ * parsers: HTML (a `<\/script>` inside the string would end the script
3920
+ * element and start a new, attacker-authored one) and JS (backticks,
3921
+ * `${...}` and quotes in the content would otherwise break out of the
3922
+ * literal). Backslash, quote, CR and LF escapes keep the literal valid;
3923
+ * every `<` becomes `\u003c`, which no HTML parser reads as a tag opener.
3924
+ * The escapes resolve when the script parses, so the in-memory value is
3925
+ * byte-identical to the input.
3926
+ */
3927
+ function jsStringEscape(value) {
3928
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/</g, "\\u003c");
3929
+ }
3930
+ /**
3931
+ * JSON is already a valid JS object/string literal; the only HTML-hostile
3932
+ * byte left is `<` (e.g. `<\/script>` inside a string value).
3933
+ */
3934
+ function jsJsonEscape(json) {
3935
+ return json.replace(/</g, "\\u003c");
3936
+ }
3937
+ function renderGraphqlPlayground(url, query, headers = {}, variables) {
3343
3938
  return `<!doctype html>
3344
3939
  <html lang="en">
3345
3940
  <head>
@@ -3379,16 +3974,21 @@ function renderGraphqlPlayground(url, query, headers = {}) {
3379
3974
  <div id="graphiql">Loading...</div>
3380
3975
  <script>
3381
3976
  var fetcher = GraphiQL.createFetcher({
3382
- url: '${url}',
3383
- headers: ${JSON.stringify(headers)}
3977
+ url: '${jsStringEscape(url)}',
3978
+ headers: ${jsJsonEscape(JSON.stringify(headers))}
3384
3979
  });
3385
- var defaultQuery = ${query ? `\`${query}\`` : void 0};
3980
+ var defaultQuery = ${query ? `'${jsStringEscape(query)}'` : "undefined"};
3981
+ var defaultVariables = ${variables ? `'${jsStringEscape(variables)}'` : "undefined"};
3386
3982
 
3387
- if (defaultQuery) {
3388
- var sessionQuery = localStorage.getItem("graphiql:query");
3389
- if (sessionQuery) {
3390
- localStorage.setItem("graphiql:query", defaultQuery);
3391
- }
3983
+ // GraphiQL only applies defaultQuery/variables when the editor has
3984
+ // no persisted value. Overwrite any stale persisted state so a
3985
+ // returning user still gets the document-scoped query/variables
3986
+ // instead of their last session's.
3987
+ if (defaultQuery && localStorage.getItem("graphiql:query")) {
3988
+ localStorage.setItem("graphiql:query", defaultQuery);
3989
+ }
3990
+ if (defaultVariables && localStorage.getItem("graphiql:variables")) {
3991
+ localStorage.setItem("graphiql:variables", defaultVariables);
3392
3992
  }
3393
3993
 
3394
3994
  var explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
@@ -3398,7 +3998,8 @@ function renderGraphqlPlayground(url, query, headers = {}) {
3398
3998
  fetcher: fetcher,
3399
3999
  defaultEditorToolsVisibility: true,
3400
4000
  plugins: [explorerPlugin],
3401
- defaultQuery
4001
+ defaultQuery: defaultQuery,
4002
+ variables: defaultVariables
3402
4003
  });
3403
4004
  }
3404
4005
 
@@ -3536,6 +4137,17 @@ const DocumentChangeEventDTO = z.object({
3536
4137
  documents: z.array(PHDocumentDTO),
3537
4138
  context: DocumentChangeContextDTO.nullable().optional()
3538
4139
  }).strip();
4140
+ const ActionEvaluationDTO = z.object({
4141
+ decision: z.enum(["ALLOW", "DENY"]),
4142
+ reason: z.string().nullable().optional()
4143
+ }).strip();
4144
+ const ActionEvaluationsDTO = z.object({
4145
+ evaluations: z.array(ActionEvaluationDTO),
4146
+ allAllowed: z.boolean(),
4147
+ anyAllowed: z.boolean(),
4148
+ allDenied: z.boolean(),
4149
+ anyDenied: z.boolean()
4150
+ }).strip();
3539
4151
  const JobChangeEventDTO = z.object({
3540
4152
  jobId: z.string(),
3541
4153
  status: z.string(),
@@ -3569,6 +4181,9 @@ const operationValidators = {
3569
4181
  GetJobStatus: (data) => {
3570
4182
  if (data.jobStatus) JobInfoDTO.parse(data.jobStatus);
3571
4183
  },
4184
+ EvaluateActions: (data) => {
4185
+ if (data.evaluateActions) ActionEvaluationsDTO.parse(data.evaluateActions);
4186
+ },
3572
4187
  CreateDocument: (data) => {
3573
4188
  if (data.createDocument) PHDocumentDTO.parse(data.createDocument);
3574
4189
  },
@@ -3631,7 +4246,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
3631
4246
  }
3632
4247
  //#endregion
3633
4248
  //#region src/graphql/reactor/schema.graphql
3634
- 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 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 result: JSONObject!\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# Input types for sync operations\ninput ActionContextInput {\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 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 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 # Poll for sync envelopes from a channel\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 # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\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";
4249
+ 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";
3635
4250
  //#endregion
3636
4251
  //#region src/graphql/reactor/pubsub.ts
3637
4252
  const pubSub = new PubSub();
@@ -3769,6 +4384,46 @@ var ReactorSubgraph = class extends BaseSubgraph {
3769
4384
  if (!await this.canReadDocument(documentId, ctx)) forbidden.add(documentId);
3770
4385
  }
3771
4386
  }
4387
+ /**
4388
+ * Enforces which subject a sync channel answers to, adopting an unclaimed one.
4389
+ *
4390
+ * A channel is a private queue: it names a collection, and it accumulates
4391
+ * exactly the operations one subject is owed, so serving it to a second
4392
+ * subject would hand over that subject's cursor as well as its contents. An
4393
+ * unbound channel -- created anonymously, or created before binding existed --
4394
+ * is claimed by the first authenticated subject to use it, and answers to that
4395
+ * address alone thereafter. Adoption is safe precisely because serving
4396
+ * withholds rather than consumes: before it was claimed the channel served
4397
+ * only what an anonymous subject may read, and everything withheld is still
4398
+ * queued for the adopter.
4399
+ *
4400
+ * The refusal is the shape a read denial takes, so a puller treats it as a
4401
+ * signal to authenticate again rather than as a transport failure.
4402
+ *
4403
+ * Nothing is enforced or adopted without a serving gate. Below
4404
+ * `authEnforcement` there is no policy being enforced for the channel to
4405
+ * belong to, and refusing a poll there would break sync for no gain.
4406
+ */
4407
+ async #bindOrRefuseChannel(channelId, ctx) {
4408
+ if (!this.syncServingGate) return;
4409
+ let remote;
4410
+ try {
4411
+ remote = this.syncManager.getById(channelId);
4412
+ } catch {
4413
+ return;
4414
+ }
4415
+ const bound = remote.meta.options.boundAddress;
4416
+ const address = ctx.user?.address;
4417
+ if (bound === void 0) {
4418
+ if (address !== void 0) await this.syncManager.bindRemote(channelId, address);
4419
+ return;
4420
+ }
4421
+ if (bound !== address) throw new ForbiddenError("to poll this sync channel");
4422
+ }
4423
+ /** The address a channel created by this request belongs to, if any. */
4424
+ #creatorBinding(ctx) {
4425
+ return this.syncServingGate ? ctx.user?.address : void 0;
4426
+ }
3772
4427
  typeDefs = gql(schema_default);
3773
4428
  resolvers = {
3774
4429
  PHDocument: { operations: async (parent, args, ctx) => {
@@ -3787,7 +4442,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
3787
4442
  return await documentOperations(this.reactorClient, {
3788
4443
  filter,
3789
4444
  paging: args.paging
3790
- });
4445
+ }, this.viewSubject(ctx));
3791
4446
  } catch (error) {
3792
4447
  this.logger.error("Error in PHDocument.operations: @Error", error);
3793
4448
  throw error;
@@ -3810,7 +4465,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
3810
4465
  return await document(this.reactorClient, {
3811
4466
  ...args,
3812
4467
  identifier: handle.fetchIdentifier
3813
- });
4468
+ }, this.viewSubject(ctx));
3814
4469
  } catch (error) {
3815
4470
  this.logger.error("Error in document: @Error", error);
3816
4471
  throw error;
@@ -3823,7 +4478,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
3823
4478
  return await documentOutgoingRelationships(this.reactorClient, {
3824
4479
  ...args,
3825
4480
  sourceIdentifier: handle.fetchIdentifier
3826
- });
4481
+ }, this.viewSubject(ctx));
3827
4482
  } catch (error) {
3828
4483
  this.logger.error("Error in documentOutgoingRelationships: @Error", error);
3829
4484
  throw error;
@@ -3836,7 +4491,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
3836
4491
  const result = await documentIncomingRelationships(this.reactorClient, {
3837
4492
  ...args,
3838
4493
  targetIdentifier: handle.fetchIdentifier
3839
- });
4494
+ }, this.viewSubject(ctx));
3840
4495
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
3841
4496
  const filteredItems = [];
3842
4497
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
@@ -3857,7 +4512,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
3857
4512
  const result = await findDocuments(this.reactorClient, {
3858
4513
  ...args,
3859
4514
  search: args.search ?? {}
3860
- });
4515
+ }, this.viewSubject(ctx));
3861
4516
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
3862
4517
  const filteredItems = [];
3863
4518
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
@@ -3891,12 +4546,25 @@ var ReactorSubgraph = class extends BaseSubgraph {
3891
4546
  ...args.filter,
3892
4547
  documentId: handle.fetchIdentifier
3893
4548
  }
3894
- });
4549
+ }, this.viewSubject(ctx));
3895
4550
  } catch (error) {
3896
4551
  this.logger.error("Error in documentOperations: @Error", error);
3897
4552
  throw error;
3898
4553
  }
3899
4554
  },
4555
+ evaluateActions: async (_parent, args, ctx) => {
4556
+ this.logger.debug("evaluateActions(@args)", args);
4557
+ try {
4558
+ const handle = await this.assertCanRead(args.documentIdentifier, ctx);
4559
+ return await evaluateActions(this.reactorClient, {
4560
+ ...args,
4561
+ documentIdentifier: handle.fetchIdentifier
4562
+ }, this.viewSubject(ctx));
4563
+ } catch (error) {
4564
+ this.logger.error("Error in evaluateActions: @Error", error);
4565
+ throw error;
4566
+ }
4567
+ },
3900
4568
  pollSyncEnvelopes: async (_parent, args, ctx) => {
3901
4569
  this.logger.debug("pollSyncEnvelopes(@args)", args);
3902
4570
  try {
@@ -3909,12 +4577,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
3909
4577
  const driveId = remote.meta.collectionId.driveId;
3910
4578
  const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
3911
4579
  if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
4580
+ await this.#bindOrRefuseChannel(args.channelId, ctx);
3912
4581
  const forbiddenIds = /* @__PURE__ */ new Set();
3913
4582
  if (!isAdmin) {
3914
4583
  if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
3915
4584
  await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
3916
4585
  }
3917
- const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds);
4586
+ const heldOpIds = this.syncServingGate ? await collectHeldSyncOperations([...remote.channel.outbox.items, ...remote.channel.deadLetter.items], this.syncServingGate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
4587
+ const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds);
3918
4588
  return {
3919
4589
  envelopes,
3920
4590
  ackOrdinal,
@@ -3960,6 +4630,32 @@ var ReactorSubgraph = class extends BaseSubgraph {
3960
4630
  throw error;
3961
4631
  }
3962
4632
  },
4633
+ execute: async (_parent, args, ctx) => {
4634
+ this.logger.debug("execute(@args)", args);
4635
+ try {
4636
+ const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
4637
+ return await execute(this.reactorClient, {
4638
+ ...args,
4639
+ documentIdentifier: handle.fetchIdentifier
4640
+ });
4641
+ } catch (error) {
4642
+ this.logger.error("Error in execute(@args): @Error", args, error);
4643
+ throw error;
4644
+ }
4645
+ },
4646
+ executeAsync: async (_parent, args, ctx) => {
4647
+ this.logger.debug("executeAsync(@args)", args);
4648
+ try {
4649
+ const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
4650
+ return await executeAsync(this.reactorClient, {
4651
+ ...args,
4652
+ documentIdentifier: handle.fetchIdentifier
4653
+ });
4654
+ } catch (error) {
4655
+ this.logger.error("Error in executeAsync(@args): @Error", args, error);
4656
+ throw error;
4657
+ }
4658
+ },
3963
4659
  mutateDocument: async (_parent, args, ctx) => {
3964
4660
  this.logger.debug("mutateDocument(@args)", args);
3965
4661
  try {
@@ -4093,7 +4789,8 @@ var ReactorSubgraph = class extends BaseSubgraph {
4093
4789
  const driveId = DriveCollectionId.fromKey(args.input.collectionId).driveId;
4094
4790
  await this.assertCanReadCanonical(driveId, ctx);
4095
4791
  }
4096
- return await touchChannel(this.syncManager, args);
4792
+ await this.#bindOrRefuseChannel(args.input.id, ctx);
4793
+ return await touchChannel(this.syncManager, args, this.#creatorBinding(ctx));
4097
4794
  } catch (error) {
4098
4795
  this.logger.error("Error in touchChannel(@args): @Error", args, error);
4099
4796
  throw error;
@@ -4205,10 +4902,10 @@ const ADMIN_USERS = getAdminUsers();
4205
4902
  //#endregion
4206
4903
  //#region src/graphql/system/version.ts
4207
4904
  function getVersion() {
4208
- return "6.2.2-dev.9";
4905
+ return "6.2.2";
4209
4906
  }
4210
4907
  function getGitHash() {
4211
- return "ec23e4c4dd92ea6fa8ae283471c757e68e92b552";
4908
+ return "f1edb55f5581a52badb784d25c78666b266f9caf";
4212
4909
  }
4213
4910
  function getGitUrl() {
4214
4911
  return buildTreeUrl(getGitHash());
@@ -4306,6 +5003,13 @@ var HttpPackageLoader = class {
4306
5003
  this.logger.verbose(`Loaded ${models.length} document models from ${packageName}`);
4307
5004
  return models;
4308
5005
  }
5006
+ async loadUpgradeManifests(packageSpec) {
5007
+ const { name: packageName } = this.parsePackageSpec(packageSpec);
5008
+ if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
5009
+ const manifests = extractUpgradeManifests(await import(`${this.registryUrl}-/cdn/${packageSpec}/node/document-models/index.mjs`));
5010
+ if (manifests.length > 0) this.logger.verbose(`Loaded ${manifests.length} upgrade manifests from ${packageName}`);
5011
+ return manifests;
5012
+ }
4309
5013
  async loadSubgraphs(packageSpec) {
4310
5014
  const { name: packageName } = this.parsePackageSpec(packageSpec);
4311
5015
  if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
@@ -4431,7 +5135,7 @@ var ImportPackageLoader = class {
4431
5135
  this.logger.verbose(`Loading document models from package: ${identifier}`);
4432
5136
  const pkgModule = await loadDocumentModels(identifier);
4433
5137
  if (pkgModule) {
4434
- const models = Object.values(pkgModule);
5138
+ const models = Object.values(pkgModule).filter((m) => m !== null && typeof m === "object" && "documentModel" in m);
4435
5139
  this.logger.verbose(` ➜ Loaded ${models.length} Document Models from: ${identifier}`);
4436
5140
  return models;
4437
5141
  } else {
@@ -4439,6 +5143,13 @@ var ImportPackageLoader = class {
4439
5143
  return [];
4440
5144
  }
4441
5145
  }
5146
+ async loadUpgradeManifests(identifier) {
5147
+ const pkgModule = await loadDocumentModels(identifier);
5148
+ if (!pkgModule) return [];
5149
+ const manifests = extractUpgradeManifests(pkgModule);
5150
+ if (manifests.length > 0) this.logger.verbose(` ➜ Loaded ${manifests.length} Upgrade Manifests from: ${identifier}`);
5151
+ return manifests;
5152
+ }
4442
5153
  async loadSubgraphs(identifier) {
4443
5154
  this.logger.verbose(`Loading subgraphs from package: ${identifier}`);
4444
5155
  const pkgModule = await loadSubgraphs(identifier);
@@ -4487,13 +5198,19 @@ function isExpectedLoaderMiss(error, pkg) {
4487
5198
  }
4488
5199
  function getUniqueDocumentModels(...documentModels) {
4489
5200
  const uniqueModels = /* @__PURE__ */ new Map();
4490
- for (const models of documentModels) for (const model of models) uniqueModels.set(model.documentModel.global.id, model);
5201
+ for (const models of documentModels) for (const model of models) uniqueModels.set(`${model.documentModel.global.id}@${model.version ?? 1}`, model);
4491
5202
  return Array.from(uniqueModels.values());
4492
5203
  }
5204
+ function getUniqueUpgradeManifests(...upgradeManifests) {
5205
+ const uniqueManifests = /* @__PURE__ */ new Map();
5206
+ for (const manifests of upgradeManifests) for (const manifest of manifests) uniqueManifests.set(manifest.documentType, manifest);
5207
+ return Array.from(uniqueManifests.values());
5208
+ }
4493
5209
  var PackageManager = class {
4494
5210
  logger = childLogger(["reactor-api", "package-manager"]);
4495
5211
  loaders;
4496
5212
  docModelsMap = /* @__PURE__ */ new Map();
5213
+ upgradeManifestsMap = /* @__PURE__ */ new Map();
4497
5214
  subgraphsMap = /* @__PURE__ */ new Map();
4498
5215
  processorMap = /* @__PURE__ */ new Map();
4499
5216
  configWatcher;
@@ -4511,8 +5228,10 @@ var PackageManager = class {
4511
5228
  async loadPackages(packages) {
4512
5229
  this.logger.info("Loading packages: @packages", packages.join(", "));
4513
5230
  const documentModelsMap = await this.loadDocumentModels(packages);
5231
+ const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
4514
5232
  const subgraphsMap = await this.loadSubgraphs(packages);
4515
5233
  const processorsMap = await this.loadProcessors(packages);
5234
+ this.upgradeManifestsMap = upgradeManifestsMap;
4516
5235
  this.updatePackagesMap(documentModelsMap);
4517
5236
  this.updateSubgraphsMap(subgraphsMap);
4518
5237
  this.updateProcessorsMap(processorsMap);
@@ -4523,6 +5242,7 @@ var PackageManager = class {
4523
5242
  }
4524
5243
  return {
4525
5244
  documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
5245
+ upgradeManifests: this.getUniqueUpgradeManifests(),
4526
5246
  subgraphs: subgraphsMap,
4527
5247
  processors: processorsMap
4528
5248
  };
@@ -4533,6 +5253,7 @@ var PackageManager = class {
4533
5253
  documentModelModuleMap.set("document-drive", [driveDocumentModelModule]);
4534
5254
  documentModelModuleMap.set("document-model", [documentModelDocumentModelModule]);
4535
5255
  documentModelModuleMap.set("reactor-drive", [reactorDriveDocumentModelModule]);
5256
+ documentModelModuleMap.set("reactor-group", [ReactorGroupV1]);
4536
5257
  for (const pkg of packages) {
4537
5258
  const allDocumentModels = [];
4538
5259
  const failures = [];
@@ -4555,6 +5276,26 @@ var PackageManager = class {
4555
5276
  }
4556
5277
  return documentModelModuleMap;
4557
5278
  }
5279
+ /** Upgrade manifests currently loaded across all packages, one per type. */
5280
+ getUniqueUpgradeManifests() {
5281
+ return getUniqueUpgradeManifests(...Array.from(this.upgradeManifestsMap.values()));
5282
+ }
5283
+ async loadUpgradeManifests(packages) {
5284
+ const manifestsMap = /* @__PURE__ */ new Map();
5285
+ for (const pkg of packages) for (const loader of this.loaders) {
5286
+ if (!loader.loadUpgradeManifests) continue;
5287
+ try {
5288
+ const manifests = await loader.loadUpgradeManifests(pkg);
5289
+ if (manifests.length > 0) {
5290
+ manifestsMap.set(pkg, manifests);
5291
+ break;
5292
+ }
5293
+ } catch (error) {
5294
+ this.logger.debug(`[${loader.name}] Failed to load upgrade manifests from package @pkg: @error`, pkg, error);
5295
+ }
5296
+ }
5297
+ return manifestsMap;
5298
+ }
4558
5299
  async loadSubgraphs(packages) {
4559
5300
  this.logger.debug(`Loading subgraphs from packages: ${packages.join(", ")}`);
4560
5301
  const subgraphsMap = /* @__PURE__ */ new Map();
@@ -4601,7 +5342,6 @@ var PackageManager = class {
4601
5342
  this.maybeWarnAllLoadersFailed("processors", pkg, succeeded, failures);
4602
5343
  processorsMap.set(pkg, allProcessors);
4603
5344
  }
4604
- this.updateProcessorsMap(processorsMap);
4605
5345
  return processorsMap;
4606
5346
  }
4607
5347
  maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
@@ -4616,23 +5356,58 @@ var PackageManager = class {
4616
5356
  async updateDocumentModelsForPackage(pkg) {
4617
5357
  this.logger.debug(`Updating document models for package: ${pkg}`);
4618
5358
  const documentModels = await this.loadDocumentModels([pkg]);
5359
+ const upgradeManifests = await this.loadUpgradeManifests([pkg]);
5360
+ const upgradeManifestsMap = new Map(this.upgradeManifestsMap);
5361
+ const pkgManifests = upgradeManifests.get(pkg) ?? [];
5362
+ if (pkgManifests.length === 0) upgradeManifestsMap.delete(pkg);
5363
+ else upgradeManifestsMap.set(pkg, pkgManifests);
5364
+ this.upgradeManifestsMap = upgradeManifestsMap;
4619
5365
  const documentModelsMap = new Map(this.docModelsMap);
4620
- documentModelsMap.set(pkg, documentModels.get(pkg) ?? []);
5366
+ const pkgModels = documentModels.get(pkg) ?? [];
5367
+ if (pkgModels.length === 0) documentModelsMap.delete(pkg);
5368
+ else documentModelsMap.set(pkg, pkgModels);
4621
5369
  this.updatePackagesMap(documentModelsMap);
4622
5370
  }
5371
+ async updateSubgraphsForPackage(pkg) {
5372
+ this.logger.debug(`Updating subgraphs for package: ${pkg}`);
5373
+ const subgraphs = await this.loadSubgraphs([pkg]);
5374
+ const subgraphsMap = new Map(this.subgraphsMap);
5375
+ const pkgSubgraphs = subgraphs.get(pkg) ?? [];
5376
+ if (pkgSubgraphs.length === 0) subgraphsMap.delete(pkg);
5377
+ else subgraphsMap.set(pkg, pkgSubgraphs);
5378
+ this.updateSubgraphsMap(subgraphsMap);
5379
+ }
5380
+ async updateProcessorsForPackage(pkg) {
5381
+ this.logger.debug(`Updating processors for package: ${pkg}`);
5382
+ const processors = await this.loadProcessors([pkg]);
5383
+ const processorsMap = new Map(this.processorMap);
5384
+ const pkgProcessors = processors.get(pkg) ?? [];
5385
+ if (pkgProcessors.length === 0) processorsMap.delete(pkg);
5386
+ else processorsMap.set(pkg, pkgProcessors);
5387
+ this.updateProcessorsMap(processorsMap);
5388
+ }
5389
+ /** Debounced per key so repeated subscribePackages calls reuse one timer. */
5390
+ getDebouncedUpdateCallback(key, update) {
5391
+ if (!this.debouncedUpdateCallbacks.has(key)) {
5392
+ const debouncedFn = debounce(update, 1e3);
5393
+ this.debouncedUpdateCallbacks.set(key, () => {
5394
+ debouncedFn().catch((error) => {
5395
+ this.logger.error("Failed to update package: @error", error);
5396
+ });
5397
+ });
5398
+ }
5399
+ return this.debouncedUpdateCallbacks.get(key);
5400
+ }
4623
5401
  subscribePackages(packages) {
4624
5402
  const unsubs = [];
4625
5403
  for (const pkg of packages) {
4626
- if (!this.debouncedUpdateCallbacks.has(pkg)) {
4627
- const debouncedFn = debounce(() => this.updateDocumentModelsForPackage(pkg), 1e3);
4628
- this.debouncedUpdateCallbacks.set(pkg, () => {
4629
- debouncedFn();
4630
- });
4631
- }
4632
- const debouncedCallback = this.debouncedUpdateCallbacks.get(pkg);
4633
- for (const loader of this.loaders) if (loader.onDocumentModelsChange) {
4634
- const unsub = loader.onDocumentModelsChange(pkg, debouncedCallback);
4635
- unsubs.push(unsub);
5404
+ const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
5405
+ const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
5406
+ const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
5407
+ for (const loader of this.loaders) {
5408
+ if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
5409
+ if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
5410
+ if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
4636
5411
  }
4637
5412
  }
4638
5413
  return () => {
@@ -4680,6 +5455,28 @@ var PackageManager = class {
4680
5455
  this.processorMap = processorsMap;
4681
5456
  this.eventEmitter.emit("processorsChange", processorsMap);
4682
5457
  }
5458
+ /**
5459
+ * Remove `pkg` from all package maps and emit the change events, tearing
5460
+ * down everything the package registered (subgraphs, processors, document
5461
+ * models). For an uninstalled package no loader can serve it again, so
5462
+ * nothing can re-add it.
5463
+ */
5464
+ removePackage(pkg) {
5465
+ this.logger.info(`Removing package: ${pkg}`);
5466
+ const dropKey = (map) => {
5467
+ if (!map.has(pkg)) return map;
5468
+ const next = new Map(map);
5469
+ next.delete(pkg);
5470
+ return next;
5471
+ };
5472
+ this.docModelsMap = dropKey(this.docModelsMap);
5473
+ this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
5474
+ this.subgraphsMap = dropKey(this.subgraphsMap);
5475
+ this.processorMap = dropKey(this.processorMap);
5476
+ this.updatePackagesMap(this.docModelsMap);
5477
+ this.updateSubgraphsMap(this.subgraphsMap);
5478
+ this.updateProcessorsMap(this.processorMap);
5479
+ }
4683
5480
  onDocumentModelsChange(handler) {
4684
5481
  this.eventEmitter.on("documentModelsChange", handler);
4685
5482
  }
@@ -4695,6 +5492,104 @@ var PackageManager = class {
4695
5492
  const config = { basePath: process.env.BASE_PATH || "/" };
4696
5493
  const DefaultCoreSubgraphs = [AnalyticsSubgraph, SystemSubgraph];
4697
5494
  //#endregion
5495
+ //#region src/attachment-backend.ts
5496
+ /** Filesystem is the default; explicit S3 is readiness-gated without fallback. */
5497
+ async function createStartupAttachmentBackend({ db, env = process.env, s3Dependencies }) {
5498
+ const storage = parseAttachmentStorageConfig(env);
5499
+ if (storage.kind === "filesystem") return void 0;
5500
+ const backend = createS3AttachmentBackend(db.withSchema("attachments"), storage.s3, s3Dependencies);
5501
+ if (!(await backend.health()).ready) throw new Error("S3 attachment backend readiness check failed");
5502
+ return backend;
5503
+ }
5504
+ //#endregion
5505
+ //#region src/services/attachment-access.service.ts
5506
+ const HASH_PATTERN = /^[a-f0-9]{64}$/;
5507
+ /**
5508
+ * Composes document authorization with the projected document/ref
5509
+ * relationship. Order is fixed: validate ref, resolve the canonical document
5510
+ * id, check `canRead`, then check the reference index. A denied document
5511
+ * never reaches the reference reader, and the facade never touches
5512
+ * attachment metadata, storage backends, or presigners.
5513
+ */
5514
+ var AttachmentAccessService = class {
5515
+ constructor(resolveCanonicalId, authorization, references, projection) {
5516
+ this.resolveCanonicalId = resolveCanonicalId;
5517
+ this.authorization = authorization;
5518
+ this.references = references;
5519
+ this.projection = projection;
5520
+ }
5521
+ async canReadAttachment(request) {
5522
+ if (this.projection.status !== "available") return { kind: "projection-unavailable" };
5523
+ const ref = normalizeAttachmentRef(request.attachmentRef);
5524
+ if (ref === null) return { kind: "denied" };
5525
+ let documentId;
5526
+ try {
5527
+ documentId = await this.resolveCanonicalId(request.documentId);
5528
+ } catch {
5529
+ return { kind: "denied" };
5530
+ }
5531
+ if (!await this.authorization.canRead(documentId, request.userAddress)) return { kind: "denied" };
5532
+ if (!await this.references.hasReference(documentId, ref)) return { kind: "denied" };
5533
+ return {
5534
+ kind: "allowed",
5535
+ documentId,
5536
+ ref
5537
+ };
5538
+ }
5539
+ };
5540
+ /**
5541
+ * Parses and canonicalizes a caller-supplied ref through the shared parser.
5542
+ * Returns null for anything that is not a v1 ref over a 64-char SHA-256 hex
5543
+ * hash; hex case is normalized so index lookups use the canonical form.
5544
+ */
5545
+ function normalizeAttachmentRef(value) {
5546
+ let hash;
5547
+ let version;
5548
+ try {
5549
+ const parsed = parseRef(value);
5550
+ hash = parsed.hash.toLowerCase();
5551
+ version = parsed.version;
5552
+ } catch {
5553
+ return null;
5554
+ }
5555
+ if (version !== 1 || !HASH_PATTERN.test(hash)) return null;
5556
+ return createRef(hash, version);
5557
+ }
5558
+ //#endregion
5559
+ //#region src/graphql/gateway/require-auth-middleware.ts
5560
+ /**
5561
+ * Returns a fetch middleware that rejects anonymous callers with a 401.
5562
+ *
5563
+ * This is the enforcement half of the resolve-and-don't-enforce split set up
5564
+ * by `RESOLVE_CALLER_IDENTITY`: the auth middleware must run before this
5565
+ * one — it verifies the bearer (an invalid token is still a 401 from
5566
+ * there) and records the resolved caller on the request. This middleware
5567
+ * then refuses the request when no caller was resolved, so under `OPEN`
5568
+ * (or `AUTH_ENABLED` with identity resolution) the whole generic surface —
5569
+ * document CRUD, sync, every custom subgraph — is reachable only by
5570
+ * authenticated callers, without locking out non-admins the way
5571
+ * `ADMIN_ONLY` does.
5572
+ *
5573
+ * It composes in one place in the fetch chain, so it covers every mounted
5574
+ * subgraph, the supergraph, and the SSE handler alike.
5575
+ *
5576
+ * OPTIONS is let through: a CORS preflight never carries a bearer, and
5577
+ * `AuthService.authenticateRequest` already answers it with an anonymous
5578
+ * context — refusing it would break every cross-origin client with a 401.
5579
+ *
5580
+ * Fails closed: a request that reaches this middleware without a resolved
5581
+ * context has no caller, and is rejected. The server never composes this
5582
+ * middleware without the auth middleware in the chain (see
5583
+ * `assertRequireAuthenticatedCallerAllowed` in `server.ts`).
5584
+ */
5585
+ function createRequireAuthFetchMiddleware() {
5586
+ return (next) => async (request) => {
5587
+ if (request.method === "OPTIONS") return next(request);
5588
+ if (getAuthContext(request)?.user === void 0) return new globalThis.Response(JSON.stringify({ error: "Authentication required" }), { status: 401 });
5589
+ return next(request);
5590
+ };
5591
+ }
5592
+ //#endregion
4698
5593
  //#region src/migrations/001_create_document_permissions.ts
4699
5594
  var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
4700
5595
  down: () => down$2,
@@ -4915,17 +5810,27 @@ var AuthService = class {
4915
5810
  constructor(config) {
4916
5811
  this.config = config;
4917
5812
  }
4918
- async authenticateRequest(request) {
4919
- if (!this.config.enabled) return {
5813
+ /**
5814
+ * Whether this service reads the bearer at all. Defaults to `enabled`, so a
5815
+ * config written before `resolveIdentity` existed behaves exactly as it did.
5816
+ */
5817
+ get resolvesIdentity() {
5818
+ return this.config.resolveIdentity ?? this.config.enabled;
5819
+ }
5820
+ /** The context an unresolved request gets: no user, and no admin list to match against. */
5821
+ anonymousContext() {
5822
+ return {
4920
5823
  user: void 0,
4921
5824
  admins: [],
4922
- auth_enabled: false
5825
+ auth_enabled: this.config.enabled
4923
5826
  };
4924
- const method = request.method;
4925
- if (method === "OPTIONS" || method === "GET") return {
5827
+ }
5828
+ async authenticateRequest(request) {
5829
+ if (!this.resolvesIdentity) return this.anonymousContext();
5830
+ if (request.method === "OPTIONS") return {
4926
5831
  user: void 0,
4927
5832
  admins: this.config.admins,
4928
- auth_enabled: true
5833
+ auth_enabled: this.config.enabled
4929
5834
  };
4930
5835
  return this.verifyBearer(request.headers.get("authorization") ?? void 0);
4931
5836
  }
@@ -4934,16 +5839,12 @@ var AuthService = class {
4934
5839
  * middleware that must enforce authentication on every request.
4935
5840
  */
4936
5841
  async verifyBearer(authorization) {
4937
- if (!this.config.enabled) return {
4938
- user: void 0,
4939
- admins: [],
4940
- auth_enabled: false
4941
- };
5842
+ if (!this.resolvesIdentity) return this.anonymousContext();
4942
5843
  const token = authorization?.split(" ")[1];
4943
5844
  if (!token) return {
4944
5845
  user: void 0,
4945
5846
  admins: this.config.admins,
4946
- auth_enabled: true
5847
+ auth_enabled: this.config.enabled
4947
5848
  };
4948
5849
  try {
4949
5850
  const verified = await this.verifyToken(token);
@@ -4956,16 +5857,19 @@ var AuthService = class {
4956
5857
  return {
4957
5858
  user,
4958
5859
  admins: this.config.admins,
4959
- auth_enabled: true
5860
+ auth_enabled: this.config.enabled
4960
5861
  };
4961
5862
  } catch {
4962
5863
  return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
4963
5864
  }
4964
5865
  }
4965
5866
  async authenticateWebSocketConnection(connectionParams) {
4966
- if (!this.config.enabled) return null;
5867
+ if (!this.resolvesIdentity) return null;
4967
5868
  const authHeader = connectionParams.authorization;
4968
- if (!authHeader) throw new Error("Missing authorization in connection parameters");
5869
+ if (!authHeader) {
5870
+ if (!this.config.enabled) return null;
5871
+ throw new Error("Missing authorization in connection parameters");
5872
+ }
4969
5873
  const token = authHeader.split(" ")[1];
4970
5874
  if (!token) throw new Error("Invalid authorization format");
4971
5875
  const verified = await this.verifyToken(token);
@@ -4984,16 +5888,23 @@ var AuthService = class {
4984
5888
  return await verifyAuthBearerToken(token);
4985
5889
  }
4986
5890
  /**
4987
- * Extract user information from verification result
5891
+ * Extract user information from verification result.
5892
+ *
5893
+ * The issuer is required along with the credential subject. A credential that
5894
+ * verified must carry one -- resolving it is how the signature was checked --
5895
+ * so this rejects a shape that cannot have come from verification rather than
5896
+ * admitting a keyless principal.
4988
5897
  */
4989
5898
  extractUserFromVerification(verified) {
4990
5899
  try {
4991
5900
  const { address, chainId, networkId } = verified.verifiableCredential.credentialSubject;
4992
- if (!address || !chainId || !networkId) return null;
5901
+ const appKey = verified.issuer;
5902
+ if (!address || !chainId || !networkId || !appKey) return null;
4993
5903
  return {
4994
5904
  address,
4995
5905
  chainId,
4996
- networkId
5906
+ networkId,
5907
+ appKey
4997
5908
  };
4998
5909
  } catch {
4999
5910
  return null;
@@ -5042,14 +5953,33 @@ var AuthService = class {
5042
5953
  }
5043
5954
  }
5044
5955
  async fetchCredentialExists(address, chainId, appId) {
5045
- return await fetchDelegationCredential({
5956
+ if (!this.config.verifyCredential) throw new Error("AuthService: credential verification is enabled but no verifyCredential was provided");
5957
+ return this.config.verifyCredential({
5046
5958
  address,
5047
5959
  chainId,
5048
- appDid: appId
5049
- }) !== void 0;
5960
+ appId
5961
+ });
5050
5962
  }
5051
5963
  };
5052
5964
  //#endregion
5965
+ //#region src/services/renown-credential-verifier.ts
5966
+ async function createRenownCredentialVerifier(config = {}) {
5967
+ const switchboardUrl = await resolveSwitchboardEndpoint({
5968
+ switchboardUrl: config.switchboardUrl,
5969
+ baseUrl: config.renownUrl
5970
+ });
5971
+ return async ({ address, chainId, appId }) => {
5972
+ return await fetchDelegationCredential({
5973
+ address,
5974
+ chainId,
5975
+ appDid: appId,
5976
+ switchboardUrl,
5977
+ baseUrl: switchboardUrl ? void 0 : config.renownUrl,
5978
+ discover: false
5979
+ }) !== void 0;
5980
+ };
5981
+ }
5982
+ //#endregion
5053
5983
  //#region src/services/document-permission.service.ts
5054
5984
  /**
5055
5985
  * Service for managing document-level permissions.
@@ -5365,7 +6295,35 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
5365
6295
  };
5366
6296
  }
5367
6297
  //#endregion
6298
+ //#region src/services/renown-config.ts
6299
+ /** Reading credentials from this reactor's own read model needs a host-supplied
6300
+ * verifier: core has no idea which subgraph serves that read model. */
6301
+ function assertCredentialVerifierForSource(source, hasVerifier) {
6302
+ if (source !== "self" || hasVerifier) return;
6303
+ throw new Error("Renown credential verification is set to \"self\" (auth.renown.source or RENOWN_SOURCE) but no verifyCredential was provided. A host that reads its own read model must pass one — see apps/switchboard, which builds it from @renown/sdk createLocalCredentialVerifier — or set RENOWN_SOURCE=remote to verify against a Renown instance.");
6304
+ }
6305
+ function envOverride(value) {
6306
+ const trimmed = value?.trim();
6307
+ return trimmed ? trimmed : void 0;
6308
+ }
6309
+ /** Resolve `auth.renown`; RENOWN_SOURCE / RENOWN_URL / SWITCHBOARD_URL win
6310
+ * over the config file, which wins over the SDK defaults. */
6311
+ function resolveRenownConfig(configured, env, logger) {
6312
+ return {
6313
+ source: resolveRenownSource(envOverride(env.RENOWN_SOURCE) ?? configured?.source, logger),
6314
+ url: envOverride(env.RENOWN_URL) ?? configured?.url,
6315
+ switchboardUrl: envOverride(env.SWITCHBOARD_URL) ?? configured?.switchboardUrl
6316
+ };
6317
+ }
6318
+ function resolveRenownSource(value, logger) {
6319
+ if (value === void 0 || value === null) return "remote";
6320
+ if (value === "self" || value === "remote") return value;
6321
+ logger.warn(`Ignoring invalid renown source "${value}" (expected "self" or "remote") — using "remote"`);
6322
+ return "remote";
6323
+ }
6324
+ //#endregion
5368
6325
  //#region src/utils/db.ts
6326
+ const PGLITE_UTC_PARSERS = { 1114: (value) => /* @__PURE__ */ new Date(`${value.replace(" ", "T")}Z`) };
5369
6327
  function isPG(connectionString) {
5370
6328
  if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
5371
6329
  return false;
@@ -5396,7 +6354,10 @@ function getDbClient(connectionString = void 0, pgliteFactory) {
5396
6354
  if (cached) return cached;
5397
6355
  const isPg = connectionString && isPG(connectionString);
5398
6356
  const client = isPg ? "pg" : ClientPgLite;
5399
- const pgliteInstance = isPg ? void 0 : pgliteFactory ? pgliteFactory(connectionString) : connectionString ? new PGlite({ fs: new AtomicNodeFs(connectionString) }) : new PGlite();
6357
+ const pgliteInstance = isPg ? void 0 : pgliteFactory ? pgliteFactory(connectionString) : connectionString ? new PGlite({
6358
+ fs: new AtomicNodeFs(connectionString),
6359
+ parsers: PGLITE_UTC_PARSERS
6360
+ }) : new PGlite({ parsers: PGLITE_UTC_PARSERS });
5400
6361
  const connection = isPg ? { connectionString } : { pglite: pgliteInstance };
5401
6362
  if (connectionString && !isPg) {
5402
6363
  const dirPath = path.resolve(connectionString, "..");
@@ -5502,12 +6463,23 @@ function assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissio
5502
6463
  * opt-in: it removes the only binding between a token's claimed address and its
5503
6464
  * signing key. Fail-closed — unset NODE_ENV counts as production.
5504
6465
  */
5505
- function assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, env) {
5506
- if (!authEnabled || !skipCredentialVerification) return;
6466
+ function assertSkipCredentialVerificationAllowed(resolvesCallerIdentity, skipCredentialVerification, env) {
6467
+ if (!resolvesCallerIdentity || !skipCredentialVerification) return;
5507
6468
  const inAutomatedTest = env.VITEST === "true" || env.NODE_ENV === "test";
5508
6469
  const acknowledged = env.ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION === "true";
5509
6470
  if (!inAutomatedTest && !acknowledged) throw new Error("SKIP_CREDENTIAL_VERIFICATION is set but refused: it disables the live Renown credential check — the only check binding a token's claimed address to the key that signed it — so honoring it allows identity spoofing, including of admins. It is never safe in production. For local or sandbox use, also set ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION=true to acknowledge the risk; automated test runs (VITEST=true or NODE_ENV=test) are exempt.");
5510
6471
  }
6472
+ /**
6473
+ * Requiring an authenticated caller requires identity resolution to exist at
6474
+ * all: with neither AUTH_ENABLED nor RESOLVE_CALLER_IDENTITY the middleware
6475
+ * never reads a bearer, no `user` is ever resolved, and the
6476
+ * require-authenticated-caller middleware would reject every caller —
6477
+ * authenticated ones included. Refuse to boot rather than run broken.
6478
+ */
6479
+ function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity) {
6480
+ if (!requireAuthenticatedCaller || resolvesCallerIdentity) return;
6481
+ 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).");
6482
+ }
5511
6483
  function createReadinessGate() {
5512
6484
  let ready = false;
5513
6485
  return {
@@ -5517,6 +6489,15 @@ function createReadinessGate() {
5517
6489
  }
5518
6490
  };
5519
6491
  }
6492
+ /**
6493
+ * The GraphiQL explorer page's mount prefix. `path.posix.join` normalizes the
6494
+ * join with `basePath` — a naive template literal would produce `//explorer`
6495
+ * for the default `/` basePath, which express compiles into a route that only
6496
+ * matches `//explorer`, leaving `GET /explorer` a 404.
6497
+ */
6498
+ function getExplorerPrefix(basePath) {
6499
+ return path.posix.join(basePath, "explorer");
6500
+ }
5520
6501
  function resolveAttachmentStoragePath(options) {
5521
6502
  if (options.attachmentStoragePath) return options.attachmentStoragePath;
5522
6503
  if (options.dbPath && !options.dbPath.startsWith("postgres")) return path.resolve(options.dbPath, "..", "attachments");
@@ -5553,12 +6534,33 @@ function makeDbClosers(knexInstance, pglite) {
5553
6534
  return closers;
5554
6535
  }
5555
6536
  /**
6537
+ * The gate sync serving evaluates a document's own policy through, or undefined
6538
+ * when there is none to evaluate.
6539
+ *
6540
+ * It is built here rather than taken off the reactor client because it is not
6541
+ * the same gate reads use: it carries the host's closes-by-default setting,
6542
+ * which withholds the domain scopes of a document nobody has policied yet. That
6543
+ * answer belongs to serving alone -- replay must keep reading an uninitialized
6544
+ * document in full -- so the two gates are deliberately separate objects over
6545
+ * the same model.
6546
+ *
6547
+ * Undefined below `authEnforcement`, where the registered model ignores the auth
6548
+ * scope: gating through it would serve every domain scope of a policied document
6549
+ * to anyone, which is worse than not gating at all.
6550
+ */
6551
+ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
6552
+ if (!reactorModule) return;
6553
+ const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
6554
+ if (!model) return;
6555
+ return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
6556
+ }
6557
+ /**
5556
6558
  * Sets up the subgraph manager and registers subgraphs
5557
6559
  */
5558
- async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient) {
5559
- const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient);
5560
- await graphqlManager.init(subgraphs.core, authFetchMiddleware);
5561
- for (const [, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql");
6560
+ async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate) {
6561
+ const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate);
6562
+ await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
6563
+ for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
5562
6564
  await graphqlManager.updateRouter();
5563
6565
  return graphqlManager;
5564
6566
  }
@@ -5568,26 +6570,45 @@ async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer,
5568
6570
  function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager, module, documentModelRegistry) {
5569
6571
  pkgManager.onDocumentModelsChange((packagedModels) => {
5570
6572
  if (documentModelRegistry) {
5571
- const newModules = Object.values(packagedModels).flat();
5572
- const registeredModules = documentModelRegistry.getAllModules();
5573
- const registeredTypes = new Set(registeredModules.map((m) => m.documentModel.global.id));
5574
- const modulesToRegister = newModules.filter((mod) => !registeredTypes.has(mod.documentModel.global.id));
5575
- if (modulesToRegister.length > 0) {
5576
- const results = documentModelRegistry.registerModules(...modulesToRegister);
5577
- for (const result of results) if (result.status === "success") defaultLogger.info(`Registered new document model: ${result.item.documentModel.global.id}`);
6573
+ const newModules = getUniqueDocumentModels(Object.values(packagedModels).flat());
6574
+ const incomingTypes = new Set(newModules.map((m) => m.documentModel.global.id));
6575
+ if (incomingTypes.size > 0) {
6576
+ documentModelRegistry.unregisterModules(...incomingTypes);
6577
+ const results = documentModelRegistry.registerModules(...newModules);
6578
+ for (const result of results) if (result.status === "success") defaultLogger.info(`Registered document model: ${result.item.documentModel.global.id} v${result.item.version ?? 1}`);
5578
6579
  else defaultLogger.error(`Failed to register document model: ${result.error.message}`);
5579
6580
  }
6581
+ const manifests = pkgManager.getUniqueUpgradeManifests();
6582
+ if (manifests.length > 0) {
6583
+ documentModelRegistry.unregisterUpgradeManifests(...manifests.map((m) => m.documentType));
6584
+ const manifestResults = documentModelRegistry.registerUpgradeManifests(...manifests);
6585
+ for (const result of manifestResults) if (result.status === "error") defaultLogger.error(`Failed to register upgrade manifest: ${result.error.message}`);
6586
+ }
5580
6587
  }
5581
6588
  graphqlManager.regenerateDocumentModelSubgraphs();
5582
6589
  });
6590
+ let knownSubgraphPackages = /* @__PURE__ */ new Set();
5583
6591
  pkgManager.onSubgraphsChange((packagedSubgraphs) => {
5584
6592
  (async () => {
5585
- for (const [, subgraphs] of packagedSubgraphs) for (const subgraph of subgraphs) await graphqlManager.registerSubgraph(subgraph, "graphql");
6593
+ for (const [packageName, subgraphs] of packagedSubgraphs) {
6594
+ const incomingNames = /* @__PURE__ */ new Set();
6595
+ for (const subgraph of subgraphs) {
6596
+ const instance = await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
6597
+ if (!instance) continue;
6598
+ incomingNames.add(instance.name);
6599
+ }
6600
+ await graphqlManager.prunePackageSubgraphs(packageName, incomingNames);
6601
+ }
6602
+ for (const packageName of knownSubgraphPackages) if (!packagedSubgraphs.has(packageName)) await graphqlManager.unregisterPackage(packageName);
6603
+ knownSubgraphPackages = new Set(packagedSubgraphs.keys());
5586
6604
  await graphqlManager.updateRouter();
5587
6605
  })();
5588
6606
  });
6607
+ let knownProcessorPackages = /* @__PURE__ */ new Set();
5589
6608
  pkgManager.onProcessorsChange((processors) => {
5590
6609
  (async () => {
6610
+ for (const packageName of knownProcessorPackages) if (!processors.has(packageName)) await reactorProcessorManager.unregisterFactory(packageName);
6611
+ knownProcessorPackages = new Set(processors.keys());
5591
6612
  for (const [packageName, fns] of processors) {
5592
6613
  await reactorProcessorManager.unregisterFactory(packageName);
5593
6614
  const validBuilders = fns.map((fn) => fn(module)).filter((factory) => typeof factory === "function");
@@ -5633,16 +6654,51 @@ async function _setupCommonInfrastructure(options) {
5633
6654
  const logger = options.logger ?? defaultLogger;
5634
6655
  let admins = [];
5635
6656
  let authEnabled = false;
6657
+ let configuredResolveIdentity;
6658
+ let configuredRequireAuth;
6659
+ let configuredRenown;
5636
6660
  if (options.configFile) {
5637
6661
  const config = getConfig(options.configFile);
5638
6662
  admins = config.auth?.admins.map((a) => a.toLowerCase()) ?? [];
5639
6663
  authEnabled = config.auth?.enabled ?? false;
6664
+ configuredRenown = config.auth?.renown;
5640
6665
  } else if (options.auth) {
5641
6666
  admins = options.auth.admins.map((a) => a.toLowerCase());
5642
6667
  authEnabled = options.auth.enabled;
6668
+ configuredResolveIdentity = options.auth.resolveIdentity;
6669
+ configuredRequireAuth = options.auth.requireAuthenticatedCaller;
5643
6670
  }
5644
- const { AUTH_ENABLED, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
6671
+ 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;
5645
6672
  if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
6673
+ /**
6674
+ * Whether the auth middleware reads the bearer and populates `ctx.user`,
6675
+ * independently of the authorization policy.
6676
+ *
6677
+ * Defaults to `authEnabled`, so a deployment that never sets it behaves
6678
+ * exactly as it did: auth on means the token is read AND the policy is
6679
+ * `ADMIN_ONLY`; auth off means neither. Setting it explicitly is what
6680
+ * separates the two — a server can then know who is calling while the policy
6681
+ * stays `OPEN`, which is the combination custom subgraphs need and the one
6682
+ * `AUTH_ENABLED` alone cannot express.
6683
+ */
6684
+ let resolveCallerIdentity = configuredResolveIdentity ?? authEnabled;
6685
+ if (RESOLVE_CALLER_IDENTITY !== void 0) resolveCallerIdentity = RESOLVE_CALLER_IDENTITY === "true";
6686
+ /**
6687
+ * Whether the require-authenticated-caller middleware is active: anonymous
6688
+ * callers are rejected with a 401 before any subgraph sees the request.
6689
+ *
6690
+ * Off by default, so nothing changes for existing deployments. This is the
6691
+ * enforcement half of the `RESOLVE_CALLER_IDENTITY` split: that one lets a
6692
+ * server know who is calling while the policy stays `OPEN`, and this one
6693
+ * closes the hole it leaves open — under `OPEN`, an anonymous caller
6694
+ * reaches the whole generic surface (document CRUD, sync, every custom
6695
+ * subgraph), because `OpenAuthorizationService` answers `true` to
6696
+ * everything. `ADMIN_ONLY` is the only alternative today, and it locks
6697
+ * out every non-admin; this expresses "authenticated callers allowed,
6698
+ * anonymous not".
6699
+ */
6700
+ let requireAuthenticatedCaller = configuredRequireAuth ?? false;
6701
+ if (REQUIRE_AUTHENTICATED_CALLER !== void 0) requireAuthenticatedCaller = REQUIRE_AUTHENTICATED_CALLER === "true";
5646
6702
  if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
5647
6703
  let defaultProtection = false;
5648
6704
  if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
@@ -5656,30 +6712,46 @@ async function _setupCommonInfrastructure(options) {
5656
6712
  }
5657
6713
  const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
5658
6714
  assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
5659
- assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, process.env);
6715
+ assertSkipCredentialVerificationAllowed(resolveCallerIdentity, skipCredentialVerification, process.env);
6716
+ assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity);
5660
6717
  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.");
5661
6718
  httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
5662
6719
  const readiness = createReadinessGate();
5663
6720
  httpAdapter.getRoute("/ready", () => readiness.isReady() ? new Response("OK", { status: 200 }) : new Response("starting", { status: 503 }));
5664
- const explorerPrefix = `${config.basePath}/explorer`;
6721
+ const explorerPrefix = getExplorerPrefix(config.basePath);
5665
6722
  httpAdapter.getRoute(`${explorerPrefix}/:endpoint?`, (request) => {
5666
6723
  const url = new URL(request.url);
5667
6724
  const suffix = url.pathname.slice(explorerPrefix.length).replace(/^\//, "");
5668
6725
  const endpoint = suffix ? `/${suffix}` : "/graphql";
5669
- const query = url.searchParams.get("query") ?? void 0;
5670
- return new Response(renderGraphqlPlayground(endpoint, query), { headers: { "Content-Type": "text/html" } });
6726
+ const explorerState = decodeExplorerUrlState(url.searchParams.get("explorerURLState") ?? "");
6727
+ const query = explorerState?.query ?? url.searchParams.get("query") ?? void 0;
6728
+ return new Response(renderGraphqlPlayground(endpoint, query, explorerState?.headers ?? {}, explorerState?.variables), { headers: { "Content-Type": "text/html" } });
5671
6729
  });
5672
6730
  let authFetchMiddleware;
6731
+ let requireAuthFetchMiddleware;
5673
6732
  let authService;
5674
- if (authEnabled) {
5675
- logger.info("Setting up Auth middleware");
6733
+ if (resolveCallerIdentity || authEnabled) {
6734
+ logger.info("Setting up Auth middleware (policy enforcement: @enabled, caller identity: @resolve)", authEnabled, resolveCallerIdentity);
6735
+ const renown = options.renown ?? resolveRenownConfig(configuredRenown, process.env, logger);
6736
+ assertCredentialVerifierForSource(renown.source, options.verifyCredential !== void 0);
6737
+ if (options.verifyCredential) logger.info("Renown credentials will be verified by the host's verifier");
6738
+ else logger.info("Renown credentials will be verified against @url", renown.switchboardUrl ?? renown.url ?? "the default Renown instance");
5676
6739
  authService = new AuthService({
5677
6740
  enabled: authEnabled,
6741
+ resolveIdentity: resolveCallerIdentity,
5678
6742
  admins,
5679
6743
  skipCredentialVerification,
5680
- credentialVerificationCacheTtlMs
6744
+ credentialVerificationCacheTtlMs,
6745
+ verifyCredential: options.verifyCredential ?? await createRenownCredentialVerifier({
6746
+ renownUrl: renown.url,
6747
+ switchboardUrl: renown.switchboardUrl
6748
+ })
5681
6749
  });
5682
6750
  authFetchMiddleware = createAuthFetchMiddleware(authService);
6751
+ if (requireAuthenticatedCaller) {
6752
+ requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
6753
+ logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
6754
+ }
5683
6755
  }
5684
6756
  const dbClosers = [];
5685
6757
  const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
@@ -5703,7 +6775,12 @@ async function _setupCommonInfrastructure(options) {
5703
6775
  await mkdir(attachmentStoragePath, { recursive: true });
5704
6776
  const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
5705
6777
  dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
5706
- const attachments = await new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(3600 * 1e3).build();
6778
+ const ATTACHMENT_SWEEP_INTERVAL_MS = 3600 * 1e3;
6779
+ const attachmentBackend = await createStartupAttachmentBackend({ db: attachmentDb });
6780
+ const attachmentBuilder = new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(ATTACHMENT_SWEEP_INTERVAL_MS);
6781
+ if (attachmentBackend) attachmentBuilder.withBackend(attachmentBackend);
6782
+ const attachments = await attachmentBuilder.build();
6783
+ const attachmentReferenceIndex = await new AttachmentReferenceIndexBuilder(attachmentDb).build();
5707
6784
  dbClosers.push(() => {
5708
6785
  attachments.destroy();
5709
6786
  return Promise.resolve();
@@ -5717,12 +6794,14 @@ async function _setupCommonInfrastructure(options) {
5717
6794
  port,
5718
6795
  httpAdapter,
5719
6796
  authFetchMiddleware,
6797
+ requireAuthFetchMiddleware,
5720
6798
  authService,
5721
6799
  relationalDb,
5722
6800
  analyticsStore,
5723
6801
  documentPermissionService,
5724
6802
  authorizationConfig,
5725
6803
  attachments,
6804
+ attachmentReferenceIndex,
5726
6805
  packages,
5727
6806
  dbClosers,
5728
6807
  readiness
@@ -5731,26 +6810,17 @@ async function _setupCommonInfrastructure(options) {
5731
6810
  /**
5732
6811
  * Private helper function containing common setup logic for API initialization
5733
6812
  */
5734
- async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient) {
6813
+ 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) {
5735
6814
  const hostModule = {
5736
- relationalDb,
5737
- analyticsStore,
5738
- processorApp,
5739
- config: options.processorConfig,
5740
- client: reactorClient,
5741
- attachments: createAttachmentClient(attachments.service),
5742
- dispatch: { async execute(docId, branch, actions, signal) {
5743
- const jobInfo = await reactorClient.executeAsync(docId, branch, actions, signal);
5744
- return {
5745
- id: jobInfo.id,
5746
- status: jobInfo.status
5747
- };
5748
- } },
5749
- getReadModel(name) {
5750
- const model = readModels.find((m) => m.name === name);
5751
- if (!model) throw new Error(`Read model "${name}" not found`);
5752
- return model;
5753
- }
6815
+ ...createReactorHostModuleBase({
6816
+ client: reactorClient,
6817
+ readModels,
6818
+ relationalDb,
6819
+ analyticsStore,
6820
+ processorApp,
6821
+ config: options.processorConfig
6822
+ }),
6823
+ attachments: createAttachmentClient(attachments.service)
5754
6824
  };
5755
6825
  const mcpServerEnabled = options.mcp ?? true;
5756
6826
  const logger = options.logger ?? defaultLogger;
@@ -5778,16 +6848,17 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
5778
6848
  const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
5779
6849
  const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
5780
6850
  logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
6851
+ const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection);
5781
6852
  const coreSubgraphs = DefaultCoreSubgraphs.slice();
5782
6853
  coreSubgraphs.push(ReactorSubgraph);
5783
6854
  if (documentPermissionService) {
5784
6855
  coreSubgraphs.push(AuthSubgraph);
5785
6856
  logger.info("Auth subgraph registered (document permissions enabled)");
5786
6857
  }
5787
- const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
6858
+ const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
5788
6859
  extended: subgraphs,
5789
6860
  core: coreSubgraphs
5790
- }, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient);
6861
+ }, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate);
5791
6862
  setupEventListeners(packages, graphqlManager, reactorProcessorManager, hostModule, documentModelRegistry);
5792
6863
  if (mcpServerEnabled) {
5793
6864
  await setupMcpServer({
@@ -5802,6 +6873,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
5802
6873
  graphqlManager,
5803
6874
  packages,
5804
6875
  attachments,
6876
+ attachmentReferenceIndex,
6877
+ attachmentAccess,
5805
6878
  authService,
5806
6879
  dispose: buildApiDispose({
5807
6880
  graphqlManager,
@@ -5850,9 +6923,15 @@ function buildApiDispose(args) {
5850
6923
  };
5851
6924
  }
5852
6925
  async function initializeAndStartAPI(clientInitializer, options, processorApp) {
5853
- const { port, httpAdapter, authFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
5854
- const { documentModels, processors, subgraphs } = await packages.init();
5855
- const { module: reactorClientModule, reactorDriveClient } = await clientInitializer(documentModels);
6926
+ const { port, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
6927
+ const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
6928
+ const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
6929
+ status: "unavailable",
6930
+ reason: "initializer-did-not-report"
6931
+ } } = await clientInitializer(documentModels, {
6932
+ attachmentReferenceWriter: attachmentReferenceIndex.store,
6933
+ upgradeManifests
6934
+ });
5856
6935
  const reactorClient = reactorClientModule.client;
5857
6936
  const syncManager = reactorClientModule.reactorModule?.syncModule?.syncManager;
5858
6937
  if (!syncManager) throw new Error("SyncManager not available from InProcessReactorClientModule");
@@ -5861,11 +6940,13 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
5861
6940
  const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
5862
6941
  if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
5863
6942
  return {
5864
- ...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient),
6943
+ ...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)),
5865
6944
  client: reactorClient,
5866
6945
  syncManager,
5867
6946
  documentModelRegistry,
5868
- readiness
6947
+ readiness,
6948
+ attachmentReferenceProjection,
6949
+ packageManager: packages
5869
6950
  };
5870
6951
  }
5871
6952
  //#endregion
@@ -5896,6 +6977,7 @@ var PackageManagementService = class {
5896
6977
  defaultRegistryUrl;
5897
6978
  httpLoader;
5898
6979
  documentModelRegistry;
6980
+ packageManager;
5899
6981
  logger = childLogger(["reactor-api", "package-management-service"]);
5900
6982
  onModelsChanged;
5901
6983
  loadedModulesCache = /* @__PURE__ */ new Map();
@@ -5904,6 +6986,7 @@ var PackageManagementService = class {
5904
6986
  this.defaultRegistryUrl = options.defaultRegistryUrl;
5905
6987
  this.httpLoader = options.httpLoader;
5906
6988
  this.documentModelRegistry = options.documentModelRegistry;
6989
+ this.packageManager = options.packageManager;
5907
6990
  }
5908
6991
  setOnModelsChanged(callback) {
5909
6992
  this.onModelsChanged = callback;
@@ -5946,6 +7029,7 @@ var PackageManagementService = class {
5946
7029
  this.loadedModulesCache.delete(name);
5947
7030
  if (this.httpLoader) this.httpLoader.documentModelLoader.removeFromCache(name);
5948
7031
  if (this.documentModelRegistry) this.documentModelRegistry.unregisterModules(...existing.documentTypes);
7032
+ if (this.packageManager) this.packageManager.removePackage(name);
5949
7033
  this.triggerModelsChanged();
5950
7034
  this.logger.info("Uninstalled package @name", name);
5951
7035
  return true;
@@ -5969,7 +7053,7 @@ var PackageManagementService = class {
5969
7053
  }
5970
7054
  };
5971
7055
  //#endregion
5972
- export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, assertAuthRequiredForDocumentPermissions, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground };
7056
+ export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground, resolveRenownConfig };
5973
7057
 
5974
7058
  //# sourceMappingURL=index.mjs.map
5975
- //# debugId=e58cea2c-2319-52a8-90cd-3e99d94ba98f
7059
+ //# debugId=f43e08dc-7398-57dd-8824-db5d97ccc6ca