@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.mjs CHANGED
@@ -3534,6 +3534,47 @@ function serviceAuthorizationInput(policy, targets) {
3534
3534
  }
3535
3535
  return { rows: policy.rows, outputFields: policy.outputFields, related };
3536
3536
  }
3537
+ function hasStableConfigurationCopies(value) {
3538
+ if (value === null || typeof value !== "object") return true;
3539
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype) return false;
3540
+ return Object.values(value).every(hasStableConfigurationCopies);
3541
+ }
3542
+ function captureAuthorizationAccess(value) {
3543
+ const input = authorizationRecord(value, "access");
3544
+ const access = {};
3545
+ for (const key of ["operation", "rowAccess", "virtualAccess", "outputAccess", "docPermissionsAccess"]) {
3546
+ if (key === "rowAccess" && input[key] === void 0) continue;
3547
+ if (typeof input[key] !== "string") authorizationError(`invalid access.${key}`);
3548
+ access[key] = input[key];
3549
+ }
3550
+ return Object.freeze(access);
3551
+ }
3552
+ function captureAuthorizationCatalog(value) {
3553
+ const input = authorizationRecord(value, "catalog");
3554
+ const catalog = {};
3555
+ for (const key of [
3556
+ "paths",
3557
+ "persistedPaths",
3558
+ "arrayPaths",
3559
+ "schemaVirtualPaths",
3560
+ "metadataPaths",
3561
+ "dynamicPaths",
3562
+ "containerPaths",
3563
+ "virtualPaths",
3564
+ "virtualScopePaths",
3565
+ "opaquePaths",
3566
+ "referencePaths",
3567
+ "protectedPaths"
3568
+ ]) {
3569
+ const paths = input[key];
3570
+ if (key !== "paths" && paths === void 0) continue;
3571
+ if (!Array.isArray(paths) || Array.from(paths).some((path) => typeof path !== "string")) {
3572
+ authorizationError(`invalid catalog.${key}`);
3573
+ }
3574
+ catalog[key] = paths;
3575
+ }
3576
+ return Object.freeze(catalog);
3577
+ }
3537
3578
  function assertAuthorizationPath(path) {
3538
3579
  if (typeof path !== "string" || !isSafeOutputPath(path)) {
3539
3580
  authorizationError(`invalid response path: ${String(path)}`);
@@ -3663,6 +3704,9 @@ function resolveServiceAuthorization(input) {
3663
3704
  const raw = copyAuthorizationValue(input.authorization);
3664
3705
  const configuration = copyAuthorizationValue(input.configuration, true);
3665
3706
  const targets = copyAuthorizationValue(input.relatedTargets ?? [], true);
3707
+ const access = copyAuthorizationValue(input.access);
3708
+ const outputAccess = captureAuthorizationAccess(access);
3709
+ const catalog = captureAuthorizationCatalog(configuration.catalog);
3666
3710
  const policy = copyAuthorizationValue(normalizePolicy(raw, input.applicability ?? "service", configuration, targets));
3667
3711
  const scopePath = Array.from(scope(input.binding));
3668
3712
  for (const part of scopePath) {
@@ -3675,17 +3719,21 @@ function resolveServiceAuthorization(input) {
3675
3719
  authorizationError("derived call request/runtime mismatch");
3676
3720
  }
3677
3721
  }
3678
- const permissions = Object.fromEntries(
3679
- input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key)])
3722
+ const permissions = new permission_default(
3723
+ Object.fromEntries(input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key) === true]))
3680
3724
  );
3725
+ for (const value of Object.values(permissions)) if (value && typeof value === "object") Object.freeze(value);
3726
+ Object.freeze(permissions);
3681
3727
  const call = Object.freeze(/* @__PURE__ */ Object.create(null));
3682
3728
  calls.set(call, {
3683
3729
  binding: Object.freeze({ ...input.binding, scopePath: Object.freeze(scopePath) }),
3684
- access: copyAuthorizationValue(input.access),
3730
+ access,
3685
3731
  configuration,
3686
3732
  policy,
3687
3733
  relatedTargets: targets,
3688
- permissions: Object.freeze(permissions),
3734
+ // normalizeFields constructs only mode strings and validated string paths.
3735
+ output: Object.freeze({ access: outputAccess, catalog, outputFields: policy.outputFields }),
3736
+ permissions,
3689
3737
  provenance: input.parent ? callRecord(input.parent).provenance : call
3690
3738
  });
3691
3739
  return call;
@@ -3703,12 +3751,75 @@ function readServiceAuthorization(call, binding) {
3703
3751
  true
3704
3752
  );
3705
3753
  }
3754
+ function readServiceAuthorizationAccess(call, binding) {
3755
+ assertAuthorizationBinding(call, binding);
3756
+ return callRecord(call).output.access;
3757
+ }
3758
+ function readServiceAuthorizationHydration(call, binding) {
3759
+ assertAuthorizationBinding(call, binding);
3760
+ return copyAuthorizationValue({ hydration: callRecord(call).configuration.hydration }, true);
3761
+ }
3762
+ function outputMetadata(configuration) {
3763
+ return {
3764
+ documentPermissionField: configuration.documentPermissionField,
3765
+ exposedDocPermissionKeys: configuration.exposedDocPermissionKeys,
3766
+ stripPermissionsField: configuration.stripPermissionsField,
3767
+ disableFieldPermissions: configuration.disableFieldPermissions
3768
+ };
3769
+ }
3770
+ function readServiceOutputMetadata(call, binding) {
3771
+ assertAuthorizationBinding(call, binding);
3772
+ return copyAuthorizationValue(outputMetadata(callRecord(call).configuration), true);
3773
+ }
3774
+ function readServiceDocumentPermissions(call, binding) {
3775
+ assertAuthorizationBinding(call, binding);
3776
+ const record3 = callRecord(call);
3777
+ return Object.freeze({
3778
+ catalog: record3.output.catalog,
3779
+ ...copyAuthorizationValue({ docPermissions: record3.configuration.docPermissions }, true)
3780
+ });
3781
+ }
3782
+ function readServiceFinalizerConfiguration(call, binding) {
3783
+ assertAuthorizationBinding(call, binding);
3784
+ const record3 = callRecord(call);
3785
+ return Object.freeze({
3786
+ catalog: record3.output.catalog,
3787
+ ...copyAuthorizationValue(
3788
+ {
3789
+ ...outputMetadata(record3.configuration),
3790
+ virtuals: record3.configuration.virtuals,
3791
+ docPermissions: record3.configuration.docPermissions
3792
+ },
3793
+ true
3794
+ )
3795
+ });
3796
+ }
3797
+ function readServiceOutputPolicy(call, binding) {
3798
+ assertAuthorizationBinding(call, binding);
3799
+ return callRecord(call).output;
3800
+ }
3801
+ function readServiceOutputFieldAuthorization(call, binding) {
3802
+ assertAuthorizationBinding(call, binding);
3803
+ const record3 = callRecord(call);
3804
+ const reusable = record3.reusableFieldConfiguration ??= hasStableConfigurationCopies(record3.configuration.permissionSchema) && // Coercion methods can observe an object's identity even without native
3805
+ // leaves. Keep those receivers local to the original resolution boundary.
3806
+ (record3.configuration.modelPermissionPrefix === null || typeof record3.configuration.modelPermissionPrefix !== "object");
3807
+ return Object.freeze({
3808
+ access: record3.output.access,
3809
+ outputFields: record3.output.outputFields,
3810
+ reusable,
3811
+ ...copyAuthorizationValue(
3812
+ {
3813
+ permissionSchema: record3.configuration.permissionSchema,
3814
+ modelPermissionPrefix: record3.configuration.modelPermissionPrefix
3815
+ },
3816
+ true
3817
+ )
3818
+ });
3819
+ }
3706
3820
  function getAuthorizationPermissions(call, binding) {
3707
3821
  assertAuthorizationBinding(call, binding);
3708
- const permission = new permission_default({ ...callRecord(call).permissions });
3709
- for (const value of Object.values(permission)) if (value && typeof value === "object") Object.freeze(value);
3710
- Object.freeze(permission);
3711
- return permission;
3822
+ return callRecord(call).permissions;
3712
3823
  }
3713
3824
  function getAuthorizationProvenance(call) {
3714
3825
  return callRecord(call).provenance;
@@ -3748,7 +3859,7 @@ function normalizeOutputAuthorizationOptions(input, options, purpose) {
3748
3859
  const stage = stageRecord(documentRecord(handle).stage);
3749
3860
  const binding = Object.freeze({ ...input.binding, scopePath: stage.binding.scopePath });
3750
3861
  assertAuthorizationBinding(stage.call, binding);
3751
- const access = readServiceAuthorization(stage.call, binding).access;
3862
+ const access = readServiceAuthorizationAccess(stage.call, binding);
3752
3863
  if (input.access.outputAccess !== access.outputAccess) authorizationError("document access mismatch");
3753
3864
  return Object.freeze({ kind: "document", documentAuthorization: handle, binding });
3754
3865
  }
@@ -3826,7 +3937,16 @@ function readDocumentAuthorization(handle, binding) {
3826
3937
  authorization: stage.call,
3827
3938
  stage: record3.stage,
3828
3939
  grants: copyAuthorizationValue(record3.grants),
3829
- ...readServiceAuthorization(stage.call, binding).access
3940
+ ...readServiceAuthorizationAccess(stage.call, binding)
3941
+ });
3942
+ }
3943
+ function readDocumentAuthorizationAccess(handle, binding) {
3944
+ const record3 = documentRecord(handle);
3945
+ const stage = stageRecord(record3.stage);
3946
+ return Object.freeze({
3947
+ authorization: stage.call,
3948
+ stage: record3.stage,
3949
+ ...readServiceAuthorizationAccess(stage.call, binding)
3830
3950
  });
3831
3951
  }
3832
3952
  function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
@@ -3834,15 +3954,23 @@ function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
3834
3954
  const stage = stageRecord(record3.stage);
3835
3955
  assertAuthorizationBinding(stage.call, binding);
3836
3956
  let decision = record3.decisions.get(field);
3837
- if (!decision) {
3957
+ if (decision) {
3958
+ return decision.then((result2) => {
3959
+ stageRecord(record3.stage);
3960
+ assertAuthorizationBinding(stage.call, binding);
3961
+ return result2;
3962
+ });
3963
+ } else {
3838
3964
  decision = Promise.resolve().then(async () => {
3839
3965
  stageRecord(record3.stage);
3966
+ assertAuthorizationBinding(stage.call, binding);
3840
3967
  const result2 = await evaluate.call(
3841
3968
  stage.binding.request,
3842
3969
  getAuthorizationPermissions(stage.call, binding),
3843
3970
  copyAuthorizationValue(record3.grants)
3844
3971
  );
3845
3972
  stageRecord(record3.stage);
3973
+ assertAuthorizationBinding(stage.call, binding);
3846
3974
  if (typeof result2 !== "boolean") authorizationError("field decision must be boolean");
3847
3975
  return result2;
3848
3976
  });
@@ -3859,7 +3987,7 @@ function assertDocumentAuthorization(handle, use) {
3859
3987
  const record3 = documentRecord(handle);
3860
3988
  const stage = stageRecord(record3.stage);
3861
3989
  assertAuthorizationBinding(stage.call, use.binding);
3862
- 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) {
3990
+ 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) {
3863
3991
  authorizationError("document call/access/stage mismatch");
3864
3992
  }
3865
3993
  const association = outputs.get(use.output);
@@ -3914,7 +4042,7 @@ function transferDocumentAuthorizationGraph(source, copy, copyOrigins) {
3914
4042
  {
3915
4043
  binding: stage.binding,
3916
4044
  output: original,
3917
- outputAccess: readServiceAuthorization(stage.call, stage.binding).access.outputAccess,
4045
+ outputAccess: readServiceAuthorizationAccess(stage.call, stage.binding).outputAccess,
3918
4046
  authorization: stage.call,
3919
4047
  stage: documentRecord(association.handle).stage
3920
4048
  },
@@ -4078,12 +4206,20 @@ function classifyRequestFieldRule(rule, permissions, modelPermissionPrefix = "")
4078
4206
  );
4079
4207
  return (typeof rule === "string" ? possible.stringHandler(rule) : possible.arrayHandler(rule)) ? "deferred" : "denied";
4080
4208
  }
4209
+ function prepareRequestFieldRule(rule, permissions, modelPermissionPrefix = "") {
4210
+ const state = classifyRequestFieldRule(rule, permissions, modelPermissionPrefix);
4211
+ const invariant = state === "denied" && modelPermissionPrefix ? classifyRequestFieldRule(rule, permissions) : state;
4212
+ return Object.freeze({ state, decision: invariant === "deferred" ? void 0 : invariant === "granted" });
4213
+ }
4214
+ function combineFieldAuthorizationStates(states2) {
4215
+ return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
4216
+ }
4081
4217
  function classifyEffectiveFieldAuthorization(args) {
4082
4218
  if (hasExplicitFieldGrant(args.outputFields, args.path)) return "granted";
4083
4219
  const states2 = args.rules.map(
4084
4220
  (rule) => classifyRequestFieldRule(rule, args.globalPermissions, args.modelPermissionPrefix)
4085
4221
  );
4086
- return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
4222
+ return combineFieldAuthorizationStates(states2);
4087
4223
  }
4088
4224
  async function evaluateScopedAccessRule(args) {
4089
4225
  const { req, rule, globalPermissions, docPermissions, modelPermissionPrefix } = args;
@@ -5742,16 +5878,111 @@ var init_virtual_populate_execution = __esm({
5742
5878
 
5743
5879
  // src/acl/output-field-filter.ts
5744
5880
  import { isPlainObject as isPlainObject21 } from "@web-ts-toolkit/utils";
5881
+ function callFieldPolicy(authorization, binding) {
5882
+ assertAuthorizationBinding(authorization, binding);
5883
+ let policy = fieldPolicies.get(authorization);
5884
+ if (!policy) {
5885
+ const view = readServiceOutputFieldAuthorization(authorization, binding);
5886
+ policy = {
5887
+ schema: view.permissionSchema,
5888
+ requestPaths: view.permissionSchema == null || isPlainObject21(view.permissionSchema) && Object.isFrozen(view.permissionSchema) ? Object.freeze(Object.keys(view.permissionSchema ?? {})) : void 0,
5889
+ prefix: view.modelPermissionPrefix ?? "",
5890
+ outputFields: view.outputFields,
5891
+ accesses: /* @__PURE__ */ new Map()
5892
+ };
5893
+ if (view.reusable) fieldPolicies.set(authorization, policy);
5894
+ }
5895
+ return policy;
5896
+ }
5897
+ function requestRuleClassification(entry, authorization, permissions, prefix) {
5898
+ if (typeof prefix !== "string") return void 0;
5899
+ let classification = entry.classifications.get(authorization);
5900
+ if (!classification) {
5901
+ classification = prepareRequestFieldRule(entry.rule, permissions, prefix);
5902
+ entry.classifications.set(authorization, classification);
5903
+ }
5904
+ return classification;
5905
+ }
5906
+ function invariantRuleLookup(schema, path) {
5907
+ if (schema == null) return true;
5908
+ if (!isPlainObject21(schema) || !Object.isFrozen(schema)) return false;
5909
+ const exact = Object.getOwnPropertyDescriptor(schema, path);
5910
+ if (exact) {
5911
+ const raw2 = exact.value;
5912
+ return !raw2 || typeof raw2 !== "object" || Array.isArray(raw2) || isPlainObject21(raw2) && Object.isFrozen(raw2);
5913
+ }
5914
+ const [head, ...rest] = path.split(".");
5915
+ const raw = Object.getOwnPropertyDescriptor(schema, head)?.value;
5916
+ if (raw == null) return true;
5917
+ if (!isPlainObject21(raw) || !Object.isFrozen(raw)) return false;
5918
+ const sub = Object.getOwnPropertyDescriptor(raw, "sub");
5919
+ return !rest.length || !sub || invariantRuleLookup(sub.value, rest.join("."));
5920
+ }
5921
+ function prepareOutputPath(policy, authorization, binding, path, access) {
5922
+ let cache = policy.accesses.get(access);
5923
+ if (!cache) {
5924
+ cache = /* @__PURE__ */ new Map();
5925
+ policy.accesses.set(access, cache);
5926
+ }
5927
+ let prepared = cache.get(path);
5928
+ if (!prepared) {
5929
+ const invariant = invariantRuleLookup(policy.schema, path);
5930
+ const permissions = getAuthorizationPermissions(authorization, binding);
5931
+ const entries = extractOutputFieldRuleEntries(policy.schema, path, access).map((entry) => ({
5932
+ ...entry,
5933
+ classifications: /* @__PURE__ */ new WeakMap()
5934
+ }));
5935
+ const classifications = entries.map(
5936
+ (entry) => requestRuleClassification(entry, authorization, permissions, policy.prefix)
5937
+ );
5938
+ const backendGrant = hasExplicitFieldGrant(policy.outputFields, path);
5939
+ prepared = {
5940
+ entries,
5941
+ backendGrant,
5942
+ state: backendGrant ? "granted" : classifications.every((value) => value !== void 0) ? combineFieldAuthorizationStates(classifications.map((value) => value.state)) : void 0
5943
+ };
5944
+ if (invariant) cache.set(path, prepared);
5945
+ }
5946
+ return prepared;
5947
+ }
5948
+ function assertOwningOutputDocuments(handle) {
5949
+ const owner = owningDocuments.get(handle);
5950
+ if (owner) {
5951
+ readDocumentAuthorizationAccess(owner.handle, owner.binding);
5952
+ assertOwningOutputDocuments(owner.handle);
5953
+ }
5954
+ }
5955
+ function prepareOutputDocumentAuthorization(authorization, binding, configuration) {
5956
+ const access = readServiceAuthorizationAccess(authorization, binding);
5957
+ const key = `docPermissions.${access.docPermissionsAccess}`;
5958
+ const docPermissions = configuration.docPermissions;
5959
+ const invariant = /^[A-Za-z0-9_-]+$/.test(access.docPermissionsAccess) && (docPermissions == null || isPlainObject21(docPermissions));
5960
+ const option = invariant ? getCapturedAuthorizationOption(configuration, key) : void 0;
5961
+ const hook = typeof option === "function" ? option : void 0;
5962
+ const resolveHook = invariant ? () => hook : () => {
5963
+ const slice = { catalog: configuration.catalog, ...copyAuthorizationValue({ docPermissions }, true) };
5964
+ const option2 = getCapturedAuthorizationOption(slice, key);
5965
+ return typeof option2 === "function" ? option2 : void 0;
5966
+ };
5967
+ return async (args) => {
5968
+ if (args.authorization !== authorization) authorizationError("document resolver call mismatch");
5969
+ assertAuthorizationBinding(authorization, args.binding);
5970
+ return resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook);
5971
+ };
5972
+ }
5745
5973
  async function resolveOutputDocumentAuthorization(args) {
5974
+ const configuration = readServiceDocumentPermissions(args.authorization, args.binding);
5975
+ return prepareOutputDocumentAuthorization(args.authorization, args.binding, configuration)(args);
5976
+ }
5977
+ async function resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook) {
5746
5978
  const { authorization, binding } = args;
5747
- const { configuration, access } = readServiceAuthorization(authorization, binding);
5748
5979
  const stage = args.stage ?? createDocumentAuthorizationStage(authorization, binding, args.snapshot);
5749
5980
  if (args.stage) assertDocumentAuthorizationStage(stage, authorization, binding, args.snapshot);
5750
5981
  const handle = await resolveDocumentAuthorization(stage, async function(doc, permissions) {
5751
5982
  if (args.owningDocument) return readDocumentAuthorization(args.owningDocument, args.owningBinding).grants;
5752
5983
  if (args.knownOutput) return {};
5753
- const hook = getCapturedAuthorizationOption(configuration, `docPermissions.${access.docPermissionsAccess}`);
5754
- if (typeof hook !== "function") return {};
5984
+ const hook = resolveHook();
5985
+ if (!hook) return {};
5755
5986
  try {
5756
5987
  const run = () => hook.call(this, doc, permissions, args.context);
5757
5988
  const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
@@ -5772,6 +6003,7 @@ async function resolveOutputDocumentAuthorization(args) {
5772
6003
  return handle;
5773
6004
  }
5774
6005
  function evaluateRequestRule(handle, binding, entry, prefix, access) {
6006
+ assertOwningOutputDocuments(handle);
5775
6007
  const owner = owningDocuments.get(handle);
5776
6008
  if (owner && !entry.path.startsWith(`${(binding.scopePath ?? []).join(".")}.`)) {
5777
6009
  return evaluateRequestRule(owner.handle, owner.binding, entry, prefix, access);
@@ -5780,65 +6012,78 @@ function evaluateRequestRule(handle, binding, entry, prefix, access) {
5780
6012
  handle,
5781
6013
  binding,
5782
6014
  `request:${access}:${entry.path}`,
5783
- function(permissions, grants) {
5784
- return evaluateScopedAccessRule({
5785
- req: this,
5786
- rule: entry.rule,
5787
- globalPermissions: permissions,
5788
- docPermissions: grants,
5789
- modelPermissionPrefix: prefix
5790
- });
6015
+ async function(permissions, grants) {
6016
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6017
+ const classification = requestRuleClassification(entry, authorization, permissions, prefix);
6018
+ let allowed = classification?.decision;
6019
+ if (allowed === void 0) {
6020
+ allowed = await evaluateScopedAccessRule({
6021
+ req: this,
6022
+ rule: entry.rule,
6023
+ globalPermissions: permissions,
6024
+ docPermissions: grants,
6025
+ modelPermissionPrefix: prefix
6026
+ });
6027
+ }
6028
+ assertOwningOutputDocuments(handle);
6029
+ return allowed;
5791
6030
  }
5792
6031
  );
5793
6032
  }
5794
6033
  function evaluateAuthorizedOutputField(handle, binding, field) {
5795
- const { authorization } = readDocumentAuthorization(handle, binding);
5796
- const { configuration, authorization: policy, access } = readServiceAuthorization(authorization, binding);
6034
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6035
+ assertOwningOutputDocuments(handle);
5797
6036
  const path = [...binding.scopePath ?? [], field].join(".");
5798
- const entries = extractOutputFieldRuleEntries(
5799
- configuration.permissionSchema,
5800
- path,
5801
- access.outputAccess
5802
- );
5803
- return memoizeDocumentFieldDecision(handle, binding, `output:${path}`, function(permissions, grants) {
5804
- const args = {
6037
+ const decision = memoizeDocumentFieldDecision(handle, binding, `output:${path}`, async function(permissions) {
6038
+ const policy = callFieldPolicy(authorization, binding);
6039
+ const { outputAccess } = readDocumentAuthorizationAccess(handle, binding);
6040
+ const prepared = prepareOutputPath(policy, authorization, binding, path, outputAccess);
6041
+ const unresolved = unresolvedDocuments.has(handle);
6042
+ const state = unresolved ? prepared.state ?? classifyEffectiveFieldAuthorization({
5805
6043
  path,
5806
- rules: entries.map((entry) => entry.rule),
6044
+ rules: prepared.entries.map((entry) => entry.rule),
6045
+ modelPermissionPrefix: policy.prefix,
5807
6046
  outputFields: policy.outputFields,
5808
- globalPermissions: permissions,
5809
- modelPermissionPrefix: configuration.modelPermissionPrefix ?? ""
5810
- };
5811
- if (unresolvedDocuments.has(handle) && classifyEffectiveFieldAuthorization(args) !== "granted") return false;
5812
- return evaluateEffectiveFieldAuthorization({
5813
- ...args,
5814
- req: this,
5815
- docPermissions: grants,
5816
- evaluateRule: (_rule, index) => evaluateRequestRule(handle, binding, entries[index], args.modelPermissionPrefix, access.outputAccess)
5817
- });
6047
+ globalPermissions: permissions
6048
+ }) : void 0;
6049
+ let allowed;
6050
+ if (unresolved && state !== "granted") allowed = false;
6051
+ else if (prepared.backendGrant) allowed = true;
6052
+ else {
6053
+ allowed = true;
6054
+ for (const entry of prepared.entries) {
6055
+ if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, outputAccess)) {
6056
+ allowed = false;
6057
+ break;
6058
+ }
6059
+ }
6060
+ }
6061
+ assertOwningOutputDocuments(handle);
6062
+ return allowed;
5818
6063
  });
6064
+ return owningDocuments.has(handle) ? decision.then((allowed) => {
6065
+ assertOwningOutputDocuments(handle);
6066
+ return allowed;
6067
+ }) : decision;
5819
6068
  }
5820
6069
  async function requestOutputFieldPaths(handle, binding, access) {
5821
- const { authorization } = readDocumentAuthorization(handle, binding);
5822
- const { configuration } = readServiceAuthorization(authorization, binding);
5823
- const schema = configuration.permissionSchema;
6070
+ const { authorization } = readDocumentAuthorizationAccess(handle, binding);
6071
+ assertOwningOutputDocuments(handle);
6072
+ const policy = callFieldPolicy(authorization, binding);
5824
6073
  const fields = [];
5825
- for (const path of Object.keys(schema ?? {})) {
5826
- const entries = extractOutputFieldRuleEntries(schema, path, access);
6074
+ for (const path of policy.requestPaths ?? Object.keys(policy.schema ?? {})) {
6075
+ const { entries } = prepareOutputPath(policy, authorization, binding, path, access);
5827
6076
  let allowed = true;
5828
6077
  for (const entry of entries) {
5829
- if (!await evaluateRequestRule(
5830
- handle,
5831
- binding,
5832
- entry,
5833
- configuration.modelPermissionPrefix ?? "",
5834
- access
5835
- )) {
6078
+ if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, access)) {
5836
6079
  allowed = false;
5837
6080
  break;
5838
6081
  }
5839
6082
  }
5840
6083
  if (allowed && entries.length) fields.push(path);
5841
6084
  }
6085
+ assertOwningOutputDocuments(handle);
6086
+ readDocumentAuthorizationAccess(handle, binding);
5842
6087
  return fields;
5843
6088
  }
5844
6089
  function authorizeEmbeddedOutputPlan(parent, child) {
@@ -5859,13 +6104,13 @@ function authorizeEmbeddedOutputPlan(parent, child) {
5859
6104
  function hasWholeOutputValueGrant(plan, field) {
5860
6105
  if (plan.fieldStates?.[field] !== "granted") return false;
5861
6106
  const binding = plan.authorizationBinding;
5862
- const { authorization } = readServiceAuthorization(plan.authorization, binding);
5863
- return hasExplicitFieldGrant(authorization.outputFields, [...binding.scopePath ?? [], field].join("."));
6107
+ const { outputFields } = readServiceOutputPolicy(plan.authorization, binding);
6108
+ return hasExplicitFieldGrant(outputFields, [...binding.scopePath ?? [], field].join("."));
5864
6109
  }
5865
6110
  function copyOutputValue(value, plan, path) {
5866
- const { configuration } = readServiceAuthorization(plan.authorization, plan.authorizationBinding);
5867
- const virtualValue = configuration.catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
5868
- const opaque = configuration.catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
6111
+ const { catalog } = readServiceOutputPolicy(plan.authorization, plan.authorizationBinding);
6112
+ const virtualValue = catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
6113
+ const opaque = catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
5869
6114
  const excluded = plan.effectiveSelectNorm.filter((token) => token.startsWith("-")).map((token) => [...plan.authorizationBinding.scopePath ?? [], token.slice(1)].join("."));
5870
6115
  const visit = (input, current) => {
5871
6116
  if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
@@ -5876,7 +6121,7 @@ function copyOutputValue(value, plan, path) {
5876
6121
  const child = `${current}.${key}`;
5877
6122
  if (excluded.some((entry2) => child === entry2 || child.startsWith(`${entry2}.`))) continue;
5878
6123
  const safeValueKey = !["__proto__", "constructor", "prototype", "_doc", "$__", "__v"].includes(key);
5879
- if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || configuration.catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(configuration.catalog, child))
6124
+ if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(catalog, child))
5880
6125
  continue;
5881
6126
  output[key] = visit(entry, child);
5882
6127
  }
@@ -5956,7 +6201,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
5956
6201
  const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
5957
6202
  let child = scoped;
5958
6203
  if (childHandle) {
5959
- const state = readDocumentAuthorization(childHandle, scoped.authorizationBinding);
6204
+ const state = readDocumentAuthorizationAccess(childHandle, scoped.authorizationBinding);
5960
6205
  if (getAuthorizationProvenance(state.authorization) !== getAuthorizationProvenance(plan.authorization))
5961
6206
  authorizationError("child call mismatch");
5962
6207
  child = helperPlan(state.authorization, scoped.authorizationBinding);
@@ -6020,7 +6265,7 @@ async function filterModelOutputForHelper(args) {
6020
6265
  if (normalized.kind === "document") {
6021
6266
  const { documentAuthorization: handle2, binding: binding2 } = normalized;
6022
6267
  assertDocumentAuthorization(handle2, { binding: binding2, output: args.doc, outputAccess: access });
6023
- const state = readDocumentAuthorization(handle2, binding2);
6268
+ const state = readDocumentAuthorizationAccess(handle2, binding2);
6024
6269
  return filterExistingOutput(
6025
6270
  args.doc,
6026
6271
  helperPlan(state.authorization, binding2),
@@ -6043,7 +6288,7 @@ async function filterModelOutputForHelper(args) {
6043
6288
  });
6044
6289
  return filterExistingOutput(source, plan, handle, false);
6045
6290
  }
6046
- var unresolvedDocuments, owningDocuments;
6291
+ var unresolvedDocuments, owningDocuments, fieldPolicies;
6047
6292
  var init_output_field_filter = __esm({
6048
6293
  "src/acl/output-field-filter.ts"() {
6049
6294
  init_permissions_alias();
@@ -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
 
@@ -6128,15 +6374,41 @@ __export(finalize_model_output_exports, {
6128
6374
  finalizeModelOutputs: () => finalizeModelOutputs
6129
6375
  });
6130
6376
  import { get as get9, isPlainObject as isPlainObject22, set as set7 } from "@web-ts-toolkit/utils";
6131
- async function finalizeSingleDoc(args, input) {
6132
- const scopePath = args.scopePath;
6377
+ function assertFinalizerCall(args, prepared) {
6133
6378
  const plan = args.plan;
6134
6379
  const binding = plan.authorizationBinding;
6135
- const captured = plan.authorization && binding ? readServiceAuthorization(plan.authorization, binding) : void 0;
6136
- 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("."))) {
6380
+ if (plan.authorization !== prepared.authorization) authorizationError("finalizer call changed during pass");
6381
+ if (plan.authorization && binding) assertAuthorizationBinding(plan.authorization, binding);
6382
+ const access = prepared.policy?.access;
6383
+ 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("."))) {
6137
6384
  authorizationError("finalizer call/model/scope/access mismatch");
6138
6385
  }
6139
- const snapshot = captured ? captured.configuration : args.snapshot;
6386
+ }
6387
+ function prepareFinalizer(args, sharedContext = false) {
6388
+ const { authorization, authorizationBinding: binding } = args.plan;
6389
+ const policy = authorization && binding ? readServiceOutputPolicy(authorization, binding) : void 0;
6390
+ assertFinalizerCall(args, { authorization, policy });
6391
+ const configuration = authorization ? readServiceFinalizerConfiguration(authorization, binding) : void 0;
6392
+ const snapshot = configuration ? configuration : deepIsolateValue(args.snapshot);
6393
+ return {
6394
+ authorization,
6395
+ policy,
6396
+ snapshot,
6397
+ baseContext: deepIsolateValue(args.context ?? {}),
6398
+ sharedContext,
6399
+ scopeVirtualNames: getScopeVirtualNames(snapshot, args.scopePath),
6400
+ scopedSchema: configuration ? null : getScopedPermissionSchema2(snapshot.permissionSchema, args.scopePath),
6401
+ permissions: authorization ? getAuthorizationPermissions(authorization, binding) : void 0,
6402
+ resolveDocument: authorization ? prepareOutputDocumentAuthorization(authorization, binding, configuration) : void 0
6403
+ };
6404
+ }
6405
+ async function finalizeSingleDoc(args, input, prepared) {
6406
+ assertFinalizerCall(args, prepared);
6407
+ const scopePath = args.scopePath;
6408
+ const plan = args.plan;
6409
+ const binding = plan.authorizationBinding;
6410
+ const captured = prepared.policy;
6411
+ const snapshot = prepared.snapshot;
6140
6412
  const limit = args.resolvedLimit;
6141
6413
  const gate = args.sharedGate;
6142
6414
  const original = getIncludeOriginal(input);
@@ -6148,7 +6420,7 @@ async function finalizeSingleDoc(args, input) {
6148
6420
  for (const key of Object.keys(output)) {
6149
6421
  transferDocumentAuthorizationGraph(input[key], output[key]);
6150
6422
  }
6151
- const baseContext = args.context ?? {};
6423
+ const baseContext = prepared.sharedContext ? deepIsolateValue(prepared.baseContext) : prepared.baseContext;
6152
6424
  const operation = captured?.access.operation ?? baseContext.operation ?? args.operation ?? args.virtualAccess;
6153
6425
  const scopeContext = {
6154
6426
  ...baseContext,
@@ -6166,7 +6438,7 @@ async function finalizeSingleDoc(args, input) {
6166
6438
  if (captured) {
6167
6439
  const projected = args.documentAuthorizationStage ? input : deepIsolateValue(output);
6168
6440
  metadataSource = deepIsolateValue(output);
6169
- documentAuthorization = await resolveOutputDocumentAuthorization({
6441
+ documentAuthorization = await prepared.resolveDocument({
6170
6442
  authorization: plan.authorization,
6171
6443
  binding,
6172
6444
  snapshot: projected,
@@ -6179,13 +6451,10 @@ async function finalizeSingleDoc(args, input) {
6179
6451
  }
6180
6452
  const docGrants = documentAuthorization ? readDocumentAuthorization(documentAuthorization, binding).grants : await resolveDocPermissions(args, output, scopeContext);
6181
6453
  scopeContext.docPermissions = deepIsolateValue(docGrants);
6182
- const globalPerms = captured ? getAuthorizationPermissions(plan.authorization, binding) : resolveGlobalPermissions(args);
6183
- const modelPermissionPrefix = snapshot.modelPermissionPrefix ?? "";
6184
- const documentPermissionField = snapshot.documentPermissionField ?? "_permissions";
6185
- const scopedSchema = getScopedPermissionSchema2(
6186
- snapshot.permissionSchema ?? null,
6187
- scopePath
6188
- );
6454
+ const globalPerms = prepared.permissions ?? resolveGlobalPermissions(args);
6455
+ const modelPermissionPrefix = captured ? "" : deepIsolateValue(snapshot.modelPermissionPrefix ?? "");
6456
+ const documentPermissionField = captured ? "_permissions" : deepIsolateValue(snapshot.documentPermissionField ?? "_permissions");
6457
+ const scopedSchema = deepIsolateValue(prepared.scopedSchema);
6189
6458
  const childPlans = plan.childPlans ?? {};
6190
6459
  for (const [containerName, childPlan] of Object.entries(childPlans)) {
6191
6460
  if (!(containerName in output)) continue;
@@ -6204,12 +6473,13 @@ async function finalizeSingleDoc(args, input) {
6204
6473
  continue;
6205
6474
  }
6206
6475
  retainRawAssociation(output, containerName, rawContainer);
6207
- const childSnapshot = snapshot;
6208
6476
  const childBase = {
6209
6477
  ...args,
6210
6478
  scopePath: [...childPlan.scopePath ?? [...scopePath, containerName, "sub"]],
6211
6479
  plan: captured ? authorizeEmbeddedOutputPlan(plan, childPlan) : childPlan,
6212
- snapshot: childSnapshot,
6480
+ // Authenticated preparation reads its own authority. Do not clone an
6481
+ // otherwise unused public configuration graph on default traversal.
6482
+ snapshot: args.snapshot,
6213
6483
  // Embedded reuses the owning parent's grants + accesses (VIRT-00A D2).
6214
6484
  docPermissions: docGrants,
6215
6485
  context: scopeContext,
@@ -6220,6 +6490,7 @@ async function finalizeSingleDoc(args, input) {
6220
6490
  related: [],
6221
6491
  virtualPopulateRow: privateRow
6222
6492
  };
6493
+ let childPreparation;
6223
6494
  const finalizeOneChild = async (rawChild, address) => {
6224
6495
  if (captured && Array.isArray(rawChild))
6225
6496
  return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
@@ -6240,7 +6511,14 @@ async function finalizeSingleDoc(args, input) {
6240
6511
  } : { ...args, virtualPopulateRow: privateRow, virtualPopulateAddress: address }
6241
6512
  });
6242
6513
  }
6243
- return finalizeSingleDoc({ ...childBase, virtualPopulateAddress: address }, rawChild);
6514
+ return finalizeSingleDoc(
6515
+ { ...childBase, virtualPopulateAddress: address },
6516
+ rawChild,
6517
+ childPreparation ??= prepareFinalizer(
6518
+ childBase,
6519
+ Array.isArray(rawContainer) && (rawContainer.length > 1 || Array.isArray(rawContainer[0]))
6520
+ )
6521
+ );
6244
6522
  };
6245
6523
  if (Array.isArray(rawContainer)) {
6246
6524
  const finalized = await mapWithConcurrencyLimit(
@@ -6292,6 +6570,7 @@ async function finalizeSingleDoc(args, input) {
6292
6570
  context: scopeContext,
6293
6571
  related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
6294
6572
  };
6573
+ let relatedPreparation;
6295
6574
  const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
6296
6575
  if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return rawChild;
6297
6576
  if (typeof rawChild?._bsontype === "string") return rawChild;
@@ -6305,7 +6584,11 @@ async function finalizeSingleDoc(args, input) {
6305
6584
  parentArgs: { ...args, virtualPopulateRow: childRow, virtualPopulateAddress: [] }
6306
6585
  });
6307
6586
  }
6308
- return finalizeSingleDoc({ ...relatedBase, virtualPopulateRow: childRow }, rawChild);
6587
+ return finalizeSingleDoc(
6588
+ { ...relatedBase, virtualPopulateRow: childRow },
6589
+ rawChild,
6590
+ relatedPreparation ??= prepareFinalizer(relatedBase, Array.isArray(rawRelated) && rawRelated.length > 1)
6591
+ );
6309
6592
  };
6310
6593
  let finalized;
6311
6594
  if (Array.isArray(rawRelated)) {
@@ -6325,18 +6608,24 @@ async function finalizeSingleDoc(args, input) {
6325
6608
  } catch {
6326
6609
  }
6327
6610
  }
6328
- const scopeVirtualNames = getScopeVirtualNames(snapshot, scopePath);
6329
- for (const virtualName of scopeVirtualNames) {
6611
+ for (const virtualName of prepared.scopeVirtualNames) {
6330
6612
  if (virtualName in output) delete output[virtualName];
6331
6613
  }
6332
- const stableBase = deepIsolateValue(output);
6333
- const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
6334
6614
  const candidates = plan.candidates ?? [];
6615
+ const needsGetterView = candidates.some((candidate) => {
6616
+ const descriptor = candidate.descriptor;
6617
+ if (typeof descriptor?.get !== "function") return false;
6618
+ if (!privateRow && descriptor.dependsOnPopulate?.length) return false;
6619
+ const dependsOn = Array.isArray(descriptor.dependsOn) ? descriptor.dependsOn : [];
6620
+ return dependsOn.every((dep) => typeof dep === "string" && !isAbsentDependency(output, dep));
6621
+ });
6622
+ let stableBase = needsGetterView ? deepIsolateValue(output) : void 0;
6623
+ const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
6335
6624
  const staged = /* @__PURE__ */ new Map();
6336
6625
  const runCandidate = async (candidate) => {
6337
6626
  const dependsOn = Array.isArray(candidate.descriptor?.dependsOn) ? candidate.descriptor.dependsOn : [];
6338
6627
  for (const dep of dependsOn) {
6339
- if (typeof dep !== "string" || isAbsentDependency(stableBase, dep)) return;
6628
+ if (typeof dep !== "string" || isAbsentDependency(stableBase ?? output, dep)) return;
6340
6629
  }
6341
6630
  const allowed = documentAuthorization ? await evaluateAuthorizedOutputField(documentAuthorization, binding, candidate.name) : await evaluateEffectiveFieldAuthorization({
6342
6631
  req: args.request,
@@ -6351,9 +6640,9 @@ async function finalizeSingleDoc(args, input) {
6351
6640
  if (typeof getter !== "function") return;
6352
6641
  const privateInputs = privateRow ? readVirtualPopulateInputs(privateRow, privateAddress, candidate.name) : candidate.descriptor.dependsOnPopulate?.length ? { available: false } : { available: true, populated: {} };
6353
6642
  if (!privateInputs.available) return;
6354
- const isolatedView = deepIsolateValue(stableBase);
6643
+ const isolatedView = deepIsolateValue(stableBase ??= deepIsolateValue(output));
6355
6644
  const virtualContext = {
6356
- ...scopeContext,
6645
+ ...deepIsolateValue(scopeContext),
6357
6646
  docPermissions: deepIsolateValue(docGrants)
6358
6647
  };
6359
6648
  const outcome = await runVirtualGetter({
@@ -6444,12 +6733,25 @@ async function finalizeSingleDoc(args, input) {
6444
6733
  if (!(key in output)) continue;
6445
6734
  if (captured && !plan.childPlans[key]) {
6446
6735
  const path = [...binding.scopePath ?? [], key].join(".");
6447
- trimmed[key] = captured.configuration.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
6736
+ trimmed[key] = captured.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
6448
6737
  if (trimmed[key] === void 0) delete trimmed[key];
6449
6738
  } else trimmed[key] = output[key];
6450
6739
  }
6451
6740
  if (documentAuthorization) {
6452
- retainOutputMetadata(trimmed, metadataSource, captured.configuration, binding.scopePath ?? []);
6741
+ retainOutputMetadata(
6742
+ trimmed,
6743
+ metadataSource,
6744
+ copyAuthorizationValue(
6745
+ {
6746
+ documentPermissionField: snapshot.documentPermissionField,
6747
+ stripPermissionsField: snapshot.stripPermissionsField,
6748
+ exposedDocPermissionKeys: snapshot.exposedDocPermissionKeys,
6749
+ disableFieldPermissions: snapshot.disableFieldPermissions
6750
+ },
6751
+ true
6752
+ ),
6753
+ binding.scopePath ?? []
6754
+ );
6453
6755
  associateDocumentAuthorization(documentAuthorization, binding, trimmed);
6454
6756
  args.onFinalized?.(
6455
6757
  trimmed,
@@ -6458,7 +6760,7 @@ async function finalizeSingleDoc(args, input) {
6458
6760
  output: trimmed,
6459
6761
  outputAccess: args.outputAccess,
6460
6762
  authorization: plan.authorization,
6461
- stage: readDocumentAuthorization(documentAuthorization, binding).stage
6763
+ stage: readDocumentAuthorizationAccess(documentAuthorization, binding).stage
6462
6764
  })
6463
6765
  );
6464
6766
  }
@@ -6479,7 +6781,7 @@ async function finalizeModelOutput(args) {
6479
6781
  sharedGate: gate,
6480
6782
  resolvedLimit: limit
6481
6783
  };
6482
- return finalizeSingleDoc(internal, args.input);
6784
+ return finalizeSingleDoc(internal, args.input, prepareFinalizer(internal));
6483
6785
  }
6484
6786
  async function finalizeModelOutputs(args) {
6485
6787
  const gate = finalizerGate(args);
@@ -6488,16 +6790,24 @@ async function finalizeModelOutputs(args) {
6488
6790
  if ("documentAuthorizationStage" in args || documentAuthorizationStages && (documentAuthorizationStages.length !== inputs.length || new Set(documentAuthorizationStages).size !== inputs.length)) {
6489
6791
  authorizationError("list finalization requires a distinct document stage per row");
6490
6792
  }
6793
+ if (!inputs.length) return [];
6794
+ const internal = {
6795
+ ...rest,
6796
+ scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
6797
+ sharedGate: gate,
6798
+ resolvedLimit: limit
6799
+ };
6800
+ const prepared = prepareFinalizer(internal, inputs.length > 1);
6491
6801
  return mapWithConcurrencyLimit(inputs, limit, (input, index) => {
6492
- const internal = {
6493
- ...rest,
6494
- scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
6495
- onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
6496
- documentAuthorizationStage: documentAuthorizationStages?.[index],
6497
- sharedGate: gate,
6498
- resolvedLimit: limit
6499
- };
6500
- return finalizeSingleDoc(internal, input);
6802
+ return finalizeSingleDoc(
6803
+ {
6804
+ ...internal,
6805
+ onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
6806
+ documentAuthorizationStage: documentAuthorizationStages?.[index]
6807
+ },
6808
+ input,
6809
+ prepared
6810
+ );
6501
6811
  });
6502
6812
  }
6503
6813
  var 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
  }
@@ -7510,9 +7820,9 @@ function createServiceExecution(receiver, access, authorization, applicability =
7510
7820
  receiver,
7511
7821
  binding,
7512
7822
  authorization: call,
7513
- access: readServiceAuthorization(call, binding).access,
7823
+ access: readServiceAuthorizationAccess(call, binding),
7514
7824
  family: execution,
7515
- hydration: resolveHydration(readServiceAuthorization(call, binding).configuration, hydration)
7825
+ hydration: resolveHydration(readServiceAuthorizationHydration(call, binding), hydration)
7516
7826
  })
7517
7827
  );
7518
7828
  return execution;
@@ -7523,7 +7833,7 @@ function resolveHydration(configuration, source) {
7523
7833
  }
7524
7834
  function withExecutionHydration(receiver, parent, source) {
7525
7835
  const record3 = readServiceExecution(receiver, parent);
7526
- const { configuration } = readServiceAuthorization(record3.authorization, record3.binding);
7836
+ const configuration = readServiceAuthorizationHydration(record3.authorization, record3.binding);
7527
7837
  const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
7528
7838
  executions.set(execution, Object.freeze({ ...record3, hydration: resolveHydration(configuration, source) }));
7529
7839
  return execution;
@@ -7560,7 +7870,7 @@ function deriveServiceExecution(receiver, parent, access, applicability = "servi
7560
7870
  const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
7561
7871
  executions.set(
7562
7872
  execution,
7563
- Object.freeze({ ...record3, authorization: call, access: readServiceAuthorization(call, record3.binding).access })
7873
+ Object.freeze({ ...record3, authorization: call, access: readServiceAuthorizationAccess(call, record3.binding) })
7564
7874
  );
7565
7875
  return execution;
7566
7876
  }
@@ -10648,7 +10958,8 @@ var Service = class extends Base {
10648
10958
  async finalizeDirectOutput(args) {
10649
10959
  const { plan, context } = args;
10650
10960
  const { authorization, authorizationBinding: binding } = plan;
10651
- const { access, configuration } = readServiceAuthorization(authorization, binding);
10961
+ const access = readServiceAuthorizationAccess(authorization, binding);
10962
+ const configuration = readServiceOutputMetadata(authorization, binding);
10652
10963
  const source = getIncludeSource(args.doc);
10653
10964
  const snapshot = isolateDocForOutput(source);
10654
10965
  for (const key of Object.keys(snapshot))