@almadar/runtime 6.45.0 → 6.47.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.
@@ -1348,6 +1348,15 @@ declare class OrbitalServerRuntime {
1348
1348
  setDefaultUser(user: UserContext | undefined): void;
1349
1349
  /** The viewer a dev host is currently presenting the app as. */
1350
1350
  getDefaultUser(): UserContext | undefined;
1351
+ /**
1352
+ * The registered app's declared persona roster: the live rows of its
1353
+ * `[identity]` entity, mapped onto viewers (`Almadar_LOLO_Identity.md` §4.3).
1354
+ * Live-store rows rather than a re-derivation, so the roster carries the ids
1355
+ * ownership scoping actually compares `@user.id` against. Empty when no
1356
+ * schema is registered or the app declares no `[identity]` entity — there is
1357
+ * no global fallback roster by design.
1358
+ */
1359
+ getIdentityRoster(): Promise<UserContext[]>;
1351
1360
  /**
1352
1361
  * `traitFieldStates` key for one trait: its own name, or — when its linked
1353
1362
  * entity is `[shared]` — `$shared::<entityName>` so every trait bound to
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { F as ClientEffectTuple, G as ClientNavigateTuple, H as ClientNotifyTuple, J as ClientRenderUITuple, K as EffectResult, e as InMemoryPersistence, M as LiveBroadcastItem, N as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, Q as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, T as RuntimeTraitTick, p as collectDeclaredConfigDefaults, V as createOrbitalServerRuntime } from './OrbitalServerRuntime-A2h0EikK.js';
2
+ export { F as ClientEffectTuple, G as ClientNavigateTuple, H as ClientNotifyTuple, J as ClientRenderUITuple, K as EffectResult, e as InMemoryPersistence, M as LiveBroadcastItem, N as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, Q as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, T as RuntimeTraitTick, p as collectDeclaredConfigDefaults, V as createOrbitalServerRuntime } from './OrbitalServerRuntime-e-5490xl.js';
3
3
  import './types-CL03tjGU.js';
4
4
  import '@almadar/core';
@@ -7,8 +7,8 @@ import './chunk-MLKGABMK.js';
7
7
  import { createLogger } from '@almadar/logger';
8
8
  import * as nodeModule from 'module';
9
9
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
10
- import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, normalizeUserContext, isRuntimeEntity } from '@almadar/core';
11
- import { ownerFieldsFromSchema, identityEntityName } from '@almadar/core/mock';
10
+ import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity } from '@almadar/core';
11
+ import { ownerFieldsFromSchema, identityEntityName, entityAccessPolicies } from '@almadar/core/mock';
12
12
 
13
13
  // src/identity/routing.ts
14
14
  function eventRouteKey(eventName, eventId) {
@@ -33,6 +33,41 @@ function buildSourceMatcher(src, listenerOrbital) {
33
33
  const wantedTrait = src.trait;
34
34
  return (source) => !!source && source.orbital === wantedOrbital && source.trait === wantedTrait;
35
35
  }
36
+ function applyRowAccess(rows, policy, filter, bindings) {
37
+ if (policy === void 0 && filter === void 0) {
38
+ return rows;
39
+ }
40
+ const predicates = [policy, filter].filter((p) => p !== void 0);
41
+ return rows.filter((entity) => {
42
+ const ctx = createContextFromBindings(
43
+ { entity, payload: bindings.payload, current: entity, user: bindings.user, config: bindings.config },
44
+ false
45
+ );
46
+ return predicates.every((predicate) => {
47
+ try {
48
+ return Boolean(evaluate(predicate, ctx));
49
+ } catch {
50
+ return false;
51
+ }
52
+ });
53
+ });
54
+ }
55
+ function checkMutationAccess(row, policy, bindings) {
56
+ if (policy === void 0) {
57
+ return true;
58
+ }
59
+ const ctx = createContextFromBindings(
60
+ { entity: row, payload: bindings.payload, current: row, user: bindings.user, config: bindings.config },
61
+ false
62
+ );
63
+ try {
64
+ return Boolean(evaluate(policy, ctx));
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // src/OrbitalServerRuntime.ts
36
71
  var _resolvedNodeRequire = null;
37
72
  function nodeRequire(modulePath) {
38
73
  if (!_resolvedNodeRequire) {
@@ -922,6 +957,22 @@ var OrbitalServerRuntime = class {
922
957
  getDefaultUser() {
923
958
  return this.config.defaultUser;
924
959
  }
960
+ /**
961
+ * The registered app's declared persona roster: the live rows of its
962
+ * `[identity]` entity, mapped onto viewers (`Almadar_LOLO_Identity.md` §4.3).
963
+ * Live-store rows rather than a re-derivation, so the roster carries the ids
964
+ * ownership scoping actually compares `@user.id` against. Empty when no
965
+ * schema is registered or the app declares no `[identity]` entity — there is
966
+ * no global fallback roster by design.
967
+ */
968
+ async getIdentityRoster() {
969
+ const schema = this.resolvedSchema;
970
+ if (!schema) return [];
971
+ const entityName = identityEntityName(schema);
972
+ if (!entityName) return [];
973
+ const rows = await this.persistence.list(entityName);
974
+ return rows.map((row) => personaFromIdentityRow(row)).filter((p) => p !== void 0);
975
+ }
925
976
  // ==========================================================================
926
977
  // Event Processing
927
978
  // ==========================================================================
@@ -1134,6 +1185,15 @@ var OrbitalServerRuntime = class {
1134
1185
  */
1135
1186
  async executeEffects(registered, traitName, effects, payload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user, clientEffectsByTrait, onPush, originClientId) {
1136
1187
  const entityType = registered.entity.name;
1188
+ for (const eff of effects) {
1189
+ if (Array.isArray(eff) && eff[0] === "fetch") {
1190
+ xOrbitalLog.debug("fetch:pre-exec-keys", () => ({
1191
+ trait: traitName,
1192
+ entity: String(eff[1]),
1193
+ optKeys: Object.keys(eff[2] ?? {}).join("+")
1194
+ }));
1195
+ }
1196
+ }
1137
1197
  const pushClientEffect = (effect) => {
1138
1198
  clientEffects.push(effect);
1139
1199
  clientEffectsByTrait?.push({ traitName, effect });
@@ -1291,8 +1351,15 @@ var OrbitalServerRuntime = class {
1291
1351
  if (action === "create" || action === "update") {
1292
1352
  this.validateRelationCardinality(type, data || {});
1293
1353
  }
1354
+ const accessBindings = { user: bindingsRef?.user, payload: bindingsRef?.payload, config: bindingsRef?.config };
1355
+ const mutationPolicy = this.resolvedSchema ? entityAccessPolicies(this.resolvedSchema, type)?.[action === "create" ? "create" : action === "update" ? "update" : "delete"] : void 0;
1294
1356
  switch (action) {
1295
1357
  case "create": {
1358
+ if (!checkMutationAccess(data || {}, mutationPolicy, accessBindings)) {
1359
+ throw new Error(
1360
+ `@create denied: the declared access policy for '${type}' rejected this row`
1361
+ );
1362
+ }
1296
1363
  const { id } = await this.persistence.create(type, data || {});
1297
1364
  resultData = { id, ...data || {} };
1298
1365
  break;
@@ -1300,6 +1367,14 @@ var OrbitalServerRuntime = class {
1300
1367
  case "update":
1301
1368
  if (data?.id || entityId) {
1302
1369
  const updateId = data?.id || entityId;
1370
+ if (mutationPolicy !== void 0) {
1371
+ const existing = await this.persistence.getById(type, updateId);
1372
+ if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1373
+ throw new Error(
1374
+ `@update denied: the declared access policy for '${type}' rejected this row`
1375
+ );
1376
+ }
1377
+ }
1303
1378
  await this.persistence.update(type, updateId, data || {});
1304
1379
  const updated = await this.persistence.getById(type, updateId);
1305
1380
  resultData = updated || { id: updateId, ...data || {} };
@@ -1310,6 +1385,14 @@ var OrbitalServerRuntime = class {
1310
1385
  const nestedId = typeof data === "object" && data !== null ? data.id : void 0;
1311
1386
  const deleteId = directId ?? nestedId ?? entityId;
1312
1387
  if (deleteId) {
1388
+ if (mutationPolicy !== void 0) {
1389
+ const existing = await this.persistence.getById(type, deleteId);
1390
+ if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1391
+ throw new Error(
1392
+ `@delete denied: the declared access policy for '${type}' rejected this row`
1393
+ );
1394
+ }
1395
+ }
1313
1396
  await this.enforceOnDeleteRules(type, deleteId);
1314
1397
  await this.persistence.delete(type, deleteId);
1315
1398
  resultData = { id: deleteId, deleted: true };
@@ -1396,20 +1479,13 @@ var OrbitalServerRuntime = class {
1396
1479
  },
1397
1480
  fetch: async (fetchEntityType, options) => {
1398
1481
  try {
1399
- xOrbitalLog.info("fetch:enter", () => ({
1400
- entityType: fetchEntityType,
1401
- hasOptions: options !== void 0 && options !== null,
1402
- optionsKeys: options ? Object.keys(options).join(",") : "",
1403
- filterType: typeof options?.filter,
1404
- filterIsArray: Array.isArray(options?.filter),
1405
- filterJson: JSON.stringify(options?.filter ?? null).slice(0, 300),
1406
- payloadJson: JSON.stringify(bindingsRef?.payload ?? null).slice(0, 300)
1407
- }));
1408
1482
  let result = null;
1409
1483
  let total = 0;
1484
+ const readPolicy = this.resolvedSchema ? entityAccessPolicies(this.resolvedSchema, fetchEntityType)?.read : void 0;
1485
+ const accessBindings = { user: bindingsRef?.user, payload: bindingsRef?.payload, config: bindingsRef?.config };
1410
1486
  if (options?.id) {
1411
1487
  const entity = await this.persistence.getById(fetchEntityType, options.id);
1412
- if (entity) {
1488
+ if (entity && applyRowAccess([entity], readPolicy, void 0, accessBindings).length > 0) {
1413
1489
  if (options?.include && options.include.length > 0) {
1414
1490
  await this.populateRelations([entity], fetchEntityType, options.include);
1415
1491
  }
@@ -1419,24 +1495,12 @@ var OrbitalServerRuntime = class {
1419
1495
  }
1420
1496
  } else {
1421
1497
  let entities = await this.persistence.list(fetchEntityType);
1422
- if (options?.filter !== void 0 && options.filter !== null) {
1423
- const predicate = options.filter;
1424
- entities = entities.filter((entity) => {
1425
- const ctx = createContextFromBindings(
1426
- { entity, payload: bindingsRef?.payload, current: entity, user: bindingsRef?.user },
1427
- false
1428
- );
1429
- try {
1430
- return Boolean(evaluate(predicate, ctx));
1431
- } catch (err) {
1432
- effectLog.error("fetch:filter-eval-error", {
1433
- entityType: fetchEntityType,
1434
- error: err instanceof Error ? err : String(err)
1435
- });
1436
- return false;
1437
- }
1438
- });
1439
- }
1498
+ entities = applyRowAccess(
1499
+ entities,
1500
+ readPolicy,
1501
+ options?.filter !== void 0 && options.filter !== null ? options.filter : void 0,
1502
+ accessBindings
1503
+ );
1440
1504
  total = entities.length;
1441
1505
  if (options?.offset && options.offset > 0) {
1442
1506
  entities = entities.slice(options.offset);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { g as RuntimePatternValue, B as BindingContext, f as EvaluationContextExtensions, P as PatternProps, E as EffectHandlers, h as EffectContext, i as ExecutionEnvironment, j as EffectResult, T as TraitDefinition } from './types-CL03tjGU.js';
2
2
  export { k as BrowserFileMeta, l as BrowserFilePickerOptions, m as BrowserGeolocationOptions, n as BrowserGeolocationPosition, C as ConfigContext, o as Effect, a as EventListener, H as HANDLER_MANIFEST, I as IEventBus, b as RuntimeConfig, R as RuntimeEvent, d as TraitState, c as TransitionObserver, e as TransitionResult, U as Unsubscribe } from './types-CL03tjGU.js';
3
- import { U as UnifiedLoaderOptions, S as SchemaLoader, I as ImportChainLike, L as LoadResult, a as LoadedSchema, b as LoadedOrbital, P as PersistenceAdapter } from './OrbitalServerRuntime-A2h0EikK.js';
4
- export { E as EntitySharingMap, c as EventBus, d as EventNamespaceMap, e as InMemoryPersistence, O as OrbitalEventRequest, f as OrbitalEventResponse, g as OrbitalServerRuntimeConfig, h as PreprocessOptions, i as PreprocessResult, j as PreprocessedSchema, k as ProcessEventOptions, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, o as StateMachineManager, p as collectDeclaredConfigDefaults, q as collectDeclaredEntityDefaults, r as createInitialTraitState, s as findInitialState, t as findTransition, u as getIsolatedCollectionName, v as getNamespacedEvent, w as isBrowser, x as isElectron, y as isNamespacedEvent, z as isNode, A as normalizeEventKey, B as parseNamespacedEvent, C as preprocessSchema, D as processEvent } from './OrbitalServerRuntime-A2h0EikK.js';
3
+ import { U as UnifiedLoaderOptions, S as SchemaLoader, I as ImportChainLike, L as LoadResult, a as LoadedSchema, b as LoadedOrbital, P as PersistenceAdapter } from './OrbitalServerRuntime-e-5490xl.js';
4
+ export { E as EntitySharingMap, c as EventBus, d as EventNamespaceMap, e as InMemoryPersistence, O as OrbitalEventRequest, f as OrbitalEventResponse, g as OrbitalServerRuntimeConfig, h as PreprocessOptions, i as PreprocessResult, j as PreprocessedSchema, k as ProcessEventOptions, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, o as StateMachineManager, p as collectDeclaredConfigDefaults, q as collectDeclaredEntityDefaults, r as createInitialTraitState, s as findInitialState, t as findTransition, u as getIsolatedCollectionName, v as getNamespacedEvent, w as isBrowser, x as isElectron, y as isNamespacedEvent, z as isNode, A as normalizeEventKey, B as parseNamespacedEvent, C as preprocessSchema, D as processEvent } from './OrbitalServerRuntime-e-5490xl.js';
5
5
  import { EvaluationContext, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
7
7
  import { RenderBindingMarker, SExpr, TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
package/dist/index.js CHANGED
@@ -409,15 +409,6 @@ function createServerEffectHandlers(opts) {
409
409
  },
410
410
  fetch: async (fetchEntityType, options) => {
411
411
  try {
412
- effectLog.info("clientFetch:enter", () => ({
413
- entityType: fetchEntityType,
414
- hasOptions: options !== void 0 && options !== null,
415
- optionsKeys: options ? Object.keys(options).join(",") : "",
416
- filterType: typeof options?.filter,
417
- filterIsArray: Array.isArray(options?.filter),
418
- filterJson: JSON.stringify(options?.filter ?? null).slice(0, 300),
419
- payloadJson: JSON.stringify(bindings?.payload ?? null).slice(0, 300)
420
- }));
421
412
  let result = null;
422
413
  let total = 0;
423
414
  if (options?.id) {
@@ -433,7 +424,7 @@ function createServerEffectHandlers(opts) {
433
424
  const predicate = options.filter;
434
425
  entities = entities.filter((entity) => {
435
426
  const ctx = createContextFromBindings(
436
- { entity, payload: bindings?.payload, current: entity },
427
+ { entity, payload: bindings?.payload, current: entity, user: bindings?.user, config: bindings?.config },
437
428
  false
438
429
  );
439
430
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.45.0",
3
+ "version": "6.47.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,11 +52,11 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@almadar/core": "^10.43.0",
55
+ "@almadar/core": "^10.45.0",
56
56
  "@almadar/evaluator": "^2.38.0",
57
57
  "@almadar/logger": "^1.10.0",
58
- "@almadar/server": "^2.29.0",
59
- "@almadar/std": "^16.153.0"
58
+ "@almadar/server": "^2.30.0",
59
+ "@almadar/std": "^16.155.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "express": "^5.0.0"