@astram/engine 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -11,9 +11,11 @@
11
11
  *
12
12
  * FRAMEWORK-FACING (composition/tests; a FEATURE must NOT import these — the app's
13
13
  * architectural lint rule blocks raw DB access under src/features/**):
14
- * - the adapters (`MariaDbUnitOfWork`, `MariaDbAuditSink`), the `RecordEngineImpl`,
15
- * the `TableRegistry`, and the ports. Exported so the framework + integration
16
- * tests can compose the engine directly; NOT for feature code.
14
+ * - the `MariaDbAuditSink` adapter, the `RecordEngineImpl`, the `TableRegistry`, and the
15
+ * ports. Exported so the framework + integration tests can compose the engine
16
+ * directly; NOT for feature code.
17
+ * - The raw unit of work (`MariaDbUnitOfWork`, it owns a pool) is NOT here: it is on
18
+ * `@astram/engine/internal`, which only the composition root may import.
17
19
  */
18
20
  export type { RecordEngine, AccessDeclaration, FieldSpec, FieldType, TrustedSource, IdentityRole, PkStrategy, JunctionSpec, RecordInput, RecordId, RecordView, ListQuery, RecordPage, Principal, ReadScope, ReadScopeCreateMode, } from './types.js';
19
21
  export { READ_SCOPE_NODES_TOKEN } from './types.js';
@@ -24,7 +26,7 @@ export { EngineModule, RECORD_ENGINE, ENGINE_ALL_REGISTRATIONS, ENGINE_ALL_ACCES
24
26
  export { TableRegistry, columnOf, uuidPkColumn, isOwned, ownerColumn, tenantColumnOf, columnForRole, requireRole, } from './registry.js';
25
27
  export { RecordEngineImpl, type RecordEngineDeps } from './pipe/record-engine.js';
26
28
  export { RecordsService, BoundRecords, type ServiceAccountResolver, SERVICE_ACCOUNT_RESOLVER, } from './pipe/records-service.js';
27
- export { MariaDbUnitOfWork, type EngineDbConfig } from './adapters/mariadb-unit-of-work.js';
29
+ export type { EngineDbConfig } from './adapters/mariadb-unit-of-work.js';
28
30
  export { MariaDbAuditSink, type AuditSinkConfig } from './adapters/mariadb-audit-sink.js';
29
31
  export type { QueryFn, UnitOfWork, DecisionPort, AuditPort } from './ports.js';
30
32
  export type { Stage, WriteContext, WriteOp, PipeBase } from './pipe/context.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,EACT,SAAS,EACT,mBAAmB,GACpB,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,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC1F,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,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;;;;;;;;;;;;;;;;;;GAkBG;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,EACT,mBAAmB,GACpB,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;AAGnC,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAC1F,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -12,12 +12,14 @@
12
12
  *
13
13
  * FRAMEWORK-FACING (composition/tests; a FEATURE must NOT import these — the app's
14
14
  * architectural lint rule blocks raw DB access under src/features/**):
15
- * - the adapters (`MariaDbUnitOfWork`, `MariaDbAuditSink`), the `RecordEngineImpl`,
16
- * the `TableRegistry`, and the ports. Exported so the framework + integration
17
- * tests can compose the engine directly; NOT for feature code.
15
+ * - the `MariaDbAuditSink` adapter, the `RecordEngineImpl`, the `TableRegistry`, and the
16
+ * ports. Exported so the framework + integration tests can compose the engine
17
+ * directly; NOT for feature code.
18
+ * - The raw unit of work (`MariaDbUnitOfWork`, it owns a pool) is NOT here: it is on
19
+ * `@astram/engine/internal`, which only the composition root may import.
18
20
  */
19
21
  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 = exports.READ_SCOPE_NODES_TOKEN = void 0;
22
+ exports.MariaDbAuditSink = 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
23
  // The read-scope subtree-id placeholder token (ADR-0027) — a `subtreeSubquery` template
22
24
  // uses it; the engine expands it to a `?`-list. Exported so per-type declarations can
23
25
  // reference the canonical token (rather than a stringly-typed duplicate).
@@ -79,7 +81,5 @@ var records_service_js_1 = require("./pipe/records-service.js");
79
81
  Object.defineProperty(exports, "RecordsService", { enumerable: true, get: function () { return records_service_js_1.RecordsService; } });
80
82
  Object.defineProperty(exports, "BoundRecords", { enumerable: true, get: function () { return records_service_js_1.BoundRecords; } });
81
83
  Object.defineProperty(exports, "SERVICE_ACCOUNT_RESOLVER", { enumerable: true, get: function () { return records_service_js_1.SERVICE_ACCOUNT_RESOLVER; } });
82
- var mariadb_unit_of_work_js_1 = require("./adapters/mariadb-unit-of-work.js");
83
- Object.defineProperty(exports, "MariaDbUnitOfWork", { enumerable: true, get: function () { return mariadb_unit_of_work_js_1.MariaDbUnitOfWork; } });
84
84
  var mariadb_audit_sink_js_1 = require("./adapters/mariadb-audit-sink.js");
85
85
  Object.defineProperty(exports, "MariaDbAuditSink", { enumerable: true, get: function () { return mariadb_audit_sink_js_1.MariaDbAuditSink; } });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @astram/engine/internal — composition-root and framework-test surface (Phase 8 #5).
3
+ *
4
+ * The raw unit of work is NOT on the package entry point (`@astram/engine`): feature code gets
5
+ * only the RecordEngine facade (`RECORD_ENGINE` / `RecordsService`). Wiring code (an app's
6
+ * `src/app.module.ts` / `src/main.ts`, and framework tests) imports the unit of work from here;
7
+ * the scaffolded app's lint wall rejects this subpath everywhere else.
8
+ */
9
+ export { MariaDbUnitOfWork, type EngineDbConfig } from './adapters/mariadb-unit-of-work.js';
10
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MariaDbUnitOfWork = void 0;
4
+ /**
5
+ * @astram/engine/internal — composition-root and framework-test surface (Phase 8 #5).
6
+ *
7
+ * The raw unit of work is NOT on the package entry point (`@astram/engine`): feature code gets
8
+ * only the RecordEngine facade (`RECORD_ENGINE` / `RecordsService`). Wiring code (an app's
9
+ * `src/app.module.ts` / `src/main.ts`, and framework tests) imports the unit of work from here;
10
+ * the scaffolded app's lint wall rejects this subpath everywhere else.
11
+ */
12
+ var mariadb_unit_of_work_js_1 = require("./adapters/mariadb-unit-of-work.js");
13
+ Object.defineProperty(exports, "MariaDbUnitOfWork", { enumerable: true, get: function () { return mariadb_unit_of_work_js_1.MariaDbUnitOfWork; } });
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Every write (create/update/delete) runs the FIXED, ordered stage sequence inside
5
5
  * ONE transaction:
6
- * fetch-record (update/delete) → permission-gate (MANDATORY) → derive-write-context →
6
+ * type-pre-gate (update/delete) → fetch-record (update/delete) → permission-gate (MANDATORY) →
7
+ * derive-write-context →
7
8
  * validate → compute (no-op Phase 1) → write → junction-sync → audit (MANDATORY) →
8
9
  * emit-event (no-op).
9
10
  * A caller cannot reorder, add, or drop stages — the pipe shape is fixed here, so
@@ -1 +1 @@
1
- {"version":3,"file":"record-engine.d.ts","sourceRoot":"","sources":["../../src/pipe/record-engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record-engine.d.ts","sourceRoot":"","sources":["../../src/pipe/record-engine.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Every write (create/update/delete) runs the FIXED, ordered stage sequence inside
6
6
  * ONE transaction:
7
- * fetch-record (update/delete) → permission-gate (MANDATORY) → derive-write-context →
7
+ * type-pre-gate (update/delete) → fetch-record (update/delete) → permission-gate (MANDATORY) →
8
+ * derive-write-context →
8
9
  * validate → compute (no-op Phase 1) → write → junction-sync → audit (MANDATORY) →
9
10
  * emit-event (no-op).
10
11
  * A caller cannot reorder, add, or drop stages — the pipe shape is fixed here, so
@@ -65,7 +66,10 @@ class RecordEngineImpl {
65
66
  // fetch-record runs FIRST (before the gate): for update/delete on EVERY type it row-locks
66
67
  // and reads the existing row through the shared whereById, so decide() sees the record's
67
68
  // fields, owner and tenant (block D, #10). A missing row throws there. No-op for create.
69
+ // type-pre-gate runs before it (Phase 8 #7): a coarse object-type decide() so an actor with
70
+ // no permission on the type is denied before it can take any row lock. No-op for create.
68
71
  this.writeStages = [
72
+ (0, permission_gate_js_1.typePreGateStage)(this.decision),
69
73
  (0, fetch_record_js_1.fetchRecordStage)(),
70
74
  (0, permission_gate_js_1.permissionGateStage)(this.decision),
71
75
  (0, derive_write_context_js_1.deriveWriteContextStage)(),
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Stage: fetch-record (update/delete on EVERY type — runs BEFORE the permission gate).
3
- * Security hardening block D (#10); replaces the owner-only `fetch-owner` stage.
3
+ * Security hardening block D (#10); replaces the owner-only `fetch-owner` stage. Since Phase 8
4
+ * #7 it runs AFTER `type-pre-gate` (permission-gate.ts), so an actor with no permission on the
5
+ * type is denied before this stage can take a row lock.
4
6
  *
5
7
  * Core's decision authority is zero-dep (no DB), so it cannot read the record it judges. This
6
8
  * stage reads the EXISTING row, inside the pipe's unit of work, with
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-record.d.ts","sourceRoot":"","sources":["../../../src/pipe/stages/fetch-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,wBAAwB,EAAqB,MAAM,iBAAiB,CAAC;AAE9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AAGzD,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAetF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,SAAS,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,GAAG,wBAAwB,CAK5E;AAED,wBAAgB,gBAAgB,IAAI,KAAK,CAwBxC"}
1
+ {"version":3,"file":"fetch-record.d.ts","sourceRoot":"","sources":["../../../src/pipe/stages/fetch-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,wBAAwB,EAAqB,MAAM,iBAAiB,CAAC;AAE9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AAGzD,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAetF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,SAAS,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,GAAG,wBAAwB,CAK5E;AAED,wBAAgB,gBAAgB,IAAI,KAAK,CAwBxC"}
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Stage: fetch-record (update/delete on EVERY type — runs BEFORE the permission gate).
4
- * Security hardening block D (#10); replaces the owner-only `fetch-owner` stage.
4
+ * Security hardening block D (#10); replaces the owner-only `fetch-owner` stage. Since Phase 8
5
+ * #7 it runs AFTER `type-pre-gate` (permission-gate.ts), so an actor with no permission on the
6
+ * type is denied before this stage can take a row lock.
5
7
  *
6
8
  * Core's decision authority is zero-dep (no DB), so it cannot read the record it judges. This
7
9
  * stage reads the EXISTING row, inside the pipe's unit of work, with
@@ -8,4 +8,33 @@
8
8
  import type { DecisionPort } from '../../ports.js';
9
9
  import type { Stage } from '../context.js';
10
10
  export declare function permissionGateStage(decision: DecisionPort): Stage;
11
+ /**
12
+ * Stage: type pre-gate (update/delete only; runs FIRST, before fetch-record's row lock).
13
+ * Security-hardening Phase 8 #7.
14
+ *
15
+ * fetch-record takes `SELECT * … FOR UPDATE` so the gate judges the row the write changes; but
16
+ * run before any check, that let an actor with NO permission on the type take row locks. This
17
+ * stage asks the same `decide()` the coarse question first — `{ level: 'object_type', typeKey,
18
+ * tenantId: actor.tenantId }` for the op's action, no attributes — and stops the pipe on deny
19
+ * BEFORE any SQL. On allow, fetch-record locks and the record-level permission gate runs
20
+ * unchanged.
21
+ *
22
+ * ── It never denies what the record-level gate would allow. In core's one decision path the
23
+ * record-level and object-type-level calls differ only after gate 5 (record ⇒ gate 6 runs;
24
+ * object type ⇒ allow terminates), and every input gates 0–5 and 7 read is identical here:
25
+ * - typeKey and action: identical;
26
+ * - resource.tenantId: the record gate uses the row's tenant column, and `whereById`
27
+ * pins that column to `actor.tenantId` (a type without one falls back to it) — so it is
28
+ * always `actor.tenantId`, exactly what this stage sends (gates 3 and 5 agree);
29
+ * - gate 5's coarse key and root-plane rule depend only on (typeKey, action, principal);
30
+ * - gate 7 (delegation) reads only the `shield.delegation` attribute, which the engine
31
+ * never supplies (the record gate's attributes are registered field values).
32
+ * So a deny here is a deny at gate ≤ 5 (or 7) that the record-level call reaches too.
33
+ *
34
+ * ── Existence: it runs before any read, so its answer cannot depend on whether the id exists
35
+ * (a missing and an existing id give the same error). It throws the decision's own reason,
36
+ * as the record gate would for the same actor. `create` no-ops: the permission gate already
37
+ * judges a create at object-type level, and a create takes no lock.
38
+ */
39
+ export declare function typePreGateStage(decision: DecisionPort): Stage;
11
40
  //# sourceMappingURL=permission-gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../../../src/pipe/stages/permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,KAAK,EAAE,KAAK,EAAyB,MAAM,eAAe,CAAC;AAQlE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,KAAK,CA+CjE"}
1
+ {"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../../../src/pipe/stages/permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,KAAK,EAAE,KAAK,EAAyB,MAAM,eAAe,CAAC;AAQlE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,KAAK,CAiCjE;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,KAAK,CAe9D"}
@@ -8,6 +8,7 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.permissionGateStage = permissionGateStage;
11
+ exports.typePreGateStage = typePreGateStage;
11
12
  const errors_js_1 = require("../../errors.js");
12
13
  const fetch_record_js_1 = require("./fetch-record.js");
13
14
  /** Map the pipe op to the stable authorization action key. */
@@ -43,19 +44,68 @@ function permissionGateStage(decision) {
43
44
  }
44
45
  : {}),
45
46
  };
46
- let outcome;
47
- try {
48
- outcome = await decision.decide(ctx.actor, action, resource, context);
49
- }
50
- catch (err) {
51
- // A thrown decision = transient/unavailable (ADR-0016). Fail-closed: do NOT
52
- // proceed; signal retryable.
53
- throw new errors_js_1.EngineUnavailableError(`decision authority unavailable for ${ctx.op} ${ctx.registration.typeKey}: ` +
54
- `${err.message}`);
55
- }
56
- if (outcome.effect !== 'allow') {
57
- throw new errors_js_1.AuthorizationDeniedError(outcome.reason, outcome.detail);
58
- }
47
+ await decideOrThrow(decision, ctx, action, resource, context);
48
+ },
49
+ };
50
+ }
51
+ /** Ask `decide()` once; throw on deny (AuthorizationDeniedError) or a throw (unavailable). */
52
+ async function decideOrThrow(decision, ctx, action, resource, context) {
53
+ let outcome;
54
+ try {
55
+ outcome = await decision.decide(ctx.actor, action, resource, context);
56
+ }
57
+ catch (err) {
58
+ // A thrown decision = transient/unavailable (ADR-0016). Fail-closed: do NOT
59
+ // proceed; signal retryable.
60
+ throw new errors_js_1.EngineUnavailableError(`decision authority unavailable for ${ctx.op} ${ctx.registration.typeKey}: ` +
61
+ `${err.message}`);
62
+ }
63
+ if (outcome.effect !== 'allow') {
64
+ throw new errors_js_1.AuthorizationDeniedError(outcome.reason, outcome.detail);
65
+ }
66
+ }
67
+ /**
68
+ * Stage: type pre-gate (update/delete only; runs FIRST, before fetch-record's row lock).
69
+ * Security-hardening Phase 8 #7.
70
+ *
71
+ * fetch-record takes `SELECT * … FOR UPDATE` so the gate judges the row the write changes; but
72
+ * run before any check, that let an actor with NO permission on the type take row locks. This
73
+ * stage asks the same `decide()` the coarse question first — `{ level: 'object_type', typeKey,
74
+ * tenantId: actor.tenantId }` for the op's action, no attributes — and stops the pipe on deny
75
+ * BEFORE any SQL. On allow, fetch-record locks and the record-level permission gate runs
76
+ * unchanged.
77
+ *
78
+ * ── It never denies what the record-level gate would allow. In core's one decision path the
79
+ * record-level and object-type-level calls differ only after gate 5 (record ⇒ gate 6 runs;
80
+ * object type ⇒ allow terminates), and every input gates 0–5 and 7 read is identical here:
81
+ * - typeKey and action: identical;
82
+ * - resource.tenantId: the record gate uses the row's tenant column, and `whereById`
83
+ * pins that column to `actor.tenantId` (a type without one falls back to it) — so it is
84
+ * always `actor.tenantId`, exactly what this stage sends (gates 3 and 5 agree);
85
+ * - gate 5's coarse key and root-plane rule depend only on (typeKey, action, principal);
86
+ * - gate 7 (delegation) reads only the `shield.delegation` attribute, which the engine
87
+ * never supplies (the record gate's attributes are registered field values).
88
+ * So a deny here is a deny at gate ≤ 5 (or 7) that the record-level call reaches too.
89
+ *
90
+ * ── Existence: it runs before any read, so its answer cannot depend on whether the id exists
91
+ * (a missing and an existing id give the same error). It throws the decision's own reason,
92
+ * as the record gate would for the same actor. `create` no-ops: the permission gate already
93
+ * judges a create at object-type level, and a create takes no lock.
94
+ */
95
+ function typePreGateStage(decision) {
96
+ return {
97
+ name: 'type-pre-gate',
98
+ async run(ctx) {
99
+ if (ctx.op === 'create')
100
+ return;
101
+ const resource = {
102
+ level: 'object_type',
103
+ typeKey: ctx.registration.typeKey,
104
+ tenantId: ctx.actor.tenantId,
105
+ };
106
+ await decideOrThrow(decision, ctx, { key: actionKey(ctx.op) }, resource, {
107
+ isAuthenticated: true,
108
+ });
59
109
  },
60
110
  };
61
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/engine",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",
@@ -10,6 +10,17 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "default": "./dist/index.js"
13
+ },
14
+ "./internal": {
15
+ "types": "./dist/internal.d.ts",
16
+ "default": "./dist/internal.js"
17
+ }
18
+ },
19
+ "typesVersions": {
20
+ "*": {
21
+ "internal": [
22
+ "dist/internal.d.ts"
23
+ ]
13
24
  }
14
25
  },
15
26
  "files": [
@@ -20,7 +31,7 @@
20
31
  "access": "public"
21
32
  },
22
33
  "dependencies": {
23
- "mariadb": "^3.4.0",
34
+ "mariadb": "^3.5.4",
24
35
  "@astram/core": "1.4.0"
25
36
  },
26
37
  "peerDependencies": {