@almadar/core 10.86.0 → 10.87.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.
Files changed (41) hide show
  1. package/dist/builders.d.ts +5 -4
  2. package/dist/builders.js +68 -29
  3. package/dist/builders.js.map +1 -1
  4. package/dist/{effect-BgDiw_bG.d.ts → effect-DMA97JxX.d.ts} +22 -6
  5. package/dist/{entityAccess-DK5S_2cT.d.ts → entityAccess-DNDEF75i.d.ts} +2 -2
  6. package/dist/{expression-Fk8bQWef.d.ts → expression-WfTp2arD.d.ts} +2 -65
  7. package/dist/factory/index.d.ts +6 -5
  8. package/dist/factory/index.js +1194 -401
  9. package/dist/factory/index.js.map +1 -1
  10. package/dist/factory-runtime/index.d.ts +35 -5
  11. package/dist/factory-runtime/index.js +127 -52
  12. package/dist/factory-runtime/index.js.map +1 -1
  13. package/dist/i18n/index.d.ts +111 -1
  14. package/dist/i18n/index.js +500 -19
  15. package/dist/i18n/index.js.map +1 -1
  16. package/dist/{index-ChYsqVJj.d.ts → index-xZP_Ajx3.d.ts} +18 -5
  17. package/dist/index.d.ts +82 -14
  18. package/dist/index.js +1876 -333
  19. package/dist/index.js.map +1 -1
  20. package/dist/json-D8gmyK3l.d.ts +65 -0
  21. package/dist/mock/index.d.ts +129 -12
  22. package/dist/mock/index.js +192 -61
  23. package/dist/mock/index.js.map +1 -1
  24. package/dist/patterns/component-mapping.json +1 -1
  25. package/dist/patterns/event-contracts.json +1 -1
  26. package/dist/patterns/index.d.ts +2525 -609
  27. package/dist/patterns/index.js +1238 -384
  28. package/dist/patterns/index.js.map +1 -1
  29. package/dist/patterns/integrators-registry.json +107 -23
  30. package/dist/patterns/patterns-registry.json +1170 -400
  31. package/dist/patterns/registry.json +1170 -400
  32. package/dist/patterns/services-registry.json +170 -35
  33. package/dist/{schema-BhfQb1oe.d.ts → schema-_SPrm5Ic.d.ts} +46381 -21335
  34. package/dist/state-machine/index.d.ts +2 -1
  35. package/dist/trait-9tfq2tQb.d.ts +10554 -0
  36. package/dist/types/index.d.ts +7 -6
  37. package/dist/types/index.js +132 -43
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/{types-CCAmdxcH.d.ts → types-CjRjhiaO.d.ts} +19 -3
  40. package/package.json +2 -2
  41. package/dist/trait-pavNlGqm.d.ts +0 -5385
@@ -0,0 +1,65 @@
1
+ /**
2
+ * JSON primitives — the universal "data crossed a boundary" type.
3
+ *
4
+ * Every value that arrives over the wire from an LLM (tool-call args),
5
+ * from disk (workspace files), or from an HTTP body before
6
+ * domain-specific validation is a `JsonValue`. Narrow with a typed
7
+ * predicate (`is`-guard) at the boundary; don't widen back to `unknown`.
8
+ *
9
+ * `JsonObject` and `ToolArgs` are aliases for the common
10
+ * `Record<string, JsonValue>` shape. `ToolArgs` is the name the
11
+ * agent surface uses for LLM-emitted tool-call arguments; `JsonObject`
12
+ * is the general-purpose alias. They are the same type — the alias
13
+ * exists so call sites read at the right semantic level.
14
+ *
15
+ * Why not `Record<string, unknown>`? Two reasons. (1) `unknown` widens
16
+ * back to anything, which defeats the purpose of typing the boundary.
17
+ * (2) The `@almadar/eslint-plugin/no-record-string-unknown` rule blocks
18
+ * the wider form — `JsonValue`-based records are the typed answer.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+
23
+ /**
24
+ * Recursive JSON value union — every shape JSON can carry.
25
+ */
26
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
27
+ [key: string]: JsonValue;
28
+ };
29
+ /**
30
+ * JSON object — keyed string→JsonValue. The wire form of arbitrary
31
+ * structured data. Replaces `Record<string, unknown>` at typed
32
+ * boundaries (LLM emits, file reads, HTTP bodies).
33
+ */
34
+ type JsonObject = {
35
+ [key: string]: JsonValue;
36
+ };
37
+ /**
38
+ * LLM tool-call arguments — same shape as `JsonObject`, named for the
39
+ * agent-surface call site. Each tool's `execute(args: ToolArgs)`
40
+ * receives this and narrows via an `is`-guard predicate before any
41
+ * field access.
42
+ */
43
+ type ToolArgs = JsonObject;
44
+ /**
45
+ * Universal type-guard input — every runtime value shape a predicate can
46
+ * be handed, enumerated instead of `unknown` (the repo bans `unknown`
47
+ * even at guard boundaries). Primitives cover every `typeof` result;
48
+ * `object` covers arrays, records, class instances, and functions.
49
+ */
50
+ type RuntimeValue = string | number | bigint | boolean | symbol | Date | null | undefined | object;
51
+ /**
52
+ * Type guard: is the given value a JSON primitive (non-array,
53
+ * non-object)? Used by walkers that decide whether to recurse.
54
+ */
55
+ declare function isJsonPrimitive(value: JsonValue): value is string | number | boolean | null;
56
+ /**
57
+ * Type guard: is the given value a JSON object (non-array, non-null)?
58
+ */
59
+ declare function isJsonObject(value: JsonValue): value is JsonObject;
60
+ /**
61
+ * Type guard: is the given value a JSON array?
62
+ */
63
+ declare function isJsonArray(value: JsonValue): value is JsonValue[];
64
+
65
+ export { type JsonValue as J, type RuntimeValue as R, type ToolArgs as T, type JsonObject as a, isJsonObject as b, isJsonPrimitive as c, isJsonArray as i };
@@ -1,9 +1,10 @@
1
- import { O as OrbitalSchema } from '../schema-BhfQb1oe.js';
2
- export { E as EntityAccessPolicies, e as entityAccessPolicies, a as entityAccessPoliciesByStoreKey, b as entityAccessTable } from '../entityAccess-DK5S_2cT.js';
3
- import { a as EntityPersistence, E as EntityField, F as FieldValue, f as EntityRow } from '../effect-BgDiw_bG.js';
4
- import '../trait-pavNlGqm.js';
5
- import '../expression-Fk8bQWef.js';
1
+ import { f as OrbitalEntity, a as EntityPersistence, E as EntityField, F as FieldValue, g as EntityRow } from '../effect-DMA97JxX.js';
2
+ import { a as OrbitalDefinition, O as OrbitalSchema } from '../schema-_SPrm5Ic.js';
3
+ import { S as SExpr } from '../expression-WfTp2arD.js';
4
+ export { E as EntityAccessPolicies, e as entityAccessPolicies, a as entityAccessPoliciesByStoreKey, b as entityAccessTable } from '../entityAccess-DNDEF75i.js';
6
5
  import 'zod';
6
+ import '../json-D8gmyK3l.js';
7
+ import '../trait-9tfq2tQb.js';
7
8
 
8
9
  /**
9
10
  * Lightweight seeded pseudo-random generator for mock data.
@@ -78,7 +79,8 @@ declare function randomPhone(): string;
78
79
  */
79
80
 
80
81
  /**
81
- * The name of the schema's `[identity]` entity, if it declares one.
82
+ * The `[identity]` entities that decide what `@user` resolves to, primaries
83
+ * first then auxiliaries.
82
84
  *
83
85
  * A behavior declares its own roster so it runs standalone. Composing it never
84
86
  * imports that orbital, but a trait bound to one of its siblings drags the
@@ -88,16 +90,125 @@ declare function randomPhone(): string;
88
90
  * wraps one behavior inherit that behavior's roster.
89
91
  *
90
92
  * Compiled-path twin: `identity_entities` in
93
+ * `orbital-compiler/src/phases/validation/user_identity.rs` (L94-102).
94
+ */
95
+ declare function identityEntitiesOf(orbitals: readonly OrbitalDefinition[]): OrbitalEntity[];
96
+ /**
97
+ * The name of the schema's `[identity]` entity, if it declares one.
98
+ *
99
+ * Compiled-path twin: `identity_entities` in
91
100
  * `orbital-compiler/src/phases/validation/user_identity.rs`.
92
101
  */
93
102
  declare function identityEntityName(schema: OrbitalSchema): string | undefined;
94
103
  /**
95
- * Owner columns as `Entity.field` pairs every relation field pointing at an
96
- * `[identity]`-tagged entity, shadowed imported rosters included. Empty when the
104
+ * Every `[identity]`-tagged name, shadowed copies included.
105
+ *
106
+ * Owner-column derivation asks whether a relation targets *a* roster, not *the*
107
+ * one: `Timesheet.employeeId : Employee` is an owner column whether or not
108
+ * `Employee` won the `@user` binding. Dropping the shadowed names here would
109
+ * leave an imported behavior's rows unscoped at runtime while the compiled path
110
+ * treats the very same column as an owner column.
111
+ *
112
+ * Compiled-path twin: `identity_entity_names` in
113
+ * `orbital-compiler/src/phases/validation/user_identity.rs`.
114
+ */
115
+ declare function identityEntityNames(schema: OrbitalSchema): string[];
116
+ /**
117
+ * The declared vocabulary for an identity field, following `items` for the
118
+ * array/map form. `roles : ["a" | "b"]` keeps the union on the ELEMENT, so a
119
+ * multi-valued role field is read through `items` — one level, not recursive.
120
+ *
121
+ * Compiled-path twin: `vocabulary` in
122
+ * `orbital-compiler/src/phases/validation/user_identity.rs` (L125-134).
123
+ */
124
+ declare function roleVocabularyOf(entity: OrbitalEntity, field?: string): readonly string[] | undefined;
125
+ /**
126
+ * Every column `expr` compares to `@user.id` via direct equality — the
127
+ * general form of "self-identity": `@entity.<field> == @user.id` (either
128
+ * operand order, either spelling — see `fieldRefName`) names `field` as an
129
+ * owner column, whether `field` is the row's OWN `id` (distinct from a
130
+ * relation column like `@entity.managerId == @user.id`) or any other
131
+ * declared field (the shape `ORB_S_OWNER_FIELD_NOT_IDENTITY_TYPED` flags as
132
+ * an authoring error when it is neither — codegen still needs to seed it).
133
+ * Recurses through `and`/`or`/`not`/any other combinator —
134
+ * `std-time-tracking`'s `Employee [identity]` declares
135
+ * `@read (or (= @user.role "approver") (= (object/get @entity id) @user.id))`,
136
+ * and the self-access arm counts even though it isn't the whole policy.
137
+ * Rust twin: `owner_columns_from_policy` in `orbital-core/src/runtime/seed.rs`.
138
+ */
139
+ declare function ownerColumnsFromPolicy(expr: SExpr, out?: string[]): string[];
140
+ /**
141
+ * Owner columns as `Entity.field` pairs — every NON-INTRINSIC relation field
142
+ * pointing at an `[identity]`-tagged entity, shadowed imported rosters
143
+ * included, PLUS `Entity.id` for an `[identity]` entity whose own declared
144
+ * access policy compares its row directly to the viewer (self-identity), PLUS
145
+ * any other column an entity's own declared `@read`/`@update`/`@delete`
146
+ * compares to `@user.id` (see `ownerColumnsFromPolicy`). Empty when the
97
147
  * program declares no identity, which keeps every unmigrated app behaving
98
148
  * exactly as before.
149
+ *
150
+ * An `intrinsic: true` relation field is excluded even when it targets the
151
+ * identity entity: intrinsic marks framework plumbing the owning trait
152
+ * computes itself (`EntityField.intrinsic` doc, `types/field.ts`), never
153
+ * domain ownership — a generic atom's self-relation (`ModalRecord.seedRow :
154
+ * ModalRecord`, record-detail/modal edit-seed plumbing) gets its relation
155
+ * target rewritten onto whatever entity the trait binds, so a self-identity
156
+ * entity like `std-time-tracking`'s `Employee [identity]` ends up with an
157
+ * `Employee.seedRow : Employee` self-relation that is structurally
158
+ * indistinguishable from a real owner column by type/target alone. Stamping
159
+ * it with the viewer id (as an owner column would be) creates a row that
160
+ * references itself, which trips the entity's own `onDelete: restrict`.
99
161
  */
100
162
  declare function ownerFieldsFromSchema(schema: OrbitalSchema): string[];
163
+ /**
164
+ * Every literal an expression compares against `@user.<field>` (any field),
165
+ * keyed by field name — the collecting counterpart of `orbital-compiler`'s
166
+ * `validation::user_identity::check_comparison` (B4-R4; the two must stay
167
+ * shape-identical, each cites the other): an equality node (`EQUALITY_OPS`)
168
+ * or an `array/includes` node, walked transparently through `and`/`or`/`not`
169
+ * and any other combinator (the Rust check runs on EVERY array node
170
+ * regardless of what wraps it, so this does too).
171
+ *
172
+ * `array/includes` is directional, but the sigil can sit on EITHER side:
173
+ * `(array/includes @user.roles "ghost")` checks a literal against a
174
+ * multi-valued field's own vocabulary (field is the haystack, position 0);
175
+ * the Stage-B orbital-import role rewrite (Rust `inline/rewrite.rs`
176
+ * `rewrite_role_literals`, which `check_comparison` mirrors) instead
177
+ * produces `(array/includes ["owner","project_manager"] @user.role)` — a
178
+ * literal ARRAY haystack with the sigil as the needle, position 1 (found
179
+ * live: Project Friday's imported `Timesheet.@delete` after `roles {
180
+ * approver: [owner, project_manager] }`). So the sigil is located BY
181
+ * CONTENT, never position: check position 0 first, fall back to position 1.
182
+ * The OTHER side is then flattened to a literal list — a scalar (equality,
183
+ * or an `array/includes` whose haystack is the sigil's own multi-valued
184
+ * field) or, for the role-rewrite shape, every string element of a literal
185
+ * array.
186
+ *
187
+ * A collected literal is a CANDIDATE a viewer might satisfy the expression
188
+ * with, never a proof: a policy that ANDs the field check with an unrelated
189
+ * condition can still reject a viewer carrying it. Callers that need
190
+ * certainty re-check with the real evaluator (`checkMutationAccess` /
191
+ * `evaluate`), the way any other synthesized dispatch is confirmed.
192
+ */
193
+ declare function collectUserFieldLiterals(expr: SExpr, out?: Map<string, Set<string>>): Map<string, Set<string>>;
194
+ /**
195
+ * A `@user.<field>` value that satisfies `policy`, chosen from the identity
196
+ * entity's own declared vocabulary — never a guess: only a literal BOTH
197
+ * (a) compared against `field` in an equality/`array/includes` node
198
+ * somewhere in `policy` (see {@link collectUserFieldLiterals}) AND (b) a
199
+ * declared member of `identity`'s `field` vocabulary (see
200
+ * {@link roleVocabularyOf}) is a candidate. The pick is deterministic — the
201
+ * first vocabulary value, in declaration order, that is also a candidate —
202
+ * so two callers asking about the same policy always agree.
203
+ *
204
+ * `undefined` when `policy` is absent/`null` (no restriction — nothing to
205
+ * satisfy), `field` carries no declared vocabulary on `identity`, or no
206
+ * declared value appears anywhere in the policy. The last case is a genuine
207
+ * finding for the CALLER to report, not a probe defect to paper over: the
208
+ * policy structurally cannot be satisfied by any roster member, so forcing a
209
+ * viewer past it would misreport an unreachable transition as reachable.
210
+ */
211
+ declare function roleSatisfyingPolicy(policy: SExpr | undefined, identity: OrbitalEntity, field?: string): string | undefined;
101
212
 
102
213
  /**
103
214
  * The one owner of mock-seed value synthesis.
@@ -152,13 +263,19 @@ declare function sampleRowCount(entity: SampleEntity, requested: number): number
152
263
  /**
153
264
  * One sample value for one field. `undefined` means OMIT the key.
154
265
  *
155
- * Order matters — see the three gates in the plan. Gates 1 and 2 read declared
156
- * schema properties (`persistence`, `intrinsic`), never field names.
266
+ * Order matters — see the four gates in the plan. Gates 1, 2 and 3 read
267
+ * declared schema properties (`persistence`, `intrinsic`, `required`,
268
+ * `default`, `type`), never field names.
157
269
  */
158
270
  declare function sampleFieldValue(field: EntityField, ctx: SampleContext): FieldValue | undefined;
159
- /** One row. Reserved keys are left to the caller. */
271
+ /**
272
+ * One row. Reserved keys are left to the caller. Under `strategy: 'seeded'`,
273
+ * every other row (see {@link omitsUndefaultedOptionalFields}) omits its
274
+ * undefaulted optional fields — required/defaulted/relation fields are
275
+ * always present.
276
+ */
160
277
  declare function sampleRow(entity: SampleEntity, ctx: Omit<SampleContext, 'entityName' | 'depth'>): EntityRow;
161
278
  /** `count` rows, 1-based, honoring the runtime-singleton gate. */
162
279
  declare function sampleRows(entity: SampleEntity, count: number, strategy: SampleStrategy): EntityRow[];
163
280
 
164
- export { IMAGE_FIELD_NAMES, type SampleContext, type SampleEntity, type SampleStrategy, identityEntityName, isDeclaredDefaultHonored, ownerFieldsFromSchema, randomAnytimeDate, randomArrayElement, randomBoolean, randomColor, randomEmail, randomFloat, randomInt, randomPassword, randomPastDate, randomPhone, randomRecentDate, randomSentence, randomStraddlingDate, randomUrl, randomUuid, randomWords, sampleFieldValue, sampleImageUrl, sampleRow, sampleRowCount, sampleRows, seedRandom, shuffleArray };
281
+ export { IMAGE_FIELD_NAMES, type SampleContext, type SampleEntity, type SampleStrategy, collectUserFieldLiterals, identityEntitiesOf, identityEntityName, identityEntityNames, isDeclaredDefaultHonored, ownerColumnsFromPolicy, ownerFieldsFromSchema, randomAnytimeDate, randomArrayElement, randomBoolean, randomColor, randomEmail, randomFloat, randomInt, randomPassword, randomPastDate, randomPhone, randomRecentDate, randomSentence, randomStraddlingDate, randomUrl, randomUuid, randomWords, roleSatisfyingPolicy, roleVocabularyOf, sampleFieldValue, sampleImageUrl, sampleRow, sampleRowCount, sampleRows, seedRandom, shuffleArray };
@@ -166,7 +166,7 @@ function randomPhone() {
166
166
  return `+1 (${area}) ${prefix}-${line}`;
167
167
  }
168
168
 
169
- // src/mock/identityOwners.ts
169
+ // src/access/entityAccess.ts
170
170
  function inlineEntities(schema) {
171
171
  const out = [];
172
172
  for (const orbital of schema.orbitals ?? []) {
@@ -179,15 +179,74 @@ function inlineEntities(schema) {
179
179
  }
180
180
  return out;
181
181
  }
182
- function identityEntitiesTagged(schema) {
182
+ function isDeclared(p) {
183
+ return p.read !== void 0 || p.create !== void 0 || p.update !== void 0 || p.delete !== void 0;
184
+ }
185
+ function entityAccessTable(schema) {
186
+ const table = /* @__PURE__ */ new Map();
187
+ const collectionOf = /* @__PURE__ */ new Map();
188
+ for (const def of inlineEntities(schema)) {
189
+ table.set(def.name, {
190
+ read: def.read_policy,
191
+ create: def.create_policy,
192
+ update: def.update_policy,
193
+ delete: def.delete_policy
194
+ });
195
+ if (def.collection) collectionOf.set(def.name, def.collection);
196
+ }
197
+ const byCollection = /* @__PURE__ */ new Map();
198
+ for (const [name, policies] of table) {
199
+ const collection = collectionOf.get(name);
200
+ if (collection && isDeclared(policies) && !byCollection.has(collection)) {
201
+ byCollection.set(collection, policies);
202
+ }
203
+ }
204
+ for (const [name, policies] of table) {
205
+ if (isDeclared(policies)) continue;
206
+ const collection = collectionOf.get(name);
207
+ const inherited = collection ? byCollection.get(collection) : void 0;
208
+ if (inherited) table.set(name, { ...inherited });
209
+ }
210
+ return table;
211
+ }
212
+ function entityAccessPolicies(schema, entityName) {
213
+ return entityAccessTable(schema).get(entityName);
214
+ }
215
+ function entityAccessPoliciesByStoreKey(schema) {
216
+ const collectionOf = /* @__PURE__ */ new Map();
217
+ for (const def of inlineEntities(schema)) {
218
+ if (def.collection) collectionOf.set(def.name, def.collection);
219
+ }
220
+ const byKey = /* @__PURE__ */ new Map();
221
+ for (const [name, policies] of entityAccessTable(schema)) {
222
+ const key = collectionOf.get(name) ?? name.toLowerCase();
223
+ if (!byKey.has(key) && isDeclared(policies)) byKey.set(key, policies);
224
+ }
225
+ return byKey;
226
+ }
227
+
228
+ // src/mock/identityOwners.ts
229
+ function inlineEntities2(schema) {
183
230
  const out = [];
184
231
  for (const orbital of schema.orbitals ?? []) {
232
+ const refs = [orbital.entity, ...orbital.auxiliaryEntities ?? []];
233
+ for (const ref of refs) {
234
+ if (typeof ref === "object" && ref !== null && "fields" in ref) {
235
+ out.push(ref);
236
+ }
237
+ }
238
+ }
239
+ return out;
240
+ }
241
+ function identityEntitiesTaggedOf(orbitals) {
242
+ const out = [];
243
+ for (const orbital of orbitals) {
185
244
  const ref = orbital.entity;
186
245
  if (typeof ref === "object" && ref !== null && "fields" in ref && ref.identity === true) {
187
246
  out.push({ def: ref, primary: true });
188
247
  }
189
248
  }
190
- for (const orbital of schema.orbitals ?? []) {
249
+ for (const orbital of orbitals) {
191
250
  for (const ref of orbital.auxiliaryEntities ?? []) {
192
251
  if (typeof ref === "object" && ref !== null && "fields" in ref && ref.identity === true) {
193
252
  out.push({ def: ref, primary: false });
@@ -196,26 +255,74 @@ function identityEntitiesTagged(schema) {
196
255
  }
197
256
  return out;
198
257
  }
258
+ function identityEntitiesOf(orbitals) {
259
+ const tagged = identityEntitiesTaggedOf(orbitals);
260
+ const hasPrimary = tagged.some((e) => e.primary);
261
+ return tagged.filter((e) => e.primary || !hasPrimary).map((e) => e.def);
262
+ }
199
263
  function identityEntityName(schema) {
200
- const tagged = identityEntitiesTagged(schema);
201
- return (tagged.find((e) => e.primary) ?? tagged[0])?.def.name;
264
+ return identityEntitiesOf(schema.orbitals ?? [])[0]?.name;
202
265
  }
203
266
  function identityEntityNames(schema) {
204
267
  const out = [];
205
- for (const { def } of identityEntitiesTagged(schema)) {
268
+ for (const { def } of identityEntitiesTaggedOf(schema.orbitals ?? [])) {
206
269
  if (!out.includes(def.name)) out.push(def.name);
207
270
  }
208
271
  return out;
209
272
  }
273
+ function roleVocabularyOf(entity, field = "role") {
274
+ const def = entity.fields.find((f) => f.name === field);
275
+ if (!def) return void 0;
276
+ return fieldVocabulary(def);
277
+ }
278
+ function fieldVocabulary(field) {
279
+ if ("values" in field && field.values && field.values.length > 0) {
280
+ return field.values;
281
+ }
282
+ if ("items" in field && field.items && "values" in field.items && field.items.values && field.items.values.length > 0) {
283
+ return field.items.values;
284
+ }
285
+ return void 0;
286
+ }
287
+ function fieldRefName(x, binding) {
288
+ if (typeof x === "string" && x.startsWith(`${binding}.`)) return x.slice(binding.length + 1);
289
+ if (Array.isArray(x) && x.length === 3 && x[0] === "object/get" && x[1] === binding && typeof x[2] === "string") {
290
+ return x[2];
291
+ }
292
+ return void 0;
293
+ }
294
+ function isFieldRef(x, binding, field) {
295
+ return fieldRefName(x, binding) === field;
296
+ }
297
+ var OWNER_EQ_OPS = ["=", "=="];
298
+ function ownerColumnsFromPolicy(expr, out = []) {
299
+ if (Array.isArray(expr)) {
300
+ if (expr.length === 3) {
301
+ const [op, a, b] = expr;
302
+ if (typeof op === "string" && OWNER_EQ_OPS.includes(op)) {
303
+ const field = isFieldRef(b, "@user", "id") ? fieldRefName(a, "@entity") : isFieldRef(a, "@user", "id") ? fieldRefName(b, "@entity") : void 0;
304
+ if (field !== void 0 && !out.includes(field)) out.push(field);
305
+ }
306
+ }
307
+ for (const item of expr) ownerColumnsFromPolicy(item, out);
308
+ }
309
+ return out;
310
+ }
311
+ function policiesSelfIdentify(policies) {
312
+ if (policies === void 0) return false;
313
+ return [policies.read, policies.create, policies.update, policies.delete].some(
314
+ (expr) => expr !== void 0 && ownerColumnsFromPolicy(expr).includes("id")
315
+ );
316
+ }
210
317
  function ownerFieldsFromSchema(schema) {
211
318
  const identities = identityEntityNames(schema);
212
319
  if (identities.length === 0) return [];
213
320
  const out = [];
214
- const defs = inlineEntities(schema);
321
+ const defs = inlineEntities2(schema);
215
322
  const declaredByCollection = /* @__PURE__ */ new Map();
216
323
  for (const def of defs) {
217
324
  for (const field of def.fields ?? []) {
218
- if (field.name && field.type === "relation" && identities.includes(field.relation.entity)) {
325
+ if (field.name && field.type === "relation" && !field.intrinsic && identities.includes(field.relation.entity)) {
219
326
  out.push(`${def.name}.${field.name}`);
220
327
  if (def.collection) {
221
328
  const cols = declaredByCollection.get(def.collection) ?? [];
@@ -225,6 +332,23 @@ function ownerFieldsFromSchema(schema) {
225
332
  }
226
333
  }
227
334
  }
335
+ for (const name of identities) {
336
+ if (policiesSelfIdentify(entityAccessPolicies(schema, name))) {
337
+ const key = `${name}.id`;
338
+ if (!out.includes(key)) out.push(key);
339
+ }
340
+ }
341
+ for (const def of defs) {
342
+ const policies = entityAccessPolicies(schema, def.name);
343
+ if (policies === void 0) continue;
344
+ for (const policy of [policies.read, policies.update, policies.delete]) {
345
+ if (policy === void 0) continue;
346
+ for (const field of ownerColumnsFromPolicy(policy)) {
347
+ const key = `${def.name}.${field}`;
348
+ if (!out.includes(key)) out.push(key);
349
+ }
350
+ }
351
+ }
228
352
  for (const def of defs) {
229
353
  const cols = def.collection ? declaredByCollection.get(def.collection) : void 0;
230
354
  if (!cols) continue;
@@ -236,64 +360,62 @@ function ownerFieldsFromSchema(schema) {
236
360
  }
237
361
  return out;
238
362
  }
239
-
240
- // src/access/entityAccess.ts
241
- function inlineEntities2(schema) {
242
- const out = [];
243
- for (const orbital of schema.orbitals ?? []) {
244
- const refs = [orbital.entity, ...orbital.auxiliaryEntities ?? []];
245
- for (const ref of refs) {
246
- if (typeof ref === "object" && ref !== null && "fields" in ref) {
247
- out.push(ref);
248
- }
249
- }
250
- }
251
- return out;
363
+ var EQUALITY_OPS = ["=", "==", "!=", "!==", "eq", "neq"];
364
+ function isUserFieldSigil(x) {
365
+ return typeof x === "string" && fieldRefName(x, "@user") !== void 0;
252
366
  }
253
- function isDeclared(p) {
254
- return p.read !== void 0 || p.create !== void 0 || p.update !== void 0 || p.delete !== void 0;
367
+ function literalHaystackMembers(items) {
368
+ const isListCall = items[0] === "list";
369
+ const members = isListCall ? items.slice(1) : items;
370
+ return members.every((v) => typeof v === "string") ? [...members] : void 0;
255
371
  }
256
- function entityAccessTable(schema) {
257
- const table = /* @__PURE__ */ new Map();
258
- const collectionOf = /* @__PURE__ */ new Map();
259
- for (const def of inlineEntities2(schema)) {
260
- table.set(def.name, {
261
- read: def.read_policy,
262
- create: def.create_policy,
263
- update: def.update_policy,
264
- delete: def.delete_policy
265
- });
266
- if (def.collection) collectionOf.set(def.name, def.collection);
267
- }
268
- const byCollection = /* @__PURE__ */ new Map();
269
- for (const [name, policies] of table) {
270
- const collection = collectionOf.get(name);
271
- if (collection && isDeclared(policies) && !byCollection.has(collection)) {
272
- byCollection.set(collection, policies);
372
+ function collectUserFieldLiterals(expr, out = /* @__PURE__ */ new Map()) {
373
+ if (Array.isArray(expr)) {
374
+ const [op, ...args] = expr;
375
+ if (typeof op === "string" && args.length >= 2 && (EQUALITY_OPS.includes(op) || op === "array/includes")) {
376
+ const isIncludes = op === "array/includes";
377
+ let sigilSide;
378
+ let literalSide;
379
+ if (isIncludes) {
380
+ if (isUserFieldSigil(args[0])) {
381
+ sigilSide = args.slice(0, 1);
382
+ literalSide = args.slice(1, 2);
383
+ } else {
384
+ sigilSide = args.slice(1, 2);
385
+ literalSide = args.slice(0, 1);
386
+ }
387
+ } else {
388
+ sigilSide = args;
389
+ literalSide = args;
390
+ }
391
+ const field = sigilSide.map((arg) => fieldRefName(arg, "@user")).find((f) => f !== void 0);
392
+ if (field !== void 0) {
393
+ const literalArrayHaystack = literalSide[0];
394
+ const literals = isIncludes && Array.isArray(literalArrayHaystack) ? literalHaystackMembers(literalArrayHaystack) ?? [] : literalSide;
395
+ for (const literal of literals) {
396
+ if (typeof literal === "string" && !literal.startsWith("@")) {
397
+ const set = out.get(field) ?? /* @__PURE__ */ new Set();
398
+ set.add(literal);
399
+ out.set(field, set);
400
+ }
401
+ }
402
+ }
273
403
  }
404
+ for (const item of expr) collectUserFieldLiterals(item, out);
405
+ return out;
274
406
  }
275
- for (const [name, policies] of table) {
276
- if (isDeclared(policies)) continue;
277
- const collection = collectionOf.get(name);
278
- const inherited = collection ? byCollection.get(collection) : void 0;
279
- if (inherited) table.set(name, { ...inherited });
407
+ if (expr !== null && typeof expr === "object") {
408
+ for (const value of Object.values(expr)) collectUserFieldLiterals(value, out);
280
409
  }
281
- return table;
410
+ return out;
282
411
  }
283
- function entityAccessPolicies(schema, entityName) {
284
- return entityAccessTable(schema).get(entityName);
285
- }
286
- function entityAccessPoliciesByStoreKey(schema) {
287
- const collectionOf = /* @__PURE__ */ new Map();
288
- for (const def of inlineEntities2(schema)) {
289
- if (def.collection) collectionOf.set(def.name, def.collection);
290
- }
291
- const byKey = /* @__PURE__ */ new Map();
292
- for (const [name, policies] of entityAccessTable(schema)) {
293
- const key = collectionOf.get(name) ?? name.toLowerCase();
294
- if (!byKey.has(key) && isDeclared(policies)) byKey.set(key, policies);
295
- }
296
- return byKey;
412
+ function roleSatisfyingPolicy(policy, identity, field = "role") {
413
+ if (policy === void 0 || policy === null) return void 0;
414
+ const vocab = roleVocabularyOf(identity, field);
415
+ if (vocab === void 0) return void 0;
416
+ const accepted = collectUserFieldLiterals(policy).get(field);
417
+ if (accepted === void 0) return void 0;
418
+ return vocab.find((value) => accepted.has(value));
297
419
  }
298
420
  var ID_PREFIXES = {
299
421
  orbital: "orb_",
@@ -712,6 +834,9 @@ var EntityPersistenceSchema = z.enum([
712
834
  ]);
713
835
  z.object({
714
836
  name: z.string().min(1, "Entity name is required"),
837
+ // V4 arena id (`EntityDefinition.id`); declared so the strip-mode zod gate
838
+ // carries an imported entity's id through instead of erasing it.
839
+ id: EntityIdSchema.optional(),
715
840
  persistence: EntityPersistenceSchema.default("persistent"),
716
841
  shared: z.boolean().optional(),
717
842
  // Must stay in step with the Rust serde field (`EntityDefinition.identity`,
@@ -870,6 +995,9 @@ function sampleUnion(field, ctx) {
870
995
  const child = { ...firstVariant, name: field.name };
871
996
  return sampleFieldValue(child, { ...ctx, depth: depth + 1 });
872
997
  }
998
+ function omitsUndefaultedOptionalFields(ctx) {
999
+ return ctx.strategy === "seeded" && ctx.index % 2 === 0;
1000
+ }
873
1001
  function sampleFieldValue(field, ctx) {
874
1002
  const honoredDefault = field.default !== void 0 && isFieldValue(field.default) ? field.default : void 0;
875
1003
  if (field.intrinsic === true) return honoredDefault;
@@ -878,6 +1006,9 @@ function sampleFieldValue(field, ctx) {
878
1006
  if (!isRuntime && isDeclaredDefaultHonored(field) && honoredDefault !== void 0) {
879
1007
  return honoredDefault;
880
1008
  }
1009
+ if (!field.required && field.default === void 0 && field.type !== "relation" && omitsUndefaultedOptionalFields(ctx)) {
1010
+ return void 0;
1011
+ }
881
1012
  const values = declaredValues(field);
882
1013
  if (values) {
883
1014
  const ordinal = isRuntime ? 1 : ctx.index;
@@ -977,6 +1108,6 @@ function sampleRows(entity, count, strategy) {
977
1108
  return rows;
978
1109
  }
979
1110
 
980
- export { IMAGE_FIELD_NAMES, entityAccessPolicies, entityAccessPoliciesByStoreKey, entityAccessTable, identityEntityName, isDeclaredDefaultHonored, ownerFieldsFromSchema, randomAnytimeDate, randomArrayElement, randomBoolean, randomColor, randomEmail, randomFloat, randomInt, randomPassword, randomPastDate, randomPhone, randomRecentDate, randomSentence, randomStraddlingDate, randomUrl, randomUuid, randomWords, sampleFieldValue, sampleImageUrl, sampleRow, sampleRowCount, sampleRows, seedRandom, shuffleArray };
1111
+ export { IMAGE_FIELD_NAMES, collectUserFieldLiterals, entityAccessPolicies, entityAccessPoliciesByStoreKey, entityAccessTable, identityEntitiesOf, identityEntityName, identityEntityNames, isDeclaredDefaultHonored, ownerColumnsFromPolicy, ownerFieldsFromSchema, randomAnytimeDate, randomArrayElement, randomBoolean, randomColor, randomEmail, randomFloat, randomInt, randomPassword, randomPastDate, randomPhone, randomRecentDate, randomSentence, randomStraddlingDate, randomUrl, randomUuid, randomWords, roleSatisfyingPolicy, roleVocabularyOf, sampleFieldValue, sampleImageUrl, sampleRow, sampleRowCount, sampleRows, seedRandom, shuffleArray };
981
1112
  //# sourceMappingURL=index.js.map
982
1113
  //# sourceMappingURL=index.js.map