@almadar/runtime 6.63.0 → 6.64.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.
@@ -1416,6 +1416,29 @@ declare class OrbitalServerRuntime {
1416
1416
  * instead of re-seeding.
1417
1417
  */
1418
1418
  setDefaultUser(user: UserContext | undefined): void;
1419
+ /** setDefaultUser can land before register() finishes resolving the schema
1420
+ * (a dev host pins a persona right after boot). An ungated restamp there
1421
+ * would hand a read-only persona authorship of drafts, so the restamp is
1422
+ * DEFERRED until the schema exists — register()/registerSync() flush it. */
1423
+ private pendingOwnerRestamp;
1424
+ /**
1425
+ * Re-point the mock seeder's owner stamps at `user`. The adapter's
1426
+ * installed owner gate (see installOwnerGate) decides per row whether the
1427
+ * persona could have CREATED it — a reader keeps seeing the store's
1428
+ * published face instead of inheriting draft ownership from the demo
1429
+ * stamper. Deferred when the schema (and so the gate) isn't resolved yet.
1430
+ */
1431
+ private restampOwnerGated;
1432
+ /**
1433
+ * Install the schema's `@create` directives as the mock seeder's owner
1434
+ * gate — the single authority on who may author a row. Consulted by BOTH
1435
+ * `restampOwner` (persona switches) and `seed()` (a client connect
1436
+ * re-registers, and the deterministic reseed re-stamps with the current
1437
+ * ownerId — gating only the switch path would leak right there). Reads the
1438
+ * CURRENT default user at evaluation time so one installation covers every
1439
+ * later switch. Then flushes any switch that arrived before the schema.
1440
+ */
1441
+ private installOwnerGate;
1419
1442
  /** The viewer a dev host is currently presenting the app as. */
1420
1443
  getDefaultUser(): UserContext | undefined;
1421
1444
  /**
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { F as ClientEffectTuple, G as ClientNavigateBackTuple, H as ClientNavigateTuple, J as ClientNotifyTuple, K as ClientRenderUITuple, M as EffectResult, e as InMemoryPersistence, N as LiveBroadcastItem, Q as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, T as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, V as RuntimeTraitTick, p as collectDeclaredConfigDefaults, W as createOrbitalServerRuntime } from './OrbitalServerRuntime-nqwGldMC.js';
2
+ export { F as ClientEffectTuple, G as ClientNavigateBackTuple, H as ClientNavigateTuple, J as ClientNotifyTuple, K as ClientRenderUITuple, M as EffectResult, e as InMemoryPersistence, N as LiveBroadcastItem, Q as LoaderConfig, O as OrbitalEventRequest, f as OrbitalEventResponse, T as OrbitalServerRuntime, g as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RegisteredOrbital, l as RuntimeOrbital, m as RuntimeOrbitalSchema, n as RuntimeTrait, V as RuntimeTraitTick, p as collectDeclaredConfigDefaults, W as createOrbitalServerRuntime } from './OrbitalServerRuntime-DOJDoqXu.js';
3
3
  import './types-BaD_ox7e.js';
4
4
  import '@almadar/core';
@@ -1,7 +1,7 @@
1
- import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-C7SIRVGS.js';
2
- export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-C7SIRVGS.js';
1
+ import { createTickScheduler, EventBus, createUnifiedLoader, MockPersistenceAdapter, InMemoryPersistence, preprocessSchema, normalizeCallSiteConfigToValues, StateMachineManager, parseDurationString, validateEventPayload, formatPayloadValidationError, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, EffectExecutor } from './chunk-IFSFX43J.js';
2
+ export { InMemoryPersistence, collectDeclaredConfigDefaults } from './chunk-IFSFX43J.js';
3
3
  import { isValidCronExpression } from './chunk-OU3ITB5S.js';
4
- import { createContextFromBindings, resolveCallSitePayloadCaptures, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
4
+ import { createContextFromBindings, checkMutationAccess, resolveCallSitePayloadCaptures, applyRowAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
5
5
  import './chunk-T4VDAB4C.js';
6
6
  import './chunk-SCRAHWOC.js';
7
7
  import './chunk-MLKGABMK.js';
@@ -9,7 +9,7 @@ import { createLogger } from '@almadar/logger';
9
9
  import * as nodeModule from 'module';
10
10
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
11
11
  import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity, isPageReference } from '@almadar/core';
12
- import { ownerFieldsFromSchema, identityEntityName, entityAccessPolicies } from '@almadar/core/mock';
12
+ import { ownerFieldsFromSchema, identityEntityName, entityAccessPoliciesByStoreKey, entityAccessPolicies } from '@almadar/core/mock';
13
13
  import { getPatternFieldsContract } from '@almadar/core/patterns';
14
14
 
15
15
  // src/identity/routing.ts
@@ -355,6 +355,7 @@ var OrbitalServerRuntime = class {
355
355
  this.setupTicks();
356
356
  this.resolvedSchema = schema;
357
357
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
358
+ this.installOwnerGate();
358
359
  }
359
360
  /**
360
361
  * Teach the mock seeder which columns hold the viewer's id, derived from the
@@ -391,7 +392,7 @@ var OrbitalServerRuntime = class {
391
392
  this.setupTicks();
392
393
  this.resolvedSchema = schema;
393
394
  this.resolvedTraitConfigs = buildResolvedTraitConfigs(schema);
394
- this.applyIdentityOwnerFields(schema);
395
+ this.installOwnerGate();
395
396
  }
396
397
  /**
397
398
  * Returns the schema that this runtime is currently executing, post-
@@ -1065,7 +1066,55 @@ var OrbitalServerRuntime = class {
1065
1066
  const previousId = this.config.defaultUser?.id;
1066
1067
  this.config.defaultUser = user;
1067
1068
  if (this.persistence instanceof MockPersistenceAdapter && user?.id !== void 0 && user.id !== previousId) {
1068
- this.persistence.restampOwner(user.id);
1069
+ this.restampOwnerGated(user);
1070
+ }
1071
+ }
1072
+ /** setDefaultUser can land before register() finishes resolving the schema
1073
+ * (a dev host pins a persona right after boot). An ungated restamp there
1074
+ * would hand a read-only persona authorship of drafts, so the restamp is
1075
+ * DEFERRED until the schema exists — register()/registerSync() flush it. */
1076
+ pendingOwnerRestamp = false;
1077
+ /**
1078
+ * Re-point the mock seeder's owner stamps at `user`. The adapter's
1079
+ * installed owner gate (see installOwnerGate) decides per row whether the
1080
+ * persona could have CREATED it — a reader keeps seeing the store's
1081
+ * published face instead of inheriting draft ownership from the demo
1082
+ * stamper. Deferred when the schema (and so the gate) isn't resolved yet.
1083
+ */
1084
+ restampOwnerGated(user) {
1085
+ if (!(this.persistence instanceof MockPersistenceAdapter)) return;
1086
+ if (!this.resolvedSchema) {
1087
+ this.pendingOwnerRestamp = true;
1088
+ return;
1089
+ }
1090
+ this.persistence.restampOwner(user.id);
1091
+ }
1092
+ /**
1093
+ * Install the schema's `@create` directives as the mock seeder's owner
1094
+ * gate — the single authority on who may author a row. Consulted by BOTH
1095
+ * `restampOwner` (persona switches) and `seed()` (a client connect
1096
+ * re-registers, and the deterministic reseed re-stamps with the current
1097
+ * ownerId — gating only the switch path would leak right there). Reads the
1098
+ * CURRENT default user at evaluation time so one installation covers every
1099
+ * later switch. Then flushes any switch that arrived before the schema.
1100
+ */
1101
+ installOwnerGate() {
1102
+ if (!(this.persistence instanceof MockPersistenceAdapter) || !this.resolvedSchema) return;
1103
+ const policiesByStore = entityAccessPoliciesByStoreKey(this.resolvedSchema);
1104
+ this.persistence.setOwnerGate((storeKey, candidateRow) => {
1105
+ const user = this.config.defaultUser;
1106
+ if (!user) return true;
1107
+ return checkMutationAccess(candidateRow, policiesByStore.get(storeKey)?.create, { user });
1108
+ });
1109
+ this.persistence.setOwnerCandidateGate((storeKey, candidateRow, candidateIdentityRow) => {
1110
+ const persona = personaFromIdentityRow(candidateIdentityRow);
1111
+ if (!persona) return false;
1112
+ return checkMutationAccess(candidateRow, policiesByStore.get(storeKey)?.create, { user: persona });
1113
+ });
1114
+ if (this.pendingOwnerRestamp) {
1115
+ this.pendingOwnerRestamp = false;
1116
+ const user = this.config.defaultUser;
1117
+ if (user?.id !== void 0) this.restampOwnerGated(user);
1069
1118
  }
1070
1119
  }
1071
1120
  /** The viewer a dev host is currently presenting the app as. */
@@ -2128,6 +2128,12 @@ var mockLog = createLogger("almadar:runtime:mock");
2128
2128
  var DEFAULT_MOCK_SEED = 42;
2129
2129
  var SEED_REFERENCE_TIMESTAMP = "2024-01-01T00:00:00.000Z";
2130
2130
  var MS_PER_DAY = 864e5;
2131
+ function isRelationField(field) {
2132
+ return field.type === "relation";
2133
+ }
2134
+ function stampedCellKey(entity, id, column) {
2135
+ return `${entity}\0${id}\0${column}`;
2136
+ }
2131
2137
  var MockPersistenceAdapter = class _MockPersistenceAdapter {
2132
2138
  stores = /* @__PURE__ */ new Map();
2133
2139
  schemas = /* @__PURE__ */ new Map();
@@ -2191,21 +2197,108 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2191
2197
  * No-op when there is no new id, or it matches the current one — an
2192
2198
  * anonymous switch (`newOwnerId === undefined`) leaves existing stamps as
2193
2199
  * they are rather than clearing a non-nullable owner column.
2200
+ *
2201
+ * Both this re-labeling AND `seed()`'s original stamping consult the
2202
+ * `ownerGate` hook (see `setOwnerGate`) — a re-register reseeds identical
2203
+ * rows, so gating only the restamp path leaks the moment a client
2204
+ * connects and the deterministic reseed stamps with the updated ownerId.
2194
2205
  */
2195
2206
  restampOwner(newOwnerId) {
2196
2207
  const oldOwnerId = this.config.ownerId;
2197
2208
  this.config.ownerId = newOwnerId;
2198
2209
  if (!newOwnerId || newOwnerId === oldOwnerId) return;
2210
+ let skipped = 0;
2211
+ let fallbackStamps = 0;
2199
2212
  for (const cell of this.ownerStampedCells) {
2200
2213
  const row = this.stores.get(cell.entity)?.get(cell.id);
2201
- if (row) row[cell.column] = newOwnerId;
2214
+ if (!row) continue;
2215
+ if (this.ownerGate && !this.ownerGate(cell.entity, { ...row, [cell.column]: newOwnerId })) {
2216
+ const fallbackId = this.firstEligibleOwner(cell.entity, cell.column, row);
2217
+ if (fallbackId === void 0) {
2218
+ skipped++;
2219
+ continue;
2220
+ }
2221
+ row[cell.column] = fallbackId;
2222
+ fallbackStamps++;
2223
+ continue;
2224
+ }
2225
+ row[cell.column] = newOwnerId;
2202
2226
  }
2203
2227
  mockLog.debug("mock:owner-restamped", {
2204
2228
  from: oldOwnerId,
2205
2229
  to: newOwnerId,
2206
- cells: this.ownerStampedCells.length
2230
+ cells: this.ownerStampedCells.length,
2231
+ skipped,
2232
+ fallbackStamps
2207
2233
  });
2208
2234
  }
2235
+ /**
2236
+ * The host's answer to "may the current viewer OWN this row?" — evaluated
2237
+ * against the candidate row with the owner column already re-pointed,
2238
+ * `storeKey` being the collection key the stores are keyed by. The runtime
2239
+ * installs the registered schema's `@create` directive here (the single
2240
+ * authority on who authors; no role heuristics), so a read-only persona
2241
+ * never inherits authorship of drafts from the demo stamper — neither on a
2242
+ * persona switch (`restampOwner`) nor on a deterministic reseed (`seed`).
2243
+ * Absent hook = stamp unconditionally (pre-role-model behavior).
2244
+ */
2245
+ ownerGate;
2246
+ setOwnerGate(gate) {
2247
+ this.ownerGate = gate;
2248
+ }
2249
+ /**
2250
+ * The host's answer to "may THIS identity row own this candidate row?" —
2251
+ * evaluated per candidate rather than against the fixed default viewer, so
2252
+ * `linkRelationFields` and the eligible-owner fallback (see
2253
+ * `firstEligibleOwner`) can pick a policy-valid owner from the whole
2254
+ * identity roster instead of only ever checking the current default user.
2255
+ * Absent hook = no candidate filtering (pre-role-model behavior).
2256
+ */
2257
+ ownerCandidateGate;
2258
+ setOwnerCandidateGate(gate) {
2259
+ this.ownerCandidateGate = gate;
2260
+ }
2261
+ /**
2262
+ * The store an owner column's relation field targets, resolved the same
2263
+ * way `linkRelationFields` resolves any relation target (id sibling wins
2264
+ * over the `entity` name string) — but keyed by STORE, mirroring that
2265
+ * method's first-declarer-per-collection rule, so `seed()` and
2266
+ * `restampOwner()` can find it before a `linkRelationFields` pass has run
2267
+ * this registration. Undefined when no registered schema on `storeKey`
2268
+ * declares `column` as a relation field.
2269
+ */
2270
+ resolveOwnerColumnTargetStore(storeKey, column) {
2271
+ for (const [normalizedName, schema] of this.schemas) {
2272
+ if ((this.storeKeyByEntity.get(normalizedName) ?? normalizedName) !== storeKey) continue;
2273
+ const field = schema.fields.find((f) => f.name === column);
2274
+ if (field && isRelationField(field)) {
2275
+ return (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? this.resolveStoreKey(field.relation.entity);
2276
+ }
2277
+ }
2278
+ return void 0;
2279
+ }
2280
+ /**
2281
+ * The first identity-store row (seed order) whose persona is eligible, per
2282
+ * the installed `ownerCandidateGate`, to own `row`'s `column` in `storeKey`
2283
+ * — the deterministic replacement for "skip the stamp" when the CURRENT
2284
+ * viewer fails the `@create` policy (e.g. a reader-default persona against
2285
+ * an author|reviewer-only policy). Undefined when no gate is installed, the
2286
+ * column isn't a resolvable relation, or no identity row is eligible — the
2287
+ * caller keeps today's skip behavior in that case.
2288
+ */
2289
+ firstEligibleOwner(storeKey, column, row) {
2290
+ if (!this.ownerCandidateGate) return void 0;
2291
+ const identityStoreKey = this.resolveOwnerColumnTargetStore(storeKey, column);
2292
+ const identityStore = identityStoreKey ? this.stores.get(identityStoreKey) : void 0;
2293
+ if (!identityStore) return void 0;
2294
+ for (const identityRow of identityStore.values()) {
2295
+ const candidateId = identityRow["id"];
2296
+ if (this.ownerCandidateGate(storeKey, { ...row, [column]: candidateId }, identityRow)) {
2297
+ return candidateId;
2298
+ }
2299
+ }
2300
+ return void 0;
2301
+ }
2209
2302
  /** Re-anchor the PRNG to the configured seed. Called before every
2210
2303
  * re-seed loop so identical reseed sequences produce identical rows
2211
2304
  * (timestamps + generated fields). Without this, the first
@@ -2305,16 +2398,28 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2305
2398
  relationFieldsByStore.set(storeKey, fieldMap);
2306
2399
  }
2307
2400
  for (const f of schema.fields) {
2308
- if (((candidate) => candidate.type === "relation")(f) && !fieldMap.has(f.name)) {
2401
+ if (isRelationField(f) && !fieldMap.has(f.name)) {
2309
2402
  fieldMap.set(f.name, f);
2310
2403
  }
2311
2404
  }
2312
2405
  }
2406
+ const ownerColumnsByStore = /* @__PURE__ */ new Map();
2407
+ for (const { entity, field } of this.parsedOwnerFields()) {
2408
+ const storeKey = this.resolveStoreKey(entity);
2409
+ const set = ownerColumnsByStore.get(storeKey) ?? /* @__PURE__ */ new Set();
2410
+ set.add(field);
2411
+ ownerColumnsByStore.set(storeKey, set);
2412
+ }
2413
+ const stampedCellKeys = new Set(
2414
+ this.ownerStampedCells.map((cell) => stampedCellKey(cell.entity, cell.id, cell.column))
2415
+ );
2313
2416
  for (const [storeKey, fieldMap] of relationFieldsByStore) {
2314
2417
  const store = this.stores.get(storeKey);
2315
2418
  if (!store || fieldMap.size === 0) continue;
2316
2419
  const rows = [...store.values()];
2420
+ const ownerColumns = ownerColumnsByStore.get(storeKey);
2317
2421
  for (const field of fieldMap.values()) {
2422
+ const isOwnerColumn = ownerColumns?.has(field.name) ?? false;
2318
2423
  const targetKey = (field.relation.entityId && this.storeNameById.get(field.relation.entityId)) ?? this.resolveStoreKey(field.relation.entity);
2319
2424
  const targetStore = this.stores.get(targetKey);
2320
2425
  if (!targetStore || targetStore.size === 0) continue;
@@ -2322,7 +2427,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2322
2427
  const cardinality = field.relation.cardinality ?? "many";
2323
2428
  if (sameStore && (cardinality === "one" || cardinality === "many-to-one")) {
2324
2429
  rows.forEach((row, i) => {
2325
- if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId) {
2430
+ if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId || stampedCellKeys.has(stampedCellKey(storeKey, row["id"], field.name))) {
2326
2431
  return;
2327
2432
  }
2328
2433
  row[field.name] = i === 0 ? "" : rows[Math.floor((i - 1) / 2)]["id"];
@@ -2330,15 +2435,21 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2330
2435
  continue;
2331
2436
  }
2332
2437
  for (const row of rows) {
2333
- if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId) {
2438
+ const selfId = row["id"];
2439
+ if (this.config.ownerId !== void 0 && row[field.name] === this.config.ownerId || stampedCellKeys.has(stampedCellKey(storeKey, selfId, field.name))) {
2334
2440
  continue;
2335
2441
  }
2336
- const selfId = row["id"];
2337
- const eligible = [];
2442
+ let eligible = [];
2338
2443
  for (const id of targetStore.keys()) {
2339
2444
  if (sameStore && id === selfId) continue;
2340
2445
  eligible.push(id);
2341
2446
  }
2447
+ if (isOwnerColumn && this.ownerCandidateGate) {
2448
+ const gate = this.ownerCandidateGate;
2449
+ eligible = eligible.filter(
2450
+ (id) => gate(storeKey, { ...row, [field.name]: id }, targetStore.get(id))
2451
+ );
2452
+ }
2342
2453
  if (eligible.length === 0) continue;
2343
2454
  if (cardinality === "one" || cardinality === "many-to-one") {
2344
2455
  row[field.name] = randomArrayElement(eligible);
@@ -2416,13 +2527,22 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2416
2527
  if (this.config.debug) {
2417
2528
  mockLog.debug("seeding", { count, entity: entityName });
2418
2529
  }
2419
- const ownerCols = (this.config.ownerFields ?? []).map((pair) => pair.split(".")).filter(([ent]) => ent?.toLowerCase() === normalized).map(([, field]) => field).filter((field) => Boolean(field));
2530
+ const ownerCols = this.parsedOwnerFields().filter(({ entity }) => entity.toLowerCase() === normalized).map(({ field }) => field);
2420
2531
  const ownerId = this.config.ownerId;
2421
2532
  const generated = [];
2533
+ let fallbackStamps = 0;
2422
2534
  for (let i = 0; i < count; i++) {
2423
2535
  const item = this.generateMockItem(entityName, fields, i + 1, persistence);
2424
2536
  if (ownerId && ownerCols.length > 0 && i % 2 === 0) {
2425
2537
  for (const col of ownerCols) {
2538
+ if (this.ownerGate && !this.ownerGate(storeKey, { ...item, [col]: ownerId })) {
2539
+ const fallbackId = this.firstEligibleOwner(storeKey, col, item);
2540
+ if (fallbackId === void 0) continue;
2541
+ item[col] = fallbackId;
2542
+ this.ownerStampedCells.push({ entity: storeKey, id: item.id, column: col });
2543
+ fallbackStamps++;
2544
+ continue;
2545
+ }
2426
2546
  item[col] = ownerId;
2427
2547
  this.ownerStampedCells.push({ entity: storeKey, id: item.id, column: col });
2428
2548
  }
@@ -2433,7 +2553,23 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
2433
2553
  updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : ""
2434
2554
  });
2435
2555
  }
2436
- mockLog.debug("mock:seed", () => ({ entityName, count, idsAndTimestamps: JSON.stringify(generated) }));
2556
+ mockLog.debug("mock:seed", () => ({
2557
+ entityName,
2558
+ count,
2559
+ idsAndTimestamps: JSON.stringify(generated),
2560
+ fallbackStamps
2561
+ }));
2562
+ }
2563
+ /** Parsed `config.ownerFields` pairs — every declared "Entity.field" owner
2564
+ * column, split once so `seed()` and `linkRelationFields()` don't each
2565
+ * re-parse the same strings. */
2566
+ parsedOwnerFields() {
2567
+ const out = [];
2568
+ for (const pair of this.config.ownerFields ?? []) {
2569
+ const [entity, field] = pair.split(".");
2570
+ if (entity && field) out.push({ entity, field });
2571
+ }
2572
+ return out;
2437
2573
  }
2438
2574
  /**
2439
2575
  * Generate a single mock item. Field values come from the canonical policy in
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, S as ServiceCallContext, T as TraitDefinition } from './types-BaD_ox7e.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-BaD_ox7e.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-nqwGldMC.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-nqwGldMC.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-DOJDoqXu.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-DOJDoqXu.js';
5
5
  import { EvaluationContext, SExpressionEvaluator } from '@almadar/evaluator';
6
6
  export { EvaluationContext, createMinimalContext } from '@almadar/evaluator';
7
7
  import { RenderBindingMarker, SExpr, RuntimeValue, TraitConfigObject, EventPayload, PatternConfig, EntityId, EntityField, EntityRow, EntityPersistence, ServiceParams, EntityAccessPolicies, PayloadField, OrbitalDefinition, OrbitalSchema } from '@almadar/core';
@@ -663,8 +663,55 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
663
663
  * No-op when there is no new id, or it matches the current one — an
664
664
  * anonymous switch (`newOwnerId === undefined`) leaves existing stamps as
665
665
  * they are rather than clearing a non-nullable owner column.
666
+ *
667
+ * Both this re-labeling AND `seed()`'s original stamping consult the
668
+ * `ownerGate` hook (see `setOwnerGate`) — a re-register reseeds identical
669
+ * rows, so gating only the restamp path leaks the moment a client
670
+ * connects and the deterministic reseed stamps with the updated ownerId.
666
671
  */
667
672
  restampOwner(newOwnerId: string | undefined): void;
673
+ /**
674
+ * The host's answer to "may the current viewer OWN this row?" — evaluated
675
+ * against the candidate row with the owner column already re-pointed,
676
+ * `storeKey` being the collection key the stores are keyed by. The runtime
677
+ * installs the registered schema's `@create` directive here (the single
678
+ * authority on who authors; no role heuristics), so a read-only persona
679
+ * never inherits authorship of drafts from the demo stamper — neither on a
680
+ * persona switch (`restampOwner`) nor on a deterministic reseed (`seed`).
681
+ * Absent hook = stamp unconditionally (pre-role-model behavior).
682
+ */
683
+ private ownerGate?;
684
+ setOwnerGate(gate: ((storeKey: string, candidateRow: EntityRow) => boolean) | undefined): void;
685
+ /**
686
+ * The host's answer to "may THIS identity row own this candidate row?" —
687
+ * evaluated per candidate rather than against the fixed default viewer, so
688
+ * `linkRelationFields` and the eligible-owner fallback (see
689
+ * `firstEligibleOwner`) can pick a policy-valid owner from the whole
690
+ * identity roster instead of only ever checking the current default user.
691
+ * Absent hook = no candidate filtering (pre-role-model behavior).
692
+ */
693
+ private ownerCandidateGate?;
694
+ setOwnerCandidateGate(gate: ((storeKey: string, candidateRow: EntityRow, candidateIdentityRow: EntityRow) => boolean) | undefined): void;
695
+ /**
696
+ * The store an owner column's relation field targets, resolved the same
697
+ * way `linkRelationFields` resolves any relation target (id sibling wins
698
+ * over the `entity` name string) — but keyed by STORE, mirroring that
699
+ * method's first-declarer-per-collection rule, so `seed()` and
700
+ * `restampOwner()` can find it before a `linkRelationFields` pass has run
701
+ * this registration. Undefined when no registered schema on `storeKey`
702
+ * declares `column` as a relation field.
703
+ */
704
+ private resolveOwnerColumnTargetStore;
705
+ /**
706
+ * The first identity-store row (seed order) whose persona is eligible, per
707
+ * the installed `ownerCandidateGate`, to own `row`'s `column` in `storeKey`
708
+ * — the deterministic replacement for "skip the stamp" when the CURRENT
709
+ * viewer fails the `@create` policy (e.g. a reader-default persona against
710
+ * an author|reviewer-only policy). Undefined when no gate is installed, the
711
+ * column isn't a resolvable relation, or no identity row is eligible — the
712
+ * caller keeps today's skip behavior in that case.
713
+ */
714
+ private firstEligibleOwner;
668
715
  /** Re-anchor the PRNG to the configured seed. Called before every
669
716
  * re-seed loop so identical reseed sequences produce identical rows
670
717
  * (timestamps + generated fields). Without this, the first
@@ -722,6 +769,10 @@ declare class MockPersistenceAdapter implements PersistenceAdapter {
722
769
  * Seed an entity with mock data.
723
770
  */
724
771
  seed(entityName: string, fields: EntityField[], count: number, persistence?: EntityPersistence): void;
772
+ /** Parsed `config.ownerFields` pairs — every declared "Entity.field" owner
773
+ * column, split once so `seed()` and `linkRelationFields()` don't each
774
+ * re-parse the same strings. */
775
+ private parsedOwnerFields;
725
776
  /**
726
777
  * Generate a single mock item. Field values come from the canonical policy in
727
778
  * `@almadar/core/mock`; this method owns only the id and timestamp stamping.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EffectExecutor } from './chunk-C7SIRVGS.js';
2
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes } from './chunk-C7SIRVGS.js';
1
+ import { EffectExecutor } from './chunk-IFSFX43J.js';
2
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeCallSiteConfigToValues, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes } from './chunk-IFSFX43J.js';
3
3
  export { cronMatches, cronMinuteKey, isValidCronExpression, parseCron, parseCronField } from './chunk-OU3ITB5S.js';
4
4
  import { createContextFromBindings, applyRowAccess, checkMutationAccess, accessDeniedMessage } from './chunk-ZJ62H3ES.js';
5
5
  export { CALLSITE_PAYLOAD_PREFIX, applyRowAccess, checkMutationAccess, containsBindings, createContextFromBindings, createMinimalContext, deferEntityBindings, extractBindings, interpolateProps, interpolateValue, resolveCallSitePayloadCaptures } from './chunk-ZJ62H3ES.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.63.0",
3
+ "version": "6.64.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,11 +57,11 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@almadar/core": "^10.73.0",
60
+ "@almadar/core": "^10.75.0",
61
61
  "@almadar/evaluator": "^2.42.0",
62
62
  "@almadar/logger": "^1.11.0",
63
63
  "@almadar/server": "^2.38.0",
64
- "@almadar/std": "^16.192.0"
64
+ "@almadar/std": "^16.194.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "express": "^5.0.0"