@powerhousedao/reactor-api 6.2.3-dev.25 → 6.2.3-dev.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
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]="6828688f-9737-5852-b599-51439acc4894")}catch(e){}}();
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]="8ab62893-8127-51eb-9d6f-a5900b1823de")}catch(e){}}();
3
3
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
4
- import { C as createAuthorizationService, D as MalformedStoredOperationError, E as ForbiddenError, S as AuthorizedDocumentHandle, T as AuthenticationRequiredError, _ as loadSubgraphs, a as piecesFromCdnList, b as createCanonicalDocumentIdResolver, c as buildGraphQlDriveDocument, d as isSubgraphClass, f as debounce, g as loadProcessors, h as loadDocumentModels, i as pieceListLocation, l as buildGraphqlOperation, o as piecesFromListModule, p as extractUpgradeManifests, r as PIECE_SOURCE_LIST, s as buildGraphQlDocument, u as buildGraphqlOperations, v as BaseSubgraph, w as AuthEvaluationUnsupportedError, x as AuthorizationPolicy, y as CanonicalDocumentIdResolutionError } from "./pieces-BOwV3Ql9.mjs";
4
+ import { C as AuthorizedDocumentHandle, D as ForbiddenError, E as AuthenticationRequiredError, O as MalformedStoredOperationError, S as AuthorizationPolicy, T as AuthEvaluationUnsupportedError, _ as loadSubgraphs, a as piecesFromCdnList, b as CanonicalDocumentIdResolutionError, c as buildGraphQlDriveDocument, d as isSubgraphClass, f as debounce, g as loadProcessors, h as loadDocumentModels, i as pieceListLocation, l as buildGraphqlOperation, o as piecesFromListModule, p as extractUpgradeManifests, r as PIECE_SOURCE_LIST, s as buildGraphQlDocument, u as buildGraphqlOperations, v as BaseSubgraph, w as createAuthorizationService, x as createCanonicalDocumentIdResolver, y as callerSubject } from "./pieces-DMQotfIR.mjs";
5
5
  import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
6
6
  import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
7
7
  import { gql } from "graphql-tag";
8
8
  import { GraphQLError, Kind, parse, print } from "graphql";
9
- import { AuthEnforcementDisabledError, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, ModelReadGate, PropagationMode as PropagationMode$1, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
9
+ import { AuthEnforcementDisabledError, BareReadGate, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, JobStatus, ModelReadGate, PropagationMode as PropagationMode$1, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
10
10
  import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
11
11
  import path, { isAbsolute, join } from "node:path";
12
12
  import { match } from "path-to-regexp";
@@ -49,6 +49,7 @@ import knex from "knex";
49
49
  import ClientPgLite from "knex-pglite";
50
50
  import { KyselyKnexDialect, PGColdDialect } from "kysely-knex";
51
51
  //#region src/graphql/analytics-subgraph.ts
52
+ const analyticsResolvers = AnalyticsResolvers;
52
53
  var AnalyticsSubgraph = class extends BaseSubgraph {
53
54
  analyticsStore;
54
55
  analyticsModel;
@@ -56,13 +57,33 @@ var AnalyticsSubgraph = class extends BaseSubgraph {
56
57
  typeDefs = gql`
57
58
  ${typedefs}
58
59
  `;
59
- resolvers = AnalyticsResolvers;
60
+ /**
61
+ * Every analytics field hangs off `Query.analytics`, so gating it gates them
62
+ * all. Series are aggregates a processor built from many documents and carry
63
+ * no document to decide on, so only a caller with policy-wide read (anyone
64
+ * under OPEN, an admin otherwise) may read them.
65
+ */
66
+ resolvers = {
67
+ ...analyticsResolvers,
68
+ Query: {
69
+ ...analyticsResolvers.Query,
70
+ analytics: (parent, args, ctx) => {
71
+ this.#assertCanReadAnalytics(ctx);
72
+ return analyticsResolvers.Query.analytics(parent, args, ctx);
73
+ }
74
+ }
75
+ };
60
76
  constructor(args) {
61
77
  super(args);
62
78
  this.analyticsStore = args.analyticsStore;
63
79
  this.analyticsModel = new AnalyticsModel(new AnalyticsQueryEngine(this.analyticsStore));
64
80
  args.graphqlManager.setAdditionalContextFields({ dataSources: { db: { Analytics: this.analyticsModel } } });
65
81
  }
82
+ #assertCanReadAnalytics(ctx) {
83
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return;
84
+ if (ctx.user?.address) throw new ForbiddenError("to read analytics");
85
+ throw new AuthenticationRequiredError("to read analytics");
86
+ }
66
87
  };
67
88
  //#endregion
68
89
  //#region src/graphql/auth/schema.graphql
@@ -317,11 +338,11 @@ async function createGatewayAdapter(type, logger) {
317
338
  async function createHttpAdapter(type) {
318
339
  switch (type) {
319
340
  case "express": {
320
- const { createExpressHttpAdapter } = await import("./adapter-http-express-Drbaxszc.mjs");
341
+ const { createExpressHttpAdapter } = await import("./adapter-http-express-CWqziB1p.mjs");
321
342
  return createExpressHttpAdapter();
322
343
  }
323
344
  case "fastify": {
324
- const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-DMPW_huG.mjs");
345
+ const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-CYZQ6FFv.mjs");
325
346
  return createFastifyHttpAdapter();
326
347
  }
327
348
  }
@@ -1917,7 +1938,6 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
1917
1938
  """
1918
1939
  type ${documentName}_DocumentResultPage {
1919
1940
  items: [${documentName}MutationResult!]!
1920
- totalCount: Int!
1921
1941
  hasNextPage: Boolean!
1922
1942
  hasPreviousPage: Boolean!
1923
1943
  cursor: String
@@ -2243,7 +2263,6 @@ const GetDocumentModelsDocument = gql`
2243
2263
  version
2244
2264
  specification
2245
2265
  }
2246
- totalCount
2247
2266
  hasNextPage
2248
2267
  hasPreviousPage
2249
2268
  cursor
@@ -2302,7 +2321,6 @@ const GetDocumentWithOperationsDocument = gql`
2302
2321
  }
2303
2322
  }
2304
2323
  }
2305
- totalCount
2306
2324
  hasNextPage
2307
2325
  hasPreviousPage
2308
2326
  cursor
@@ -2329,7 +2347,6 @@ const GetDocumentOutgoingRelationshipsDocument = gql`
2329
2347
  items {
2330
2348
  ...PHDocumentFields
2331
2349
  }
2332
- totalCount
2333
2350
  hasNextPage
2334
2351
  hasPreviousPage
2335
2352
  cursor
@@ -2353,7 +2370,6 @@ const GetDocumentIncomingRelationshipsDocument = gql`
2353
2370
  items {
2354
2371
  ...PHDocumentFields
2355
2372
  }
2356
- totalCount
2357
2373
  hasNextPage
2358
2374
  hasPreviousPage
2359
2375
  cursor
@@ -2377,7 +2393,6 @@ const GetDocumentOutgoingRelationshipEdgesDocument = gql`
2377
2393
  items {
2378
2394
  ...DocumentRelationshipFields
2379
2395
  }
2380
- totalCount
2381
2396
  hasNextPage
2382
2397
  hasPreviousPage
2383
2398
  cursor
@@ -2401,7 +2416,6 @@ const GetDocumentIncomingRelationshipEdgesDocument = gql`
2401
2416
  items {
2402
2417
  ...DocumentRelationshipFields
2403
2418
  }
2404
- totalCount
2405
2419
  hasNextPage
2406
2420
  hasPreviousPage
2407
2421
  cursor
@@ -2419,7 +2433,6 @@ const FindDocumentsDocument = gql`
2419
2433
  items {
2420
2434
  ...PHDocumentFields
2421
2435
  }
2422
- totalCount
2423
2436
  hasNextPage
2424
2437
  hasPreviousPage
2425
2438
  cursor
@@ -2463,7 +2476,6 @@ const GetDocumentOperationsDocument = gql`
2463
2476
  }
2464
2477
  }
2465
2478
  }
2466
- totalCount
2467
2479
  hasNextPage
2468
2480
  hasPreviousPage
2469
2481
  cursor
@@ -2902,8 +2914,7 @@ function toDocumentModelResultPage(result) {
2902
2914
  cursor: result.nextCursor ?? null,
2903
2915
  hasNextPage: !!result.nextCursor,
2904
2916
  hasPreviousPage: !!result.options.cursor,
2905
- items: models.map(toGqlDocumentModelState),
2906
- totalCount: result.results.length
2917
+ items: models.map(toGqlDocumentModelState)
2907
2918
  };
2908
2919
  }
2909
2920
  /**
@@ -2935,8 +2946,7 @@ function toPhDocumentResultPage(result) {
2935
2946
  cursor: result.nextCursor ?? null,
2936
2947
  hasNextPage: !!result.nextCursor,
2937
2948
  hasPreviousPage: !!result.options.cursor,
2938
- items: result.results.map(toGqlPhDocument),
2939
- totalCount: result.totalCount ?? result.results.length
2949
+ items: result.results.map(toGqlPhDocument)
2940
2950
  };
2941
2951
  }
2942
2952
  /**
@@ -2961,8 +2971,7 @@ function toDocumentRelationshipResultPage(result) {
2961
2971
  cursor: result.nextCursor ?? null,
2962
2972
  hasNextPage: !!result.nextCursor,
2963
2973
  hasPreviousPage: !!result.options.cursor,
2964
- items: result.results.map(toGqlDocumentRelationship),
2965
- totalCount: result.totalCount ?? result.results.length
2974
+ items: result.results.map(toGqlDocumentRelationship)
2966
2975
  };
2967
2976
  }
2968
2977
  /**
@@ -3231,8 +3240,7 @@ function toOperationResultPage(result) {
3231
3240
  cursor: result.nextCursor ?? null,
3232
3241
  hasNextPage: !!result.nextCursor,
3233
3242
  hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
3234
- items: result.results.map(serializeOperationForGraphQL),
3235
- totalCount: result.results.length
3243
+ items: result.results.map(serializeOperationForGraphQL)
3236
3244
  };
3237
3245
  }
3238
3246
  function toGqlDocumentChangeEvent(event) {
@@ -3304,6 +3312,21 @@ function pickDriveClient(reactorClient, driveContainerType, reactorDriveClient)
3304
3312
  }
3305
3313
  return reactorClient.drives;
3306
3314
  }
3315
+ /**
3316
+ * A mutation's document re-read as the caller, since the client reads it back
3317
+ * as its signer. A caller who may write but not read gets it stripped.
3318
+ */
3319
+ async function readAsCaller(reactorClient, document, subject, branch) {
3320
+ if (!subject) return document;
3321
+ try {
3322
+ return await reactorClient.get(document.header.id, {
3323
+ subject,
3324
+ branch
3325
+ });
3326
+ } catch (error) {
3327
+ throw new GraphQLError(`Failed to read the document back: ${error instanceof Error ? error.message : "Unknown error"}`);
3328
+ }
3329
+ }
3307
3330
  async function documentModels(reactorClient, args) {
3308
3331
  const namespace = fromInputMaybe(args.namespace);
3309
3332
  let paging;
@@ -3491,13 +3514,42 @@ async function findDocuments(reactorClient, args, subject) {
3491
3514
  throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3492
3515
  }
3493
3516
  }
3494
- async function jobStatus(reactorClient, args) {
3517
+ /**
3518
+ * The answer for a job the caller may not see, shaped as the reactor answers
3519
+ * an unknown one so that the two cannot be told apart.
3520
+ */
3521
+ function unknownJob(jobId) {
3522
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3523
+ return {
3524
+ id: jobId,
3525
+ documentId: "",
3526
+ status: JobStatus.FAILED,
3527
+ createdAtUtcIso: now,
3528
+ completedAtUtcIso: now,
3529
+ error: {
3530
+ name: "Error",
3531
+ message: "Job not found",
3532
+ stack: ""
3533
+ },
3534
+ consistencyToken: {
3535
+ version: 1,
3536
+ createdAtUtcIso: now,
3537
+ coordinates: []
3538
+ },
3539
+ meta: {
3540
+ batchId: jobId,
3541
+ batchJobIds: [jobId]
3542
+ }
3543
+ };
3544
+ }
3545
+ async function jobStatus(reactorClient, args, serves) {
3495
3546
  let result;
3496
3547
  try {
3497
3548
  result = await reactorClient.getJobStatus(args.jobId);
3498
3549
  } catch (error) {
3499
3550
  throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
3500
3551
  }
3552
+ if (!await serves(result.documentId)) result = unknownJob(args.jobId);
3501
3553
  try {
3502
3554
  return toGqlJobInfo(result);
3503
3555
  } catch (error) {
@@ -3580,7 +3632,7 @@ async function evaluateActions(reactorClient, args, subject) {
3580
3632
  anyDenied: result.anyDenied
3581
3633
  };
3582
3634
  }
3583
- async function createDocument(reactorClient, args, reactorDriveClient) {
3635
+ async function createDocument(reactorClient, args, reactorDriveClient, subject) {
3584
3636
  if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
3585
3637
  const document = args.document;
3586
3638
  if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
@@ -3588,26 +3640,27 @@ async function createDocument(reactorClient, args, reactorDriveClient) {
3588
3640
  let result;
3589
3641
  try {
3590
3642
  if (parentIdentifier) {
3591
- const parent = await reactorClient.get(parentIdentifier);
3643
+ const parent = await reactorClient.get(parentIdentifier, subject && { subject });
3592
3644
  if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parentIdentifier, document);
3593
3645
  else result = await reactorClient.create(document, parentIdentifier);
3594
3646
  } else result = await reactorClient.create(document);
3595
3647
  } catch (error) {
3596
3648
  throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
3597
3649
  }
3650
+ result = await readAsCaller(reactorClient, result, subject);
3598
3651
  try {
3599
3652
  return toGqlPhDocument(result);
3600
3653
  } catch (error) {
3601
3654
  throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3602
3655
  }
3603
3656
  }
3604
- async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
3657
+ async function createEmptyDocument(reactorClient, args, reactorDriveClient, subject) {
3605
3658
  const parentIdentifier = fromInputMaybe(args.parentIdentifier);
3606
3659
  const name = fromInputMaybe(args.name);
3607
3660
  let result;
3608
3661
  try {
3609
3662
  if (parentIdentifier) {
3610
- const parent = await reactorClient.get(parentIdentifier);
3663
+ const parent = await reactorClient.get(parentIdentifier, subject && { subject });
3611
3664
  if (isDriveContainerType(parent.header.documentType)) {
3612
3665
  const module = await reactorClient.getDocumentModelModule(args.documentType);
3613
3666
  const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
@@ -3618,13 +3671,14 @@ async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
3618
3671
  } catch (error) {
3619
3672
  throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
3620
3673
  }
3674
+ result = await readAsCaller(reactorClient, result, subject);
3621
3675
  try {
3622
3676
  return toGqlPhDocument(result);
3623
3677
  } catch (error) {
3624
3678
  throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3625
3679
  }
3626
3680
  }
3627
- async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient) {
3681
+ async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient, subject) {
3628
3682
  const parentIdentifier = fromInputMaybe(args.parentIdentifier);
3629
3683
  const name = fromInputMaybe(args.name);
3630
3684
  const slug = fromInputMaybe(args.slug);
@@ -3652,7 +3706,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
3652
3706
  if (parentIdentifier) {
3653
3707
  let parent;
3654
3708
  try {
3655
- parent = await reactorClient.get(parentIdentifier);
3709
+ parent = await reactorClient.get(parentIdentifier, subject && { subject });
3656
3710
  } catch (error) {
3657
3711
  throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3658
3712
  }
@@ -3673,6 +3727,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
3673
3727
  } catch (error) {
3674
3728
  throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
3675
3729
  }
3730
+ result = await readAsCaller(reactorClient, result, subject);
3676
3731
  try {
3677
3732
  return toGqlPhDocument(result);
3678
3733
  } catch (error) {
@@ -3689,12 +3744,12 @@ const DEFAULT_BRANCH = "main";
3689
3744
  * the schema cannot express: a signature travels as one string, and verification
3690
3745
  * reads it as the tuple it was.
3691
3746
  */
3692
- async function execute(reactorClient, args) {
3747
+ async function execute(reactorClient, args, subject) {
3693
3748
  const actions = toSubmittableActions(args.actions);
3694
3749
  const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
3695
3750
  let result;
3696
3751
  try {
3697
- result = await reactorClient.execute(args.documentIdentifier, branch, actions);
3752
+ result = await reactorClient.execute(args.documentIdentifier, branch, actions, void 0, subject);
3698
3753
  } catch (error) {
3699
3754
  throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
3700
3755
  }
@@ -3722,7 +3777,7 @@ async function executeAsync(reactorClient, args) {
3722
3777
  }
3723
3778
  return toGqlJobInfo(job);
3724
3779
  }
3725
- async function mutateDocument(reactorClient, args) {
3780
+ async function mutateDocument(reactorClient, args, subject) {
3726
3781
  let validatedActions;
3727
3782
  try {
3728
3783
  validatedActions = validateActions(args.actions);
@@ -3733,7 +3788,7 @@ async function mutateDocument(reactorClient, args) {
3733
3788
  const branch = args.view?.branch ?? "main";
3734
3789
  let result;
3735
3790
  try {
3736
- result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
3791
+ result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions, void 0, subject);
3737
3792
  } catch (error) {
3738
3793
  throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
3739
3794
  }
@@ -3760,7 +3815,7 @@ async function mutateDocumentAsync(reactorClient, args) {
3760
3815
  }
3761
3816
  return result.id;
3762
3817
  }
3763
- async function renameDocument(reactorClient, args, signal) {
3818
+ async function renameDocument(reactorClient, args, signal, subject) {
3764
3819
  const branch = fromInputMaybe(args.branch);
3765
3820
  let result;
3766
3821
  try {
@@ -3768,13 +3823,14 @@ async function renameDocument(reactorClient, args, signal) {
3768
3823
  } catch (error) {
3769
3824
  throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
3770
3825
  }
3826
+ result = await readAsCaller(reactorClient, result, subject, branch);
3771
3827
  try {
3772
3828
  return toGqlPhDocument(result);
3773
3829
  } catch (error) {
3774
3830
  throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3775
3831
  }
3776
3832
  }
3777
- async function setPreferredEditor(reactorClient, args, signal) {
3833
+ async function setPreferredEditor(reactorClient, args, signal, subject) {
3778
3834
  const branch = fromInputMaybe(args.branch);
3779
3835
  const preferredEditor = fromInputMaybe(args.preferredEditor) ?? null;
3780
3836
  let result;
@@ -3783,13 +3839,14 @@ async function setPreferredEditor(reactorClient, args, signal) {
3783
3839
  } catch (error) {
3784
3840
  throw new GraphQLError(`Failed to set preferred editor: ${error instanceof Error ? error.message : "Unknown error"}`);
3785
3841
  }
3842
+ result = await readAsCaller(reactorClient, result, subject, branch);
3786
3843
  try {
3787
3844
  return toGqlPhDocument(result);
3788
3845
  } catch (error) {
3789
3846
  throw new GraphQLError(`Failed to convert updated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3790
3847
  }
3791
3848
  }
3792
- async function addRelationship(reactorClient, args) {
3849
+ async function addRelationship(reactorClient, args, subject) {
3793
3850
  const branch = fromInputMaybe(args.branch);
3794
3851
  const metadata = fromInputMaybe(args.metadata);
3795
3852
  let result;
@@ -3798,13 +3855,14 @@ async function addRelationship(reactorClient, args) {
3798
3855
  } catch (error) {
3799
3856
  throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3800
3857
  }
3858
+ result = await readAsCaller(reactorClient, result, subject, branch);
3801
3859
  try {
3802
3860
  return toGqlPhDocument(result);
3803
3861
  } catch (error) {
3804
3862
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3805
3863
  }
3806
3864
  }
3807
- async function updateRelationship(reactorClient, args) {
3865
+ async function updateRelationship(reactorClient, args, subject) {
3808
3866
  const branch = fromInputMaybe(args.branch);
3809
3867
  const metadata = fromInputMaybe(args.metadata) ?? null;
3810
3868
  let result;
@@ -3813,13 +3871,14 @@ async function updateRelationship(reactorClient, args) {
3813
3871
  } catch (error) {
3814
3872
  throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3815
3873
  }
3874
+ result = await readAsCaller(reactorClient, result, subject, branch);
3816
3875
  try {
3817
3876
  return toGqlPhDocument(result);
3818
3877
  } catch (error) {
3819
3878
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3820
3879
  }
3821
3880
  }
3822
- async function removeRelationship(reactorClient, args) {
3881
+ async function removeRelationship(reactorClient, args, subject) {
3823
3882
  const branch = fromInputMaybe(args.branch);
3824
3883
  let result;
3825
3884
  try {
@@ -3827,13 +3886,14 @@ async function removeRelationship(reactorClient, args) {
3827
3886
  } catch (error) {
3828
3887
  throw new GraphQLError(`Failed to remove relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3829
3888
  }
3889
+ result = await readAsCaller(reactorClient, result, subject, branch);
3830
3890
  try {
3831
3891
  return toGqlPhDocument(result);
3832
3892
  } catch (error) {
3833
3893
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3834
3894
  }
3835
3895
  }
3836
- async function moveRelationship(reactorClient, args) {
3896
+ async function moveRelationship(reactorClient, args, subject) {
3837
3897
  const branch = fromInputMaybe(args.branch);
3838
3898
  let result;
3839
3899
  try {
@@ -3841,6 +3901,10 @@ async function moveRelationship(reactorClient, args) {
3841
3901
  } catch (error) {
3842
3902
  throw new GraphQLError(`Failed to move relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3843
3903
  }
3904
+ result = {
3905
+ source: await readAsCaller(reactorClient, result.source, subject, branch),
3906
+ target: await readAsCaller(reactorClient, result.target, subject, branch)
3907
+ };
3844
3908
  try {
3845
3909
  return {
3846
3910
  source: toGqlPhDocument(result.source),
@@ -4130,6 +4194,16 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4130
4194
  const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
4131
4195
  return this.resolvers[`${documentName}Mutations`];
4132
4196
  }
4197
+ /** Drops the items the host's own ACL refuses the caller. */
4198
+ async #readableItems(page, ctx) {
4199
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
4200
+ const items = [];
4201
+ for (const item of page.items) if (await this.canReadDocument(item.id, ctx)) items.push(item);
4202
+ return {
4203
+ ...page,
4204
+ items
4205
+ };
4206
+ }
4133
4207
  /**
4134
4208
  * Generate __resolveType functions for union types found in the document model schema.
4135
4209
  * Parses the state schema to find union definitions and their member types,
@@ -4190,7 +4264,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4190
4264
  const result = await document(this.reactorClient, {
4191
4265
  identifier,
4192
4266
  view
4193
- });
4267
+ }, this.viewSubject(ctx));
4194
4268
  if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
4195
4269
  await this.assertCanReadCanonical(result.document.id, ctx);
4196
4270
  return result;
@@ -4200,17 +4274,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4200
4274
  const result = await findDocuments(this.reactorClient, {
4201
4275
  search: { type: documentType },
4202
4276
  paging
4203
- });
4204
- if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
4205
- const filteredItems = [];
4206
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
4207
- return {
4208
- ...result,
4209
- items: filteredItems,
4210
- totalCount: filteredItems.length
4211
- };
4212
- }
4213
- return result;
4277
+ }, this.viewSubject(ctx));
4278
+ return this.#readableItems(result, ctx);
4214
4279
  },
4215
4280
  findDocuments: async (_, args, ctx) => {
4216
4281
  const { search, view, paging } = args;
@@ -4221,17 +4286,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4221
4286
  },
4222
4287
  view,
4223
4288
  paging
4224
- });
4225
- if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
4226
- const filteredItems = [];
4227
- for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
4228
- return {
4229
- ...result,
4230
- items: filteredItems,
4231
- totalCount: filteredItems.length
4232
- };
4233
- }
4234
- return result;
4289
+ }, this.viewSubject(ctx));
4290
+ return this.#readableItems(result, ctx);
4235
4291
  },
4236
4292
  documentOutgoingRelationships: async (_, args, ctx) => {
4237
4293
  const { relationshipType, view, paging } = args;
@@ -4241,23 +4297,24 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4241
4297
  relationshipType,
4242
4298
  view,
4243
4299
  paging
4244
- });
4245
- const filteredItems = result.items.filter((item) => item.documentType === documentType);
4300
+ }, this.viewSubject(ctx));
4301
+ const readable = await this.#readableItems(result, ctx);
4302
+ const filteredItems = readable.items.filter((item) => item.documentType === documentType);
4246
4303
  return {
4247
- ...result,
4248
- items: filteredItems,
4249
- totalCount: filteredItems.length
4304
+ ...readable,
4305
+ items: filteredItems
4250
4306
  };
4251
4307
  },
4252
4308
  documentIncomingRelationships: async (_, args, ctx) => {
4253
4309
  const { relationshipType, view, paging } = args;
4254
4310
  const handle = await this.assertCanRead(args.targetIdentifier, ctx);
4255
- return documentIncomingRelationships(this.reactorClient, {
4311
+ const result = await documentIncomingRelationships(this.reactorClient, {
4256
4312
  targetIdentifier: handle.fetchIdentifier,
4257
4313
  relationshipType,
4258
4314
  view,
4259
4315
  paging
4260
- });
4316
+ }, this.viewSubject(ctx));
4317
+ return this.#readableItems(result, ctx);
4261
4318
  }
4262
4319
  },
4263
4320
  Mutation: { [documentName]: () => ({}) },
@@ -4275,14 +4332,14 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4275
4332
  slug,
4276
4333
  preferredEditor,
4277
4334
  initialState: initialState ?? {}
4278
- }, this.graphqlManager.reactorDriveClient);
4335
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4279
4336
  else createdDoc = await createEmptyDocument(this.reactorClient, {
4280
4337
  documentType,
4281
4338
  parentIdentifier,
4282
4339
  name
4283
- }, this.graphqlManager.reactorDriveClient);
4340
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4284
4341
  if (ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4285
- if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
4342
+ if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)], void 0, this.viewSubject(ctx)));
4286
4343
  return createdDoc;
4287
4344
  },
4288
4345
  createEmptyDocument: async (_, args, ctx) => {
@@ -4292,7 +4349,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4292
4349
  const result = await createEmptyDocument(this.reactorClient, {
4293
4350
  documentType,
4294
4351
  parentIdentifier
4295
- }, this.graphqlManager.reactorDriveClient);
4352
+ }, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
4296
4353
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4297
4354
  return result;
4298
4355
  },
@@ -4300,11 +4357,11 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4300
4357
  mutations[camelCase(op.name)] = async (_, args, ctx) => {
4301
4358
  const { docId, input } = args;
4302
4359
  const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
4303
- if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4360
+ if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4304
4361
  const action = this.documentModel.actions[camelCase(op.name)];
4305
4362
  if (!action) throw new GraphQLError(`Action ${op.name} not found`);
4306
4363
  try {
4307
- return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
4364
+ return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)], void 0, this.viewSubject(ctx)));
4308
4365
  } catch (error) {
4309
4366
  throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
4310
4367
  }
@@ -4312,7 +4369,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
4312
4369
  mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
4313
4370
  const { docId, input } = args;
4314
4371
  const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
4315
- if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4372
+ if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
4316
4373
  const action = this.documentModel.actions[camelCase(op.name)];
4317
4374
  if (!action) throw new GraphQLError(`Action ${op.name} not found`);
4318
4375
  try {
@@ -4616,19 +4673,21 @@ var GraphQLManager = class {
4616
4673
  const matched = driveMatcher(url.pathname);
4617
4674
  const driveIdOrSlug = matched ? matched.params.drive : void 0;
4618
4675
  if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
4676
+ const user = getAuthContext(request)?.user;
4619
4677
  try {
4620
- const driveDoc = await this.reactorClient.get(driveIdOrSlug);
4621
- if (!await this.authorizationService.canRead(driveDoc.header.id, getAuthContext(request)?.user?.address)) {
4678
+ const driveDoc = await this.reactorClient.get(driveIdOrSlug, { subject: callerSubject(user) });
4679
+ if (!await this.authorizationService.canRead(driveDoc.header.id, user?.address)) {
4622
4680
  this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
4623
4681
  return Response.json({ error: "Drive not found" }, { status: 404 });
4624
4682
  }
4625
4683
  const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto")?.split(",")[0].trim() ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("x-forwarded-host")?.split(",")[0].trim() ?? request.headers.get("host") ?? ""}${(request.headers.get("x-forwarded-prefix")?.split(",")[0].trim().replace(/\/$/, "") ?? "") + (this.path === "/" ? "" : this.path)}/graphql/r`;
4684
+ const global = driveDoc.state.global;
4626
4685
  return Response.json({
4627
4686
  id: driveDoc.header.id,
4628
4687
  slug: driveDoc.header.slug,
4629
4688
  meta: driveDoc.header.meta,
4630
- name: driveDoc.state.global.name || driveDoc.header.name,
4631
- icon: driveDoc.state.global.icon ?? void 0,
4689
+ name: global?.name || driveDoc.header.name,
4690
+ icon: global?.icon ?? void 0,
4632
4691
  ...graphqlEndpoint && { graphqlEndpoint }
4633
4692
  });
4634
4693
  } catch (error) {
@@ -5412,7 +5471,6 @@ const DocumentModelStateDTO = z.object({
5412
5471
  }).strip();
5413
5472
  const DocumentModelResultPageDTO = z.object({
5414
5473
  items: z.array(DocumentModelStateDTO),
5415
- totalCount: z.number().int(),
5416
5474
  hasNextPage: z.boolean(),
5417
5475
  hasPreviousPage: z.boolean(),
5418
5476
  cursor: z.string().nullable().optional()
@@ -5433,7 +5491,6 @@ const PHDocumentDTO = z.object({
5433
5491
  }).strip();
5434
5492
  const PHDocumentResultPageDTO = z.object({
5435
5493
  items: z.array(PHDocumentDTO),
5436
- totalCount: z.number().int(),
5437
5494
  hasNextPage: z.boolean(),
5438
5495
  hasPreviousPage: z.boolean(),
5439
5496
  cursor: z.string().nullable().optional()
@@ -5448,7 +5505,6 @@ const DocumentRelationshipDTO = z.object({
5448
5505
  }).strip();
5449
5506
  const DocumentRelationshipResultPageDTO = z.object({
5450
5507
  items: z.array(DocumentRelationshipDTO),
5451
- totalCount: z.number().int(),
5452
5508
  hasNextPage: z.boolean(),
5453
5509
  hasPreviousPage: z.boolean(),
5454
5510
  cursor: z.string().nullable().optional()
@@ -5503,7 +5559,6 @@ const OperationDTO = z.object({
5503
5559
  }).strip();
5504
5560
  const OperationResultPageDTO = z.object({
5505
5561
  items: z.array(OperationDTO),
5506
- totalCount: z.number().int(),
5507
5562
  hasNextPage: z.boolean(),
5508
5563
  hasPreviousPage: z.boolean(),
5509
5564
  cursor: z.string().nullable().optional()
@@ -5639,7 +5694,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
5639
5694
  }
5640
5695
  //#endregion
5641
5696
  //#region src/graphql/reactor/schema.graphql
5642
- 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\n# A directed edge between two documents, with the metadata recorded against it.\ntype DocumentRelationship {\n sourceId: String!\n targetId: String!\n relationshipType: String!\n metadata: JSONObject\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\ntype DocumentRelationshipResultPage {\n items: [DocumentRelationship!]!\n 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\n \"\"\"\n What the job produced, once it has produced anything. Nullable for the same\n reason as `JobInfo.result`: a job that has not written yet has produced\n nothing, and an empty object would claim otherwise.\n \"\"\"\n result: JSONObject\n\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"\n The index of the last operation in the scope the action applies to.\n \"\"\"\n prevOpIndex: Int\n\n \"\"\"\n The hash of the state the action applies to.\n \"\"\"\n prevOpHash: String\n\n \"\"\"\n Covers replay of an operation that would otherwise be a no-op.\n \"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput 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 # Get the outgoing relationship edges of a source document. Unlike\n # documentOutgoingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentOutgoingRelationshipEdges(\n sourceIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Get the incoming relationship edges of a target document. Unlike\n # documentIncomingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentIncomingRelationshipEdges(\n targetIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n 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. Adding one that already exists is\n # a no-op, metadata included -- use updateRelationship to change an edge.\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Replace the metadata of an existing relationship, preserving its createdAt.\n # Omitting metadata, or passing null, clears it.\n updateRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\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";
5697
+ 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 hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\n# A directed edge between two documents, with the metadata recorded against it.\ntype DocumentRelationship {\n sourceId: String!\n targetId: String!\n relationshipType: String!\n metadata: JSONObject\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\ntype DocumentRelationshipResultPage {\n items: [DocumentRelationship!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n \"\"\"\n How the origin classified the failure. A peer needs this to mirror the origin's\n quarantine decision; without it a held auth operation would freeze the document\n on the peer side while the origin kept syncing.\n \"\"\"\n errorType: String\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Null until then, which\n is the state every job is in when it is handed back from a submission.\n \"\"\"\n result: JSONObject\n\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Nullable for the same\n reason as `JobInfo.result`: a job that has not written yet has produced\n nothing, and an empty object would claim otherwise.\n \"\"\"\n result: JSONObject\n\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"\n The index of the last operation in the scope the action applies to.\n \"\"\"\n prevOpIndex: Int\n\n \"\"\"\n The hash of the state the action applies to.\n \"\"\"\n prevOpHash: String\n\n \"\"\"\n Covers replay of an operation that would otherwise be a no-op.\n \"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput 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 # Get the outgoing relationship edges of a source document. Unlike\n # documentOutgoingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentOutgoingRelationshipEdges(\n sourceIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Get the incoming relationship edges of a target document. Unlike\n # documentIncomingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentIncomingRelationshipEdges(\n targetIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n 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. Adding one that already exists is\n # a no-op, metadata included -- use updateRelationship to change an edge.\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Replace the metadata of an existing relationship, preserving its createdAt.\n # Omitting metadata, or passing null, clears it.\n updateRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\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";
5643
5698
  //#endregion
5644
5699
  //#region src/graphql/reactor/pubsub.ts
5645
5700
  const pubSub = new PubSub();
@@ -5650,26 +5705,79 @@ const SUBSCRIPTION_TRIGGERS = {
5650
5705
  DOCUMENT_CHANGES: "DOCUMENT_CHANGES",
5651
5706
  JOB_CHANGES: "JOB_CHANGES"
5652
5707
  };
5653
- let globalDocumentUnsubscribe = null;
5654
- let documentSubscriberCount = 0;
5655
- function ensureGlobalDocumentSubscription(reactorClient) {
5656
- if (documentSubscriberCount === 0) globalDocumentUnsubscribe = reactorClient.subscribe({}, (event) => {
5657
- const payload = {
5658
- documentChanges: event,
5659
- search: {},
5660
- view: void 0
5708
+ function subjectKey(subject) {
5709
+ return `${subject.address ?? ""}|${subject.key ?? ""}`;
5710
+ }
5711
+ /**
5712
+ * The reactor's change feed, read once per distinct subject and shared by that
5713
+ * subject's GraphQL subscriptions. Each feed is read as its subject, so the
5714
+ * client withholds what that subject may not read; one feed read with no view
5715
+ * would gate every subscriber as the host.
5716
+ */
5717
+ var DocumentChangeFeed = class {
5718
+ reactorClient;
5719
+ #pubSub = new PubSub();
5720
+ #feeds = /* @__PURE__ */ new Map();
5721
+ constructor(reactorClient) {
5722
+ this.reactorClient = reactorClient;
5723
+ }
5724
+ subscribe(subject) {
5725
+ const key = subjectKey(subject);
5726
+ const topic = `${SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES}:${key}`;
5727
+ const iterator = this.#pubSub.asyncIterableIterator(topic);
5728
+ this.#acquire(key, topic, subject);
5729
+ let released = false;
5730
+ const release = () => {
5731
+ if (!released) {
5732
+ released = true;
5733
+ this.#release(key);
5734
+ }
5661
5735
  };
5662
- pubSub.publish(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES, payload);
5663
- });
5664
- documentSubscriberCount++;
5665
- return () => {
5666
- documentSubscriberCount--;
5667
- if (documentSubscriberCount === 0 && globalDocumentUnsubscribe) {
5668
- globalDocumentUnsubscribe();
5669
- globalDocumentUnsubscribe = null;
5736
+ return {
5737
+ next: () => iterator.next(),
5738
+ return: () => {
5739
+ release();
5740
+ return iterator.return();
5741
+ },
5742
+ throw: (error) => {
5743
+ release();
5744
+ return iterator.throw(error);
5745
+ },
5746
+ [Symbol.asyncIterator]() {
5747
+ return this;
5748
+ }
5749
+ };
5750
+ }
5751
+ #acquire(key, topic, subject) {
5752
+ const feed = this.#feeds.get(key);
5753
+ if (feed) {
5754
+ feed.subscribers++;
5755
+ return;
5670
5756
  }
5671
- };
5672
- }
5757
+ const view = { subject };
5758
+ const unsubscribe = this.reactorClient.subscribe({}, (event) => {
5759
+ const payload = {
5760
+ documentChanges: event,
5761
+ search: {},
5762
+ view
5763
+ };
5764
+ this.#pubSub.publish(topic, payload);
5765
+ }, view);
5766
+ this.#feeds.set(key, {
5767
+ unsubscribe,
5768
+ subscribers: 1
5769
+ });
5770
+ }
5771
+ #release(key) {
5772
+ const feed = this.#feeds.get(key);
5773
+ if (!feed) return;
5774
+ feed.subscribers--;
5775
+ if (feed.subscribers === 0) {
5776
+ this.#feeds.delete(key);
5777
+ feed.unsubscribe();
5778
+ }
5779
+ }
5780
+ };
5673
5781
  const activeJobSubscriptions = /* @__PURE__ */ new Map();
5674
5782
  function ensureJobSubscription(reactorClient, jobId) {
5675
5783
  let subscription = activeJobSubscriptions.get(jobId);
@@ -5734,6 +5842,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
5734
5842
  }
5735
5843
  name = "r";
5736
5844
  hasSubscriptions = true;
5845
+ #documentChanges;
5846
+ get documentChanges() {
5847
+ this.#documentChanges ??= new DocumentChangeFeed(this.reactorClient);
5848
+ return this.#documentChanges;
5849
+ }
5737
5850
  /**
5738
5851
  * Check operation-level permissions for an array of actions.
5739
5852
  * Delegates to base assertCanExecuteOperation for each action.
@@ -5772,10 +5885,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5772
5885
  * or they become a way to enumerate around that check. Supreme admins read
5773
5886
  * everything, so the per-edge check is skipped for them.
5774
5887
  *
5775
- * The counts describe what is served; the cursor describes where to resume in
5776
- * the underlying unfiltered stream, so it is left as it came. A filtered page
5777
- * is therefore often shorter than the requested limit, and that is the stream
5778
- * continuing rather than a sign it ended.
5888
+ * The cursor describes where to resume in the underlying unfiltered stream,
5889
+ * so it is left as it came. A filtered page is therefore often shorter than
5890
+ * the requested limit, and that is the stream continuing rather than a sign
5891
+ * it ended.
5779
5892
  */
5780
5893
  async filterRelationshipEdges(page, farEnd, ctx) {
5781
5894
  if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
@@ -5790,19 +5903,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
5790
5903
  }
5791
5904
  if (canRead) items.push(edge);
5792
5905
  }
5793
- return this.servedPage(page, items);
5794
- }
5795
- /**
5796
- * Restates a page's totalCount over the items that survived a read filter, so
5797
- * that the difference between the two cannot be read off as a count of what
5798
- * the caller was not allowed to see.
5799
- */
5800
- servedPage(page, items) {
5801
- const withheld = page.items.length - items.length;
5802
5906
  return {
5803
5907
  ...page,
5804
- items,
5805
- totalCount: Math.max(0, page.totalCount - withheld)
5908
+ items
5806
5909
  };
5807
5910
  }
5808
5911
  /**
@@ -5838,9 +5941,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
5838
5941
  * reports the liveness that keeps it from being reclaimed -- but they never
5839
5942
  * adopt, since the push path has no drive check to clear the claimant.
5840
5943
  *
5841
- * Nothing is enforced or adopted without a serving gate. Below
5842
- * `authEnforcement` there is no policy being enforced for the channel to
5843
- * belong to, and refusing a poll there would break sync for no gain.
5944
+ * Nothing is enforced or adopted without a serving gate: with no gate the
5945
+ * channel serves every subject alike, so there is no subject for it to
5946
+ * belong to.
5844
5947
  */
5845
5948
  async #bindOrRefuseChannel(channelId, ctx, options = {}) {
5846
5949
  if (!this.syncServingGate) return;
@@ -5934,7 +6037,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5934
6037
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
5935
6038
  const filteredItems = [];
5936
6039
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
5937
- return this.servedPage(result, filteredItems);
6040
+ return {
6041
+ ...result,
6042
+ items: filteredItems
6043
+ };
5938
6044
  }
5939
6045
  return result;
5940
6046
  } catch (error) {
@@ -5980,7 +6086,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5980
6086
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
5981
6087
  const filteredItems = [];
5982
6088
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
5983
- return this.servedPage(result, filteredItems);
6089
+ return {
6090
+ ...result,
6091
+ items: filteredItems
6092
+ };
5984
6093
  }
5985
6094
  return result;
5986
6095
  } catch (error) {
@@ -5988,10 +6097,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5988
6097
  throw error;
5989
6098
  }
5990
6099
  },
5991
- jobStatus: async (_parent, args) => {
6100
+ jobStatus: async (_parent, args, ctx) => {
5992
6101
  this.logger.debug("jobStatus(@args)", args);
5993
6102
  try {
5994
- return await jobStatus(this.reactorClient, args);
6103
+ return await jobStatus(this.reactorClient, args, (id) => this.servesDocument(id, ctx));
5995
6104
  } catch (error) {
5996
6105
  this.logger.error("Error in jobStatus: @Error", error);
5997
6106
  throw error;
@@ -6075,7 +6184,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6075
6184
  const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
6076
6185
  await this.assertCanWriteCanonical(parent.document.id, ctx);
6077
6186
  } else this.assertCanCreate(ctx);
6078
- const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
6187
+ const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
6079
6188
  if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
6080
6189
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
6081
6190
  return result;
@@ -6091,7 +6200,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6091
6200
  const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
6092
6201
  await this.assertCanWriteCanonical(parent.document.id, ctx);
6093
6202
  } else this.assertCanCreate(ctx);
6094
- const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
6203
+ const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
6095
6204
  if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
6096
6205
  if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
6097
6206
  return result;
@@ -6107,7 +6216,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6107
6216
  return await execute(this.reactorClient, {
6108
6217
  ...args,
6109
6218
  documentIdentifier: handle.fetchIdentifier
6110
- });
6219
+ }, this.viewSubject(ctx));
6111
6220
  } catch (error) {
6112
6221
  this.logger.error("Error in execute(@args): @Error", args, error);
6113
6222
  throw error;
@@ -6133,7 +6242,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6133
6242
  return await mutateDocument(this.reactorClient, {
6134
6243
  ...args,
6135
6244
  documentIdentifier: handle.fetchIdentifier
6136
- });
6245
+ }, this.viewSubject(ctx));
6137
6246
  } catch (error) {
6138
6247
  this.logger.error("Error in mutateDocument(@args): @Error", args, error);
6139
6248
  throw error;
@@ -6159,7 +6268,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6159
6268
  return await renameDocument(this.reactorClient, {
6160
6269
  ...args,
6161
6270
  documentIdentifier: handle.fetchIdentifier
6162
- });
6271
+ }, void 0, this.viewSubject(ctx));
6163
6272
  } catch (error) {
6164
6273
  this.logger.error("Error in renameDocument(@args): @Error", args, error);
6165
6274
  throw error;
@@ -6172,7 +6281,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6172
6281
  return await setPreferredEditor(this.reactorClient, {
6173
6282
  ...args,
6174
6283
  documentIdentifier: handle.fetchIdentifier
6175
- });
6284
+ }, void 0, this.viewSubject(ctx));
6176
6285
  } catch (error) {
6177
6286
  this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
6178
6287
  throw error;
@@ -6185,7 +6294,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6185
6294
  return await addRelationship(this.reactorClient, {
6186
6295
  ...args,
6187
6296
  sourceIdentifier: handle.fetchIdentifier
6188
- });
6297
+ }, this.viewSubject(ctx));
6189
6298
  } catch (error) {
6190
6299
  this.logger.error("Error in addRelationship(@args): @Error", args, error);
6191
6300
  throw error;
@@ -6198,7 +6307,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6198
6307
  return await updateRelationship(this.reactorClient, {
6199
6308
  ...args,
6200
6309
  sourceIdentifier: handle.fetchIdentifier
6201
- });
6310
+ }, this.viewSubject(ctx));
6202
6311
  } catch (error) {
6203
6312
  this.logger.error("Error in updateRelationship(@args): @Error", args, error);
6204
6313
  throw error;
@@ -6211,7 +6320,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6211
6320
  return await removeRelationship(this.reactorClient, {
6212
6321
  ...args,
6213
6322
  sourceIdentifier: handle.fetchIdentifier
6214
- });
6323
+ }, this.viewSubject(ctx));
6215
6324
  } catch (error) {
6216
6325
  this.logger.error("Error in removeRelationship(@args): @Error", args, error);
6217
6326
  throw error;
@@ -6226,7 +6335,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6226
6335
  ...args,
6227
6336
  sourceParentIdentifier: sourceHandle.fetchIdentifier,
6228
6337
  targetParentIdentifier: targetHandle.fetchIdentifier
6229
- });
6338
+ }, this.viewSubject(ctx));
6230
6339
  } catch (error) {
6231
6340
  this.logger.error("Error in moveRelationship(@args): @Error @args", error, args);
6232
6341
  throw error;
@@ -6337,10 +6446,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6337
6446
  documentChanges: {
6338
6447
  subscribe: (rootValue, args, ctx, info) => {
6339
6448
  this.logger.debug("documentChanges subscription started");
6340
- return withFilter(() => {
6341
- ensureGlobalDocumentSubscription(this.reactorClient);
6342
- return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES);
6343
- }, async (payload, filterArgs, filterCtx) => {
6449
+ return withFilter(() => this.documentChanges.subscribe(this.viewSubject(ctx)), async (payload, filterArgs, filterCtx) => {
6344
6450
  if (!payload) return false;
6345
6451
  const search = {
6346
6452
  type: filterArgs?.search?.type ?? void 0,
@@ -6367,6 +6473,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
6367
6473
  }, async (payload, filterArgs, filterCtx) => {
6368
6474
  if (!payload || !filterArgs) return false;
6369
6475
  if (!matchesJobFilter(payload, filterArgs)) return false;
6476
+ if (!await this.servesDocument(payload.documentId, filterCtx)) return false;
6370
6477
  if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
6371
6478
  if (!payload.documentId) return false;
6372
6479
  return this.canReadDocument(payload.documentId, filterCtx);
@@ -6395,10 +6502,10 @@ const ADMIN_USERS = getAdminUsers();
6395
6502
  //#endregion
6396
6503
  //#region src/graphql/system/version.ts
6397
6504
  function getVersion() {
6398
- return "6.2.3-dev.25";
6505
+ return "6.2.3-dev.26";
6399
6506
  }
6400
6507
  function getGitHash() {
6401
- return "e44ced14916428517ebc9e26d9bfb53d1d86cbc0";
6508
+ return "25dd1c83d3dd25a0a08cc0e858e91a55da97043e";
6402
6509
  }
6403
6510
  function getGitUrl() {
6404
6511
  return buildTreeUrl(getGitHash());
@@ -7108,9 +7215,14 @@ const HASH_PATTERN = /^[a-f0-9]{64}$/;
7108
7215
  /**
7109
7216
  * Composes document authorization with the projected document/ref
7110
7217
  * relationship. Order is fixed: validate ref, resolve the canonical document
7111
- * id, decide the read, then check the reference index. A denied document
7112
- * never reaches the reference reader, and the facade never touches
7113
- * attachment metadata, storage backends, or presigners.
7218
+ * id, decide the read, check the reactor's read gate serves the document, then
7219
+ * check the reference index and that the subject may read a scope whose
7220
+ * operations reference the attachment. A denied document never reaches the
7221
+ * reference reader, and the facade never touches attachment metadata, storage
7222
+ * backends, or presigners.
7223
+ *
7224
+ * The named document decides alone: an attachment several documents reference
7225
+ * is readable through each of them on that document's own terms.
7114
7226
  *
7115
7227
  * The read is decided by the document's own policy when this composition has a
7116
7228
  * model to enforce it with, and by the host's permission tables when it does
@@ -7128,12 +7240,14 @@ var AttachmentAccessService = class {
7128
7240
  authorization;
7129
7241
  references;
7130
7242
  projection;
7243
+ readGate;
7131
7244
  scopeGate;
7132
- constructor(resolveCanonicalId, authorization, references, projection, scopeGate) {
7245
+ constructor(resolveCanonicalId, authorization, references, projection, readGate, scopeGate) {
7133
7246
  this.resolveCanonicalId = resolveCanonicalId;
7134
7247
  this.authorization = authorization;
7135
7248
  this.references = references;
7136
7249
  this.projection = projection;
7250
+ this.readGate = readGate;
7137
7251
  this.scopeGate = scopeGate;
7138
7252
  }
7139
7253
  async canReadAttachment(request) {
@@ -7147,7 +7261,29 @@ var AttachmentAccessService = class {
7147
7261
  return { kind: "denied" };
7148
7262
  }
7149
7263
  if (!await this.canReadDocument(documentId, request)) return { kind: "denied" };
7150
- if (!await this.references.hasReference(documentId, ref)) return { kind: "denied" };
7264
+ const subject = {
7265
+ address: request.userAddress,
7266
+ key: request.appKey
7267
+ };
7268
+ let served;
7269
+ try {
7270
+ served = await this.readGate.isServed(documentId, { subject });
7271
+ } catch {
7272
+ return { kind: "denied" };
7273
+ }
7274
+ if (!served) return { kind: "denied" };
7275
+ const scopes = await this.references.referencingScopes(documentId, ref);
7276
+ if (scopes.length === 0) return { kind: "denied" };
7277
+ let held;
7278
+ try {
7279
+ held = (await this.readGate.get(documentId, {
7280
+ subject,
7281
+ scopes
7282
+ })).state;
7283
+ } catch {
7284
+ return { kind: "denied" };
7285
+ }
7286
+ if (!scopes.some((scope) => scope in held)) return { kind: "denied" };
7151
7287
  return {
7152
7288
  kind: "allowed",
7153
7289
  documentId,
@@ -7923,12 +8059,16 @@ function createGetParentIdsFn(reactorClient) {
7923
8059
  * Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
7924
8060
  * unrestricted reactor access, so access is limited to supreme admins when
7925
8061
  * auth is enabled. OPEN policy is public; any other policy without an
7926
- * AuthService fails closed.
8062
+ * AuthService fails closed. The tools read as the caller: anonymous under OPEN,
8063
+ * else the admin's address and the app key its token carries.
7927
8064
  */
7928
8065
  function createMcpRequestAuthorizer(authService, authorizationService) {
7929
8066
  return async (req) => {
7930
8067
  if (!authService) {
7931
- if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return { authorized: true };
8068
+ if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
8069
+ authorized: true,
8070
+ subject: {}
8071
+ };
7932
8072
  return {
7933
8073
  authorized: false,
7934
8074
  status: 401,
@@ -7951,10 +8091,39 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
7951
8091
  status: 403,
7952
8092
  message: "Forbidden: MCP access requires an administrator"
7953
8093
  };
7954
- return { authorized: true };
8094
+ return {
8095
+ authorized: true,
8096
+ subject: {
8097
+ address: context.user.address,
8098
+ key: context.user.appKey
8099
+ }
8100
+ };
7955
8101
  };
7956
8102
  }
7957
8103
  //#endregion
8104
+ //#region src/services/sync-serving-gate.ts
8105
+ /**
8106
+ * The gate sync serving evaluates a document's own policy through, deciding
8107
+ * what the reactor client's read gate decides at the same flag level.
8108
+ *
8109
+ * Under `authEnforcement` it is built here rather than taken off the client
8110
+ * because it also carries the host's closes-by-default setting, which withholds
8111
+ * the domain scopes of a document nobody has policied yet. That answer belongs
8112
+ * to serving alone -- replay must keep reading an uninitialized document in
8113
+ * full -- so the two gates are separate objects over the same model.
8114
+ *
8115
+ * Below it the registered model ignores the auth scope, so the client reads
8116
+ * through the policy alone, and so does serving: a policy written below the
8117
+ * flag still withholds, where serving ungated would sync to a peer what `get`
8118
+ * refuses it.
8119
+ */
8120
+ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
8121
+ if (!reactorModule) return;
8122
+ const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
8123
+ if (!model) return new SyncScopeGate(new BareReadGate(), reactorModule.documentView, logger);
8124
+ return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
8125
+ }
8126
+ //#endregion
7958
8127
  //#region src/services/renown-config.ts
7959
8128
  /** Reading credentials from this reactor's own read model needs a host-supplied
7960
8129
  * verifier: core has no idea which subgraph serves that read model. */
@@ -8220,27 +8389,6 @@ function makeDbClosers(knexInstance, pglite) {
8220
8389
  return closers;
8221
8390
  }
8222
8391
  /**
8223
- * The gate sync serving evaluates a document's own policy through, or undefined
8224
- * when there is none to evaluate.
8225
- *
8226
- * It is built here rather than taken off the reactor client because it is not
8227
- * the same gate reads use: it carries the host's closes-by-default setting,
8228
- * which withholds the domain scopes of a document nobody has policied yet. That
8229
- * answer belongs to serving alone -- replay must keep reading an uninitialized
8230
- * document in full -- so the two gates are deliberately separate objects over
8231
- * the same model.
8232
- *
8233
- * Undefined below `authEnforcement`, where the registered model ignores the auth
8234
- * scope: gating through it would serve every domain scope of a policied document
8235
- * to anyone, which is worse than not gating at all.
8236
- */
8237
- function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
8238
- if (!reactorModule) return;
8239
- const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
8240
- if (!model) return;
8241
- return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
8242
- }
8243
- /**
8244
8392
  * Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
8245
8393
  * Defaults to "apollo" (the federation gateway, production behavior).
8246
8394
  * "stitching" selects the in-process graphql-tools merge gateway (#1565
@@ -8597,7 +8745,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
8597
8745
  const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
8598
8746
  logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
8599
8747
  assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, syncServingGate !== void 0);
8600
- const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0);
8748
+ const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, reactorClient, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0);
8601
8749
  const coreSubgraphs = DefaultCoreSubgraphs.slice();
8602
8750
  coreSubgraphs.push(ReactorSubgraph);
8603
8751
  if (documentPermissionService) {
@@ -8833,7 +8981,7 @@ var PackageManagementService = class {
8833
8981
  }
8834
8982
  };
8835
8983
  //#endregion
8836
- export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthEvaluationUnsupportedError, AuthService, AuthSubgraph, AuthenticationRequiredError, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, DocumentRelationshipFieldsFragmentDoc, EvaluateActionsDocument, FindDocumentsDocument, ForbiddenError, GetDocumentDocument, GetDocumentIncomingRelationshipEdgesDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipEdgesDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MalformedStoredOperationError, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, UpdateRelationshipDocument, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAttachmentPolicyReadsAllowed, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, createWsAuthHandlers, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
8984
+ export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthEvaluationUnsupportedError, AuthService, AuthSubgraph, AuthenticationRequiredError, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, DocumentRelationshipFieldsFragmentDoc, EvaluateActionsDocument, FindDocumentsDocument, ForbiddenError, GetDocumentDocument, GetDocumentIncomingRelationshipEdgesDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipEdgesDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MalformedStoredOperationError, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, UpdateRelationshipDocument, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAttachmentPolicyReadsAllowed, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, callerSubject, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, createWsAuthHandlers, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
8837
8985
 
8838
8986
  //# sourceMappingURL=index.mjs.map
8839
- //# debugId=6828688f-9737-5852-b599-51439acc4894
8987
+ //# debugId=8ab62893-8127-51eb-9d6f-a5900b1823de