@githolon/dsl 0.33.0 → 0.34.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.
@@ -411,3 +411,46 @@ export function collectValueObjectDescriptors(
411
411
  }
412
412
  return [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
413
413
  }
414
+
415
+ /**
416
+ * ONE VO↔FIELD ATTACHMENT (the IR pointer): aggregate `aggregate`'s field `field`
417
+ * stores value object `vo`. Rides the additive `nomosValueObjects` USD key as
418
+ * `usages` so tooling (the scene projector, the explorer glyphs) can attach a VO
419
+ * molecule to the exact field that carries it. NEVER in the canonical
420
+ * manifest/domainHash — a VO field's wire schema stays a plain `jsonObject` leaf.
421
+ */
422
+ export interface ValueObjectUsage {
423
+ readonly aggregate: string;
424
+ readonly field: string;
425
+ readonly vo: string;
426
+ }
427
+
428
+ /**
429
+ * Collect the sorted VO↔field usage pointers of a set of aggregates (one entry per
430
+ * VO-typed field; sorted by aggregate then field — byte-deterministic). Returns `[]`
431
+ * when no field carries a VO — the omit-when-empty input to the `usages` entry of
432
+ * the `nomosValueObjects` era key.
433
+ */
434
+ export function collectValueObjectUsages(
435
+ aggregates: readonly { id: string; fields: Record<string, Field> }[],
436
+ ): ValueObjectUsage[] {
437
+ const usages: ValueObjectUsage[] = [];
438
+ for (const agg of aggregates) {
439
+ for (const fieldName of Object.keys(agg.fields)) {
440
+ const meta = agg.fields[fieldName]!.valueObject;
441
+ if (meta === undefined) continue;
442
+ usages.push({ aggregate: agg.id, field: fieldName, vo: meta.name });
443
+ }
444
+ }
445
+ return usages.sort((a, b) =>
446
+ a.aggregate !== b.aggregate
447
+ ? a.aggregate < b.aggregate
448
+ ? -1
449
+ : 1
450
+ : a.field < b.field
451
+ ? -1
452
+ : a.field > b.field
453
+ ? 1
454
+ : 0,
455
+ );
456
+ }
@@ -20,9 +20,16 @@
20
20
  * LATER coordinated migration — we target the current serde_json shape now.
21
21
  */
22
22
  import { encodeDriver } from "./drivers.js";
23
+ import { __qualifyAggregateType } from "./framework/relations.js";
23
24
  import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
24
25
  import { __resetReads, __drainReads } from "./read.js";
25
26
  import { __resetBirths, __drainBirths } from "./births.js";
27
+ import {
28
+ birthMeterSubject,
29
+ birthMeterBornAt,
30
+ captureBirthMeterReads,
31
+ planBirthRecordOps,
32
+ } from "./birth_meter.js";
26
33
  import type { AggregateHandle } from "./aggregate.js";
27
34
  import type { Field, EvidenceRefValue } from "./fields.js";
28
35
  import type { Directive, ReferentialMarker } from "./directive.js";
@@ -203,13 +210,33 @@ export function executeDirectiveToIntent<P>(
203
210
  __resetReads();
204
211
  __resetBirths();
205
212
  const returned = directive.plan(parsed, ctx);
206
- const planned = [...__drainAuthoring(), ...returned];
207
- const reads = __drainReads();
208
213
  // THE BIRTH EFFECT (G3 — birth is a declared kernel primitive): a `.births()` directive's plan records
209
214
  // the child's genesis recipe via `birth(recipe)`; it rides the intent as a DECLARED output (like a
210
215
  // captured read), drained here. Empty for every non-birth directive — OMITTED below so a birth-free
211
216
  // intent is byte-identical (the era rule).
212
217
  const births = __drainBirths();
218
+ // METERED BIRTH (slice A — architecture/metered_birth_and_attested_reads.md). For every PLANNED birth,
219
+ // the library — HERE, in the ONE plan-output assembly both lanes (client authoring + the sealed engine's
220
+ // `plan`) funnel through, so the halves are byte-identical by construction — does two things the tenant
221
+ // never writes:
222
+ // A1 THE AUTO-RECORD: append the `NomosBirthRecord` field ops (deterministic id per child; Lww upsert)
223
+ // to the SAME intent that carries the birth effect. The kernel's `nomosBirthRecordGate` era arm
224
+ // refuses a new-era birth whose intent carries no record — this is what satisfies it.
225
+ // A2 THE AUTO-CAPTURE: capture the three meter reads (records-by-subject + grant + policy — the #58
226
+ // lane `.births()` auto-declares) so the synthesized birth-quota invariant judges over COMMITTED,
227
+ // CAS-re-derived pre-apply state on every lane.
228
+ // A `.births()` dispatch that planned NO birth (e.g. birthChild without a genesis) appends/captures
229
+ // nothing — byte-identical to before (the era rule); pre-era bundles ship an older lump and are untouched.
230
+ const meterOps: PlannedOp[] = [];
231
+ if (births.length > 0) {
232
+ const payloadRecord = parsed as unknown as Record<string, unknown>;
233
+ const subject = birthMeterSubject(payloadRecord);
234
+ captureBirthMeterReads(subject);
235
+ const bornAt = birthMeterBornAt(payloadRecord);
236
+ for (const b of births) meterOps.push(...planBirthRecordOps(b, subject, bornAt));
237
+ }
238
+ const planned = [...__drainAuthoring(), ...returned, ...meterOps];
239
+ const reads = __drainReads();
213
240
 
214
241
  // Partition the plan output: STRIKE ops route onto the intent's `strikes` channel
215
242
  // (the kernel's strikeout / revert facet, folded by parity); FIELD ops group into
@@ -293,7 +320,11 @@ export function executeDirectiveToIntent<P>(
293
320
  aggregate,
294
321
  marker: eventMarker,
295
322
  ops: [
296
- { field: "__type", op: { Set: { Str: ty } } },
323
+ // READ-PROJECTION NAMESPACING: the stamped TYPE is qualified by the dispatching domain iff
324
+ // the domain declares it non-global — so the folded row's `__type` equals the qualified
325
+ // schema key the SAME compile wrote (rows and read manifest agree by construction). Outside
326
+ // a plan dispatch (codegen probes) the type passes through bare.
327
+ { field: "__type", op: { Set: { Str: __qualifyAggregateType(ty) } } },
297
328
  { field: "__id", op: { Set: { Str: aggregate } } },
298
329
  ...ops,
299
330
  ],