@web-ts-toolkit/access-router 0.56.0 → 0.57.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.
package/llms.txt CHANGED
@@ -36,8 +36,8 @@
36
36
  - [Correlated includes and literal escapes](README.md#correlated-includes) · [Nested writes and trusted hooks](README.md#nested-updates-and-trusted-hooks)
37
37
  - [Virtuals, literal-filter preservation and output compatibility](README.md#virtuals-computed-fields) · [Request/runtime reentrant hook chains](README.md#hook-admission-and-reentrant-service-calls) · [Separate persistence limits](README.md#persistence-concurrency-and-resource-limits)
38
38
  - [Private populated dependencies](README.md#private-populated-dependencies) · [Runnable example](README.md#runnable-private-dependency-example): server virtual `dependsOnPopulate: [{ path, select? }]`, automatically retained scope-local static ObjectId ref/1D array, eagerly registered same-runtime/same-connection target. Explicit `ModelVirtualDescriptor<Source, Value, { ref: Target }>` types only optional getter argument four (`populated = {}`); argument one/local dependsOn and persisted/write/public types stay unchanged. Root/advanced types: ModelVirtualPopulateDependency, ModelVirtualPopulateInputs. No client/query authority or new entrypoint.
39
- - [Runtime private-read mode](README.md#runtime-private-read-mode): `api.setGlobalOptions({ requireReadOperationAccessForPopulateDependencies: false })` opts out of private target operationAccess.read only; default true, only literal false bypasses, malformed JS/undefined stays conservative true. Typed GlobalOptions/exact setters/getters; server/runtime-only, isolated and captured with source/target/query intent before awaits, including nested output. Not requireRegisteredPopulateModels: eager private registration/static declarations remain. Source/target input-field permissions are already bypassed; virtual output authorization and request baseFilter.read/overrideFilter.read/retained IDs still govern. False + virtual-only selection adds no operation check solely to fetch.
40
- - [Private target/read/selection rules](README.md#target-authority-selection-and-reconstruction) · [Release compatibility](README.md#release-compatibility-dependency-reference-omission): BOTH modes apply an independent captured caller target BASE-read visibility gate to active selected/applicable dependency refs. Source unselected/denied -> omit; target read denied/failed -> omit entire ref INCLUDING ID, outranking list/custom populate access and public related-operation grants. Read allowed + ordinary population -> original authorized mask; read allowed + no population -> exact original refs/arrays/nulls/misses/order/duplicates/presence. This intentionally supersedes prior D1 denied-ID fallback even in default mode; no active plan/no feature keeps ordinary behavior. Private availability/getter outcomes never grant reference output. Compatible positive fetch unions retain separate masks/private request row scope, independent of public match/access/related grants. Each getter gets only its declared fourth-argument mask; no implicit DTO/handle/metadata/hook/task transport, only its authorized deliberate return. No recursive private target virtuals or successful-write retries. See README for selection/errors/inheritance/native isolation/hydration caveats and measured compatible-group batching.
39
+ - [Runtime populate operation policies](README.md#runtime-populate-operation-policies): exactly `requireOperationAccessForPopulateQueries` and `requireOperationAccessForPopulateDependencies`, both default true; only literal false bypasses its operation gate (malformed JS/undefined is true). Q=false skips ordinary effective target read/list/custom admission only; source entry/field authorization, target field/document grants, rows/transforms, original select/_id, match and mandatory IDs remain. D=false skips private target base operationAccess.read only; server declarations grant input fields, virtual output authorization and request baseFilter.read/overrideFilter.read/original IDs remain. Four independent modes: true/true strict both; false/true public only; true/false private only; false/false both, subject to those policies. Direct/root target access stays denied. Every target needs an actual runtime-registered model and effective object permissionSchema (defaults allowed, {} closes business fields); a configuration-only Document needs no HTTP routes. Missing policy is controlled bad_request before target I/O/write. Typed GlobalOptions/setters/aliases/getGlobalOptions frozen snapshots; server/runtime-only, captured before awaits through nested output, later calls see updates, runtimes isolated.
40
+ - [Reference reconstruction](README.md#target-authority-selection-and-reconstruction) · [Breaking migration](README.md#breaking-release-populate-policy-migration): no additional target-base-read reference-visibility gate. Adding a virtual does not change ordinary refs. Source unselected/field-denied -> omit; no admitted public population -> exact original IDs/arrays/nulls/missing IDs/order/duplicates/presence, even on strict private denial or private row/query/getter failure. Admitted population -> original authorized mask, scalar null/filtered-array misses (not IDs). Compatible positive fetch unions retain separate immutable public/private masks and request read-row scope, independent of public match/access/related grants. Each getter sees only its declared fourth-argument mask; no implicit DTO/handle/metadata/hook/snapshot/task transport, only its authorized deliberate return. No recursive private target virtuals or successful-write retries. Removed old flags have no aliases/raw mapping; see the migration note. README covers error guards/static validation/native isolation/hydration caveats and measured batching.
41
41
 
42
42
  ## Additional examples
43
43
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@web-ts-toolkit/access-router",
3
3
  "description": "ACL-aware Express routers and in-memory data services for Mongoose-backed APIs",
4
4
  "homepage": "https://web-ts-toolkit.pages.dev/docs/packages/access-router",
5
- "version": "0.56.0",
5
+ "version": "0.57.0",
6
6
  "sideEffects": [
7
7
  "./**/index.js",
8
8
  "./**/index.mjs",
@@ -57,8 +57,8 @@
57
57
  "node": ">=22"
58
58
  },
59
59
  "dependencies": {
60
- "@web-ts-toolkit/express-json-router": "0.56.0",
61
- "@web-ts-toolkit/utils": "0.56.0",
60
+ "@web-ts-toolkit/express-json-router": "0.57.0",
61
+ "@web-ts-toolkit/utils": "0.57.0",
62
62
  "just-diff": "^6.0.2",
63
63
  "mongoose-schema-jsonschema": "^4.0.1",
64
64
  "sift": "^17.1.3",
@@ -421,18 +421,21 @@ type ModelVirtualDoc<TModel = unknown> = IsUnknownVirtual<TModel> extends true ?
421
421
  * Static string refs must bind the actual same-connection ObjectId model; register
422
422
  * its model instance AND options first. Validation is eager/atomic, including setters.
423
423
  * This bypasses source/target input-field permissions, not the virtual's own output
424
- * authorization. GlobalOptions.requireReadOperationAccessForPopulateDependencies defaults
424
+ * authorization. GlobalOptions.requireOperationAccessForPopulateDependencies defaults
425
425
  * to true, requiring the captured caller's target base operationAccess.read for private
426
426
  * loading. Only literal false bypasses that gate; private-only loading then adds no
427
427
  * operation check solely for the fetch. Target overrideFilter.read/baseFilter.read and
428
428
  * retained source IDs always use request authority/tenant scope, independently of ordinary
429
429
  * populate match/access and authorization.related. Private target virtuals never run.
430
- * In both modes, active selected/applicable dependency refs denied caller-context base
431
- * read visibility are omitted entirely, including IDs; public list/custom access or a
432
- * related-operation grant cannot override this. Allowed selected/permitted refs keep the
433
- * original authorized populate mask or exact original refs. No active plan keeps ordinary
434
- * behavior. The mode and source/target policy are captured before awaits; registration
435
- * remains mandatory independently of requireRegisteredPopulateModels.
430
+ * Private work does not change ordinary reference shape. Selected/permitted source refs
431
+ * keep exact original IDs, including array order/duplicates/missing IDs/nulls, unless an
432
+ * admitted ordinary population owns the slot (then its original public mask/miss applies).
433
+ * Strict private denial, row/query/getter failure and deferred virtual denial cannot hide
434
+ * permitted IDs. Only source unselection/field denial omits them; there is no extra target
435
+ * base-read visibility gate. GlobalOptions.requireOperationAccessForPopulateQueries
436
+ * independently controls ordinary target operation admission. Both flags and source/target
437
+ * policy are captured before awaits; registration and an effective object permissionSchema
438
+ * remain mandatory. Private inputs never implicitly enter hooks, handles or metadata.
436
439
  */
437
440
  interface ModelVirtualPopulateDependency<TModel = unknown> {
438
441
  /** Top-level ref relative to this virtual's scope; no dotted source paths. Retained automatically. */
@@ -503,8 +506,9 @@ type ModelVirtualGetter<TModel = unknown, TValue = unknown, TPopulated extends V
503
506
  * generic never changes the persisted model, write types or public output.
504
507
  * Use `populated = {}` or guard undefined to retain three-argument direct calls.
505
508
  * See the shipped README's "Private populated dependencies" for registration,
506
- * reference omission/population/ID shapes, captured private-read mode, mandatory row
507
- * gates and absence/error rules. Private computation does not imply reference visibility.
509
+ * source-field omission/population/ID shapes, independent captured operation policies,
510
+ * mandatory row gates and absence/error rules. Private computation does not grant output
511
+ * or remove otherwise permitted parent IDs.
508
512
  *
509
513
  * @example
510
514
  * ```ts
@@ -1154,9 +1158,9 @@ interface AuthorizationConfiguration {
1154
1158
  /** Opaque terminal private-input capture. Never related authority or public document metadata. */
1155
1159
  readonly virtualPopulateSnapshot?: VirtualPopulateSnapshotToken;
1156
1160
  /** Runtime-owned private input admission mode, not public output/related authority. */
1157
- readonly requireReadOperationAccessForPopulateDependencies?: boolean;
1158
- /** Independent ordinary-populate registration posture from the same global snapshot. */
1159
- readonly requireRegisteredPopulateModels?: boolean;
1161
+ readonly requireOperationAccessForPopulateDependencies?: boolean;
1162
+ /** Independent ordinary-populate operation admission from the same global snapshot. */
1163
+ readonly requireOperationAccessForPopulateQueries?: boolean;
1160
1164
  readonly documentPermissionField?: string;
1161
1165
  /** Captured permission schema, virtual descriptors, always-select and metadata posture. */
1162
1166
  readonly [key: string]: unknown;
@@ -1170,8 +1174,7 @@ interface AuthorizationRelatedTarget {
1170
1174
  readonly queryIntent?: ModelServiceIntent;
1171
1175
  /** Existing include operation gate, resolved synchronously with its target. */
1172
1176
  readonly operationGuard?: unknown;
1173
- /** Passthrough is the existing global unregistered-populate opt-out, not a caller policy. */
1174
- readonly kind?: 'output' | 'count' | 'passthrough';
1177
+ readonly kind?: 'output' | 'count';
1175
1178
  readonly relatedTargets?: readonly AuthorizationRelatedTarget[];
1176
1179
  /** Captured supported raw-ref transport shape; exotic ordinary populates keep their adapter path. */
1177
1180
  readonly staticObjectIdReference?: boolean;
@@ -2110,7 +2113,7 @@ declare class Core {
2110
2113
  /** Persisted request-only populate admission; fetch planning is not field authority. */
2111
2114
  private requestPopulateSelect;
2112
2115
  /** Request-only population. Use named response scopePath; positional subPaths are removed. */
2113
- genPopulate(modelName: string, access?: SelectAccess | BaseFilterAccess, _populate?: Populate | Populate[] | string | null, options?: PopulatePlanningOptions): Promise<(string | Populate)[]>;
2116
+ genPopulate(modelName: string, access?: SelectAccess | BaseFilterAccess, _populate?: Populate | Populate[] | string | null, options?: PopulatePlanningOptions): Promise<Populate[]>;
2114
2117
  validate(modelName: string, allowedData: unknown, access: ValidateAccess, context: ModelHookContext): Promise<boolean | unknown[]>;
2115
2118
  prepare<T>(modelName: string, allowedData: T, access: PrepareAccess, context: ModelHookContext): Promise<T>;
2116
2119
  transform<T>(modelName: string, doc: T, access: TransformAccess, context: ModelHookContext): Promise<T>;
@@ -3130,7 +3133,7 @@ interface AccessRouterLogger {
3130
3133
  isLevelEnabled?: (level: string) => boolean;
3131
3134
  }
3132
3135
  /**
3133
- * Runtime-wide request initialization, private-input policy and resource settings, configured with
3136
+ * Runtime-wide request initialization, populate operation policies and resource settings, configured with
3134
3137
  * api.setGlobalOptions / api.setGlobalOption. These are not model options or wire
3135
3138
  * options. Object assignment is shallow; nested requestComplexity replaces as a unit.
3136
3139
  */
@@ -3146,8 +3149,20 @@ interface GlobalOptions {
3146
3149
  * context. Generated routers do not authenticate client claims for this hook.
3147
3150
  */
3148
3151
  globalPermissions?: (this: AccessRouterRequest, req: AccessRouterRequest) => MaybePromise<GlobalPermissionValue>;
3149
- /** Require populate target models to be registered in the active runtime. Defaults to `true`. */
3150
- requireRegisteredPopulateModels?: boolean;
3152
+ /**
3153
+ * Require the caller's effective target operation (`read`, supported `list` or trusted
3154
+ * custom access) for ordinary query population (default `true`). Only literal `false`
3155
+ * skips that admission gate; source entry/field authorization, target field/document
3156
+ * grants, row/base/transform policies, original selection, match and mandatory reference
3157
+ * IDs still constrain output. Denied population retains selected/permitted original
3158
+ * source IDs; admitted population misses return null/filtered arrays, not restored IDs.
3159
+ * Targets always require an actual Mongoose model and ACL configuration with an
3160
+ * effective object permissionSchema in this runtime; `{}` closes business fields.
3161
+ * Runtime-owned and captured before awaits, independently of private dependency
3162
+ * admission. A configuration-only Document needs no HTTP routes. Its denied direct/root
3163
+ * read/list operations stay denied. No per-model/query/client override.
3164
+ */
3165
+ requireOperationAccessForPopulateQueries?: boolean;
3151
3166
  /**
3152
3167
  * Require the original caller's target base `operationAccess.read` before loading
3153
3168
  * declared private `dependsOnPopulate` inputs (default `true`). Only literal boolean
@@ -3159,28 +3174,32 @@ interface GlobalOptions {
3159
3174
  * nested output scopes. In-flight calls retain their mode; later calls see updates.
3160
3175
  * Runtime-isolated server configuration only: no caller/query or per-model override.
3161
3176
  *
3162
- * In BOTH modes, references in an active selected/applicable dependency plan require
3163
- * source selection/field permission AND the captured caller-context target base read
3164
- * grant. Denied/failed read visibility omits the entire reference, including its ID,
3165
- * even with ordinary list/custom populate access or a public related-operation grant.
3166
- * Allowed read keeps the original authorized public populate mask, or exact original
3167
- * refs without population. No active dependency plan leaves ordinary behavior intact.
3168
- * False-mode private-only loading adds no operation check solely for that fetch.
3169
- * This never grants public fields, row/write authority or query controls. Registration
3170
- * and static input declarations remain required, independently of requireRegisteredPopulateModels.
3177
+ * Neither mode changes ordinary reference shape: selected/permitted parent fields keep
3178
+ * exact original IDs (including array order, duplicates, missing IDs and nulls) when no
3179
+ * admitted ordinary population owns them. Private denial, row/query/getter failure or
3180
+ * deferred virtual denial cannot hide those IDs. Omit only for parent unselection/field
3181
+ * denial; admitted public DTO/null/filtered-array results retain their original masks.
3182
+ * There is no additional target-base-read reference-visibility gate. False-mode loading
3183
+ * adds no operation check solely for its private fetch. Private fourth-argument inputs
3184
+ * never implicitly enter DTOs, hooks, handles, snapshots or permission metadata.
3185
+ * An actual registered model, effective object permissionSchema and static declarations
3186
+ * remain mandatory, independently of requireOperationAccessForPopulateQueries.
3171
3187
  *
3172
3188
  * @example
3173
3189
  * ```ts
3174
3190
  * import { createAccessRuntime, type GlobalOptions } from '@web-ts-toolkit/access-router';
3175
3191
  * const api = createAccessRuntime();
3176
- * const policy = { requireReadOperationAccessForPopulateDependencies: false } satisfies GlobalOptions;
3177
- * api.setGlobalOptions(policy); // privileged computation inputs; no public target-read grant
3192
+ * const policy = {
3193
+ * requireOperationAccessForPopulateQueries: true,
3194
+ * requireOperationAccessForPopulateDependencies: false,
3195
+ * } satisfies GlobalOptions;
3196
+ * api.setGlobalOptions(policy); // private computation; denied public population retains permitted IDs
3178
3197
  * const snapshot: GlobalOptions = api.getGlobalOptions();
3179
- * api.setGlobalOption('requireReadOperationAccessForPopulateDependencies', true);
3198
+ * api.setGlobalOption('requireOperationAccessForPopulateDependencies', true);
3180
3199
  * void snapshot; // stored snapshot; read again to observe the setter
3181
3200
  * ```
3182
3201
  */
3183
- requireReadOperationAccessForPopulateDependencies?: boolean;
3202
+ requireOperationAccessForPopulateDependencies?: boolean;
3184
3203
  logger?: AccessRouterLogger;
3185
3204
  /** Request budgets and separate hook/persistence pools; concurrency limits are captured on first owner use. */
3186
3205
  requestComplexity?: RequestComplexityOptions;
@@ -3336,6 +3355,12 @@ interface ModelHydrationOptions {
3336
3355
  }
3337
3356
  /** Model starting defaults; data/root routers do not inherit these. Nested values assign shallowly. */
3338
3357
  interface DefaultModelRouterOptions<TModel = unknown> {
3358
+ /**
3359
+ * Default business-field policy when a model's permissionSchema is undefined.
3360
+ * Populate targets require an effective object schema; `{}` explicitly closes
3361
+ * business fields. A model's defined schema replaces this entire default.
3362
+ */
3363
+ permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel>>;
3339
3364
  /** Server-owned fetch choices, captured per call. Not a per-call or method-default option. */
3340
3365
  hydration?: ModelHydrationOptions;
3341
3366
  /** Maximum returned list rows (default 1000), independent of exact match/count totals. */
@@ -3461,6 +3486,8 @@ interface ModelRouterOptions<TModel = unknown, TVirtuals extends object = Record
3461
3486
  * siblings; a whole-object/array grant permits replacement (no generic deep merge).
3462
3487
  * Send nested JSON; literal dotted client keys are not update operators.
3463
3488
  * Basic/advanced keys are route guards in operationAccess, not field grants here.
3489
+ * Requested populate targets require an effective object schema in their owning
3490
+ * runtime (model defaults may supply it); `{}` explicitly closes business fields.
3464
3491
  */
3465
3492
  permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel> | Extract<keyof TVirtuals, string>>;
3466
3493
  /**
@@ -3488,12 +3515,13 @@ interface ModelRouterOptions<TModel = unknown, TVirtuals extends object = Record
3488
3515
  * that getter's fourth argument. Annotate ModelVirtualDescriptor<Source, Value,
3489
3516
  * { ref: Target }> separately, then assign here; persisted/write/public types
3490
3517
  * stay unchanged. Register target models/options first in this runtime. Source
3491
- * refs are retained automatically. GlobalOptions.requireReadOperationAccessForPopulateDependencies
3518
+ * refs are retained automatically. GlobalOptions.requireOperationAccessForPopulateDependencies
3492
3519
  * defaults to true; false bypasses only private target-operation admission, never
3493
- * request read-row filters/retained IDs. In both modes an active dependency ref's
3494
- * denied caller-context target base read omits the entire ref, including its ID.
3495
- * Allowed refs keep the original authorized populate mask or exact original IDs;
3496
- * unselected/denied source fields remain hidden. No active plan keeps ordinary behavior.
3520
+ * request read-row filters/retained IDs. Neither mode changes ordinary reference
3521
+ * shape: admitted population keeps its original authorized mask/miss; otherwise
3522
+ * selected/permitted parent fields keep exact original IDs, even on private failure.
3523
+ * Only unselected/field-denied source refs are omitted. Query operation admission is
3524
+ * independently controlled by GlobalOptions.requireOperationAccessForPopulateQueries.
3497
3525
  *
3498
3526
  * @example Shortest typed happy-path (persisted model lacks `fullAddress`):
3499
3527
  * ```ts
@@ -3618,7 +3646,7 @@ interface DataRouterOptions<TData = unknown> {
3618
3646
  decorateAll?: DataListHook<TData> | Record<string, DataListHook<TData>>;
3619
3647
  requestSchemas?: DataRequestSchemas;
3620
3648
  }
3621
- interface ExtendedModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends ModelRouterOptions<TModel, TVirtuals>, ExtendedDefaultModelRouterOptions<TModel> {
3649
+ interface ExtendedModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends ModelRouterOptions<TModel, TVirtuals>, Omit<ExtendedDefaultModelRouterOptions<TModel>, 'permissionSchema'> {
3622
3650
  'alwaysSelectFields.default'?: string[];
3623
3651
  'alwaysSelectFields.list'?: string[];
3624
3652
  'alwaysSelectFields.create'?: string[];
@@ -421,18 +421,21 @@ type ModelVirtualDoc<TModel = unknown> = IsUnknownVirtual<TModel> extends true ?
421
421
  * Static string refs must bind the actual same-connection ObjectId model; register
422
422
  * its model instance AND options first. Validation is eager/atomic, including setters.
423
423
  * This bypasses source/target input-field permissions, not the virtual's own output
424
- * authorization. GlobalOptions.requireReadOperationAccessForPopulateDependencies defaults
424
+ * authorization. GlobalOptions.requireOperationAccessForPopulateDependencies defaults
425
425
  * to true, requiring the captured caller's target base operationAccess.read for private
426
426
  * loading. Only literal false bypasses that gate; private-only loading then adds no
427
427
  * operation check solely for the fetch. Target overrideFilter.read/baseFilter.read and
428
428
  * retained source IDs always use request authority/tenant scope, independently of ordinary
429
429
  * populate match/access and authorization.related. Private target virtuals never run.
430
- * In both modes, active selected/applicable dependency refs denied caller-context base
431
- * read visibility are omitted entirely, including IDs; public list/custom access or a
432
- * related-operation grant cannot override this. Allowed selected/permitted refs keep the
433
- * original authorized populate mask or exact original refs. No active plan keeps ordinary
434
- * behavior. The mode and source/target policy are captured before awaits; registration
435
- * remains mandatory independently of requireRegisteredPopulateModels.
430
+ * Private work does not change ordinary reference shape. Selected/permitted source refs
431
+ * keep exact original IDs, including array order/duplicates/missing IDs/nulls, unless an
432
+ * admitted ordinary population owns the slot (then its original public mask/miss applies).
433
+ * Strict private denial, row/query/getter failure and deferred virtual denial cannot hide
434
+ * permitted IDs. Only source unselection/field denial omits them; there is no extra target
435
+ * base-read visibility gate. GlobalOptions.requireOperationAccessForPopulateQueries
436
+ * independently controls ordinary target operation admission. Both flags and source/target
437
+ * policy are captured before awaits; registration and an effective object permissionSchema
438
+ * remain mandatory. Private inputs never implicitly enter hooks, handles or metadata.
436
439
  */
437
440
  interface ModelVirtualPopulateDependency<TModel = unknown> {
438
441
  /** Top-level ref relative to this virtual's scope; no dotted source paths. Retained automatically. */
@@ -503,8 +506,9 @@ type ModelVirtualGetter<TModel = unknown, TValue = unknown, TPopulated extends V
503
506
  * generic never changes the persisted model, write types or public output.
504
507
  * Use `populated = {}` or guard undefined to retain three-argument direct calls.
505
508
  * See the shipped README's "Private populated dependencies" for registration,
506
- * reference omission/population/ID shapes, captured private-read mode, mandatory row
507
- * gates and absence/error rules. Private computation does not imply reference visibility.
509
+ * source-field omission/population/ID shapes, independent captured operation policies,
510
+ * mandatory row gates and absence/error rules. Private computation does not grant output
511
+ * or remove otherwise permitted parent IDs.
508
512
  *
509
513
  * @example
510
514
  * ```ts
@@ -1154,9 +1158,9 @@ interface AuthorizationConfiguration {
1154
1158
  /** Opaque terminal private-input capture. Never related authority or public document metadata. */
1155
1159
  readonly virtualPopulateSnapshot?: VirtualPopulateSnapshotToken;
1156
1160
  /** Runtime-owned private input admission mode, not public output/related authority. */
1157
- readonly requireReadOperationAccessForPopulateDependencies?: boolean;
1158
- /** Independent ordinary-populate registration posture from the same global snapshot. */
1159
- readonly requireRegisteredPopulateModels?: boolean;
1161
+ readonly requireOperationAccessForPopulateDependencies?: boolean;
1162
+ /** Independent ordinary-populate operation admission from the same global snapshot. */
1163
+ readonly requireOperationAccessForPopulateQueries?: boolean;
1160
1164
  readonly documentPermissionField?: string;
1161
1165
  /** Captured permission schema, virtual descriptors, always-select and metadata posture. */
1162
1166
  readonly [key: string]: unknown;
@@ -1170,8 +1174,7 @@ interface AuthorizationRelatedTarget {
1170
1174
  readonly queryIntent?: ModelServiceIntent;
1171
1175
  /** Existing include operation gate, resolved synchronously with its target. */
1172
1176
  readonly operationGuard?: unknown;
1173
- /** Passthrough is the existing global unregistered-populate opt-out, not a caller policy. */
1174
- readonly kind?: 'output' | 'count' | 'passthrough';
1177
+ readonly kind?: 'output' | 'count';
1175
1178
  readonly relatedTargets?: readonly AuthorizationRelatedTarget[];
1176
1179
  /** Captured supported raw-ref transport shape; exotic ordinary populates keep their adapter path. */
1177
1180
  readonly staticObjectIdReference?: boolean;
@@ -2110,7 +2113,7 @@ declare class Core {
2110
2113
  /** Persisted request-only populate admission; fetch planning is not field authority. */
2111
2114
  private requestPopulateSelect;
2112
2115
  /** Request-only population. Use named response scopePath; positional subPaths are removed. */
2113
- genPopulate(modelName: string, access?: SelectAccess | BaseFilterAccess, _populate?: Populate | Populate[] | string | null, options?: PopulatePlanningOptions): Promise<(string | Populate)[]>;
2116
+ genPopulate(modelName: string, access?: SelectAccess | BaseFilterAccess, _populate?: Populate | Populate[] | string | null, options?: PopulatePlanningOptions): Promise<Populate[]>;
2114
2117
  validate(modelName: string, allowedData: unknown, access: ValidateAccess, context: ModelHookContext): Promise<boolean | unknown[]>;
2115
2118
  prepare<T>(modelName: string, allowedData: T, access: PrepareAccess, context: ModelHookContext): Promise<T>;
2116
2119
  transform<T>(modelName: string, doc: T, access: TransformAccess, context: ModelHookContext): Promise<T>;
@@ -3130,7 +3133,7 @@ interface AccessRouterLogger {
3130
3133
  isLevelEnabled?: (level: string) => boolean;
3131
3134
  }
3132
3135
  /**
3133
- * Runtime-wide request initialization, private-input policy and resource settings, configured with
3136
+ * Runtime-wide request initialization, populate operation policies and resource settings, configured with
3134
3137
  * api.setGlobalOptions / api.setGlobalOption. These are not model options or wire
3135
3138
  * options. Object assignment is shallow; nested requestComplexity replaces as a unit.
3136
3139
  */
@@ -3146,8 +3149,20 @@ interface GlobalOptions {
3146
3149
  * context. Generated routers do not authenticate client claims for this hook.
3147
3150
  */
3148
3151
  globalPermissions?: (this: AccessRouterRequest, req: AccessRouterRequest) => MaybePromise<GlobalPermissionValue>;
3149
- /** Require populate target models to be registered in the active runtime. Defaults to `true`. */
3150
- requireRegisteredPopulateModels?: boolean;
3152
+ /**
3153
+ * Require the caller's effective target operation (`read`, supported `list` or trusted
3154
+ * custom access) for ordinary query population (default `true`). Only literal `false`
3155
+ * skips that admission gate; source entry/field authorization, target field/document
3156
+ * grants, row/base/transform policies, original selection, match and mandatory reference
3157
+ * IDs still constrain output. Denied population retains selected/permitted original
3158
+ * source IDs; admitted population misses return null/filtered arrays, not restored IDs.
3159
+ * Targets always require an actual Mongoose model and ACL configuration with an
3160
+ * effective object permissionSchema in this runtime; `{}` closes business fields.
3161
+ * Runtime-owned and captured before awaits, independently of private dependency
3162
+ * admission. A configuration-only Document needs no HTTP routes. Its denied direct/root
3163
+ * read/list operations stay denied. No per-model/query/client override.
3164
+ */
3165
+ requireOperationAccessForPopulateQueries?: boolean;
3151
3166
  /**
3152
3167
  * Require the original caller's target base `operationAccess.read` before loading
3153
3168
  * declared private `dependsOnPopulate` inputs (default `true`). Only literal boolean
@@ -3159,28 +3174,32 @@ interface GlobalOptions {
3159
3174
  * nested output scopes. In-flight calls retain their mode; later calls see updates.
3160
3175
  * Runtime-isolated server configuration only: no caller/query or per-model override.
3161
3176
  *
3162
- * In BOTH modes, references in an active selected/applicable dependency plan require
3163
- * source selection/field permission AND the captured caller-context target base read
3164
- * grant. Denied/failed read visibility omits the entire reference, including its ID,
3165
- * even with ordinary list/custom populate access or a public related-operation grant.
3166
- * Allowed read keeps the original authorized public populate mask, or exact original
3167
- * refs without population. No active dependency plan leaves ordinary behavior intact.
3168
- * False-mode private-only loading adds no operation check solely for that fetch.
3169
- * This never grants public fields, row/write authority or query controls. Registration
3170
- * and static input declarations remain required, independently of requireRegisteredPopulateModels.
3177
+ * Neither mode changes ordinary reference shape: selected/permitted parent fields keep
3178
+ * exact original IDs (including array order, duplicates, missing IDs and nulls) when no
3179
+ * admitted ordinary population owns them. Private denial, row/query/getter failure or
3180
+ * deferred virtual denial cannot hide those IDs. Omit only for parent unselection/field
3181
+ * denial; admitted public DTO/null/filtered-array results retain their original masks.
3182
+ * There is no additional target-base-read reference-visibility gate. False-mode loading
3183
+ * adds no operation check solely for its private fetch. Private fourth-argument inputs
3184
+ * never implicitly enter DTOs, hooks, handles, snapshots or permission metadata.
3185
+ * An actual registered model, effective object permissionSchema and static declarations
3186
+ * remain mandatory, independently of requireOperationAccessForPopulateQueries.
3171
3187
  *
3172
3188
  * @example
3173
3189
  * ```ts
3174
3190
  * import { createAccessRuntime, type GlobalOptions } from '@web-ts-toolkit/access-router';
3175
3191
  * const api = createAccessRuntime();
3176
- * const policy = { requireReadOperationAccessForPopulateDependencies: false } satisfies GlobalOptions;
3177
- * api.setGlobalOptions(policy); // privileged computation inputs; no public target-read grant
3192
+ * const policy = {
3193
+ * requireOperationAccessForPopulateQueries: true,
3194
+ * requireOperationAccessForPopulateDependencies: false,
3195
+ * } satisfies GlobalOptions;
3196
+ * api.setGlobalOptions(policy); // private computation; denied public population retains permitted IDs
3178
3197
  * const snapshot: GlobalOptions = api.getGlobalOptions();
3179
- * api.setGlobalOption('requireReadOperationAccessForPopulateDependencies', true);
3198
+ * api.setGlobalOption('requireOperationAccessForPopulateDependencies', true);
3180
3199
  * void snapshot; // stored snapshot; read again to observe the setter
3181
3200
  * ```
3182
3201
  */
3183
- requireReadOperationAccessForPopulateDependencies?: boolean;
3202
+ requireOperationAccessForPopulateDependencies?: boolean;
3184
3203
  logger?: AccessRouterLogger;
3185
3204
  /** Request budgets and separate hook/persistence pools; concurrency limits are captured on first owner use. */
3186
3205
  requestComplexity?: RequestComplexityOptions;
@@ -3336,6 +3355,12 @@ interface ModelHydrationOptions {
3336
3355
  }
3337
3356
  /** Model starting defaults; data/root routers do not inherit these. Nested values assign shallowly. */
3338
3357
  interface DefaultModelRouterOptions<TModel = unknown> {
3358
+ /**
3359
+ * Default business-field policy when a model's permissionSchema is undefined.
3360
+ * Populate targets require an effective object schema; `{}` explicitly closes
3361
+ * business fields. A model's defined schema replaces this entire default.
3362
+ */
3363
+ permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel>>;
3339
3364
  /** Server-owned fetch choices, captured per call. Not a per-call or method-default option. */
3340
3365
  hydration?: ModelHydrationOptions;
3341
3366
  /** Maximum returned list rows (default 1000), independent of exact match/count totals. */
@@ -3461,6 +3486,8 @@ interface ModelRouterOptions<TModel = unknown, TVirtuals extends object = Record
3461
3486
  * siblings; a whole-object/array grant permits replacement (no generic deep merge).
3462
3487
  * Send nested JSON; literal dotted client keys are not update operators.
3463
3488
  * Basic/advanced keys are route guards in operationAccess, not field grants here.
3489
+ * Requested populate targets require an effective object schema in their owning
3490
+ * runtime (model defaults may supply it); `{}` explicitly closes business fields.
3464
3491
  */
3465
3492
  permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel> | Extract<keyof TVirtuals, string>>;
3466
3493
  /**
@@ -3488,12 +3515,13 @@ interface ModelRouterOptions<TModel = unknown, TVirtuals extends object = Record
3488
3515
  * that getter's fourth argument. Annotate ModelVirtualDescriptor<Source, Value,
3489
3516
  * { ref: Target }> separately, then assign here; persisted/write/public types
3490
3517
  * stay unchanged. Register target models/options first in this runtime. Source
3491
- * refs are retained automatically. GlobalOptions.requireReadOperationAccessForPopulateDependencies
3518
+ * refs are retained automatically. GlobalOptions.requireOperationAccessForPopulateDependencies
3492
3519
  * defaults to true; false bypasses only private target-operation admission, never
3493
- * request read-row filters/retained IDs. In both modes an active dependency ref's
3494
- * denied caller-context target base read omits the entire ref, including its ID.
3495
- * Allowed refs keep the original authorized populate mask or exact original IDs;
3496
- * unselected/denied source fields remain hidden. No active plan keeps ordinary behavior.
3520
+ * request read-row filters/retained IDs. Neither mode changes ordinary reference
3521
+ * shape: admitted population keeps its original authorized mask/miss; otherwise
3522
+ * selected/permitted parent fields keep exact original IDs, even on private failure.
3523
+ * Only unselected/field-denied source refs are omitted. Query operation admission is
3524
+ * independently controlled by GlobalOptions.requireOperationAccessForPopulateQueries.
3497
3525
  *
3498
3526
  * @example Shortest typed happy-path (persisted model lacks `fullAddress`):
3499
3527
  * ```ts
@@ -3618,7 +3646,7 @@ interface DataRouterOptions<TData = unknown> {
3618
3646
  decorateAll?: DataListHook<TData> | Record<string, DataListHook<TData>>;
3619
3647
  requestSchemas?: DataRequestSchemas;
3620
3648
  }
3621
- interface ExtendedModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends ModelRouterOptions<TModel, TVirtuals>, ExtendedDefaultModelRouterOptions<TModel> {
3649
+ interface ExtendedModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends ModelRouterOptions<TModel, TVirtuals>, Omit<ExtendedDefaultModelRouterOptions<TModel>, 'permissionSchema'> {
3622
3650
  'alwaysSelectFields.default'?: string[];
3623
3651
  'alwaysSelectFields.list'?: string[];
3624
3652
  'alwaysSelectFields.create'?: string[];