@web-ts-toolkit/access-router 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +421 -110
  2. package/index.mjs +421 -110
  3. package/package.json +3 -3
package/index.js CHANGED
@@ -3543,6 +3543,47 @@ function serviceAuthorizationInput(policy, targets) {
3543
3543
  }
3544
3544
  return { rows: policy.rows, outputFields: policy.outputFields, related };
3545
3545
  }
3546
+ function hasStableConfigurationCopies(value) {
3547
+ if (value === null || typeof value !== "object") return true;
3548
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype) return false;
3549
+ return Object.values(value).every(hasStableConfigurationCopies);
3550
+ }
3551
+ function captureAuthorizationAccess(value) {
3552
+ const input = authorizationRecord(value, "access");
3553
+ const access = {};
3554
+ for (const key of ["operation", "rowAccess", "virtualAccess", "outputAccess", "docPermissionsAccess"]) {
3555
+ if (key === "rowAccess" && input[key] === void 0) continue;
3556
+ if (typeof input[key] !== "string") authorizationError(`invalid access.${key}`);
3557
+ access[key] = input[key];
3558
+ }
3559
+ return Object.freeze(access);
3560
+ }
3561
+ function captureAuthorizationCatalog(value) {
3562
+ const input = authorizationRecord(value, "catalog");
3563
+ const catalog = {};
3564
+ for (const key of [
3565
+ "paths",
3566
+ "persistedPaths",
3567
+ "arrayPaths",
3568
+ "schemaVirtualPaths",
3569
+ "metadataPaths",
3570
+ "dynamicPaths",
3571
+ "containerPaths",
3572
+ "virtualPaths",
3573
+ "virtualScopePaths",
3574
+ "opaquePaths",
3575
+ "referencePaths",
3576
+ "protectedPaths"
3577
+ ]) {
3578
+ const paths = input[key];
3579
+ if (key !== "paths" && paths === void 0) continue;
3580
+ if (!Array.isArray(paths) || Array.from(paths).some((path) => typeof path !== "string")) {
3581
+ authorizationError(`invalid catalog.${key}`);
3582
+ }
3583
+ catalog[key] = paths;
3584
+ }
3585
+ return Object.freeze(catalog);
3586
+ }
3546
3587
  function assertAuthorizationPath(path) {
3547
3588
  if (typeof path !== "string" || !isSafeOutputPath(path)) {
3548
3589
  authorizationError(`invalid response path: ${String(path)}`);
@@ -3672,6 +3713,9 @@ function resolveServiceAuthorization(input) {
3672
3713
  const raw = copyAuthorizationValue(input.authorization);
3673
3714
  const configuration = copyAuthorizationValue(input.configuration, true);
3674
3715
  const targets = copyAuthorizationValue(input.relatedTargets ?? [], true);
3716
+ const access = copyAuthorizationValue(input.access);
3717
+ const outputAccess = captureAuthorizationAccess(access);
3718
+ const catalog = captureAuthorizationCatalog(configuration.catalog);
3675
3719
  const policy = copyAuthorizationValue(normalizePolicy(raw, input.applicability ?? "service", configuration, targets));
3676
3720
  const scopePath = Array.from(scope(input.binding));
3677
3721
  for (const part of scopePath) {
@@ -3684,17 +3728,21 @@ function resolveServiceAuthorization(input) {
3684
3728
  authorizationError("derived call request/runtime mismatch");
3685
3729
  }
3686
3730
  }
3687
- const permissions = Object.fromEntries(
3688
- input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key)])
3731
+ const permissions = new permission_default(
3732
+ Object.fromEntries(input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key) === true]))
3689
3733
  );
3734
+ for (const value of Object.values(permissions)) if (value && typeof value === "object") Object.freeze(value);
3735
+ Object.freeze(permissions);
3690
3736
  const call = Object.freeze(/* @__PURE__ */ Object.create(null));
3691
3737
  calls.set(call, {
3692
3738
  binding: Object.freeze({ ...input.binding, scopePath: Object.freeze(scopePath) }),
3693
- access: copyAuthorizationValue(input.access),
3739
+ access,
3694
3740
  configuration,
3695
3741
  policy,
3696
3742
  relatedTargets: targets,
3697
- permissions: Object.freeze(permissions),
3743
+ // normalizeFields constructs only mode strings and validated string paths.
3744
+ output: Object.freeze({ access: outputAccess, catalog, outputFields: policy.outputFields }),
3745
+ permissions,
3698
3746
  provenance: input.parent ? callRecord(input.parent).provenance : call
3699
3747
  });
3700
3748
  return call;
@@ -3712,12 +3760,75 @@ function readServiceAuthorization(call, binding) {
3712
3760
  true
3713
3761
  );
3714
3762
  }
3763
+ function readServiceAuthorizationAccess(call, binding) {
3764
+ assertAuthorizationBinding(call, binding);
3765
+ return callRecord(call).output.access;
3766
+ }
3767
+ function readServiceAuthorizationHydration(call, binding) {
3768
+ assertAuthorizationBinding(call, binding);
3769
+ return copyAuthorizationValue({ hydration: callRecord(call).configuration.hydration }, true);
3770
+ }
3771
+ function outputMetadata(configuration) {
3772
+ return {
3773
+ documentPermissionField: configuration.documentPermissionField,
3774
+ exposedDocPermissionKeys: configuration.exposedDocPermissionKeys,
3775
+ stripPermissionsField: configuration.stripPermissionsField,
3776
+ disableFieldPermissions: configuration.disableFieldPermissions
3777
+ };
3778
+ }
3779
+ function readServiceOutputMetadata(call, binding) {
3780
+ assertAuthorizationBinding(call, binding);
3781
+ return copyAuthorizationValue(outputMetadata(callRecord(call).configuration), true);
3782
+ }
3783
+ function readServiceDocumentPermissions(call, binding) {
3784
+ assertAuthorizationBinding(call, binding);
3785
+ const record3 = callRecord(call);
3786
+ return Object.freeze({
3787
+ catalog: record3.output.catalog,
3788
+ ...copyAuthorizationValue({ docPermissions: record3.configuration.docPermissions }, true)
3789
+ });
3790
+ }
3791
+ function readServiceFinalizerConfiguration(call, binding) {
3792
+ assertAuthorizationBinding(call, binding);
3793
+ const record3 = callRecord(call);
3794
+ return Object.freeze({
3795
+ catalog: record3.output.catalog,
3796
+ ...copyAuthorizationValue(
3797
+ {
3798
+ ...outputMetadata(record3.configuration),
3799
+ virtuals: record3.configuration.virtuals,
3800
+ docPermissions: record3.configuration.docPermissions
3801
+ },
3802
+ true
3803
+ )
3804
+ });
3805
+ }
3806
+ function readServiceOutputPolicy(call, binding) {
3807
+ assertAuthorizationBinding(call, binding);
3808
+ return callRecord(call).output;
3809
+ }
3810
+ function readServiceOutputFieldAuthorization(call, binding) {
3811
+ assertAuthorizationBinding(call, binding);
3812
+ const record3 = callRecord(call);
3813
+ const reusable = record3.reusableFieldConfiguration ??= hasStableConfigurationCopies(record3.configuration.permissionSchema) && // Coercion methods can observe an object's identity even without native
3814
+ // leaves. Keep those receivers local to the original resolution boundary.
3815
+ (record3.configuration.modelPermissionPrefix === null || typeof record3.configuration.modelPermissionPrefix !== "object");
3816
+ return Object.freeze({
3817
+ access: record3.output.access,
3818
+ outputFields: record3.output.outputFields,
3819
+ reusable,
3820
+ ...copyAuthorizationValue(
3821
+ {
3822
+ permissionSchema: record3.configuration.permissionSchema,
3823
+ modelPermissionPrefix: record3.configuration.modelPermissionPrefix
3824
+ },
3825
+ true
3826
+ )
3827
+ });
3828
+ }
3715
3829
  function getAuthorizationPermissions(call, binding) {
3716
3830
  assertAuthorizationBinding(call, binding);
3717
- const permission = new permission_default({ ...callRecord(call).permissions });
3718
- for (const value of Object.values(permission)) if (value && typeof value === "object") Object.freeze(value);
3719
- Object.freeze(permission);
3720
- return permission;
3831
+ return callRecord(call).permissions;
3721
3832
  }
3722
3833
  function getAuthorizationProvenance(call) {
3723
3834
  return callRecord(call).provenance;
@@ -3756,7 +3867,7 @@ function normalizeOutputAuthorizationOptions(input, options, purpose) {
3756
3867
  const stage = stageRecord(documentRecord(handle).stage);
3757
3868
  const binding = Object.freeze({ ...input.binding, scopePath: stage.binding.scopePath });
3758
3869
  assertAuthorizationBinding(stage.call, binding);
3759
- const access = readServiceAuthorization(stage.call, binding).access;
3870
+ const access = readServiceAuthorizationAccess(stage.call, binding);
3760
3871
  if (input.access.outputAccess !== access.outputAccess) authorizationError("document access mismatch");
3761
3872
  return Object.freeze({ kind: "document", documentAuthorization: handle, binding });
3762
3873
  }
@@ -3834,7 +3945,16 @@ function readDocumentAuthorization(handle, binding) {
3834
3945
  authorization: stage.call,
3835
3946
  stage: record3.stage,
3836
3947
  grants: copyAuthorizationValue(record3.grants),
3837
- ...readServiceAuthorization(stage.call, binding).access
3948
+ ...readServiceAuthorizationAccess(stage.call, binding)
3949
+ });
3950
+ }
3951
+ function readDocumentAuthorizationAccess(handle, binding) {
3952
+ const record3 = documentRecord(handle);
3953
+ const stage = stageRecord(record3.stage);
3954
+ return Object.freeze({
3955
+ authorization: stage.call,
3956
+ stage: record3.stage,
3957
+ ...readServiceAuthorizationAccess(stage.call, binding)
3838
3958
  });
3839
3959
  }
3840
3960
  function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
@@ -3842,15 +3962,23 @@ function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
3842
3962
  const stage = stageRecord(record3.stage);
3843
3963
  assertAuthorizationBinding(stage.call, binding);
3844
3964
  let decision = record3.decisions.get(field);
3845
- if (!decision) {
3965
+ if (decision) {
3966
+ return decision.then((result2) => {
3967
+ stageRecord(record3.stage);
3968
+ assertAuthorizationBinding(stage.call, binding);
3969
+ return result2;
3970
+ });
3971
+ } else {
3846
3972
  decision = Promise.resolve().then(async () => {
3847
3973
  stageRecord(record3.stage);
3974
+ assertAuthorizationBinding(stage.call, binding);
3848
3975
  const result2 = await evaluate.call(
3849
3976
  stage.binding.request,
3850
3977
  getAuthorizationPermissions(stage.call, binding),
3851
3978
  copyAuthorizationValue(record3.grants)
3852
3979
  );
3853
3980
  stageRecord(record3.stage);
3981
+ assertAuthorizationBinding(stage.call, binding);
3854
3982
  if (typeof result2 !== "boolean") authorizationError("field decision must be boolean");
3855
3983
  return result2;
3856
3984
  });
@@ -3867,7 +3995,7 @@ function assertDocumentAuthorization(handle, use) {
3867
3995
  const record3 = documentRecord(handle);
3868
3996
  const stage = stageRecord(record3.stage);
3869
3997
  assertAuthorizationBinding(stage.call, use.binding);
3870
- if (use.authorization !== void 0 && stage.call !== use.authorization || use.stage !== void 0 && record3.stage !== use.stage || readServiceAuthorization(stage.call, use.binding).access.outputAccess !== use.outputAccess) {
3998
+ if (use.authorization !== void 0 && stage.call !== use.authorization || use.stage !== void 0 && record3.stage !== use.stage || readServiceAuthorizationAccess(stage.call, use.binding).outputAccess !== use.outputAccess) {
3871
3999
  authorizationError("document call/access/stage mismatch");
3872
4000
  }
3873
4001
  const association = outputs.get(use.output);
@@ -3922,7 +4050,7 @@ function transferDocumentAuthorizationGraph(source, copy, copyOrigins) {
3922
4050
  {
3923
4051
  binding: stage.binding,
3924
4052
  output: original,
3925
- outputAccess: readServiceAuthorization(stage.call, stage.binding).access.outputAccess,
4053
+ outputAccess: readServiceAuthorizationAccess(stage.call, stage.binding).outputAccess,
3926
4054
  authorization: stage.call,
3927
4055
  stage: documentRecord(association.handle).stage
3928
4056
  },
@@ -4087,12 +4215,20 @@ function classifyRequestFieldRule(rule, permissions, modelPermissionPrefix = "")
4087
4215
  );
4088
4216
  return (typeof rule === "string" ? possible.stringHandler(rule) : possible.arrayHandler(rule)) ? "deferred" : "denied";
4089
4217
  }
4218
+ function prepareRequestFieldRule(rule, permissions, modelPermissionPrefix = "") {
4219
+ const state = classifyRequestFieldRule(rule, permissions, modelPermissionPrefix);
4220
+ const invariant = state === "denied" && modelPermissionPrefix ? classifyRequestFieldRule(rule, permissions) : state;
4221
+ return Object.freeze({ state, decision: invariant === "deferred" ? void 0 : invariant === "granted" });
4222
+ }
4223
+ function combineFieldAuthorizationStates(states2) {
4224
+ return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
4225
+ }
4090
4226
  function classifyEffectiveFieldAuthorization(args) {
4091
4227
  if (hasExplicitFieldGrant(args.outputFields, args.path)) return "granted";
4092
4228
  const states2 = args.rules.map(
4093
4229
  (rule) => classifyRequestFieldRule(rule, args.globalPermissions, args.modelPermissionPrefix)
4094
4230
  );
4095
- return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
4231
+ return combineFieldAuthorizationStates(states2);
4096
4232
  }
4097
4233
  async function evaluateScopedAccessRule(args) {
4098
4234
  const { req, rule, globalPermissions, docPermissions, modelPermissionPrefix } = args;
@@ -5741,16 +5877,111 @@ var init_virtual_populate_execution = __esm({
5741
5877
  });
5742
5878
 
5743
5879
  // src/acl/output-field-filter.ts
5880
+ function callFieldPolicy(authorization, binding) {
5881
+ assertAuthorizationBinding(authorization, binding);
5882
+ let policy = fieldPolicies.get(authorization);
5883
+ if (!policy) {
5884
+ const view = readServiceOutputFieldAuthorization(authorization, binding);
5885
+ policy = {
5886
+ schema: view.permissionSchema,
5887
+ requestPaths: view.permissionSchema == null || (0, import_utils28.isPlainObject)(view.permissionSchema) && Object.isFrozen(view.permissionSchema) ? Object.freeze(Object.keys(view.permissionSchema ?? {})) : void 0,
5888
+ prefix: view.modelPermissionPrefix ?? "",
5889
+ outputFields: view.outputFields,
5890
+ accesses: /* @__PURE__ */ new Map()
5891
+ };
5892
+ if (view.reusable) fieldPolicies.set(authorization, policy);
5893
+ }
5894
+ return policy;
5895
+ }
5896
+ function requestRuleClassification(entry, authorization, permissions, prefix) {
5897
+ if (typeof prefix !== "string") return void 0;
5898
+ let classification = entry.classifications.get(authorization);
5899
+ if (!classification) {
5900
+ classification = prepareRequestFieldRule(entry.rule, permissions, prefix);
5901
+ entry.classifications.set(authorization, classification);
5902
+ }
5903
+ return classification;
5904
+ }
5905
+ function invariantRuleLookup(schema, path) {
5906
+ if (schema == null) return true;
5907
+ if (!(0, import_utils28.isPlainObject)(schema) || !Object.isFrozen(schema)) return false;
5908
+ const exact = Object.getOwnPropertyDescriptor(schema, path);
5909
+ if (exact) {
5910
+ const raw2 = exact.value;
5911
+ return !raw2 || typeof raw2 !== "object" || Array.isArray(raw2) || (0, import_utils28.isPlainObject)(raw2) && Object.isFrozen(raw2);
5912
+ }
5913
+ const [head, ...rest] = path.split(".");
5914
+ const raw = Object.getOwnPropertyDescriptor(schema, head)?.value;
5915
+ if (raw == null) return true;
5916
+ if (!(0, import_utils28.isPlainObject)(raw) || !Object.isFrozen(raw)) return false;
5917
+ const sub = Object.getOwnPropertyDescriptor(raw, "sub");
5918
+ return !rest.length || !sub || invariantRuleLookup(sub.value, rest.join("."));
5919
+ }
5920
+ function prepareOutputPath(policy, authorization, binding, path, access) {
5921
+ let cache = policy.accesses.get(access);
5922
+ if (!cache) {
5923
+ cache = /* @__PURE__ */ new Map();
5924
+ policy.accesses.set(access, cache);
5925
+ }
5926
+ let prepared = cache.get(path);
5927
+ if (!prepared) {
5928
+ const invariant = invariantRuleLookup(policy.schema, path);
5929
+ const permissions = getAuthorizationPermissions(authorization, binding);
5930
+ const entries = extractOutputFieldRuleEntries(policy.schema, path, access).map((entry) => ({
5931
+ ...entry,
5932
+ classifications: /* @__PURE__ */ new WeakMap()
5933
+ }));
5934
+ const classifications = entries.map(
5935
+ (entry) => requestRuleClassification(entry, authorization, permissions, policy.prefix)
5936
+ );
5937
+ const backendGrant = hasExplicitFieldGrant(policy.outputFields, path);
5938
+ prepared = {
5939
+ entries,
5940
+ backendGrant,
5941
+ state: backendGrant ? "granted" : classifications.every((value) => value !== void 0) ? combineFieldAuthorizationStates(classifications.map((value) => value.state)) : void 0
5942
+ };
5943
+ if (invariant) cache.set(path, prepared);
5944
+ }
5945
+ return prepared;
5946
+ }
5947
+ function assertOwningOutputDocuments(handle) {
5948
+ const owner = owningDocuments.get(handle);
5949
+ if (owner) {
5950
+ readDocumentAuthorizationAccess(owner.handle, owner.binding);
5951
+ assertOwningOutputDocuments(owner.handle);
5952
+ }
5953
+ }
5954
+ function prepareOutputDocumentAuthorization(authorization, binding, configuration) {
5955
+ const access = readServiceAuthorizationAccess(authorization, binding);
5956
+ const key = `docPermissions.${access.docPermissionsAccess}`;
5957
+ const docPermissions = configuration.docPermissions;
5958
+ const invariant = /^[A-Za-z0-9_-]+$/.test(access.docPermissionsAccess) && (docPermissions == null || (0, import_utils28.isPlainObject)(docPermissions));
5959
+ const option = invariant ? getCapturedAuthorizationOption(configuration, key) : void 0;
5960
+ const hook = typeof option === "function" ? option : void 0;
5961
+ const resolveHook = invariant ? () => hook : () => {
5962
+ const slice = { catalog: configuration.catalog, ...copyAuthorizationValue({ docPermissions }, true) };
5963
+ const option2 = getCapturedAuthorizationOption(slice, key);
5964
+ return typeof option2 === "function" ? option2 : void 0;
5965
+ };
5966
+ return async (args) => {
5967
+ if (args.authorization !== authorization) authorizationError("document resolver call mismatch");
5968
+ assertAuthorizationBinding(authorization, args.binding);
5969
+ return resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook);
5970
+ };
5971
+ }
5744
5972
  async function resolveOutputDocumentAuthorization(args) {
5973
+ const configuration = readServiceDocumentPermissions(args.authorization, args.binding);
5974
+ return prepareOutputDocumentAuthorization(args.authorization, args.binding, configuration)(args);
5975
+ }
5976
+ async function resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook) {
5745
5977
  const { authorization, binding } = args;
5746
- const { configuration, access } = readServiceAuthorization(authorization, binding);
5747
5978
  const stage = args.stage ?? createDocumentAuthorizationStage(authorization, binding, args.snapshot);
5748
5979
  if (args.stage) assertDocumentAuthorizationStage(stage, authorization, binding, args.snapshot);
5749
5980
  const handle = await resolveDocumentAuthorization(stage, async function(doc, permissions) {
5750
5981
  if (args.owningDocument) return readDocumentAuthorization(args.owningDocument, args.owningBinding).grants;
5751
5982
  if (args.knownOutput) return {};
5752
- const hook = getCapturedAuthorizationOption(configuration, `docPermissions.${access.docPermissionsAccess}`);
5753
- if (typeof hook !== "function") return {};
5983
+ const hook = resolveHook();
5984
+ if (!hook) return {};
5754
5985
  try {
5755
5986
  const run = () => hook.call(this, doc, permissions, args.context);
5756
5987
  const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
@@ -5771,6 +6002,7 @@ async function resolveOutputDocumentAuthorization(args) {
5771
6002
  return handle;
5772
6003
  }
5773
6004
  function evaluateRequestRule(handle, binding, entry, prefix, access) {
6005
+ assertOwningOutputDocuments(handle);
5774
6006
  const owner = owningDocuments.get(handle);
5775
6007
  if (owner && !entry.path.startsWith(`${(binding.scopePath ?? []).join(".")}.`)) {
5776
6008
  return evaluateRequestRule(owner.handle, owner.binding, entry, prefix, access);
@@ -5779,65 +6011,78 @@ function evaluateRequestRule(handle, binding, entry, prefix, access) {
5779
6011
  handle,
5780
6012
  binding,
5781
6013
  `request:${access}:${entry.path}`,
5782
- function(permissions, grants) {
5783
- return evaluateScopedAccessRule({
5784
- req: this,
5785
- rule: entry.rule,
5786
- globalPermissions: permissions,
5787
- docPermissions: grants,
5788
- modelPermissionPrefix: prefix
5789
- });
6014
+ async function(permissions, grants) {
6015
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6016
+ const classification = requestRuleClassification(entry, authorization, permissions, prefix);
6017
+ let allowed = classification?.decision;
6018
+ if (allowed === void 0) {
6019
+ allowed = await evaluateScopedAccessRule({
6020
+ req: this,
6021
+ rule: entry.rule,
6022
+ globalPermissions: permissions,
6023
+ docPermissions: grants,
6024
+ modelPermissionPrefix: prefix
6025
+ });
6026
+ }
6027
+ assertOwningOutputDocuments(handle);
6028
+ return allowed;
5790
6029
  }
5791
6030
  );
5792
6031
  }
5793
6032
  function evaluateAuthorizedOutputField(handle, binding, field) {
5794
- const { authorization } = readDocumentAuthorization(handle, binding);
5795
- const { configuration, authorization: policy, access } = readServiceAuthorization(authorization, binding);
6033
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6034
+ assertOwningOutputDocuments(handle);
5796
6035
  const path = [...binding.scopePath ?? [], field].join(".");
5797
- const entries = extractOutputFieldRuleEntries(
5798
- configuration.permissionSchema,
5799
- path,
5800
- access.outputAccess
5801
- );
5802
- return memoizeDocumentFieldDecision(handle, binding, `output:${path}`, function(permissions, grants) {
5803
- const args = {
6036
+ const decision = memoizeDocumentFieldDecision(handle, binding, `output:${path}`, async function(permissions) {
6037
+ const policy = callFieldPolicy(authorization, binding);
6038
+ const { outputAccess } = readDocumentAuthorizationAccess(handle, binding);
6039
+ const prepared = prepareOutputPath(policy, authorization, binding, path, outputAccess);
6040
+ const unresolved = unresolvedDocuments.has(handle);
6041
+ const state = unresolved ? prepared.state ?? classifyEffectiveFieldAuthorization({
5804
6042
  path,
5805
- rules: entries.map((entry) => entry.rule),
6043
+ rules: prepared.entries.map((entry) => entry.rule),
6044
+ modelPermissionPrefix: policy.prefix,
5806
6045
  outputFields: policy.outputFields,
5807
- globalPermissions: permissions,
5808
- modelPermissionPrefix: configuration.modelPermissionPrefix ?? ""
5809
- };
5810
- if (unresolvedDocuments.has(handle) && classifyEffectiveFieldAuthorization(args) !== "granted") return false;
5811
- return evaluateEffectiveFieldAuthorization({
5812
- ...args,
5813
- req: this,
5814
- docPermissions: grants,
5815
- evaluateRule: (_rule, index) => evaluateRequestRule(handle, binding, entries[index], args.modelPermissionPrefix, access.outputAccess)
5816
- });
6046
+ globalPermissions: permissions
6047
+ }) : void 0;
6048
+ let allowed;
6049
+ if (unresolved && state !== "granted") allowed = false;
6050
+ else if (prepared.backendGrant) allowed = true;
6051
+ else {
6052
+ allowed = true;
6053
+ for (const entry of prepared.entries) {
6054
+ if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, outputAccess)) {
6055
+ allowed = false;
6056
+ break;
6057
+ }
6058
+ }
6059
+ }
6060
+ assertOwningOutputDocuments(handle);
6061
+ return allowed;
5817
6062
  });
6063
+ return owningDocuments.has(handle) ? decision.then((allowed) => {
6064
+ assertOwningOutputDocuments(handle);
6065
+ return allowed;
6066
+ }) : decision;
5818
6067
  }
5819
6068
  async function requestOutputFieldPaths(handle, binding, access) {
5820
- const { authorization } = readDocumentAuthorization(handle, binding);
5821
- const { configuration } = readServiceAuthorization(authorization, binding);
5822
- const schema = configuration.permissionSchema;
6069
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6070
+ assertOwningOutputDocuments(handle);
6071
+ const policy = callFieldPolicy(authorization, binding);
5823
6072
  const fields = [];
5824
- for (const path of Object.keys(schema ?? {})) {
5825
- const entries = extractOutputFieldRuleEntries(schema, path, access);
6073
+ for (const path of policy.requestPaths ?? Object.keys(policy.schema ?? {})) {
6074
+ const { entries } = prepareOutputPath(policy, authorization, binding, path, access);
5826
6075
  let allowed = true;
5827
6076
  for (const entry of entries) {
5828
- if (!await evaluateRequestRule(
5829
- handle,
5830
- binding,
5831
- entry,
5832
- configuration.modelPermissionPrefix ?? "",
5833
- access
5834
- )) {
6077
+ if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, access)) {
5835
6078
  allowed = false;
5836
6079
  break;
5837
6080
  }
5838
6081
  }
5839
6082
  if (allowed && entries.length) fields.push(path);
5840
6083
  }
6084
+ assertOwningOutputDocuments(handle);
6085
+ readDocumentAuthorizationAccess(handle, binding);
5841
6086
  return fields;
5842
6087
  }
5843
6088
  function authorizeEmbeddedOutputPlan(parent, child) {
@@ -5858,13 +6103,13 @@ function authorizeEmbeddedOutputPlan(parent, child) {
5858
6103
  function hasWholeOutputValueGrant(plan, field) {
5859
6104
  if (plan.fieldStates?.[field] !== "granted") return false;
5860
6105
  const binding = plan.authorizationBinding;
5861
- const { authorization } = readServiceAuthorization(plan.authorization, binding);
5862
- return hasExplicitFieldGrant(authorization.outputFields, [...binding.scopePath ?? [], field].join("."));
6106
+ const { outputFields } = readServiceOutputPolicy(plan.authorization, binding);
6107
+ return hasExplicitFieldGrant(outputFields, [...binding.scopePath ?? [], field].join("."));
5863
6108
  }
5864
6109
  function copyOutputValue(value, plan, path) {
5865
- const { configuration } = readServiceAuthorization(plan.authorization, plan.authorizationBinding);
5866
- const virtualValue = configuration.catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
5867
- const opaque = configuration.catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
6110
+ const { catalog } = readServiceOutputPolicy(plan.authorization, plan.authorizationBinding);
6111
+ const virtualValue = catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
6112
+ const opaque = catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
5868
6113
  const excluded = plan.effectiveSelectNorm.filter((token) => token.startsWith("-")).map((token) => [...plan.authorizationBinding.scopePath ?? [], token.slice(1)].join("."));
5869
6114
  const visit = (input, current) => {
5870
6115
  if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
@@ -5875,7 +6120,7 @@ function copyOutputValue(value, plan, path) {
5875
6120
  const child = `${current}.${key}`;
5876
6121
  if (excluded.some((entry2) => child === entry2 || child.startsWith(`${entry2}.`))) continue;
5877
6122
  const safeValueKey = !["__proto__", "constructor", "prototype", "_doc", "$__", "__v"].includes(key);
5878
- if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || configuration.catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(configuration.catalog, child))
6123
+ if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(catalog, child))
5879
6124
  continue;
5880
6125
  output[key] = visit(entry, child);
5881
6126
  }
@@ -5955,7 +6200,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
5955
6200
  const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
5956
6201
  let child = scoped;
5957
6202
  if (childHandle) {
5958
- const state = readDocumentAuthorization(childHandle, scoped.authorizationBinding);
6203
+ const state = readDocumentAuthorizationAccess(childHandle, scoped.authorizationBinding);
5959
6204
  if (getAuthorizationProvenance(state.authorization) !== getAuthorizationProvenance(plan.authorization))
5960
6205
  authorizationError("child call mismatch");
5961
6206
  child = helperPlan(state.authorization, scoped.authorizationBinding);
@@ -6019,7 +6264,7 @@ async function filterModelOutputForHelper(args) {
6019
6264
  if (normalized.kind === "document") {
6020
6265
  const { documentAuthorization: handle2, binding: binding2 } = normalized;
6021
6266
  assertDocumentAuthorization(handle2, { binding: binding2, output: args.doc, outputAccess: access });
6022
- const state = readDocumentAuthorization(handle2, binding2);
6267
+ const state = readDocumentAuthorizationAccess(handle2, binding2);
6023
6268
  return filterExistingOutput(
6024
6269
  args.doc,
6025
6270
  helperPlan(state.authorization, binding2),
@@ -6042,7 +6287,7 @@ async function filterModelOutputForHelper(args) {
6042
6287
  });
6043
6288
  return filterExistingOutput(source, plan, handle, false);
6044
6289
  }
6045
- var import_utils28, unresolvedDocuments, owningDocuments;
6290
+ var import_utils28, unresolvedDocuments, owningDocuments, fieldPolicies;
6046
6291
  var init_output_field_filter = __esm({
6047
6292
  "src/acl/output-field-filter.ts"() {
6048
6293
  import_utils28 = require("@web-ts-toolkit/utils");
@@ -6059,6 +6304,7 @@ var init_output_field_filter = __esm({
6059
6304
  init_output_projection();
6060
6305
  unresolvedDocuments = /* @__PURE__ */ new WeakSet();
6061
6306
  owningDocuments = /* @__PURE__ */ new WeakMap();
6307
+ fieldPolicies = /* @__PURE__ */ new WeakMap();
6062
6308
  }
6063
6309
  });
6064
6310
 
@@ -6127,15 +6373,41 @@ __export(finalize_model_output_exports, {
6127
6373
  finalizeModelOutput: () => finalizeModelOutput,
6128
6374
  finalizeModelOutputs: () => finalizeModelOutputs
6129
6375
  });
6130
- async function finalizeSingleDoc(args, input) {
6131
- const scopePath = args.scopePath;
6376
+ function assertFinalizerCall(args, prepared) {
6132
6377
  const plan = args.plan;
6133
6378
  const binding = plan.authorizationBinding;
6134
- const captured = plan.authorization && binding ? readServiceAuthorization(plan.authorization, binding) : void 0;
6135
- if (plan.authorization && (!binding || binding.request !== args.request || binding.modelName !== args.receivingModelName || captured?.access.virtualAccess !== args.virtualAccess || captured.access.outputAccess !== args.outputAccess || captured.access.docPermissionsAccess !== args.docPermissionsAccess || (binding.scopePath ?? []).join(".") !== scopePath.filter((p) => p !== "sub").join("."))) {
6379
+ if (plan.authorization !== prepared.authorization) authorizationError("finalizer call changed during pass");
6380
+ if (plan.authorization && binding) assertAuthorizationBinding(plan.authorization, binding);
6381
+ const access = prepared.policy?.access;
6382
+ if (plan.authorization && (!binding || binding.request !== args.request || binding.modelName !== args.receivingModelName || access?.virtualAccess !== args.virtualAccess || access.outputAccess !== args.outputAccess || access.docPermissionsAccess !== args.docPermissionsAccess || (binding.scopePath ?? []).join(".") !== args.scopePath.filter((p) => p !== "sub").join("."))) {
6136
6383
  authorizationError("finalizer call/model/scope/access mismatch");
6137
6384
  }
6138
- const snapshot = captured ? captured.configuration : args.snapshot;
6385
+ }
6386
+ function prepareFinalizer(args, sharedContext = false) {
6387
+ const { authorization, authorizationBinding: binding } = args.plan;
6388
+ const policy = authorization && binding ? readServiceOutputPolicy(authorization, binding) : void 0;
6389
+ assertFinalizerCall(args, { authorization, policy });
6390
+ const configuration = authorization ? readServiceFinalizerConfiguration(authorization, binding) : void 0;
6391
+ const snapshot = configuration ? configuration : deepIsolateValue(args.snapshot);
6392
+ return {
6393
+ authorization,
6394
+ policy,
6395
+ snapshot,
6396
+ baseContext: deepIsolateValue(args.context ?? {}),
6397
+ sharedContext,
6398
+ scopeVirtualNames: getScopeVirtualNames(snapshot, args.scopePath),
6399
+ scopedSchema: configuration ? null : getScopedPermissionSchema2(snapshot.permissionSchema, args.scopePath),
6400
+ permissions: authorization ? getAuthorizationPermissions(authorization, binding) : void 0,
6401
+ resolveDocument: authorization ? prepareOutputDocumentAuthorization(authorization, binding, configuration) : void 0
6402
+ };
6403
+ }
6404
+ async function finalizeSingleDoc(args, input, prepared) {
6405
+ assertFinalizerCall(args, prepared);
6406
+ const scopePath = args.scopePath;
6407
+ const plan = args.plan;
6408
+ const binding = plan.authorizationBinding;
6409
+ const captured = prepared.policy;
6410
+ const snapshot = prepared.snapshot;
6139
6411
  const limit = args.resolvedLimit;
6140
6412
  const gate = args.sharedGate;
6141
6413
  const original = getIncludeOriginal(input);
@@ -6147,7 +6419,7 @@ async function finalizeSingleDoc(args, input) {
6147
6419
  for (const key of Object.keys(output)) {
6148
6420
  transferDocumentAuthorizationGraph(input[key], output[key]);
6149
6421
  }
6150
- const baseContext = args.context ?? {};
6422
+ const baseContext = prepared.sharedContext ? deepIsolateValue(prepared.baseContext) : prepared.baseContext;
6151
6423
  const operation = captured?.access.operation ?? baseContext.operation ?? args.operation ?? args.virtualAccess;
6152
6424
  const scopeContext = {
6153
6425
  ...baseContext,
@@ -6165,7 +6437,7 @@ async function finalizeSingleDoc(args, input) {
6165
6437
  if (captured) {
6166
6438
  const projected = args.documentAuthorizationStage ? input : deepIsolateValue(output);
6167
6439
  metadataSource = deepIsolateValue(output);
6168
- documentAuthorization = await resolveOutputDocumentAuthorization({
6440
+ documentAuthorization = await prepared.resolveDocument({
6169
6441
  authorization: plan.authorization,
6170
6442
  binding,
6171
6443
  snapshot: projected,
@@ -6178,13 +6450,10 @@ async function finalizeSingleDoc(args, input) {
6178
6450
  }
6179
6451
  const docGrants = documentAuthorization ? readDocumentAuthorization(documentAuthorization, binding).grants : await resolveDocPermissions(args, output, scopeContext);
6180
6452
  scopeContext.docPermissions = deepIsolateValue(docGrants);
6181
- const globalPerms = captured ? getAuthorizationPermissions(plan.authorization, binding) : resolveGlobalPermissions(args);
6182
- const modelPermissionPrefix = snapshot.modelPermissionPrefix ?? "";
6183
- const documentPermissionField = snapshot.documentPermissionField ?? "_permissions";
6184
- const scopedSchema = getScopedPermissionSchema2(
6185
- snapshot.permissionSchema ?? null,
6186
- scopePath
6187
- );
6453
+ const globalPerms = prepared.permissions ?? resolveGlobalPermissions(args);
6454
+ const modelPermissionPrefix = captured ? "" : deepIsolateValue(snapshot.modelPermissionPrefix ?? "");
6455
+ const documentPermissionField = captured ? "_permissions" : deepIsolateValue(snapshot.documentPermissionField ?? "_permissions");
6456
+ const scopedSchema = deepIsolateValue(prepared.scopedSchema);
6188
6457
  const childPlans = plan.childPlans ?? {};
6189
6458
  for (const [containerName, childPlan] of Object.entries(childPlans)) {
6190
6459
  if (!(containerName in output)) continue;
@@ -6203,12 +6472,13 @@ async function finalizeSingleDoc(args, input) {
6203
6472
  continue;
6204
6473
  }
6205
6474
  retainRawAssociation(output, containerName, rawContainer);
6206
- const childSnapshot = snapshot;
6207
6475
  const childBase = {
6208
6476
  ...args,
6209
6477
  scopePath: [...childPlan.scopePath ?? [...scopePath, containerName, "sub"]],
6210
6478
  plan: captured ? authorizeEmbeddedOutputPlan(plan, childPlan) : childPlan,
6211
- snapshot: childSnapshot,
6479
+ // Authenticated preparation reads its own authority. Do not clone an
6480
+ // otherwise unused public configuration graph on default traversal.
6481
+ snapshot: args.snapshot,
6212
6482
  // Embedded reuses the owning parent's grants + accesses (VIRT-00A D2).
6213
6483
  docPermissions: docGrants,
6214
6484
  context: scopeContext,
@@ -6219,6 +6489,7 @@ async function finalizeSingleDoc(args, input) {
6219
6489
  related: [],
6220
6490
  virtualPopulateRow: privateRow
6221
6491
  };
6492
+ let childPreparation;
6222
6493
  const finalizeOneChild = async (rawChild, address) => {
6223
6494
  if (captured && Array.isArray(rawChild))
6224
6495
  return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
@@ -6239,7 +6510,14 @@ async function finalizeSingleDoc(args, input) {
6239
6510
  } : { ...args, virtualPopulateRow: privateRow, virtualPopulateAddress: address }
6240
6511
  });
6241
6512
  }
6242
- return finalizeSingleDoc({ ...childBase, virtualPopulateAddress: address }, rawChild);
6513
+ return finalizeSingleDoc(
6514
+ { ...childBase, virtualPopulateAddress: address },
6515
+ rawChild,
6516
+ childPreparation ??= prepareFinalizer(
6517
+ childBase,
6518
+ Array.isArray(rawContainer) && (rawContainer.length > 1 || Array.isArray(rawContainer[0]))
6519
+ )
6520
+ );
6243
6521
  };
6244
6522
  if (Array.isArray(rawContainer)) {
6245
6523
  const finalized = await mapWithConcurrencyLimit(
@@ -6291,6 +6569,7 @@ async function finalizeSingleDoc(args, input) {
6291
6569
  context: scopeContext,
6292
6570
  related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
6293
6571
  };
6572
+ let relatedPreparation;
6294
6573
  const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
6295
6574
  if (!(0, import_utils29.isPlainObject)(rawChild) && !Array.isArray(rawChild)) return rawChild;
6296
6575
  if (typeof rawChild?._bsontype === "string") return rawChild;
@@ -6304,7 +6583,11 @@ async function finalizeSingleDoc(args, input) {
6304
6583
  parentArgs: { ...args, virtualPopulateRow: childRow, virtualPopulateAddress: [] }
6305
6584
  });
6306
6585
  }
6307
- return finalizeSingleDoc({ ...relatedBase, virtualPopulateRow: childRow }, rawChild);
6586
+ return finalizeSingleDoc(
6587
+ { ...relatedBase, virtualPopulateRow: childRow },
6588
+ rawChild,
6589
+ relatedPreparation ??= prepareFinalizer(relatedBase, Array.isArray(rawRelated) && rawRelated.length > 1)
6590
+ );
6308
6591
  };
6309
6592
  let finalized;
6310
6593
  if (Array.isArray(rawRelated)) {
@@ -6324,18 +6607,24 @@ async function finalizeSingleDoc(args, input) {
6324
6607
  } catch {
6325
6608
  }
6326
6609
  }
6327
- const scopeVirtualNames = getScopeVirtualNames(snapshot, scopePath);
6328
- for (const virtualName of scopeVirtualNames) {
6610
+ for (const virtualName of prepared.scopeVirtualNames) {
6329
6611
  if (virtualName in output) delete output[virtualName];
6330
6612
  }
6331
- const stableBase = deepIsolateValue(output);
6332
- const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
6333
6613
  const candidates = plan.candidates ?? [];
6614
+ const needsGetterView = candidates.some((candidate) => {
6615
+ const descriptor = candidate.descriptor;
6616
+ if (typeof descriptor?.get !== "function") return false;
6617
+ if (!privateRow && descriptor.dependsOnPopulate?.length) return false;
6618
+ const dependsOn = Array.isArray(descriptor.dependsOn) ? descriptor.dependsOn : [];
6619
+ return dependsOn.every((dep) => typeof dep === "string" && !isAbsentDependency(output, dep));
6620
+ });
6621
+ let stableBase = needsGetterView ? deepIsolateValue(output) : void 0;
6622
+ const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
6334
6623
  const staged = /* @__PURE__ */ new Map();
6335
6624
  const runCandidate = async (candidate) => {
6336
6625
  const dependsOn = Array.isArray(candidate.descriptor?.dependsOn) ? candidate.descriptor.dependsOn : [];
6337
6626
  for (const dep of dependsOn) {
6338
- if (typeof dep !== "string" || isAbsentDependency(stableBase, dep)) return;
6627
+ if (typeof dep !== "string" || isAbsentDependency(stableBase ?? output, dep)) return;
6339
6628
  }
6340
6629
  const allowed = documentAuthorization ? await evaluateAuthorizedOutputField(documentAuthorization, binding, candidate.name) : await evaluateEffectiveFieldAuthorization({
6341
6630
  req: args.request,
@@ -6350,9 +6639,9 @@ async function finalizeSingleDoc(args, input) {
6350
6639
  if (typeof getter !== "function") return;
6351
6640
  const privateInputs = privateRow ? readVirtualPopulateInputs(privateRow, privateAddress, candidate.name) : candidate.descriptor.dependsOnPopulate?.length ? { available: false } : { available: true, populated: {} };
6352
6641
  if (!privateInputs.available) return;
6353
- const isolatedView = deepIsolateValue(stableBase);
6642
+ const isolatedView = deepIsolateValue(stableBase ??= deepIsolateValue(output));
6354
6643
  const virtualContext = {
6355
- ...scopeContext,
6644
+ ...deepIsolateValue(scopeContext),
6356
6645
  docPermissions: deepIsolateValue(docGrants)
6357
6646
  };
6358
6647
  const outcome = await runVirtualGetter({
@@ -6443,12 +6732,25 @@ async function finalizeSingleDoc(args, input) {
6443
6732
  if (!(key in output)) continue;
6444
6733
  if (captured && !plan.childPlans[key]) {
6445
6734
  const path = [...binding.scopePath ?? [], key].join(".");
6446
- trimmed[key] = captured.configuration.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
6735
+ trimmed[key] = captured.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
6447
6736
  if (trimmed[key] === void 0) delete trimmed[key];
6448
6737
  } else trimmed[key] = output[key];
6449
6738
  }
6450
6739
  if (documentAuthorization) {
6451
- retainOutputMetadata(trimmed, metadataSource, captured.configuration, binding.scopePath ?? []);
6740
+ retainOutputMetadata(
6741
+ trimmed,
6742
+ metadataSource,
6743
+ copyAuthorizationValue(
6744
+ {
6745
+ documentPermissionField: snapshot.documentPermissionField,
6746
+ stripPermissionsField: snapshot.stripPermissionsField,
6747
+ exposedDocPermissionKeys: snapshot.exposedDocPermissionKeys,
6748
+ disableFieldPermissions: snapshot.disableFieldPermissions
6749
+ },
6750
+ true
6751
+ ),
6752
+ binding.scopePath ?? []
6753
+ );
6452
6754
  associateDocumentAuthorization(documentAuthorization, binding, trimmed);
6453
6755
  args.onFinalized?.(
6454
6756
  trimmed,
@@ -6457,7 +6759,7 @@ async function finalizeSingleDoc(args, input) {
6457
6759
  output: trimmed,
6458
6760
  outputAccess: args.outputAccess,
6459
6761
  authorization: plan.authorization,
6460
- stage: readDocumentAuthorization(documentAuthorization, binding).stage
6762
+ stage: readDocumentAuthorizationAccess(documentAuthorization, binding).stage
6461
6763
  })
6462
6764
  );
6463
6765
  }
@@ -6478,7 +6780,7 @@ async function finalizeModelOutput(args) {
6478
6780
  sharedGate: gate,
6479
6781
  resolvedLimit: limit
6480
6782
  };
6481
- return finalizeSingleDoc(internal, args.input);
6783
+ return finalizeSingleDoc(internal, args.input, prepareFinalizer(internal));
6482
6784
  }
6483
6785
  async function finalizeModelOutputs(args) {
6484
6786
  const gate = finalizerGate(args);
@@ -6487,16 +6789,24 @@ async function finalizeModelOutputs(args) {
6487
6789
  if ("documentAuthorizationStage" in args || documentAuthorizationStages && (documentAuthorizationStages.length !== inputs.length || new Set(documentAuthorizationStages).size !== inputs.length)) {
6488
6790
  authorizationError("list finalization requires a distinct document stage per row");
6489
6791
  }
6792
+ if (!inputs.length) return [];
6793
+ const internal = {
6794
+ ...rest,
6795
+ scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
6796
+ sharedGate: gate,
6797
+ resolvedLimit: limit
6798
+ };
6799
+ const prepared = prepareFinalizer(internal, inputs.length > 1);
6490
6800
  return mapWithConcurrencyLimit(inputs, limit, (input, index) => {
6491
- const internal = {
6492
- ...rest,
6493
- scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
6494
- onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
6495
- documentAuthorizationStage: documentAuthorizationStages?.[index],
6496
- sharedGate: gate,
6497
- resolvedLimit: limit
6498
- };
6499
- return finalizeSingleDoc(internal, input);
6801
+ return finalizeSingleDoc(
6802
+ {
6803
+ ...internal,
6804
+ onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
6805
+ documentAuthorizationStage: documentAuthorizationStages?.[index]
6806
+ },
6807
+ input,
6808
+ prepared
6809
+ );
6500
6810
  });
6501
6811
  }
6502
6812
  var import_utils29, isVirtualContainerValue3, getScopeVirtualsObject2, getScopeVirtualNames, getScopedPermissionSchema2, resolveGlobalPermissions, resolveDocPermissions, isAbsentDependency, __finalizeInternals;
@@ -6566,7 +6876,7 @@ var init_finalize_model_output = __esm({
6566
6876
  };
6567
6877
  resolveDocPermissions = async (args, isolatedForHook, scopeContext) => {
6568
6878
  if (args.docPermissions != null && typeof args.docPermissions === "object" && !Array.isArray(args.docPermissions)) {
6569
- const grants = args.docPermissions;
6879
+ const grants = deepIsolateValue(args.docPermissions);
6570
6880
  scopeContext.docPermissions = grants;
6571
6881
  return grants;
6572
6882
  }
@@ -7537,9 +7847,9 @@ function createServiceExecution(receiver, access, authorization, applicability =
7537
7847
  receiver,
7538
7848
  binding,
7539
7849
  authorization: call,
7540
- access: readServiceAuthorization(call, binding).access,
7850
+ access: readServiceAuthorizationAccess(call, binding),
7541
7851
  family: execution,
7542
- hydration: resolveHydration(readServiceAuthorization(call, binding).configuration, hydration)
7852
+ hydration: resolveHydration(readServiceAuthorizationHydration(call, binding), hydration)
7543
7853
  })
7544
7854
  );
7545
7855
  return execution;
@@ -7550,7 +7860,7 @@ function resolveHydration(configuration, source) {
7550
7860
  }
7551
7861
  function withExecutionHydration(receiver, parent, source) {
7552
7862
  const record3 = readServiceExecution(receiver, parent);
7553
- const { configuration } = readServiceAuthorization(record3.authorization, record3.binding);
7863
+ const configuration = readServiceAuthorizationHydration(record3.authorization, record3.binding);
7554
7864
  const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
7555
7865
  executions.set(execution, Object.freeze({ ...record3, hydration: resolveHydration(configuration, source) }));
7556
7866
  return execution;
@@ -7587,7 +7897,7 @@ function deriveServiceExecution(receiver, parent, access, applicability = "servi
7587
7897
  const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
7588
7898
  executions.set(
7589
7899
  execution,
7590
- Object.freeze({ ...record3, authorization: call, access: readServiceAuthorization(call, record3.binding).access })
7900
+ Object.freeze({ ...record3, authorization: call, access: readServiceAuthorizationAccess(call, record3.binding) })
7591
7901
  );
7592
7902
  return execution;
7593
7903
  }
@@ -10663,7 +10973,8 @@ var Service = class extends Base {
10663
10973
  async finalizeDirectOutput(args) {
10664
10974
  const { plan, context } = args;
10665
10975
  const { authorization, authorizationBinding: binding } = plan;
10666
- const { access, configuration } = readServiceAuthorization(authorization, binding);
10976
+ const access = readServiceAuthorizationAccess(authorization, binding);
10977
+ const configuration = readServiceOutputMetadata(authorization, binding);
10667
10978
  const source = getIncludeSource(args.doc);
10668
10979
  const snapshot = isolateDocForOutput(source);
10669
10980
  for (const key of Object.keys(snapshot))