@astram/engine 0.1.4 → 0.2.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.
@@ -24,7 +24,7 @@
24
24
  * `undefined` keys and break `deepStrictEqual`).
25
25
  */
26
26
  import 'reflect-metadata';
27
- import type { AccessDeclaration, FieldType, IdentityRole, JunctionSpec, ObjectType as ObjectTypeContract, PkStrategy, TrustedSource } from '../types.js';
27
+ import type { AccessDeclaration, FieldType, IdentityRole, JunctionSpec, ObjectType as ObjectTypeContract, PkStrategy, ReadScope, TrustedSource } from '../types.js';
28
28
  /**
29
29
  * Class-level options for `@ObjectType`. Mirrors the top-level {@link ObjectType}
30
30
  * shape EXCEPT `primaryKey` (declared separately via `@PrimaryKey`) and `fields`
@@ -45,6 +45,12 @@ export interface ObjectTypeOptions {
45
45
  * absent/contradictory declaration at boot. See {@link AccessDeclaration}.
46
46
  */
47
47
  readonly access: AccessDeclaration;
48
+ /**
49
+ * OPTIONAL read-scope declaration (ADR-0027) — how LIST/GET reads are walled to the
50
+ * caller's reachable subtree, DECOUPLED from the write path. Reflected with the SAME
51
+ * omit-when-absent discipline as `junctions`/`extensible`. See {@link ReadScope}.
52
+ */
53
+ readonly readScope?: ReadScope;
48
54
  }
49
55
  /** Per-member options for `@Field` (the field's `key` is read from the member name). */
50
56
  export interface FieldOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"object-type.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/object-type.decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EACV,iBAAiB,EAEjB,SAAS,EACT,YAAY,EACZ,YAAY,EAKZ,UAAU,IAAI,kBAAkB,EAChC,UAAU,EACV,aAAa,EACd,MAAM,aAAa,CAAC;AAarB;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAI3C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAW,SAAQ,kBAAkB;CAAG;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAQ3D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,kBAAkB,CAqCnE"}
1
+ {"version":3,"file":"object-type.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/object-type.decorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EACV,iBAAiB,EAEjB,SAAS,EACT,YAAY,EACZ,YAAY,EAKZ,UAAU,IAAI,kBAAkB,EAChC,UAAU,EACV,SAAS,EACT,aAAa,EACd,MAAM,aAAa,CAAC;AAarB;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAI3C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAW,SAAQ,kBAAkB;CAAG;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAQ3D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAIlE;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,kBAAkB,CAwCnE"}
@@ -142,5 +142,9 @@ function reflectObjectType(cls) {
142
142
  // registry's `validate` (chokepoint 1) is what rejects the absence at boot.
143
143
  if (opts.access !== undefined)
144
144
  out.access = opts.access;
145
+ // `readScope` (ADR-0027) reflected AS-IS with the omit-when-absent discipline — a
146
+ // type without it omits the key (matching the literal), so `deepStrictEqual` holds.
147
+ if (opts.readScope !== undefined)
148
+ out.readScope = opts.readScope;
145
149
  return out;
146
150
  }
package/dist/index.d.ts CHANGED
@@ -15,7 +15,8 @@
15
15
  * the `TableRegistry`, and the ports. Exported so the framework + integration
16
16
  * tests can compose the engine directly; NOT for feature code.
17
17
  */
18
- export type { RecordEngine, AccessDeclaration, FieldSpec, FieldType, TrustedSource, IdentityRole, PkStrategy, JunctionSpec, RecordInput, RecordId, RecordView, ListQuery, RecordPage, Principal, } from './types.js';
18
+ export type { RecordEngine, AccessDeclaration, FieldSpec, FieldType, TrustedSource, IdentityRole, PkStrategy, JunctionSpec, RecordInput, RecordId, RecordView, ListQuery, RecordPage, Principal, ReadScope, } from './types.js';
19
+ export { READ_SCOPE_NODES_TOKEN } from './types.js';
19
20
  export { EngineError, CapabilityNotSupportedError, RegistrationError, AuthorizationDeniedError, ValidationError, EngineUnavailableError, } from './errors.js';
20
21
  export { ObjectType, Field, PrimaryKey, reflectObjectType, type ObjectTypeOptions, type FieldOptions, type PrimaryKeyOptions, } from './decorators/object-type.decorator.js';
21
22
  export { AccessBundle, reflectAccessBundle, type AccessBundleOptions, type AccessBundlePermission, type AccessBundleSpec, } from './decorators/access-bundle.decorator.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAiBrB,OAAO,EACL,UAAU,EACV,KAAK,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,uCAAuC,CAAC;AAO/C,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,GACtB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,sBAAsB,EAC3B,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC1F,YAAY,EACV,OAAO,EACP,UAAU,EACV,YAAY,EACZ,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,GACV,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAiBrB,OAAO,EACL,UAAU,EACV,KAAK,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,uCAAuC,CAAC;AAO/C,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,GACtB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,sBAAsB,EAC3B,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC1F,YAAY,EACV,OAAO,EACP,UAAU,EACV,YAAY,EACZ,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -17,7 +17,12 @@
17
17
  * tests can compose the engine directly; NOT for feature code.
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.MariaDbAuditSink = exports.MariaDbUnitOfWork = exports.SERVICE_ACCOUNT_RESOLVER = exports.BoundRecords = exports.RecordsService = exports.RecordEngineImpl = exports.requireRole = exports.columnForRole = exports.tenantColumnOf = exports.ownerColumn = exports.isOwned = exports.uuidPkColumn = exports.columnOf = exports.TableRegistry = exports.ENGINE_ALL_ACCESS_BUNDLES = exports.ENGINE_ALL_REGISTRATIONS = exports.RECORD_ENGINE = exports.EngineModule = exports.reflectAccessBundle = exports.AccessBundle = exports.reflectObjectType = exports.PrimaryKey = exports.Field = exports.ObjectType = exports.EngineUnavailableError = exports.ValidationError = exports.AuthorizationDeniedError = exports.RegistrationError = exports.CapabilityNotSupportedError = exports.EngineError = void 0;
20
+ exports.MariaDbAuditSink = exports.MariaDbUnitOfWork = exports.SERVICE_ACCOUNT_RESOLVER = exports.BoundRecords = exports.RecordsService = exports.RecordEngineImpl = exports.requireRole = exports.columnForRole = exports.tenantColumnOf = exports.ownerColumn = exports.isOwned = exports.uuidPkColumn = exports.columnOf = exports.TableRegistry = exports.ENGINE_ALL_ACCESS_BUNDLES = exports.ENGINE_ALL_REGISTRATIONS = exports.RECORD_ENGINE = exports.EngineModule = exports.reflectAccessBundle = exports.AccessBundle = exports.reflectObjectType = exports.PrimaryKey = exports.Field = exports.ObjectType = exports.EngineUnavailableError = exports.ValidationError = exports.AuthorizationDeniedError = exports.RegistrationError = exports.CapabilityNotSupportedError = exports.EngineError = exports.READ_SCOPE_NODES_TOKEN = void 0;
21
+ // The read-scope subtree-id placeholder token (ADR-0027) — a `subtreeSubquery` template
22
+ // uses it; the engine expands it to a `?`-list. Exported so per-type declarations can
23
+ // reference the canonical token (rather than a stringly-typed duplicate).
24
+ var types_js_1 = require("./types.js");
25
+ Object.defineProperty(exports, "READ_SCOPE_NODES_TOKEN", { enumerable: true, get: function () { return types_js_1.READ_SCOPE_NODES_TOKEN; } });
21
26
  var errors_js_1 = require("./errors.js");
22
27
  Object.defineProperty(exports, "EngineError", { enumerable: true, get: function () { return errors_js_1.EngineError; } });
23
28
  Object.defineProperty(exports, "CapabilityNotSupportedError", { enumerable: true, get: function () { return errors_js_1.CapabilityNotSupportedError; } });
@@ -22,7 +22,7 @@ import type { QueryFn } from '../ports.js';
22
22
  import { RECORD_ENGINE } from './tokens.js';
23
23
  import { type EngineDbConfig } from '../adapters/mariadb-unit-of-work.js';
24
24
  import { type AuditSinkConfig } from '../adapters/mariadb-audit-sink.js';
25
- import type { ObjectType } from '../types.js';
25
+ import type { ObjectType, Principal } from '../types.js';
26
26
  import { type AccessBundleSpec } from '../decorators/access-bundle.decorator.js';
27
27
  /**
28
28
  * Input accepted by `forFeature` / `forRoot({registrations})`: either a plain
@@ -67,6 +67,19 @@ export interface EngineModuleOptions {
67
67
  * the engine never imports guard; this is the injection seam.
68
68
  */
69
69
  readonly onRecordDeleted?: (query: QueryFn, typeKey: string, recordId: string, tenantId: string) => Promise<void>;
70
+ /**
71
+ * OPTIONAL record-level LIST SCOPE (design §8.3, Invariant #8). The consumer wires guard's
72
+ * framework-constructed `buildListScopeFn(inputs)` here — the engine's `list()` ANDs the
73
+ * returned SAFE parameterized WHERE fragment into BOTH the data SELECT and the COUNT (the
74
+ * in-query visible-set filter). ADR-0003: the engine never imports guard; this is the
75
+ * injection seam, exactly like `onRecordDeleted`. Absent ⇒ today's bare-tenant behavior.
76
+ * A throw PROPAGATES (fail-closed). It runs only AFTER the coarse object-type gate allows
77
+ * the read — it narrows rows within an allowed list, never substitutes for the gate.
78
+ */
79
+ readonly listScope?: (reg: ObjectType, actor: Principal, action: string) => Promise<{
80
+ sql: string;
81
+ params: readonly unknown[];
82
+ }>;
70
83
  }
71
84
  export declare class EngineModule {
72
85
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"engine.module.d.ts","sourceRoot":"","sources":["../../src/module/engine.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAuC,OAAO,EAAE,MAAM,aAAa,CAAC;AAIhF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAC7F,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,0CAA0C,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAMpD;;;;2EAI2E;AAC3E,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;;qCAIqC;AACrC,eAAO,MAAM,wBAAwB,eAAqC,CAAC;AAE3E;;;;+CAI+C;AAC/C,eAAO,MAAM,yBAAyB,eAAsC,CAAC;AAmC7E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B;iFAC6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAYD,qBACa,YAAY;IACvB;;;;;;;;;OASG;IACH,MAAM,CAAC,UAAU,CACf,aAAa,EAAE,SAAS,eAAe,EAAE,EACzC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,SAAS,CAAC,gBAAgB,GAAG,QAAQ,CAAC,EAAE,CAAA;KAAE,GAClE,aAAa;IAMhB;2FACuF;IACvF,MAAM,CAAC,aAAa,IAAI,IAAI;IAK5B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa;CAgG5D"}
1
+ {"version":3,"file":"engine.module.d.ts","sourceRoot":"","sources":["../../src/module/engine.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAuC,OAAO,EAAE,MAAM,aAAa,CAAC;AAIhF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAC7F,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,0CAA0C,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAMpD;;;;2EAI2E;AAC3E,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;;qCAIqC;AACrC,eAAO,MAAM,wBAAwB,eAAqC,CAAC;AAE3E;;;;+CAI+C;AAC/C,eAAO,MAAM,yBAAyB,eAAsC,CAAC;AAmC7E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B;iFAC6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAYD,qBACa,YAAY;IACvB;;;;;;;;;OASG;IACH,MAAM,CAAC,UAAU,CACf,aAAa,EAAE,SAAS,eAAe,EAAE,EACzC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,SAAS,CAAC,gBAAgB,GAAG,QAAQ,CAAC,EAAE,CAAA;KAAE,GAClE,aAAa;IAMhB;2FACuF;IACvF,MAAM,CAAC,aAAa,IAAI,IAAI;IAK5B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa;CAiG5D"}
@@ -181,6 +181,7 @@ let EngineModule = EngineModule_1 = class EngineModule {
181
181
  audit,
182
182
  ...(options.nowMs ? { nowMs: options.nowMs } : {}),
183
183
  ...(options.onRecordDeleted ? { onRecordDeleted: options.onRecordDeleted } : {}),
184
+ ...(options.listScope ? { listScope: options.listScope } : {}),
184
185
  };
185
186
  return new record_engine_js_1.RecordEngineImpl(deps);
186
187
  },
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import type { TableRegistry } from '../registry.js';
17
17
  import type { AuditPort, DecisionPort, UnitOfWork, QueryFn } from '../ports.js';
18
- import type { ListQuery, Principal, RecordEngine, RecordId, RecordInput, RecordPage, RecordView } from '../types.js';
18
+ import type { ListQuery, Principal, RecordEngine, RecordId, RecordInput, RecordPage, RecordView, ObjectType } from '../types.js';
19
19
  export interface RecordEngineDeps {
20
20
  readonly registry: TableRegistry;
21
21
  readonly uow: UnitOfWork;
@@ -45,6 +45,25 @@ export interface RecordEngineDeps {
45
45
  * store error) prevents leaving the record deleted with orphaned grants (fail-closed).
46
46
  */
47
47
  readonly onRecordDeleted?: (query: QueryFn, typeKey: string, recordId: string, tenantId: string) => Promise<void>;
48
+ /**
49
+ * OPTIONAL record-level LIST SCOPE (design §8.3, the list-scoping engine). When present,
50
+ * list() ANDs the returned SAFE parameterized WHERE fragment into BOTH the data SELECT and
51
+ * the COUNT — the in-query visible-set filter (Invariant #8). The CONSUMER wires guard's
52
+ * framework-constructed `buildListScopeFn` here (ADR-0003: the engine never imports guard —
53
+ * this is the injection seam, exactly like `onRecordDeleted`). Absent ⇒ today's bare-tenant
54
+ * behavior (back-compat until wired). Returns `{ sql, params }`: the fragment ANDed into the
55
+ * WHERE. A throw PROPAGATES (fail-closed — the list read fails, never returns unscoped rows).
56
+ *
57
+ * ⛔ SCOPE RUNS AFTER THE GATE: list() calls the coarse object-type gate (gateRead → decide())
58
+ * FIRST; the scope narrows rows WITHIN an already-allowed object-type list and NEVER substitutes
59
+ * for the gate. This ordering is load-bearing: a ROOT actor's scope is root-unbounded (no tenant
60
+ * clause, `(TRUE)` union) — the ONLY thing preventing root from reading every tenant's data-plane
61
+ * rows is that gateRead DENIES root on a data-plane type before any scope is built.
62
+ */
63
+ readonly listScope?: (reg: ObjectType, actor: Principal, action: string) => Promise<{
64
+ sql: string;
65
+ params: readonly unknown[];
66
+ }>;
48
67
  }
49
68
  export declare class RecordEngineImpl implements RecordEngine {
50
69
  private readonly registry;
@@ -53,6 +72,7 @@ export declare class RecordEngineImpl implements RecordEngine {
53
72
  private readonly audit;
54
73
  private readonly now;
55
74
  private readonly onRecordDeleted;
75
+ private readonly listScope;
56
76
  /** The FIXED write-stage order. Built once; identical for every write. */
57
77
  private readonly writeStages;
58
78
  constructor(deps: RecordEngineDeps);
@@ -68,5 +88,35 @@ export declare class RecordEngineImpl implements RecordEngine {
68
88
  list(typeKey: string, query: ListQuery, actor: Principal): Promise<RecordPage>;
69
89
  /** The physical column of a `tenantId`-trusted field, if the table has one. */
70
90
  private tenantColumn;
91
+ /**
92
+ * The set of tenant nodes a non-root actor's reads are bounded to (ADR-0027): its
93
+ * active-scope subtree (active node + descendants), resolved once at login and cached on
94
+ * the principal (`scopeSubtreeNodeIds`). The engine's built-in tenant clause emits
95
+ * `tenant_col IN (these)` — the SAME set the tenant gate + list-scope use, so the three
96
+ * agree (no self-node collapse of the subtree). FAIL-SAFE + non-widening: a malformed or
97
+ * absent subtree falls back to just the actor's home node (`[actor.tenantId]`) — exactly
98
+ * today's flat self-tenant isolation; NEVER an empty set (which would hide all own rows).
99
+ */
100
+ private tenantScopeNodes;
101
+ /**
102
+ * The read-scope predicate for a type's LIST/GET reads (ADR-0027, Task 4.1) — the wall
103
+ * that filters a `system` type's rows to the caller's reachable subtree, DECOUPLED from
104
+ * the write path (`readScope` NEVER stamps a write). Shared by list() AND get() so both
105
+ * apply the SAME wall. Returns `null` when there is nothing to AND:
106
+ * - the type declares no `readScope`, OR
107
+ * - the actor is ROOT (`isRoot === true`, strict) — root spans all, no clause.
108
+ * Otherwise ANDs, over the actor's subtree nodes (`tenantScopeNodes` — the SAME set the
109
+ * tenant clause + list-scope use, so the three agree):
110
+ * - `nodeColumn` → `` `<column>` IN (?, ...) `` — subtree ids `?`-bound.
111
+ * - `subtreeSubquery` → `` `<pkColumn>` IN (<subquerySql, :nodes → IN (?, ...)>) `` — the
112
+ * FIXED framework template with the subtree ids `?`-bound at `:nodes`.
113
+ *
114
+ * INJECTION-FREE BY CONSTRUCTION: the ONLY dynamic values are the `?`-bound subtree node
115
+ * ids; every identifier is a compile-time constant (the type's own column, or the fixed
116
+ * template). No node value is ever interpolated into the SQL string. FAIL-CLOSED: an
117
+ * empty subtree (shouldn't happen — `tenantScopeNodes` never returns empty) would still
118
+ * emit `IN ()`, which matches nothing (deny), never a widening clause.
119
+ */
120
+ private readScopeClause;
71
121
  }
72
122
  //# sourceMappingURL=record-engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"record-engine.d.ts","sourceRoot":"","sources":["../../src/pipe/record-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,UAAU,EAEX,MAAM,aAAa,CAAC;AAWrB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAOlB;IACd,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;gBAEnC,IAAI,EAAE,gBAAgB;YAwBpB,QAAQ;IAsChB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMhF,MAAM,CACV,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC;IAIV,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;YAM9D,QAAQ;IAiCtB,OAAO,CAAC,SAAS;IAIjB,mEAAmE;IACnE,OAAO,CAAC,OAAO;IAiBT,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA8DhF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAyEpF,+EAA+E;IAC/E,OAAO,CAAC,YAAY;CAIrB"}
1
+ {"version":3,"file":"record-engine.d.ts","sourceRoot":"","sources":["../../src/pipe/record-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACX,MAAM,aAAa,CAAC;AAYrB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAOlB;IACd,OAAO,CAAC,QAAQ,CAAC,SAAS,CAMZ;IACd,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;gBAEnC,IAAI,EAAE,gBAAgB;YAyBpB,QAAQ;IAsChB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMhF,MAAM,CACV,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC;IAIV,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;YAM9D,QAAQ;IAiCtB,OAAO,CAAC,SAAS;IAIjB,mEAAmE;IACnE,OAAO,CAAC,OAAO;IAiBT,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA4EhF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAsHpF,+EAA+E;IAC/E,OAAO,CAAC,YAAY;IAKpB;;;;;;;;OAQG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,eAAe;CAmBxB"}
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.RecordEngineImpl = void 0;
19
19
  const errors_js_1 = require("../errors.js");
20
20
  const registry_js_1 = require("../registry.js");
21
+ const types_js_1 = require("../types.js");
21
22
  const context_js_1 = require("./context.js");
22
23
  const fetch_owner_js_1 = require("./stages/fetch-owner.js");
23
24
  const permission_gate_js_1 = require("./stages/permission-gate.js");
@@ -34,6 +35,7 @@ class RecordEngineImpl {
34
35
  audit;
35
36
  now;
36
37
  onRecordDeleted;
38
+ listScope;
37
39
  /** The FIXED write-stage order. Built once; identical for every write. */
38
40
  writeStages;
39
41
  constructor(deps) {
@@ -43,6 +45,7 @@ class RecordEngineImpl {
43
45
  this.audit = deps.audit;
44
46
  this.now = deps.nowMs ?? (() => Date.now());
45
47
  this.onRecordDeleted = deps.onRecordDeleted;
48
+ this.listScope = deps.listScope;
46
49
  // permission-gate + audit are ALWAYS present, in this order — no skip flag.
47
50
  // fetch-owner runs FIRST (before the gate) so an OWNED update/delete's existing-row
48
51
  // owner is available to decide() as context.attributes.ownerId (core owner baseline).
@@ -166,11 +169,24 @@ class RecordEngineImpl {
166
169
  where = pk.columns.map((c) => `\`${c}\` = ?`).join(' AND ');
167
170
  params = pk.columns.map((c) => id[c]);
168
171
  }
169
- // Tenant scoping: if the table has a tenant column, constrain to the actor's tenant.
172
+ // Tenant scoping: if the table has a tenant column, constrain to the actor's reachable
173
+ // subtree (ADR-0027 — active node + descendants), NOT just the self-node. ROOT spans all
174
+ // tenants — only a STRICT `=== true` isRoot counts (fail-closed). Fail-safe to the
175
+ // self-node if the subtree is malformed/absent (non-widening).
170
176
  const tenantCol = this.tenantColumn(reg);
171
- if (tenantCol) {
172
- where += ` AND \`${tenantCol}\` = ?`;
173
- params = [...params, actor.tenantId];
177
+ if (tenantCol && actor.isRoot !== true) {
178
+ const nodes = this.tenantScopeNodes(actor);
179
+ where += ` AND \`${tenantCol}\` IN (${nodes.map(() => '?').join(', ')})`;
180
+ params = [...params, ...nodes];
181
+ }
182
+ // Read-scope wall (ADR-0027, Task 4.1): for a type declaring `readScope` (the identity
183
+ // `system` types with no tenant column), a NON-ROOT read is bounded to the caller's
184
+ // reachable subtree. ANDs AFTER the tenant clause (which won't fire for these system
185
+ // types); root gets NO clause (spans all). Injection-free — only `?`-bound node ids.
186
+ const scope = this.readScopeClause(reg, actor);
187
+ if (scope) {
188
+ where += ` AND ${scope.sql}`;
189
+ params = [...params, ...scope.params];
174
190
  }
175
191
  if ((0, registry_js_1.isOwned)(reg)) {
176
192
  // OWNED read — FETCH-THEN-GATE. Core's owner baseline needs the row's owner,
@@ -206,10 +222,39 @@ class RecordEngineImpl {
206
222
  const clauses = [];
207
223
  const params = [];
208
224
  // Mandatory tenant scoping when the table is tenant-owned (cross-tenant safety).
225
+ // ROOT spans all tenants — but only a STRICT `=== true` isRoot counts (fail-closed):
226
+ // a truthy-non-boolean must never bypass the tenant boundary.
227
+ // SUBTREE-AWARE (ADR-0027): a non-root actor is bounded to its reachable subtree
228
+ // (active node + descendants), NOT just the self-node — `tenant_col IN (?, ...)` over
229
+ // `actor.scopeSubtreeNodeIds` (the SAME cached set the tenant gate + list-scope use).
230
+ // Fail-safe to `[actor.tenantId]` (self-node) if the subtree is malformed/absent.
231
+ //
232
+ // ⛔ ONE-AUTHORITY (Invariant #3, §10bis.5): this built-in clause is the FAIL-SAFE for
233
+ // when the list-scope engine is NOT wired (pre-Phase-3 / a consumer without it). When
234
+ // `listScope` IS wired, the list-scope FRAGMENT is the single source of the tenant
235
+ // boundary — it already emits `tenant_col = ?` / `IN (subtree)` for non-root AND ORs in
236
+ // the cross-tenant grant reach (§10bis.5). Emitting BOTH here would AND this narrower
237
+ // own-subtree filter over the fragment and KILL every cross-tenant-granted row before the
238
+ // fragment's widening can include it (the exact divergence the list≡decide gate caught).
239
+ // So when `listScope` is present we DEFER the tenant bound entirely to the fragment — one
240
+ // authority, no double-filter. (readScope system-types are unaffected: they have no
241
+ // tenant column, so this clause never fired for them anyway; their wall is `readScope`.)
209
242
  const tenantCol = this.tenantColumn(reg);
210
- if (tenantCol) {
211
- clauses.push(`\`${tenantCol}\` = ?`);
212
- params.push(actor.tenantId);
243
+ if (tenantCol && actor.isRoot !== true && !this.listScope) {
244
+ const nodes = this.tenantScopeNodes(actor);
245
+ clauses.push(`\`${tenantCol}\` IN (${nodes.map(() => '?').join(', ')})`);
246
+ params.push(...nodes);
247
+ }
248
+ // Read-scope wall (ADR-0027, Task 4.1): a type declaring `readScope` (the identity
249
+ // `system` types with no tenant column) has its NON-ROOT reads bounded to the caller's
250
+ // reachable subtree. ANDed AFTER the tenant clause (which won't fire for these system
251
+ // types) into BOTH the data SELECT and the COUNT (shared clauses/params ⇒ consistent
252
+ // by construction). Root gets NO clause (spans all). Injection-free — only `?`-bound
253
+ // node ids; identifiers are the type's own column or a fixed framework template.
254
+ const readScope = this.readScopeClause(reg, actor);
255
+ if (readScope) {
256
+ clauses.push(readScope.sql);
257
+ params.push(...readScope.params);
213
258
  }
214
259
  // Equality filters on registered field columns only (unknown keys rejected).
215
260
  // The COLUMN comes from a whitelisted field-key lookup; the VALUE is a bound
@@ -238,6 +283,20 @@ class RecordEngineImpl {
238
283
  }
239
284
  clauses.push(`(${ors.join(' OR ')})`);
240
285
  }
286
+ // Record-level list scope (§8.3): AND the framework-computed visible-set fragment into
287
+ // BOTH the data SELECT and the COUNT (via the shared clauses/params — so `total` and
288
+ // `rows` stay consistent BY CONSTRUCTION). Runs only AFTER the coarse object-type gate
289
+ // (gateRead, at the TOP of list()) has allowed the read — the scope narrows rows WITHIN
290
+ // an allowed object-type list; it NEVER substitutes for the gate (the root/data-plane
291
+ // leak-guard). A scope-fn throw PROPAGATES (fail-closed — the read fails; we never fall
292
+ // back to unscoped rows). An empty `sql` is simply not ANDed (no fabricated widening).
293
+ if (this.listScope) {
294
+ const scope = await this.listScope(reg, actor, 'read');
295
+ if (scope.sql) {
296
+ clauses.push(scope.sql);
297
+ params.push(...scope.params);
298
+ }
299
+ }
241
300
  const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
242
301
  // Sort: resolve `sortBy` to a column the SAME whitelisted way as where-keys
243
302
  // (unknown key → throw). The direction is an enum-checked literal (only 'desc'
@@ -265,6 +324,55 @@ class RecordEngineImpl {
265
324
  const f = reg.fields.find((x) => x.trusted === 'tenantId');
266
325
  return f ? (0, registry_js_1.columnOf)(f) : null;
267
326
  }
327
+ /**
328
+ * The set of tenant nodes a non-root actor's reads are bounded to (ADR-0027): its
329
+ * active-scope subtree (active node + descendants), resolved once at login and cached on
330
+ * the principal (`scopeSubtreeNodeIds`). The engine's built-in tenant clause emits
331
+ * `tenant_col IN (these)` — the SAME set the tenant gate + list-scope use, so the three
332
+ * agree (no self-node collapse of the subtree). FAIL-SAFE + non-widening: a malformed or
333
+ * absent subtree falls back to just the actor's home node (`[actor.tenantId]`) — exactly
334
+ * today's flat self-tenant isolation; NEVER an empty set (which would hide all own rows).
335
+ */
336
+ tenantScopeNodes(actor) {
337
+ const subtree = actor.scopeSubtreeNodeIds;
338
+ return Array.isArray(subtree) && subtree.length > 0 ? subtree : [actor.tenantId];
339
+ }
340
+ /**
341
+ * The read-scope predicate for a type's LIST/GET reads (ADR-0027, Task 4.1) — the wall
342
+ * that filters a `system` type's rows to the caller's reachable subtree, DECOUPLED from
343
+ * the write path (`readScope` NEVER stamps a write). Shared by list() AND get() so both
344
+ * apply the SAME wall. Returns `null` when there is nothing to AND:
345
+ * - the type declares no `readScope`, OR
346
+ * - the actor is ROOT (`isRoot === true`, strict) — root spans all, no clause.
347
+ * Otherwise ANDs, over the actor's subtree nodes (`tenantScopeNodes` — the SAME set the
348
+ * tenant clause + list-scope use, so the three agree):
349
+ * - `nodeColumn` → `` `<column>` IN (?, ...) `` — subtree ids `?`-bound.
350
+ * - `subtreeSubquery` → `` `<pkColumn>` IN (<subquerySql, :nodes → IN (?, ...)>) `` — the
351
+ * FIXED framework template with the subtree ids `?`-bound at `:nodes`.
352
+ *
353
+ * INJECTION-FREE BY CONSTRUCTION: the ONLY dynamic values are the `?`-bound subtree node
354
+ * ids; every identifier is a compile-time constant (the type's own column, or the fixed
355
+ * template). No node value is ever interpolated into the SQL string. FAIL-CLOSED: an
356
+ * empty subtree (shouldn't happen — `tenantScopeNodes` never returns empty) would still
357
+ * emit `IN ()`, which matches nothing (deny), never a widening clause.
358
+ */
359
+ readScopeClause(reg, actor) {
360
+ const rs = reg.readScope;
361
+ if (!rs || actor.isRoot === true)
362
+ return null;
363
+ const nodes = this.tenantScopeNodes(actor);
364
+ const placeholders = nodes.map(() => '?').join(', ');
365
+ if (rs.kind === 'nodeColumn') {
366
+ return { sql: `\`${rs.column}\` IN (${placeholders})`, params: [...nodes] };
367
+ }
368
+ // subtreeSubquery: expand the FIXED template's `:nodes` token to a `?`-list. The
369
+ // template's identifiers are constants; only the node ids are `?`-bound (below).
370
+ const expanded = rs.subquerySql.replace(types_js_1.READ_SCOPE_NODES_TOKEN, placeholders);
371
+ return {
372
+ sql: `\`${rs.pkColumn}\` IN (${expanded})`,
373
+ params: [...nodes],
374
+ };
375
+ }
268
376
  }
269
377
  exports.RecordEngineImpl = RecordEngineImpl;
270
378
  /**
package/dist/types.d.ts CHANGED
@@ -98,6 +98,43 @@ export interface AccessDeclaration {
98
98
  */
99
99
  readonly plane: CapabilityPlane;
100
100
  }
101
+ /**
102
+ * How a type's LIST/GET reads are scoped to the caller's reachable subtree (ADR-0027) —
103
+ * DECOUPLED from `trusted:'tenantId'` (which stamps WRITES via derive-write-context).
104
+ * `readScope` filters READS ONLY; it has ZERO write effect (a create still stamps only the
105
+ * `trusted` fields). Absent ⇒ no subtree read-scope (today's behavior — the tenant clause,
106
+ * if the type has a `trusted:'tenantId'` column, still applies; a `system` type with no
107
+ * such column then returns unscoped, which is why the identity `system` types declare a
108
+ * `readScope` to wall themselves). Applied by the engine AFTER the tenant clause, for
109
+ * NON-ROOT actors only (root spans all).
110
+ *
111
+ * Two forms:
112
+ * - `nodeColumn` — rows whose `<column>` is IN the caller's subtree node ids
113
+ * (`` `<column>` IN (?, ...) ``). Use when the type's own row carries the node itself
114
+ * (e.g. `company.c_uuid`, `company_membership.cm_company_uuid`).
115
+ * - `subtreeSubquery` — rows whose PK is IN a membership-derived subquery over the
116
+ * caller's subtree. The `subquerySql` is a FIXED, FRAMEWORK-AUTHORED template per type
117
+ * (NEVER client SQL): every identifier is a compile-time constant, and the ONLY dynamic,
118
+ * `?`-bound values are the subtree node ids — injected via the `:nodes` placeholder the
119
+ * engine expands to `IN (?, ?, ...)`. This is injection-free BY CONSTRUCTION (no value
120
+ * is ever interpolated into the SQL string).
121
+ */
122
+ export type ReadScope = {
123
+ readonly kind: 'nodeColumn';
124
+ readonly column: string;
125
+ } | {
126
+ readonly kind: 'subtreeSubquery';
127
+ /** The type's OWN PK column to test membership of. */
128
+ readonly pkColumn: string;
129
+ /**
130
+ * A FIXED framework template containing the `:nodes` placeholder (expanded by the
131
+ * engine to `IN (?, ?, ...)` with the subtree ids `?`-bound). NOT client SQL —
132
+ * identifiers are constants; the ONLY dynamic values are the bound node ids.
133
+ */
134
+ readonly subquerySql: string;
135
+ };
136
+ /** The token a `subtreeSubquery` template uses for the engine-expanded subtree id list. */
137
+ export declare const READ_SCOPE_NODES_TOKEN = ":nodes";
101
138
  /** Primary-key strategy for a registered table. */
102
139
  export type PkStrategy = {
103
140
  readonly kind: 'uuid';
@@ -137,6 +174,13 @@ export interface ObjectType {
137
174
  * fails (chokepoint 1). See {@link AccessDeclaration}.
138
175
  */
139
176
  readonly access: AccessDeclaration;
177
+ /**
178
+ * OPTIONAL read-scope declaration (ADR-0027) — how LIST/GET reads are filtered to the
179
+ * caller's reachable subtree, DECOUPLED from the write path. See {@link ReadScope}.
180
+ * Absent ⇒ no subtree read-scope (the tenant clause, if any, still applies). Has ZERO
181
+ * write effect.
182
+ */
183
+ readonly readScope?: ReadScope;
140
184
  }
141
185
  /** Input to a create/update — field keys → values (+ optional junction refs). */
142
186
  export interface RecordInput {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -13,3 +13,6 @@
13
13
  * surface.
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.READ_SCOPE_NODES_TOKEN = void 0;
17
+ /** The token a `subtreeSubquery` template uses for the engine-expanded subtree id list. */
18
+ exports.READ_SCOPE_NODES_TOKEN = ':nodes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/engine",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Astram framework — the paved road. ONE unbypassable write pipe (resolve-schema → permission gate → derive-write-context → validate → compute → write → junction-sync → audit → emit-event) over real physical tables, that every feature's CRUD flows through. Feature code receives ONLY the RecordEngine facade — never a raw pool/DataSource/QueryRunner (the physical wall). Security-gate + audit are mandatory stages. Missing capability → a typed CapabilityNotSupportedError (the gap-flag STOP). Builds on @astram/core (metadata contract, decide(), audit) + @astram/guard (decision authority).",
6
6
  "type": "commonjs",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "mariadb": "^3.4.0",
24
- "@astram/core": "1.1.0"
24
+ "@astram/core": "1.2.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@nestjs/common": "^11",